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
@@ -8,7 +8,7 @@ module TypeProf::Core
8
8
  @cbase = nil
9
9
  @toplevel = false
10
10
  @cname = raw_node.name
11
- @cname_code_range = TypeProf::CodeRange.from_node(raw_node.location)
11
+ @cname_code_range_loc = raw_node.location
12
12
  when :constant_path_node, :constant_path_target_node
13
13
  if raw_node.parent
14
14
  @cbase = AST.create_node(raw_node.parent, lenv)
@@ -18,14 +18,18 @@ module TypeProf::Core
18
18
  @toplevel = true
19
19
  end
20
20
  @cname = raw_node.name
21
- @cname_code_range = TypeProf::CodeRange.from_node(raw_node.name_loc)
21
+ @cname_code_range_loc = raw_node.name_loc
22
22
  else
23
23
  raise raw_node.type.to_s
24
24
  end
25
25
  @strict_const_scope = lenv.strict_const_scope
26
26
  end
27
27
 
28
- attr_reader :cname, :cbase, :toplevel, :cname_code_range, :strict_const_scope
28
+ attr_reader :cname, :cbase, :toplevel, :strict_const_scope
29
+
30
+ def cname_code_range
31
+ @cname_code_range ||= @lenv.code_range_from_node(@cname_code_range_loc) if @cname_code_range_loc
32
+ end
29
33
 
30
34
  def attrs = { cname:, toplevel:, strict_const_scope: }
31
35
  def subnodes = { cbase: }
@@ -57,24 +61,28 @@ module TypeProf::Core
57
61
  # C = expr
58
62
  @cpath = nil
59
63
  @static_cpath = lenv.cref.cpath + [raw_node.name]
60
- @cname_code_range = TypeProf::CodeRange.from_node(raw_node.respond_to?(:name_loc) ? raw_node.name_loc : raw_node)
64
+ @cname_code_range_loc = raw_node.respond_to?(:name_loc) ? raw_node.name_loc : raw_node.location
61
65
  when :constant_path_write_node, :constant_path_operator_write_node, :constant_path_or_write_node, :constant_path_and_write_node
62
66
  # expr::C = expr
63
67
  @cpath = AST.create_node(raw_node.target, lenv)
64
68
  @static_cpath = AST.parse_cpath(raw_node.target, lenv.cref)
65
- @cname_code_range = TypeProf::CodeRange.from_node(raw_node.target)
69
+ @cname_code_range_loc = raw_node.target.location
66
70
  when :constant_path_target_node
67
71
  # expr::C, * = ary
68
72
  @cpath = ConstantReadNode.new(raw_node, lenv)
69
73
  @static_cpath = AST.parse_cpath(raw_node, lenv.cref)
70
- @cname_code_range = TypeProf::CodeRange.from_node(raw_node)
74
+ @cname_code_range_loc = raw_node.location
71
75
  else
72
76
  raise
73
77
  end
74
78
  @rhs = rhs
75
79
  end
76
80
 
77
- attr_reader :cpath, :rhs, :static_cpath, :cname_code_range
81
+ attr_reader :cpath, :rhs, :static_cpath
82
+
83
+ def cname_code_range
84
+ @cname_code_range ||= @lenv.code_range_from_node(@cname_code_range_loc) if @cname_code_range_loc
85
+ end
78
86
 
79
87
  def subnodes = { cpath:, rhs: }
80
88
  def attrs = { static_cpath: }
@@ -83,9 +91,10 @@ module TypeProf::Core
83
91
  @cpath.define(genv) if @cpath
84
92
  @rhs.define(genv) if @rhs
85
93
  if @static_cpath
86
- mod = genv.resolve_const(@static_cpath)
87
- mod.add_def(self)
88
- mod
94
+ cdef = genv.resolve_const(@static_cpath)
95
+ cdef.on_const_added(genv, @static_cpath)
96
+ cdef.add_def(self)
97
+ cdef
89
98
  else
