pry-sorbet 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfc26a70315521bb64ed1dd37c0d5270d33821417a0630052092b0f2efebba0f
4
- data.tar.gz: bd69b579be964eb4f007a4506e58aad3f864f392004aa27bd56941081d45feba
3
+ metadata.gz: a7c71e9c8280b7f14bada6f48eaa62582a74e09f578759dee11dcdcd47cc0d68
4
+ data.tar.gz: 47d8f52a4134feb895c9177344e658147c4cc15896dea9c2f8abd5d574e44c8f
5
5
  SHA512:
6
- metadata.gz: ece438c8451b3c4831d9714514e2c0cc516d0a40cf97173aff42abe76798d87f449b4701118411f76ad46f15cf965eacd28041458e3f253164b4c275cc0f79a9
7
- data.tar.gz: 7c0810dc4586b041fad1c8e734a41dbd38a8b5dfc484c9b8ac8ce7df0e23222c18e01d23034e077cce5ff00ac6c97e27b27917390f6389d57eb2282f3207188c
6
+ metadata.gz: 122b2f0548e65dd58265eab021a97b5ef16cb30b101cffe467d06ce4ca3fd332e67285da0679ed2af78e2c48539f8cdecfc40c5605612411b6a3f5ae8f04851f
7
+ data.tar.gz: be0143342a9cdd1f73227348bca5f90dcb0eb6fd8d432f5b175f9a9b3c0c7dc8d099485c2de9dc72d723d9c127fceec34d6649c65ea872c230bf25b3f5bf0dfe
data/.gitignore CHANGED
File without changes
data/Gemfile CHANGED
File without changes
File without changes
data/README.md CHANGED
@@ -3,11 +3,41 @@
3
3
  pry-sorbet is a Pry extension which enables it to work seamlessly with Sorbet
4
4
  projects.
5
5
 
6
- **Before:** Incorrect method source
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
7
9
 
10
+ ```ruby
11
+ gem 'pry-sorbet'
8
12
  ```
9
- From: /home/aaron/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.4.4929/lib/types/private/methods/_methods.rb @ line 208:
10
- Owner: #<Class:Foo>
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install pry-sorbet
21
+
22
+ ## Usage
23
+
24
+ ### Fixes method source
25
+
26
+ Since Sorbet runtime wraps methods with a signature to execute runtime checks
27
+ on parameters and return values, the source location of methods end up pointing
28
+ to a file location in the `sorbet-runtime` gem.
29
+
30
+ `pry-sorbet` automatically fixes the information returned by the `show-source`
31
+ (also known as `$`) command to list the correct method source location.
32
+
33
+ Moreover, an extra entry is added to the header of the method listing, named
34
+ `Sorbet`, which displays the method signature if it exists.
35
+
36
+ **Before:** Incorrect method source, no method signature
37
+
38
+ ```
39
+ From: /Users/user/.gem/ruby/2.6.5/gems/sorbet-runtime-0.5.5427/lib/types/private/methods/_methods.rb @ line 208:
40
+ Owner: Foo
11
41
  Visibility: public
12
42
  Number of lines: 35
13
43
 
@@ -16,42 +46,110 @@ T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
16
46
  # This should only happen if the user used alias_method to grab a handle
17
47
  # to the original pre-unwound `sig` method. I guess we'll just proxy the
18
48
  # call forever since we don't know who is holding onto this handle to
49
+ # replace it.
19
50
  ```
20
51
 
21
52
  **After:** Method source and a signature!
22
53
 
23
54
  ```
24
- From: playground/test.rb @ line 9:
25
- Owner: #<Class:Foo>
55
+ From: test/test.rb @ line 8:
56
+ Owner: Foo
26
57
  Visibility: public
27
- Sorbet: sig { returns(Integer) }
58
+ Sorbet: sig { params(bar: Symbol, baz: Integer).returns(String) }
28
59
  Number of lines: 3
29
60
 
30
- def self.bar
31
- 3
61
+ def foo(bar, baz)
62
+ [baz.to_s, bar.to_s].join(":")
32
63
  end
33
64
  ```
34
65
 
35
- ## Installation
66
+ ### Helps with debugging
36
67
 
37
- Add this line to your application's Gemfile:
68
+ Because of the same method wrapping, debugging source code that has Sorbet signatures
69
+ becomes really painful. When stepping through the code, one always hits code locations
70
+ that are in the `sorbet-runtime` library and most of the time it makes people end up
71
+ missing the actual method invocation.
38
72
 
39
- ```ruby
40
- gem 'pry-sorbet'
41
- ```
73
+ `pry-sorbet` adds a Sorbet specific command, `sorbet-unwrap` that can be ran to unwrap
74
+ all Sorbet wrapped methods. This allows developers to easily step through their code
75
+ without having to deal with the `sorbet-runtime` library.
42
76
 
