pry-sorbet 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'