Git Product home page Git Product logo

pry-sorbet's Introduction

pry-sorbet

pry-sorbet is a Pry extension which enables it to work seamlessly with Sorbet projects.

Installation

Add this line to your application's Gemfile:

gem 'pry-sorbet'

And then execute:

$ bundle

Or install it yourself as:

$ gem install pry-sorbet

Usage

Fixes method source

Since Sorbet runtime wraps methods with a signature to execute runtime checks on parameters and return values, the source location of methods end up pointing to a file location in the sorbet-runtime gem.

pry-sorbet automatically fixes the information returned by the show-source (also known as $) command to list the correct method source location.

Moreover, an extra entry is added to the header of the method listing, named Sorbet, which displays the method signature if it exists.

Before: Incorrect method source, no method signature

From: /Users/user/.gem/ruby/2.6.5/gems/sorbet-runtime-0.5.5427/lib/types/private/methods/_methods.rb @ line 208:
Owner: Foo
Visibility: public
Number of lines: 35

T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
  if !T::Private::Methods.has_sig_block_for_method(new_method)
    # This should only happen if the user used alias_method to grab a handle
    # to the original pre-unwound `sig` method. I guess we'll just proxy the
    # call forever since we don't know who is holding onto this handle to
    # replace it.

After: Method source and a signature!

From: test/test.rb @ line 8:
Owner: Foo
Visibility: public
Sorbet: sig { params(bar: Symbol, baz: Integer).returns(String) }
Number of lines: 3

def foo(bar, baz)
  [baz.to_s, bar.to_s].join(":")
end

Helps with debugging

Because of the same method wrapping, debugging source code that has Sorbet signatures becomes really painful. When stepping through the code, one always hits code locations that are in the sorbet-runtime library and most of the time it makes people end up missing the actual method invocation.

pry-sorbet adds a Sorbet specific command, sorbet-unwrap that can be ran to unwrap all Sorbet wrapped methods. This allows developers to easily step through their code without having to deal with the sorbet-runtime library.

Before

$ ruby -I lib -r pry-sorbet test/test.rb

From: test/test.rb @ line 14 :

     4: class Foo
     5:   extend T::Sig
     6:
     7:   sig { params(bar: Symbol, baz: Integer).returns(String) }
     8:   def foo(bar, baz)
     9:     [baz.to_s, bar.to_s].join(":")
    10:   end
    11: end
    12:
    13: binding.pry
 => 14: puts Foo.new.foo(:bar, 1)

[1] pry(main)> step

From: /Users/user/.gem/ruby/2.6.5/gems/sorbet-runtime-0.5.5427/lib/types/private/methods/_methods.rb @ line 209 Foo#foo:

    204:     # which is called only on the *first* invocation.
    205:     # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
    206:     # (or unwrap back to the original method).
    207:     new_method = nil
    208:     T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
 => 209:       if !T::Private::Methods.has_sig_block_for_method(new_method)
    210:         # This should only happen if the user used alias_method to grab a handle
    211:         # to the original pre-unwound `sig` method. I guess we'll just proxy the
    212:         # call forever since we don't know who is holding onto this handle to
    213:         # replace it.
    214:         new_new_method = mod.instance_method(method_name)

After

$ ruby -I lib -r pry-sorbet test/test.rb

From: test/test.rb @ line 14 :

     4: class Foo
     5:   extend T::Sig
     6:
     7:   sig { params(bar: Symbol, baz: Integer).returns(String) }
     8:   def foo(bar, baz)
     9:     [baz.to_s, bar.to_s].join(":")
    10:   end
    11: end
    12:
    13: binding.pry
 => 14: puts Foo.new.foo(:bar, 1)

[1] pry(main)> sorbet-unwrap
[2] pry(main)> step

From: test/test.rb @ line 9 Foo#foo:

     8: def foo(bar, baz)
 =>  9:   [baz.to_s, bar.to_s].join(":")
    10: end

Method unwrapping is not done automatically, you still need to call the sorbet-unwrap command before stepping through code. However, you can make this automatic by adding the following snippet to your ~/.pryrc file:

if defined?(PrySorbet)
  Pry.hooks.add_hook(:before_session, "sorbet-unwrap") do |output, binding, pry|
    pry.run_command "sorbet-unwrap"
  end
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/AaronC81/pry-sorbet.

License

The gem is available as open source under the terms of the MIT License.

pry-sorbet's People

Contributors

aaronc81 avatar md-work avatar paracycle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

paracycle md-work

pry-sorbet's Issues

sorbet-unwrap: abstracts methods may mask implementing methods

sorbet-unwrap also unwraps abstract methods.
This is a problem, because normal Ruby code wouldn't have abstract methods at all.
Usually Sorbet hides abstracts methods, so Ruby ignores this concept which it doesn't know.

@paracycle (see also #2 )
Do you have an Idea how to ignore methods with mode == T::Private::Methods::Modes.abstract when unwrapping?

Background: sorbet/sorbet#1969
There I tried another way to enable stepping though methods with signatures. And I ran into the same problem.

Here you can see the actual problem caused by this.
If the abstract method isn't hidden by Sorbet, it will overwrite the implementing method and change the behavior of the problem.
→ View on sorbet.run

module FancyModule
  extend T::Sig
  extend T::Helpers
  abstract!

  #sig { abstract.returns(Integer) }
  def foo; end

  def inspected_foo
    foo.inspect
  end
end

class Parent
  def foo
    42
  end
end

class Child < Parent
  include FancyModule
  def initialize
    puts(inspected_foo)
  end
end

if ENV['SORBET_UNWRAP']
  require 'pry-sorbet'
  Pry.run_command 'sorbet-unwrap'
end

Child.new
# ruby THIS_SCRIPT => 42
# SORBET_UNWRAP=true ruby THIS_SCRIPT => nil

Recommendations for use with debug gem

Hello!

I'm starting to use the new ruby debugger (https://github.com/ruby/debug) in VSCode and making use of this gem to skip through sorbet stuff. Right now, I'm just manually calling PrySorbet::UnwrapCommand.new.process in my debugger, and it works like a charm. Is there a recommended way to automatically run this when entering a debug session with debug? This is called pry-sorbet so maybe supporting debug is a non-goal, but I thought I'd ask!

sorbet-unwrap: debugging inside T.let / T.cast still a problem

Sadly sorbet-unwrap won't help if there's a method call inside T.let(..., or T.cast(..., .

 

Proposal: sorbet-unwrap could overwrite T.let / T.cast with a minimal implementation, which simply forwards the first argument and ignores the second.

module T
  def self.let(value, type, checked: true)
    value
  end
  
  def self.cast(value, type, checked: true)
    value
  end
end

 

Sadly I've no idea how the evaluation of the second parameter may be skipped. This would be a problem, if the second parameter is something like T::Array[Integer] because there will be code to step trough.

See also: sorbet/sorbet#3279

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.