typeprof 0.31.1 → 0.33.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/doc/report_guide.md +88 -0
  4. data/lib/typeprof/cli/cli.rb +9 -3
  5. data/lib/typeprof/code_range.rb +7 -5
  6. data/lib/typeprof/core/ast/base.rb +31 -6
  7. data/lib/typeprof/core/ast/call.rb +120 -39
  8. data/lib/typeprof/core/ast/const.rb +22 -11
  9. data/lib/typeprof/core/ast/control.rb +62 -32
  10. data/lib/typeprof/core/ast/meta.rb +279 -14
  11. data/lib/typeprof/core/ast/method.rb +82 -24
  12. data/lib/typeprof/core/ast/misc.rb +41 -12
  13. data/lib/typeprof/core/ast/module.rb +38 -4
  14. data/lib/typeprof/core/ast/pattern.rb +51 -23
  15. data/lib/typeprof/core/ast/sig_decl.rb +141 -27
  16. data/lib/typeprof/core/ast/sig_type.rb +113 -32
  17. data/lib/typeprof/core/ast/value.rb +14 -6
  18. data/lib/typeprof/core/ast/variable.rb +17 -4
  19. data/lib/typeprof/core/ast.rb +98 -14
  20. data/lib/typeprof/core/builtin.rb +184 -12
  21. data/lib/typeprof/core/env/method.rb +343 -6
  22. data/lib/typeprof/core/env/method_entity.rb +18 -15
  23. data/lib/typeprof/core/env/module_entity.rb +116 -18
  24. data/lib/typeprof/core/env/static_read.rb +4 -4
  25. data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
  26. data/lib/typeprof/core/env/value_entity.rb +25 -3
  27. data/lib/typeprof/core/env.rb +113 -23
  28. data/lib/typeprof/core/graph/box.rb +508 -63
  29. data/lib/typeprof/core/graph/change_set.rb +64 -46
  30. data/lib/typeprof/core/graph/filter.rb +8 -5
  31. data/lib/typeprof/core/graph/vertex.rb +20 -19
  32. data/lib/typeprof/core/service.rb +340 -24
  33. data/lib/typeprof/core/type.rb +41 -7
  34. data/lib/typeprof/core/util.rb +6 -0
  35. data/lib/typeprof/lsp/messages.rb +5 -0
  36. data/lib/typeprof/lsp/server.rb +35 -4
  37. data/lib/typeprof/version.rb +1 -1
  38. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 937e0870f11951c34a3c7903f0c6e02b2ba0b557d327999a8ce807e195409088
4
- data.tar.gz: ab8eca2e1cbb24729bbc0274bce7ed3ddd59917accd81285069ebc0086fe0208
3
+ metadata.gz: 8dc7b580d84d82c2a49d4a2d24ddf32ddcafc938d4f9511fc4b6d4543f0b8e19
4
+ data.tar.gz: eed64be210440c2ea2627a7b21c95c7eedee374b760e9e6aa479dfa4c27df54f
5
5
  SHA512:
6
- metadata.gz: 40b2629987ea3feed461c74c279cabb25ad462c0e0118d3d451c12387de4cdbab39b1dd1301ee86ceb6f6f5bbb25b4c5425a72749a11eabe2485a35ea463e5a3
7
- data.tar.gz: c8704511e6507f9e50df4b626fb9b481e945954688df4d6c26ecd233b530eb4f1e2e038589c2538f209e9f6cec15c26401d854f48d8ea346325507f29246a475
6
+ metadata.gz: b314eccaecb06cac94df1af7ab96084b06d1d70710c9ece6934c98ca5379ca98e82ab93838322081357d13c0200681beb24d6108f699aac4a8904952333a7d83
7
+ data.tar.gz: 7ebcd55f8c8d91b1eac583c7d585723de31a83da5fa85c7485cda77e520e24d09f1652b1dde7657db62c8151166cbfdc71d3563647ccd60871bb31e74fc6055a
data/README.md CHANGED
@@ -37,7 +37,8 @@ $ bundle exec rake test
37
37
 
38
38
  ## More details
39
39
 