90
99
  nil
91
100
  end
@@ -102,7 +111,9 @@ module TypeProf::Core
102
111
 
103
112
  def undefine0(genv)
104
113
  if @static_cpath
105
- genv.resolve_const(@static_cpath).remove_def(self)
114
+ cdef = genv.resolve_const(@static_cpath)
115
+ cdef.remove_def(self)
116
+ cdef.on_const_removed(genv, @static_cpath)
106
117
  end
107
118
  @rhs.undefine(genv) if @rhs
108
119
  @cpath.undefine(genv) if @cpath
@@ -203,7 +203,7 @@ module TypeProf::Core
203
203
 
204
204
  def install0(genv)
205
205
  arg = @arg.install(genv)
206
- @changes.add_edge(genv, arg, @lenv.get_break_vtx)
206
+ @changes.add_edge(genv, arg.new_vertex(genv, self), @lenv.get_break_vtx)
207
207
  Source.new()
208
208
  end
209
209
  end
@@ -236,7 +236,7 @@ module TypeProf::Core
236
236
  if @lenv.exist_var?(:"*expected_block_ret")
237
237
  @lenv.add_next_box(@changes.add_escape_box(genv, @arg.ret))
238
238
  end
239
- Source.new(Type::Bot.new(genv))
239
+ Source.new(genv.bot_type)
240
240
  end
241
241
  end
242
242
 
@@ -322,44 +322,74 @@ module TypeProf::Core
322
322
  ret = Vertex.new(self)
323
323
  @pivot&.install(genv)
324
324
 
325
- # case文での型絞り込みを実装
326
- if @pivot && @pivot.is_a?(LocalVariableReadNode)
327
- var = @pivot.var
328
- original_vtx = @lenv.get_var(var)
325
+ # Collect modified variables across all branches
326
+ vars = []
327
+ @when_nodes.each {|wn| wn.body.modified_vars(@lenv.locals.keys, vars) }
328
+ @else_clause.modified_vars(@lenv.locals.keys, vars) if @else_clause
329
+ vars.uniq!
329
330
 
330
- # ダミー変数に元の型情報を設定
331
- @lenv.set_var(:"*pivot", original_vtx)
331
+ # Save original variable vertices
332
+ saved_vtxs = {}
333
+ vars.each do |var|
334
+ saved_vtxs[var] = @lenv.get_var(var)
335
+ end
332
336
 
333
- # 各when節を実行
334
- @when_nodes.each do |when_node|
335
- clause_result = when_node.install(genv)
336
- @changes.add_edge(genv, clause_result, ret)
337
- # 元の型に戻す
338
- @lenv.set_var(var, original_vtx)
339
- end
337
+ # Prepare per-branch result vertices
338
+ branch_vtxs = []
340
339
 
341
- # else節(他のwhen節で除外された後の型)
342
- filtered_else_vtx = original_vtx.new_vertex(genv, self)
340
+ # Setup pivot narrowing if applicable
341
+ pivot_var = @pivot.is_a?(LocalVariableReadNode) ? @pivot.var : nil
342
+ if pivot_var
343
+ original_pivot = @lenv.get_var(pivot_var)
344
+ @lenv.set_var(:"*pivot", original_pivot)
345
+ end
346
+
347
+ # Install each when branch
348
+ @when_nodes.each do |when_node|
349
+ # Reset variables to original for each branch
350
+ saved_vtxs.each {|var, vtx| @lenv.set_var(var, vtx.new_vertex(genv, self)) }
351
+ @lenv.set_var(pivot_var, original_pivot) if pivot_var
352
+
353
+ clause_val = when_node.install(genv)
354
+ @changes.add_edge(genv, clause_val, ret)
355
+
356
+ modified = {}
357
+ vars.each {|var| modified[var] = @lenv.get_var(var) }
358
+ branch_vtxs << [clause_val, modified]
359
+ end
360
+
361
+ # Install else branch
362
+ saved_vtxs.each {|var, vtx| @lenv.set_var(var, vtx.new_vertex(genv, self)) }
363
+ if pivot_var
364
+ # Apply exclusion filters for else
365
+ filtered_else_vtx = original_pivot.new_vertex(genv, self)
343
366
  @when_nodes.each do |when_node|