43
- And then execute:
77
+ **Before**
44
78
 
45
- $ bundle
79
+ ```
80
+ $ ruby -I lib -r pry-sorbet test/test.rb
81
+
82
+ From: test/test.rb @ line 14 :
83
+
84
+ 4: class Foo
85
+ 5: extend T::Sig
86
+ 6:
87
+ 7: sig { params(bar: Symbol, baz: Integer).returns(String) }
88
+ 8: def foo(bar, baz)
89
+ 9: [baz.to_s, bar.to_s].join(":")
90
+ 10: end
91
+ 11: end
92
+ 12:
93
+ 13: binding.pry
94
+ => 14: puts Foo.new.foo(:bar, 1)
95
+
96
+ [1] pry(main)> step
97
+
98
+ From: /Users/user/.gem/ruby/2.6.5/gems/sorbet-runtime-0.5.5427/lib/types/private/methods/_methods.rb @ line 209 Foo#foo:
99
+
100
+ 204: # which is called only on the *first* invocation.
101
+ 205: # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
102
+ 206: # (or unwrap back to the original method).
103
+ 207: new_method = nil
104
+ 208: T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
105
+ => 209: if !T::Private::Methods.has_sig_block_for_method(new_method)
106
+ 210: # This should only happen if the user used alias_method to grab a handle
107
+ 211: # to the original pre-unwound `sig` method. I guess we'll just proxy the
108
+ 212: # call forever since we don't know who is holding onto this handle to
109
+ 213: # replace it.
110
+ 214: new_new_method = mod.instance_method(method_name)
111
+ ```
46
112
 
47
- Or install it yourself as:
113
+ **After**
48
114
 
49
- $ gem install pry-sorbet
115
+ ```
116
+ $ ruby -I lib -r pry-sorbet test/test.rb
117
+
118
+ From: test/test.rb @ line 14 :
119
+
120
+ 4: class Foo
121
+ 5: extend T::Sig
122
+ 6:
123
+ 7: sig { params(bar: Symbol, baz: Integer).returns(String) }
124
+ 8: def foo(bar, baz)
125
+ 9: [baz.to_s, bar.to_s].join(":")
126
+ 10: end
127
+ 11: end
128
+ 12:
129
+ 13: binding.pry
130
+ => 14: puts Foo.new.foo(:bar, 1)
131
+
132
+ [1] pry(main)> sorbet-unwrap
133
+ [2] pry(main)> step
134
+
135
+ From: test/test.rb @ line 9 Foo#foo:
136
+
137
+ 8: def foo(bar, baz)
138
+ => 9: [baz.to_s, bar.to_s].join(":")
139
+ 10: end
140
+ ```
50
141
 
51
- ## Usage
142
+ Method unwrapping is not done automatically, you still need to call the `sorbet-unwrap`
143
+ command before stepping through code. However, you can make this automatic by adding the
144
+ following snippet to your `~/.pryrc` file:
52
145
 
53
- Make sure you've required `pry-sorbet`. The `$` command in Pry will be
54
- automatically overwritten to add the Sorbet-specific functionality.
146
+ ```ruby
147
+ if defined?(PrySorbet)
148
+ Pry.hooks.add_hook(:before_session, "sorbet-unwrap") do |output, binding, pry|
149
+ pry.run_command "sorbet-unwrap"
150
+ end
151
+ end
152
+ ```
55
153
 
56
154
  ## Contributing
57
155
 