40
- https://speakerdeck.com/mame/good-first-issues-of-typeprof
40
+ - [Good first issues of TypeProf](https://speakerdeck.com/mame/good-first-issues-of-typeprof) (slides)
41
+ - [How to report bugs / propose features](doc/report_guide.md)
41
42
 
42
43
  ## LICENSE
43
44
 
@@ -0,0 +1,88 @@
1
+ # How to Report Bugs and Propose Features
2
+
3
+ Thank you for your interest in improving TypeProf! When reporting a bug or proposing a feature, including a **scenario file** is very helpful (but not mandatory).
4
+
5
+ ## What is a Scenario File?
6
+
7
+ A scenario file describes TypeProf's behavior — the input code and the expected type inference result. You can find many examples in the [`scenario/`](../scenario/) directory.
8
+
9
+ ## Writing a Scenario File
10
+
11
+ ### Basic pattern
12
+
13
+ A minimal scenario file has two sections: `## update` (input code) and `## assert` (expected type signatures in [RBS](https://github.com/ruby/rbs) syntax).
14
+
15
+ ```ruby
16
+ ## update
17
+ def foo(n)
18
+ n.to_s
19
+ end
20
+
21
+ foo(42)
22
+
23
+ ## assert
24
+ class Object
25
+ def foo: (Integer) -> String
26
+ end
27
+ ```
28
+
29
+ ### Multiple updates
30
+
31
+ You can include multiple `## update` / `## assert` pairs to test how TypeProf handles code changes:
32
+
33
+ ```ruby
34
+ ## update
35
+ def foo = "symbol#{ 42 }"
36
+
37
+ ## assert
38
+ class Object
39
+ def foo: -> String
40
+ end
41
+
42
+ ## update
43
+ def foo = :"symbol#{ 42 }"
44
+
45
+ ## assert
46
+ class Object
47
+ def foo: -> Symbol
48
+ end
49
+ ```
50
+
51
+ ### Diagnostics
52
+
53
+ Use `## diagnostics` to verify that TypeProf reports type errors at the expected locations:
54
+
55
+ ```ruby
56
+ ## update
57
+ def foo(x)
58
+ x
59
+ end
60
+
61
+ foo(1, 2)
62
+
63
+ ## diagnostics
64
+ (5,0)-(5,3): wrong number of arguments (2 for 1)
65
+ ```
66
+
67
+ ## Running a Scenario File
68
+
69
+ Run a single scenario file:
70
+
71
+ ```sh
72
+ $ ruby tool/scenario_runner.rb path/to/your_scenario.rb
73
+ ```
74
+
75
+ Run all tests:
76
+
77
+ ```sh
78
+ $ bundle exec rake test
79
+ ```
80
+
81
+ ## Reporting a Bug
82
+
83
+ 1. Create a scenario file that reproduces the issue.
84
+ 2. Run it with `ruby tool/scenario_runner.rb your_scenario.rb` to confirm the problem.
85
+ 3. Open an issue and include:
86
+ - Your TypeProf version (`typeprof --version`)
87
+ - The scenario file content
88
+ - What you expected vs. what actually happened
@@ -12,6 +12,7 @@ module TypeProf::CLI
12
12
  output = nil
13
13
  rbs_collection_path = nil
14
14
  initialize_config_file = false
15
+ exclude_patterns = []
15
16
 
16
17
  opt.separator ""
17
18
  opt.separator "Options:"
@@ -25,6 +26,7 @@ module TypeProf::CLI
25
26
  opt.on("--version", "Display typeprof version") { cli_options[:display_version] = true }
26
27
  opt.on("--collection PATH", "File path of collection configuration") { |v| rbs_collection_path = v }
27
28
  opt.on("--no-collection", "Ignore collection configuration") { rbs_collection_path = :no }
29
+ opt.on("--exclude PATTERN", "Exclude files matching glob PATTERN (can be specified multiple times)") { |v| exclude_patterns << v }
28
30
  opt.on("--lsp", "LSP server mode") do |v|
29
31
  core_options[:display_indicator] = false
30
32
  cli_options[:lsp] = true
@@ -36,6 +38,7 @@ module TypeProf::CLI
36
38
  opt.on("--[no-]show-errors", "Display possible errors found during the analysis") {|v| core_options[:output_diagnostics] = v }
37
39
  opt.on("--[no-]show-parameter-names", "Display parameter names for methods") {|v| core_options[:output_parameter_names] = v }
38
40
  opt.on("--[no-]show-source-locations", "Display definition source locations for methods") {|v| core_options[:output_source_locations] = v }
41
+ opt.on("--[no-]show-stats", "Display type inference statistics after analysis (for debugging purpose)") {|v| core_options[:output_stats] = v }
39
42
 
40
43
  opt.separator ""
41
44
  opt.separator "Advanced options:"
@@ -65,6 +68,8 @@ module TypeProf::CLI
65
68
  output_errors: false,
66
69
  output_parameter_names: false,
67
70
  output_source_locations: false,
71
+ output_stats: false,
72
+ exclude_patterns: exclude_patterns,
68
73
  }.merge(core_options)
69
74
 
70
75
  @lsp_options = {
@@ -120,7 +125,7 @@ module TypeProf::CLI
120
125
  if @lsp_options[:stdio]
121
126
  TypeProf::LSP::Server.start_stdio(@core_options)
122
127
  else
123
- TypeProf::LSP::Server.start_socket(@core_options)
128
+ TypeProf::LSP::Server.start_socket(@core_options, @lsp_options[:port])
124
129
  end
125
130
  rescue Exception
126
131
  puts $!.detailed_message(highlight: false).gsub(/^/, "---")
@@ -151,7 +156,7 @@ module TypeProf::CLI
151
156
  files = []
152
157
  @cli_options[:argv].each do |path|
153
158
  if File.directory?(path)
154
- files.concat(Dir.glob("#{ path }/**/*.{rb,rbs}"))
159
+ files.concat(Dir.glob("#{ path }/**/*.{rb,rbs}").select {|f| File.file?(f) })
155
160
  elsif File.file?(path)
156
161
  files << path
157
162
  else
@@ -189,7 +194,8 @@ module TypeProf::CLI
189
194
  {
190
195
  "typeprof_version": "experimental",
191
196
  "rbs_dir": "sig/",
192
- "analysis_unit_dirs": #{exist_dirs.inspect}
197
+ "analysis_unit_dirs": #{exist_dirs.inspect},
198
+ // "exclude": ["**/templates/**/*.rb"],
193
199
  // "diagnostic_severity": "warning"
194
200
  }
195
201
  JSONC
@@ -55,12 +55,14 @@ module TypeProf
55
55
  raise unless first
56
56
  end
57
57
 
58
- def self.from_node(node, encoding = Encoding::UTF_16LE)
58
+ def self.from_node(node, file_context)
59
59
  node = node.location if node.respond_to?(:location)
60
- if node.is_a?(Prism::Location)
61
- pos1 = CodePosition.new(node.start_line, node.start_code_units_column(encoding))
62
- pos2 = CodePosition.new(node.end_line, node.end_code_units_column(encoding))
63
- elsif node.is_a?(RBS::Location)
60
+ case node
61
+ when Prism::Location
62
+ start_col, end_col = file_context.column_offsets_for(node)
63
+ pos1 = CodePosition.new(node.start_line, start_col)
64
+ pos2 = CodePosition.new(node.end_line, end_col)
65
+ when RBS::Location
64
66
  pos1 = CodePosition.new(*node.start_loc)
65
67
  pos2 = CodePosition.new(*node.end_loc)
66
68
  else
@@ -9,7 +9,7 @@ module TypeProf::Core
9
9
  @ret = nil
10
10
 
11
11
  @changes = ChangeSet.new(self, nil)
12
- @diagnostics = Set[]
12
+ @diagnostics = Set::EMPTY
13
13
  end
14
14
 
15
15
  attr_reader :lenv
@@ -53,7 +53,7 @@ module TypeProf::Core
53
53
 
54
54
  def code_range
55
55
  if @raw_node
56
- TypeProf::CodeRange.from_node(@raw_node)
56
+ @lenv.code_range_from_node(@raw_node)
57
57
  else
58
58
  pp self
59
59
  raise
@@ -104,6 +104,7 @@ module TypeProf::Core
104
104
  @changes.reuse(self)
105
105
  (@prev_node || raise).diagnostics.each do |diag|
106
106
  diag.reuse(self)
107
+ @diagnostics = Set.empty if @diagnostics.equal?(Set::EMPTY)
107
108
  @diagnostics << diag
108
109
  end
109
110
  each_subnode do |subnode|
@@ -117,6 +118,19 @@ module TypeProf::Core
117
118
  raise "should override"
118
119
  end
119
120
 
121
+ # Counterpart of install/install0 for pattern position; reuses the install
122
+ # machinery so @changes is reconciled the same way during incremental analysis.
123
+ def install_pattern(genv, subject)
124
+ @ret = install_pattern0(genv, subject)
125
+ @changes.reinstall(genv)
126
+ @ret
127
+ end
128
+
129
+ # By default a pattern behaves as a plain expression, ignoring the matched value.
130
+ def install_pattern0(genv, subject)
131
+ install0(genv)
132
+ end
133
+
120
134
  def uninstall(genv)
121
135
  @changes.reinstall(genv)
122
136
  each_subnode do |subnode|
@@ -129,11 +143,12 @@ module TypeProf::Core
129
143
  end
130
144
 
131
145
  def add_diagnostic(diag)
146
+ @diagnostics = Set.empty if @diagnostics.equal?(Set::EMPTY)
132
147
  @diagnostics << diag
133
148
  end
134
149
 
135
150
  def remove_diagnostic(diag)
136
- @diagnostics.delete(diag)
151
+ @diagnostics.delete(diag) unless @diagnostics.equal?(Set::EMPTY)
137
152
  end
138
153
 
139
154
  def diff(prev_node)
@@ -209,20 +224,30 @@ module TypeProf::Core
209
224
  end
210
225
 
211
226
  class ProgramNode < Node
212
- def initialize(raw_node, lenv)
227
+ def initialize(raw_node, lenv, ignore_ranges: [])
213
228
  super(raw_node, lenv)
214
229
 
215
230
  @tbl = raw_node.locals
231
+ @ignore_ranges = ignore_ranges
216
232
  raw_body = raw_node.statements
217
233
 
218
234
  @body = AST.create_node(raw_body, lenv, false)
219
235
  end
220
236
 
221
- attr_reader :tbl, :body
237
+ attr_reader :tbl, :ignore_ranges, :body
222
238
 
223
239
  def subnodes = { body: }
224
240
  def attrs = { tbl: }
225
241
 
242
+ def each_diagnostic(genv, &blk)
243
+ return super if @ignore_ranges.empty?
244
+ super(genv) do |diag|
245
+ line = diag.code_range&.first&.lineno
246
+ next if line && @ignore_ranges.any? { |r| r.cover?(line) }
247
+ blk.call(diag)
248
+ end
249
+ end
250
+
226
251
  def install0(genv)
227
252
  @tbl.each {|var| @lenv.locals[var] = Source.new(genv.nil_type) }
228
253
  @lenv.locals[:"*self"] = lenv.cref.get_self(genv)
@@ -270,7 +295,7 @@ module TypeProf::Core
270
295
  @ret = ret
271
296
  end
272
297
 
273
- attr_reader :lenv, :prev_node, :ret
298
+ attr_reader :lenv, :prev_node, :code_range, :ret
274
299
 
275
300
  def boxes(_)
276
301
  []
@@ -1,12 +1,12 @@
1
1
  module TypeProf::Core
2
2
  class AST
3
3
  class CallBaseNode < Node
4
- def initialize(raw_node, recv, mid, mid_code_range, raw_args, last_arg, raw_block, lenv)
4
+ def initialize(raw_node, recv, mid, mid_code_range_loc, raw_args, last_arg, raw_block, lenv, forwarding_arguments: false)
5
5
  super(raw_node, lenv)
6
6
 
7
7
  @recv = recv
8
8
  @mid = mid
9
- @mid_code_range = mid_code_range
9
+ @mid_code_range_loc = mid_code_range_loc
10
10
 
11
11
  # args
12
12
  @positional_args = []
@@ -16,9 +16,11 @@ module TypeProf::Core
16
16
  @block_pass = nil
17
17
  @block_tbl = nil
18
18
  @block_f_args = nil
19
+ @block_opt_positional_defaults = nil
19
20
  @block_body = nil
20
21
  @safe_navigation = raw_node.respond_to?(:safe_navigation?) && raw_node.safe_navigation?
21
22
  @anonymous_block_forwarding = false
23
+ @forwarding_arguments = forwarding_arguments
22
24
 
23
25
  if raw_args
24
26
  args = []
@@ -29,13 +31,13 @@ module TypeProf::Core
29
31
  args << raw_arg.expression
30
32
  @splat_flags << true
31
33
  when Prism::ForwardingArgumentsNode
32
- # TODO: Support forwarding arguments
34
+ @forwarding_arguments = :rest
33
35
  else
34
36
  args << raw_arg
35
37
  @splat_flags << false
36
38
  end
37
39
  end
38
- @positional_args = args.map {|arg| arg ? AST.create_node(arg, lenv) : DummyNilNode.new(code_range, lenv) }
40
+ @positional_args = args.map {|arg| arg ? AST.create_node(arg, lenv) : nil }
39
41
 
40
42
  kw = @positional_args.last
41
43
  if kw.is_a?(TypeProf::Core::AST::HashNode) && kw.keywords
@@ -55,10 +57,27 @@ module TypeProf::Core
55
57
  else
56
58
  @block_pass = nil
57
59
  @block_tbl = raw_block.locals
58
- # TODO: optional args, etc.
60
+ @block_multi_targets = {}
59
61
  @block_f_args = case raw_block.parameters
60
62
  when Prism::BlockParametersNode
61
- raw_block.parameters.parameters.requireds.map {|n| n.is_a?(Prism::MultiTargetNode) ? nil : n.name }
63
+ # `{ || ... }` (empty pipes) and `{ |; x| ... }`
64
+ # (block-local-only) yield BlockParametersNode
65
+ # whose inner `parameters` is nil.
66
+ params = raw_block.parameters.parameters
67
+ if params
68
+ req = params.requireds.each_with_index.map do |n, i|
69
+ if n.is_a?(Prism::MultiTargetNode)
70
+ @block_multi_targets[i] = n
71
+ nil
72
+ else
73
+ n.name
74
+ end
75
+ end
76
+ opt = params.optionals.map {|n| n.name }
77
+ req + opt
78
+ else
79
+ []
80
+ end
62
81
  when Prism::NumberedParametersNode
63
82
  1.upto(raw_block.parameters.maximum).map { |n| :"_#{n}" }
64
83
  when Prism::ItParametersNode
@@ -69,7 +88,13 @@ module TypeProf::Core
69
88
  raise "not supported yet: #{ raw_block.parameters.class }"
70
89
  end
71
90
  ncref = CRef.new(lenv.cref.cpath, :instance, @mid, lenv.cref)
72
- nlenv = LocalEnv.new(@lenv.path, ncref, {}, @lenv.return_boxes)
91
+ nlenv = LocalEnv.new(@lenv.file_context, ncref, {}, @lenv.return_boxes)
92
+ @block_opt_positional_defaults = []
93
+ if raw_block.parameters.is_a?(Prism::BlockParametersNode) && raw_block.parameters.parameters
94
+ raw_block.parameters.parameters.optionals.each do |n|
95
+ @block_opt_positional_defaults << AST.create_node(n.value, nlenv)
96
+ end
97
+ end
73
98
  @block_body = raw_block.body ? AST.create_node(raw_block.body, nlenv) : DummyNilNode.new(code_range, lenv)
74
99
  end
75
100
  end
@@ -77,13 +102,18 @@ module TypeProf::Core
77
102
  @yield = raw_node.type == :yield_node
78
103
  end
79
104
 
80
- attr_reader :recv, :mid, :mid_code_range, :yield
105
+ attr_reader :recv, :mid, :yield
106
+
107
+ def mid_code_range
108
+ @mid_code_range ||= @lenv.code_range_from_node(@mid_code_range_loc) if @mid_code_range_loc
109
+ end
81
110
  attr_reader :positional_args, :splat_flags, :keyword_args
82
- attr_reader :block_tbl, :block_f_args, :block_body, :block_pass, :anonymous_block_forwarding
83
- attr_reader :safe_navigation
111
+ attr_reader :block_tbl, :block_f_args, :block_opt_positional_defaults, :block_body, :block_pass, :anonymous_block_forwarding
112
+ attr_reader :block_multi_targets
113
+ attr_reader :safe_navigation, :forwarding_arguments
84
114
 
85
- def subnodes = { recv:, positional_args:, keyword_args:, block_body:, block_pass: }
86
- def attrs = { mid:, splat_flags:, block_tbl:, block_f_args:, yield:, safe_navigation:, anonymous_block_forwarding: }
115
+ def subnodes = { recv:, positional_args:, keyword_args:, block_opt_positional_defaults:, block_body:, block_pass: }
116
+ def attrs = { mid:, splat_flags:, block_tbl:, block_f_args:, yield:, safe_navigation:, anonymous_block_forwarding:, forwarding_arguments: }
87
117
 
88
118
  def install0(genv)
89
119
  recv = @recv ? @recv.install(genv) : @yield ? @lenv.get_var(:"*given_block") : @lenv.get_var(:"*self")
@@ -93,22 +123,36 @@ module TypeProf::Core
93
123
  recv = NilFilter.new(genv, self, recv, false).next_vtx
94
124
  end
95
125
 
96
- positional_args = @positional_args.map do |arg|
97
- if arg.is_a?(DummyNilNode)
98
- @lenv.get_var(:"*anonymous_rest")
99
- else
100
- arg.install(genv)
126
+ if @forwarding_arguments
127
+ forward_a_args = (@lenv.forward_args || raise).to_actual_arguments(
128
+ genv,
129
+ @changes,
130
+ self,
131
+ include_leading_positionals: @forwarding_arguments != :rest,
132
+ activation_required: @forwarding_arguments == :rest,
133
+ )
134
+ # An anonymous rest cannot appear here: `bar(*, ...)` is a syntax error
135
+ leading_args = @positional_args.map {|arg| arg.install(genv) }
136
+ a_args = forward_a_args.prepend_positionals(leading_args, @splat_flags)
137
+ a_args = a_args.with_keywords(@keyword_args.install(genv)) if @keyword_args
138
+ else
139
+ positional_args = @positional_args.map do |arg|
140
+ if arg.nil?
141
+ @lenv.get_var(:"*anonymous_rest")
142
+ else
143
+ arg.install(genv)
144
+ end
101
145
  end
146
+ a_args = ActualArguments.new(positional_args, @splat_flags, @keyword_args ? @keyword_args.install(genv) : nil, nil)
102
147
  end
103
148
 
104
- keyword_args = @keyword_args ? @keyword_args.install(genv) : nil
105
-
106
149
  if @block_body
107
150
  block_body = @block_body # kinda type annotationty
108
151
  block_tbl = @block_tbl || raise
152
+ block_body.lenv.forward_args = @lenv.forward_args
109
153
  @lenv.locals.each {|var, vtx| block_body.lenv.locals[var] = vtx }
110
154
  block_tbl.each {|var| block_body.lenv.locals[var] = Source.new(genv.nil_type) }
111
- @block_body.lenv.locals[:"*self"] = @block_body.lenv.cref.get_self(genv)
155
+ block_body.lenv.locals[:"*self"] = block_body.lenv.cref.get_self(genv)
112
156
 
113
157
  blk_f_args = []
114
158
  if @block_f_args
@@ -117,11 +161,28 @@ module TypeProf::Core
117
161
  end
118
162
  end
119
163
 
164
+ if @block_opt_positional_defaults && !@block_opt_positional_defaults.empty?
165
+ req_count = blk_f_args.size - @block_opt_positional_defaults.size
166
+ @block_opt_positional_defaults.each_with_index do |expr, i|
167
+ @changes.add_edge(genv, expr.install(genv), blk_f_args[req_count + i])
168
+ end
169
+ end
170
+
171
+ if @block_multi_targets
172
+ @block_multi_targets.each do |idx, raw_multi_target|
173
+ param_vtx = blk_f_args[idx]
174
+ lefts = raw_multi_target.lefts.map do |n|
175
+ block_body.lenv.new_var(n.is_a?(Prism::MultiTargetNode) ? nil : n.name, self)
176
+ end
177
+ @changes.add_masgn_box(genv, param_vtx, lefts, nil, nil)
178
+ end
179
+ end
180
+
120
181
  @lenv.locals.each do |var, vtx|
121
182
  block_body.lenv.set_var(var, vtx)
122
183
  end
123
184
  vars = []
124
- @block_body.modified_vars(@lenv.locals.keys - block_tbl, vars)
185
+ block_body.modified_vars(@lenv.locals.keys - block_tbl, vars)
125
186
  vars.uniq!
126
187
  vars.each do |var|
127
188
  vtx = @lenv.get_var(var)
@@ -130,25 +191,35 @@ module TypeProf::Core
130
191
  block_body.lenv.set_var(var, nvtx)
131
192
  end
132
193
 
133
- @block_body.lenv.locals[:"*expected_block_ret"] = Vertex.new(self)
134
- @block_body.install(genv)
135
- @block_body.lenv.add_next_box(@changes.add_escape_box(genv, @block_body.ret))
194
+ block_body.lenv.locals[:"*expected_block_ret"] = Vertex.new(self)
195
+ block_body.install(genv)
196
+ block_body.lenv.add_next_box(@changes.add_escape_box(genv, block_body.ret))
136
197
 
137
198
  vars.each do |var|
138
199
  @changes.add_edge(genv, block_body.lenv.get_var(var), @lenv.get_var(var))
139
200
  end
140
201
 
141
202
  blk_f_ary_arg = Vertex.new(self)
142
- @changes.add_masgn_box(genv, blk_f_ary_arg, blk_f_args, nil, nil) # TODO: support splat "do |a, *b, c|"
143
- block = Block.new(self, blk_f_ary_arg, blk_f_args, @block_body.lenv.next_boxes)
203
+ # TODO: support splat "do |a, *b, c|"
204
+ blk_f_args.each_with_index do |f_arg, i|
205
+ elem_vtx = @changes.add_splat_box(genv, blk_f_ary_arg, i).ret
206
+ @changes.add_edge(genv, elem_vtx, f_arg)
207
+ end
208
+ block = Block.new(self, blk_f_ary_arg, blk_f_args, block_body.lenv.next_boxes)
144
209
  blk_ty = Source.new(Type::Proc.new(genv, block))
145
210
  elsif @block_pass
146
211
  blk_ty = @block_pass.install(genv)
147
212
  elsif @anonymous_block_forwarding
148
213
  blk_ty = @lenv.get_var(:"*anonymous_block")
214
+ elsif @forwarding_arguments
215
+ blk_ty = forward_a_args.block
149
216
  end
150
217
 
151
- a_args = ActualArguments.new(positional_args, @splat_flags, keyword_args, blk_ty)
218
+ if @forwarding_arguments
219
+ a_args = a_args.with_block(blk_ty, omittable: !@block_body && !@block_pass && !@anonymous_block_forwarding)
220
+ else
221
+ a_args = a_args.with_block(blk_ty)
222
+ end
152
223
  box = @changes.add_method_call_box(genv, recv, @mid, a_args, !@recv)
153
224
 
154
225
  block_body = @block_body
@@ -180,7 +251,7 @@ module TypeProf::Core
180
251
  end
181
252
 
182
253
  def retrieve_at(pos, &blk)
183
- yield self if @mid_code_range && @mid_code_range.include?(pos)
254
+ yield self if mid_code_range&.include?(pos)
184
255
  each_subnode do |subnode|
185
256
  next unless subnode
186
257
  subnode.retrieve_at(pos, &blk)
@@ -197,7 +268,7 @@ module TypeProf::Core
197
268
  subnode.modified_vars(tbl, vars)
198
269
  end
199
270
  else
200
- subnode.each {|n| n.modified_vars(tbl, vars) }
271
+ subnode.each {|n| n&.modified_vars(tbl, vars) }
201
272
  end
202
273
  end
203
274
  end
@@ -207,10 +278,9 @@ module TypeProf::Core
207
278
  def initialize(raw_node, lenv)
208
279
  recv = raw_node.receiver ? AST.create_node(raw_node.receiver, lenv) : nil
209
280
  mid = raw_node.name
210
- mid_code_range = TypeProf::CodeRange.from_node(raw_node.message_loc) if raw_node.message_loc
211
281
  raw_args = raw_node.arguments
212
282
  raw_block = raw_node.block
213
- super(raw_node, recv, mid, mid_code_range, raw_args, nil, raw_block, lenv)
283
+ super(raw_node, recv, mid, raw_node.message_loc, raw_args, nil, raw_block, lenv)
214
284
  end
215
285
 
216
286
  def narrowings
@@ -231,6 +301,20 @@ module TypeProf::Core
231
301
  else
232
302
  super
233
303
  end
304
+ when :nil?
305
+ if @recv.is_a?(LocalVariableReadNode)
306
+ [
307
+ Narrowing.new({ @recv.var => Narrowing::NilConstraint.new(true) }),
308
+ Narrowing.new({ @recv.var => Narrowing::NilConstraint.new(false) })
309
+ ]
310
+ elsif @recv.is_a?(InstanceVariableReadNode)
311
+ [
312
+ Narrowing.new({ @recv.var => Narrowing::NilConstraint.new(true) }),
313
+ Narrowing.new({ @recv.var => Narrowing::NilConstraint.new(false) })
314
+ ]
315
+ else
316
+ super
317
+ end
234
318
  when :!
235
319
  then_narrowing, else_narrowing = @recv.narrowings
236
320
  [else_narrowing, then_narrowing]
@@ -251,9 +335,9 @@ module TypeProf::Core
251
335
 
252
336
  class ForwardingSuperNode < CallBaseNode
253
337
  def initialize(raw_node, lenv)
254
- raw_args = nil # TODO: forward args properly
338
+ raw_args = nil
255
339
  raw_block = raw_node.block
256
- super(raw_node, nil, :"*super", nil, raw_args, nil, raw_block, lenv)
340
+ super(raw_node, nil, :"*super", nil, raw_args, nil, raw_block, lenv, forwarding_arguments: :all)
257
341
  end
258
342
  end
259
343
 
@@ -267,9 +351,8 @@ module TypeProf::Core
267
351
  class OperatorNode < CallBaseNode
268
352
  def initialize(raw_node, recv, lenv)
269
353
  mid = raw_node.binary_operator
270
- mid_code_range = TypeProf::CodeRange.from_node(raw_node.binary_operator_loc)
271
354
  last_arg = AST.create_node(raw_node.value, lenv)
272
- super(raw_node, recv, mid, mid_code_range, nil, last_arg, nil, lenv)
355
+ super(raw_node, recv, mid, raw_node.binary_operator_loc, nil, last_arg, nil, lenv)
273
356
  end
274
357
  end
275
358
 
@@ -300,8 +383,7 @@ module TypeProf::Core
300
383
  def initialize(raw_node, lenv)
301
384
  recv = AST.create_node(raw_node.receiver, lenv)
302
385
  mid = raw_node.read_name
303
- mid_code_range = TypeProf::CodeRange.from_node(raw_node.message_loc)
304
- super(raw_node, recv, mid, mid_code_range, nil, nil, nil, lenv)
386
+ super(raw_node, recv, mid, raw_node.message_loc, nil, nil, nil, lenv)
305
387
  end
306
388
  end
307
389
 
@@ -309,9 +391,8 @@ module TypeProf::Core
309
391
  def initialize(raw_node, rhs, lenv)
310
392
  recv = AST.create_node(raw_node.receiver, lenv)
311
393
  mid = raw_node.is_a?(Prism::CallTargetNode) ? raw_node.name : raw_node.write_name
312
- mid_code_range = TypeProf::CodeRange.from_node(raw_node.message_loc)
313
394
  @rhs = rhs
314
- super(raw_node, recv, mid, mid_code_range, nil, rhs, nil, lenv)
395
+ super(raw_node, recv, mid, raw_node.message_loc, nil, rhs, nil, lenv)
315
396
  end
316
397
 
317
398
  attr_reader :rhs