344
367
  when_node.get_exclusion_conditions.each do |static_ret|
345
- # 各when節の型を除外(negation)
346
368
  filtered_else_vtx = IsAFilter.new(genv, self, filtered_else_vtx, true, static_ret).next_vtx
347
369
  end
348
370
  end
349
- @lenv.set_var(var, filtered_else_vtx)
350
- @changes.add_edge(genv, @else_clause.install(genv), ret)
351
- @lenv.set_var(var, original_vtx)
371
+ @lenv.set_var(pivot_var, filtered_else_vtx)
372
+ end
373
+ else_val = @else_clause.install(genv)
374
+ @changes.add_edge(genv, else_val, ret)
352
375
 
353
- # ダミー変数をクリア
354
- @lenv.locals.delete(:"*pivot")
355
- else
356
- # pivotが変数でない場合は従来通り
357
- @when_nodes.each do |when_node|
358
- @changes.add_edge(genv, when_node.install(genv), ret)
376
+ else_modified = {}
377
+ vars.each {|var| else_modified[var] = @lenv.get_var(var) }
378
+ branch_vtxs << [else_val, else_modified]
379
+
380
+ # Join all branches
381
+ vars.each do |var|
382
+ joined = Vertex.new(self)
383
+ branch_vtxs.each do |branch_val, modified|
384
+ vtx = BotFilter.new(genv, self, modified[var], branch_val).next_vtx
385
+ @changes.add_edge(genv, vtx, joined)
359
386
  end
360
- @changes.add_edge(genv, @else_clause.install(genv), ret)
387
+ @lenv.set_var(var, joined)
361
388
  end
362
389
 
390
+ # Cleanup
391
+ @lenv.locals.delete(:"*pivot") if pivot_var
392
+
363
393
  ret
364
394
  end
365
395
  end
@@ -384,9 +414,9 @@ module TypeProf::Core
384
414
 
385
415
  def install0(genv)
386
416
  ret = Vertex.new(self)
387
- @pivot&.install(genv)
417
+ subject = @pivot ? @pivot.install(genv) : Source.new(genv.nil_type)
388
418
  @patterns.zip(@clauses) do |pattern, clause|
389
- pattern.install(genv)
419
+ pattern.install_pattern(genv, subject)
390
420
  @changes.add_edge(genv, clause.install(genv), ret)
391
421
  end
392
422
  @changes.add_edge(genv, @else_clause.install(genv), ret) if @else_clause
@@ -496,7 +526,7 @@ module TypeProf::Core
496
526
  @arg.install(genv)
497
527
  e_ret = @lenv.locals[:"*expected_method_ret"]
498
528
  @lenv.add_return_box(@changes.add_escape_box(genv, @arg.ret)) if e_ret
499
- Source.new(Type::Bot.new(genv))
529
+ Source.new(genv.bot_type)
500
530
  end
501
531
  end
502
532
 
@@ -681,7 +711,7 @@ module TypeProf::Core
681
711
  end
682
712
 
683
713
  def install0(genv)
684
- Source.new(Type::Bot.new(genv))
714
+ Source.new(genv.bot_type)
685
715
  end
686
716
  end
687
717
 
@@ -46,9 +46,56 @@ module TypeProf::Core
46
46
  end
47
47
  end
48
48
 