data/Rakefile CHANGED
File without changes
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+
5
+ module PrySorbet
6
+ end
7
+
8
+ require 'pry-sorbet/version'
9
+ require 'pry-sorbet/extensions.rb'
10
+ require 'pry-sorbet/commands.rb'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pry-sorbet/commands/unwrap_command"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrySorbet
4
+ class UnwrapCommand < Pry::ClassCommand
5
+ match "sorbet-unwrap"
6
+ group "Sorbet"
7
+ description "Unwrap all methods wrapped by Sorbet runtime"
8
+ command_options requires_gem: "sorbet-runtime"
9
+
10
+ banner <<~BANNER
11
+ Usage: sorbet-unwrap
12
+
13
+ Unwrap all methods wrapped by Sorbet runtime
14
+ BANNER
15
+
16
+ def process
17
+ return unless defined?(T::Private::Methods)
18
+
19
+ # For each signature wrapper, call the lambda associated with the wrapper
20
+ # to finalize the method wrapping
21
+ sig_wrappers = T::Private::Methods.instance_variable_get(:@sig_wrappers) || {}
22
+ sig_wrappers.values.each do |sig_wrapper|
23
+ sig_wrapper.call
24
+ rescue NameError
25
+ end
26
+
27
+ # Now that all methods are properly wrapped with optimized wrappers
28
+ # we can replace them with the original methods
29
+ signatures_by_method = T::Private::Methods.instance_variable_get(:@signatures_by_method) || {}
30
+ signatures_by_method.values.each do |signature|
31
+ T::Configuration.without_ruby_warnings do
32
+ T::Private::DeclState.current.without_on_method_added do
33
+ signature.owner.send(:define_method, signature.method_name, signature.method)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ Pry::Commands.add_command(PrySorbet::UnwrapCommand)
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pry
4
+ class Command
5
+ class ShowInfo
6
+ old_method_sections = instance_method(:method_sections)
7
+
8
+ define_method(:method_sections) do |code_object|
9
+ signature = defined?(T::Private::Methods) && T::Private::Methods.signature_for_method(code_object)
10
+
11
+ signature_string = if signature
12
+ build_signature_string(signature)
13
+ else
14
+ "Unknown"
15
+ end
16
+
17
+ old_method_sections.bind(self).(code_object).merge({
18
+ sorbet: "\n\e[1mSorbet:\e[0m #{signature_string}"
19
+ })
20
+ end
21
+
22
+ old_method_header = instance_method(:method_header)
23
+
24
+ define_method(:method_header) do |code_object, line_num|
25
+ old_method_header.bind(self).(code_object, line_num) \
26
+ + method_sections(code_object)[:sorbet]
27
+ end
28
+
29
+ private
30
+
31
+ def build_signature_string(signature)
32
+ call_chain = []
33
+
34
+ # Modifiers
35
+ if signature.mode != "standard"
36
+ # This is a string like "overridable_override"
37
+ call_chain += signature.mode.split("_")
38
+ end
39
+
40
+ # Parameters
41
+ all_parameters = []
42
+
43
+ # Positional
44
+ all_parameters += signature.arg_types.map do |(name, type)|
45
+ "#{name}: #{type}"
46
+ end
47
+
48
+ # Splat
49
+ if signature.rest_type
50
+ all_parameters << "#{signature.rest_name}: #{signature.rest_type}"
51
+ end
52
+
53
+ # Keyword
54
+ all_parameters += signature.kwarg_types.map do |(name, type)|
55
+ "#{name}: #{type}"
56
+ end
57
+
58
+ # Double-splat
59
+ if signature.rest_type
60
+ all_parameters << "#{signature.keyrest_name}: #{signature.keyrest_type}"
61
+ end
62
+
63
+ # Block
64
+ if signature.block_type
65
+ all_parameters << "#{signature.block_name}: #{signature.block_type}"
66
+ end
67
+
68
+ call_chain << "params(#{all_parameters.join(", ")})" if all_parameters.any?
69
+
70
+ # Returns
71
+ if signature.return_type.is_a?(T::Private::Types::Void)
72
+ call_chain << "void"
73
+ else
74
+ call_chain << "returns(#{signature.return_type})"
75
+ end
76
+
77
+ "sig { #{call_chain.join(".")} }"
78
+ end
79
+ end
80
+ end
81
+
82
+ class Method
83
+ # Maybe use a dict or something, I think methods are suitable keys
84
+ define_method(:source_location) do
85
+ loc = super()
86
+
87
+ return loc unless defined?(T::Private::Methods)
88
+ signature = T::Private::Methods.signature_for_method(@method)
89
+ return loc unless signature
90
+
91
+ return signature.method.source_location
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrySorbet
4
+ VERSION = '0.2.0'
5
+ end
@@ -1,11 +1,12 @@
1
1
  lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "pry-sorbet"
3
4
 
4
5
  Gem::Specification.new do |spec|
5
6
  spec.name = "pry-sorbet"
6
- spec.version = "0.1.0"
7
- spec.authors = ["Aaron Christiansen"]
8
- spec.email = ["aaronc20000@gmail.com"]
7
+ spec.version = PrySorbet::VERSION
8
+ spec.authors = ["Aaron Christiansen", "Ufuk Kayserilioglu"]
9
+ spec.email = ["aaronc20000@gmail.com", "ufuk.kayserilioglu@shopify.com"]
9
10
 
10
11
  spec.summary = %q{Pry plugin for Sorbet}
11
12
  spec.homepage = "https://github.com/AaronC81/pry-sorbet"
@@ -24,6 +25,9 @@ Gem::Specification.new do |spec|
24
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
26
  spec.require_paths = ["lib"]
26
27
 
28
+ spec.add_dependency "pry"
29
+
27
30
  spec.add_development_dependency "bundler", "~> 2.0"
28
31
  spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "sorbet-runtime"
29
33
  end
metadata CHANGED
@@ -1,15 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pry-sorbet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Christiansen
8
+ - Ufuk Kayserilioglu
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2019-10-28 00:00:00.000000000 Z
12
+ date: 2020-03-12 00:00:00.000000000 Z
12
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
13
28
  - !ruby/object:Gem::Dependency