49
+ class ExtendMetaNode < Node
50
+ def initialize(raw_node, lenv)
51
+ super(raw_node, lenv)
52
+ # TODO: error for splat
53
+ @args = raw_node.arguments.arguments.map do |raw_arg|
54
+ next if raw_arg.is_a?(Prism::SplatNode)
55
+ lenv.use_strict_const_scope do
56
+ AST.create_node(raw_arg, lenv)
57
+ end
58
+ end.compact
59
+ # TODO: error for non-LIT
60
+ # TODO: fine-grained hover
61
+ end
62
+
63
+ attr_reader :args
64
+
65
+ def subnodes = { args: }
66
+
67
+ def define0(genv)
68
+ mod = genv.resolve_cpath(@lenv.cref.cpath)
69
+ @args.each do |arg|
70
+ arg.define(genv)
71
+ if arg.static_ret
72
+ arg.static_ret.followers << mod
73
+ mod.add_extend_def(genv, arg)
74
+ end
75
+ end
76
+ end
77
+
78
+ def undefine0(genv)
79
+ mod = genv.resolve_cpath(@lenv.cref.cpath)
80
+ @args.each do |arg|
81
+ if arg.static_ret
82
+ mod.remove_extend_def(genv, arg)
83
+ end
84
+ arg.undefine(genv)
85
+ end
86
+ super(genv)
87
+ end
88
+
89
+ def install0(genv)
90
+ @args.each {|arg| arg.install(genv) }
91
+ Source.new
92
+ end
93
+ end
94
+
49
95
  class AttrReaderMetaNode < Node
50
96
  def initialize(raw_node, lenv)
51
97
  super(raw_node, lenv)
98
+ @singleton = lenv.cref.scope_level == :metaclass
52
99
  @args = []
53
100
  raw_node.arguments.arguments.each do |raw_arg|
54
101
  @args << raw_arg.value.to_sym if raw_arg.type == :symbol_node
@@ -57,9 +104,9 @@ module TypeProf::Core
57
104
  # TODO: fine-grained hover
58
105
  end
59
106
 
60
- attr_reader :args
107
+ attr_reader :singleton, :args
61
108
 
62
- def attrs = { args: }
109
+ def attrs = { singleton:, args: }
63
110
 
64
111
  def req_positionals = []
65
112
  def opt_positionals = []
@@ -68,19 +115,83 @@ module TypeProf::Core
68
115
  def req_keywords = []
69
116
  def opt_keywords = []
70
117
  def rest_keywords = nil
118
+ def no_keywords = false
71
119
 
72
120
  def mname_code_range(name)
73
121
  idx = @args.index(name.to_sym) # TODO: support string args
74
122
  node = @raw_node.arguments.arguments[idx].location
75
- TypeProf::CodeRange.from_node(node)
123
+ @lenv.code_range_from_node(node)
76
124
  end
77
125
 
78
126
  def install0(genv)
79
127
  @args.each do |arg|
80
128
  ivar_name = :"@#{ arg }"
81
- ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, false, ivar_name)
129
+ ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, @singleton, ivar_name)
82
130
  ret_box = @changes.add_escape_box(genv, ivar_box.ret)