14
29
  name: bundler
15
30
  requirement: !ruby/object:Gem::Requirement
@@ -38,9 +53,24 @@ dependencies:
38
53
  - - "~>"
39
54
  - !ruby/object:Gem::Version
40
55
  version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: sorbet-runtime
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
41
70
  description:
42
71
  email:
43
72
  - aaronc20000@gmail.com
73
+ - ufuk.kayserilioglu@shopify.com
44
74
  executables: []
45
75
  extensions: []
46
76
  extra_rdoc_files: []
@@ -50,8 +80,11 @@ files:
50
80
  - LICENSE.txt
51
81
  - README.md
52
82
  - Rakefile
53
- - lib/extension.rb
54
- - lib/pry_sorbet.rb
83
+ - lib/pry-sorbet.rb
84
+ - lib/pry-sorbet/commands.rb
85
+ - lib/pry-sorbet/commands/unwrap_command.rb
86
+ - lib/pry-sorbet/extensions.rb
87
+ - lib/pry-sorbet/version.rb
55
88
  - pry-sorbet.gemspec
56
89
  homepage: https://github.com/AaronC81/pry-sorbet
57
90
  licenses:
@@ -1,101 +0,0 @@
1
- require 'sorbet-runtime'
2
- require 'pry'
3
-
4
- class Pry
5
- class Command
6
- class ShowInfo
7
- old_method_sections = instance_method(:method_sections)
8
-
9
- define_method(:method_sections) do |code_object|
10
- sorbet_object = T::Private::Methods.signature_for_method(code_object)
11
-
12
- if sorbet_object
13
- call_chain = []
14
-
15
- # Modifiers
16
- call_chain << "generated" if sorbet_object.generated
17
-
18
- if sorbet_object.mode != "standard"
19
- # This is a string like "overridable_override"
20
- call_chain += sorbet_object.mode.split("_")
21
- end
22
-
23
- # Parameters
24
- all_parameters = []
25
-
26
- # Positional
27
- all_parameters += sorbet_object.arg_types.map do |(name, type)|
28
- "#{name}: #{type}"
29
- end
30
-
31
- # Splat
32
- if sorbet_object.rest_type
33
- all_parameters << "#{sorbet_object.rest_name}: #{sorbet_object.rest_type}"
34
- end
35
-
36
- # Keyword
37
- all_parameters += sorbet_object.kwarg_types.map do |(name, type)|
38
- "#{name}: #{type}"
39
- end
40
-
41
- # Double-splat
42
- if sorbet_object.rest_type
43
- all_parameters << "#{sorbet_object.keyrest_name}: #{sorbet_object.keyrest_type}"
44
- end
45
-
46
- # Block
47
- if sorbet_object.block_type
48
- all_parameters << "#{sorbet_object.block_name}: #{sorbet_object.block_type}"
49
- end
50
-
51
- call_chain << "params(#{all_parameters.join(", ")})" if all_parameters.any?
52
-
53
- # Returns
54
- if sorbet_object.return_type.is_a?(T::Private::Types::Void)
55
- call_chain << "void"
56
- else
57
- call_chain << "returns(#{sorbet_object.return_type})"
58
- end
59
-
60
- sorbet_string = "sig { #{call_chain.join(".")} }"
61
- else
62
- sorbet_string = "Unknown"
63
- end
64
-
65
- old_method_sections.bind(self).(code_object).merge({
66
- sorbet: "\n\e[1mSorbet:\e[0m #{sorbet_string}"
67
- })
68
- end
69
-
70
- old_method_header = instance_method(:method_header)
71
-
72
- define_method(:method_header) do |code_object, line_num|
73
- old_method_header.bind(self).(code_object, line_num) \
74
- + method_sections(code_object)[:sorbet]
75
- end
76
- end
77
- end
78
-
79
- class Method
80
- # Maybe use a dict or something, I think methods are suitable keys
81
- define_method(:source_location) do
82
- loc = super()
83
-
84
- file_path, line = loc
85
- return loc unless file_path && File.exists?(file_path)
86
-
87
- first_source_line = IO.readlines(file_path)[line - 1]
88
-
89
- # This is how Sorbet replaces methods.
90
- # If Sorbet undergoes drastic refactorings, this may need to be updated!
91
- initial_sorbet_line = "T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|"
92
- replaced_sorbet_line = "mod.send(:define_method, method_sig.method_name) do |*args, &blk|"
93
-
94
- if [initial_sorbet_line, replaced_sorbet_line].include?(first_source_line.strip)
95
- T::Private::Methods.signature_for_method(@method).method.source_location
96
- else
97
- loc
98
- end
99
- end
100
- end
101
- end
@@ -1 +0,0 @@
1
- require_relative 'extension.rb'