83
- @changes.add_method_def_box(genv, @lenv.cref.cpath, false, arg, FormalArguments::Empty, [ret_box])
131
+ @changes.add_method_def_box(genv, @lenv.cref.cpath, @singleton, arg, FormalArguments::Empty, [ret_box])
132
+ end
133
+ Source.new
134
+ end
135
+ end
136
+
137
+ class AttrWriterMetaNode < Node
138
+ def initialize(raw_node, lenv)
139
+ super(raw_node, lenv)
140
+ @singleton = lenv.cref.scope_level == :metaclass
141
+ @args = []
142
+ raw_node.arguments.arguments.each do |raw_arg|
143
+ @args << raw_arg.value.to_sym if raw_arg.type == :symbol_node
144
+ end
145
+ end
146
+
147
+ attr_reader :singleton, :args
148
+
149
+ def attrs = { singleton:, args: }
150
+
151
+ # Interface expected by MethodDefBox
152
+ def req_keywords = []
153
+ def opt_keywords = []
154
+ def rest_keywords = nil
155
+ def no_keywords = false
156
+
157
+ def mname_code_range(name)
158
+ idx = @args.index(name.to_sym)
159
+ node = @raw_node.arguments.arguments[idx].location
160
+ @lenv.code_range_from_node(node)
161
+ end
162
+
163
+ def define0(genv)
164
+ @args.map do |arg|
165
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
166
+ mod.add_def(self)
167
+ mod
168
+ end
169
+ end
170
+
171
+ def define_copy(genv)
172
+ @args.map do |arg|
173
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
174
+ mod.add_def(self)
175
+ mod.remove_def(@prev_node)
176
+ mod
177
+ end
178
+ super(genv)
179
+ end
180
+
181
+ def undefine0(genv)
182
+ @args.each do |arg|
183
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
184
+ mod.remove_def(self)
185
+ end
186
+ end
187
+
188
+ def install0(genv)
189
+ @args.zip(@static_ret) do |arg, ive|
190
+ vtx = Vertex.new(self)
191
+ @changes.add_edge(genv, vtx, ive.vtx)
192
+ ret_box = @changes.add_escape_box(genv, vtx)
193
+ f_args = FormalArguments.new([vtx], [], nil, [], [], [], nil, nil)
194
+ @changes.add_method_def_box(genv, @lenv.cref.cpath, @singleton, :"#{ arg }=", f_args, [ret_box])
84
195
  end
85
196
  Source.new
86
197
  end
@@ -89,6 +200,7 @@ module TypeProf::Core
89
200
  class AttrAccessorMetaNode < Node
90
201
  def initialize(raw_node, lenv)
91
202
  super(raw_node, lenv)
203
+ @singleton = lenv.cref.scope_level == :metaclass
92
204
  @args = []
93
205
  raw_node.arguments.arguments.each do |raw_arg|
94
206
  @args << raw_arg.value.to_sym if raw_arg.type == :symbol_node
@@ -97,19 +209,25 @@ module TypeProf::Core
97
209
  # TODO: fine-grained hover
98
210
  end
99
211
 
100
- attr_reader :args
212
+ attr_reader :singleton, :args
101
213
 
102
- def attrs = { args: }
214
+ def attrs = { singleton:, args: }
215
+
216
+ # Interface expected by MethodDefBox
217
+ def req_keywords = []
218
+ def opt_keywords = []
219
+ def rest_keywords = nil
220
+ def no_keywords = false
103
221
 
104
222
  def mname_code_range(name)
105
223
  idx = @args.index(name.to_sym) # TODO: support string args
106
224
  node = @raw_node.arguments.arguments[idx].location
107
- TypeProf::CodeRange.from_node(node)
225
+ @lenv.code_range_from_node(node)
108
226
  end
109
227
 
110
228
  def define0(genv)
111
229
  @args.map do |arg|
112
- mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }")
230
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
113
231
  mod.add_def(self)
114
232
  mod
115
233
  end
@@ -117,7 +235,7 @@ module TypeProf::Core
117
235
 
118
236
  def define_copy(genv)
119
237
  @args.map do |arg|
120
- mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }")
238
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
121
239
  mod.add_def(self)
122
240
  mod.remove_def(@prev_node)
123
241
  mod
@@ -127,24 +245,171 @@ module TypeProf::Core
127
245
 
128
246
  def undefine0(genv)
129
247
  @args.each do |arg|
130
- mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }")
248
+ mod = genv.resolve_ivar(lenv.cref.cpath, @singleton, :"@#{ arg }")
131
249
  mod.remove_def(self)
132
250
  end
133
251
  end
134
252
 
135
253
  def install0(genv)
136
254
  @args.zip(@static_ret) do |arg, ive|
137
- ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, false, :"@#{ arg }")
255
+ ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, @singleton, :"@#{ arg }")
138
256
  ret_box = @changes.add_escape_box(genv, ivar_box.ret)
139
- @changes.add_method_def_box(genv, @lenv.cref.cpath, false, arg, FormalArguments::Empty, [ret_box])
257
+ @changes.add_method_def_box(genv, @lenv.cref.cpath, @singleton, arg, FormalArguments::Empty, [ret_box])
140
258
 
141
259
  vtx = Vertex.new(self)
142
260
  @changes.add_edge(genv, vtx, ive.vtx)
143
261
  f_args = FormalArguments.new([vtx], [], nil, [], [], [], nil, nil)
144
- @changes.add_method_def_box(genv, @lenv.cref.cpath, false, :"#{ arg }=", f_args, [ret_box])
262
+ @changes.add_method_def_box(genv, @lenv.cref.cpath, @singleton, :"#{ arg }=", f_args, [ret_box])
145
263
  end
146
264
  Source.new
147
265
  end
148
266
  end
267
+ class ModuleFunctionMetaNode < Node
268
+ def install0(genv)
269
+ @lenv.module_function = true
270
+ Source.new
271
+ end
272
+ end
273
+
274
+ class StructNewNode < Node
275
+ def initialize(raw_node, members, kind, lenv)
276
+ super(raw_node, lenv)
277
+ case raw_node.type
278
+ when :constant_write_node
279
+ @static_cpath = lenv.cref.cpath + [raw_node.name]
280
+ when :constant_path_write_node
281
+ @static_cpath = AST.parse_cpath(raw_node.target, lenv.cref)
282
+ else
283
+ raise
284
+ end
285
+ @members = members
286
+ @kind = kind # :struct or :data
287
+
288
+ # Parse block body if present (Struct.new(:foo) do ... end)
289
+ raw_value = raw_node.value
290
+ if raw_value.block && raw_value.block.type == :block_node && raw_value.block.body
291
+ ncref = CRef.new(@static_cpath, :instance, nil, lenv.cref)
292
+ nlenv = LocalEnv.new(lenv.file_context, ncref, {}, [])
293
+ @block_body = AST.create_node(raw_value.block.body, nlenv)
294
+ end
295
+ end
296
+
297
+ attr_reader :static_cpath, :members, :kind, :block_body
298
+
299
+ # Not a valid constant name, so no Ruby code can refer to it
300
+ BASE_CNAME = :"<struct base>"
301
+
302
+ def struct_base_cpath = @static_cpath + [BASE_CNAME]
303
+
304
+ def subnodes = { block_body: }
305
+ def attrs = { static_cpath:, members:, kind: }
306
+
307
+ # Interface expected by MethodDefBox
308
+ def req_positionals = @kind == :struct ? @members : []
309
+ def opt_positionals = []
310
+ def rest_positionals = nil
311
+ def post_positionals = []
312
+ def req_keywords = @kind == :data ? @members : []
313
+ def opt_keywords = []
314
+ def rest_keywords = nil
315
+ def no_keywords = @kind == :struct
316
+
317
+ def define0(genv)
318
+ mod = genv.resolve_cpath(@static_cpath)
319
+ # add_module_def internally calls get_const(name).add_def(self)
320
+ cdef = mod.add_module_def(genv, self)
321
+ genv.resolve_cpath(struct_base_cpath).add_module_def(genv, self)
322
+ @members.each do |member|
323
+ ive = genv.resolve_ivar(struct_base_cpath, false, member)
324
+ ive.add_def(self)
325
+ end
326
+ @block_body.define(genv) if @block_body
327
+ cdef
328
+ end
329
+
330
+ def define_copy(genv)
331
+ mod = genv.resolve_cpath(@static_cpath)
332
+ mod.add_module_def(genv, self)
333
+ mod.remove_module_def(genv, @prev_node)
334
+ base = genv.resolve_cpath(struct_base_cpath)
335
+ base.add_module_def(genv, self)
336
+ base.remove_module_def(genv, @prev_node)
337
+ @members.each do |member|
338
+ ive = genv.resolve_ivar(struct_base_cpath, false, member)
339
+ ive.add_def(self)
340
+ ive.remove_def(@prev_node)
341
+ end
342
+ super(genv)
343
+ end
344
+
345
+ def undefine0(genv)
346
+ mod = genv.resolve_cpath(@static_cpath)
347
+ mod.remove_module_def(genv, self)
348
+ genv.resolve_cpath(struct_base_cpath).remove_module_def(genv, self)
349
+ @members.each do |member|
350
+ ive = genv.resolve_ivar(struct_base_cpath, false, member)
351
+ ive.remove_def(self)
352
+ end
353
+ @block_body.undefine(genv) if @block_body
354
+ end
355
+
356
+ def install0(genv)
357
+ # Register the class singleton type as the constant value
358
+ mod_val = Source.new(Type::Singleton.new(genv, genv.resolve_cpath(@static_cpath)))
359
+ if @static_cpath
360
+ @changes.add_edge(genv, mod_val, @static_ret.vtx)
361
+ end
362
+
363
+ cpath = struct_base_cpath
364
+ @members.each do |member|
365
+ # Use bare `:member` (not `:@member`) so the slot can't collide with a
366
+ # user-written @member ivar — Struct/Data fields are not real ivars.
367
+ ivar_box = @changes.add_ivar_read_box(genv, cpath, false, member)
368
+ ret_box = @changes.add_escape_box(genv, ivar_box.ret)
369
+ @changes.add_method_def_box(genv, cpath, false, member, FormalArguments::Empty, [ret_box])
370
+
371
+ if @kind == :struct
372
+ # attr_writer (Struct only, Data is frozen)
373
+ ive = genv.resolve_ivar(cpath, false, member)
374
+ vtx = Vertex.new(self)
375
+ @changes.add_edge(genv, vtx, ive.vtx)
376
+ writer_ret = @changes.add_escape_box(genv, vtx)
377
+ f_args = FormalArguments.new([vtx], [], nil, [], [], [], nil, nil)
378
+ @changes.add_method_def_box(genv, cpath, false, :"#{ member }=", f_args, [writer_ret])
379
+ end
380
+ end
381
+
382
+ # initialize
383
+ init_vtxs = @members.map do |member|
384
+ ive = genv.resolve_ivar(cpath, false, member)
385
+ vtx = Vertex.new(self)
386
+ @changes.add_edge(genv, vtx, ive.vtx)
387
+ vtx
388
+ end
389
+ init_ret = @changes.add_escape_box(genv, Source.new(genv.nil_type))
390
+ if @kind == :struct
391
+ init_f_args = FormalArguments.new(init_vtxs, [], nil, [], [], [], nil, nil)
392
+ else
393
+ # Data.define uses keyword arguments
394
+ init_f_args = FormalArguments.new([], [], nil, [], init_vtxs, [], nil, nil)
395
+ end
396
+ @changes.add_method_def_box(genv, cpath, false, :initialize, init_f_args, [init_ret])
397
+
398
+ # Struct.[] is an alias for Struct.new
399
+ if @kind == :struct
400
+ # Struct.[] builds the struct class itself, not the base class
401
+ self_ret = @changes.add_escape_box(genv, Source.new(Type::Instance.new(genv, genv.resolve_cpath(@static_cpath), [])))
402
+ @changes.add_method_def_box(genv, cpath, true, :[], init_f_args, [self_ret])
403
+ end
404
+
405
+ # Install block body (additional method definitions)
406
+ if @block_body
407
+ @block_body.lenv.locals[:"*self"] = @block_body.lenv.cref.get_self(genv)
408
+ @block_body.install(genv)
409
+ end
410
+
411
+ mod_val
412
+ end
413
+ end
149
414
  end
150
415
  end