@@ -208,5 +208,135 @@ def install0(genv)
208208 Source . new
209209 end
210210 end
211+
212+ class StructNewNode < Node
213+ def initialize ( raw_node , members , kind , lenv )
214+ super ( raw_node , lenv )
215+ case raw_node . type
216+ when :constant_write_node
217+ @static_cpath = lenv . cref . cpath + [ raw_node . name ]
218+ when :constant_path_write_node
219+ @static_cpath = AST . parse_cpath ( raw_node . target , lenv . cref )
220+ else
221+ raise
222+ end
223+ @members = members
224+ @kind = kind # :struct or :data
225+
226+ # Parse block body if present (Struct.new(:foo) do ... end)
227+ raw_value = raw_node . value
228+ if raw_value . block && raw_value . block . type == :block_node && raw_value . block . body
229+ ncref = CRef . new ( @static_cpath , :instance , nil , lenv . cref )
230+ nlenv = LocalEnv . new ( lenv . file_context , ncref , { } , [ ] )
231+ @block_body = AST . create_node ( raw_value . block . body , nlenv )
232+ end
233+ end
234+
235+ attr_reader :static_cpath , :members , :kind , :block_body
236+
237+ def subnodes = { block_body : }
238+ def attrs = { static_cpath :, members :, kind : }
239+
240+ # Interface expected by MethodDefBox
241+ def req_positionals = @kind == :struct ? @members : [ ]
242+ def opt_positionals = [ ]
243+ def rest_positionals = nil
244+ def post_positionals = [ ]
245+ def req_keywords = @kind == :data ? @members : [ ]
246+ def opt_keywords = [ ]
247+ def rest_keywords = nil
248+ def no_keywords = @kind == :struct
249+
250+ def define0 ( genv )
251+ mod = genv . resolve_cpath ( @static_cpath )
252+ # add_module_def internally calls get_const(name).add_def(self)
253+ cdef = mod . add_module_def ( genv , self )
254+ @members . each do |member |
255+ ive = genv . resolve_ivar ( @static_cpath , false , member )
256+ ive . add_def ( self )
257+ end
258+ @block_body . define ( genv ) if @block_body
259+ cdef
260+ end
261+
262+ def define_copy ( genv )
263+ mod = genv . resolve_cpath ( @static_cpath )
264+ mod . add_module_def ( genv , self )
265+ mod . remove_module_def ( genv , @prev_node )
266+ @members . each do |member |
267+ ive = genv . resolve_ivar ( @static_cpath , false , member )
268+ ive . add_def ( self )
269+ ive . remove_def ( @prev_node )
270+ end
271+ super ( genv )
272+ end
273+
274+ def undefine0 ( genv )
275+ mod = genv . resolve_cpath ( @static_cpath )
276+ mod . remove_module_def ( genv , self )
277+ @members . each do |member |
278+ ive = genv . resolve_ivar ( @static_cpath , false , member )
279+ ive . remove_def ( self )
280+ end
281+ @block_body . undefine ( genv ) if @block_body
282+ end
283+
284+ def install0 ( genv )
285+ # Register the class singleton type as the constant value
286+ mod_val = Source . new ( Type ::Singleton . new ( genv , genv . resolve_cpath ( @static_cpath ) ) )
287+ if @static_cpath
288+ @changes . add_edge ( genv , mod_val , @static_ret . vtx )
289+ end
290+
291+ cpath = @static_cpath
292+ @members . each do |member |
293+ # Use bare `:member` (not `:@member`) so the slot can't collide with a
294+ # user-written @member ivar — Struct/Data fields are not real ivars.
295+ ivar_box = @changes . add_ivar_read_box ( genv , cpath , false , member )
296+ ret_box = @changes . add_escape_box ( genv , ivar_box . ret )
297+ @changes . add_method_def_box ( genv , cpath , false , member , FormalArguments ::Empty , [ ret_box ] )
298+
299+ if @kind == :struct
300+ # attr_writer (Struct only, Data is frozen)
301+ ive = genv . resolve_ivar ( cpath , false , member )
302+ vtx = Vertex . new ( self )
303+ @changes . add_edge ( genv , vtx , ive . vtx )
304+ writer_ret = @changes . add_escape_box ( genv , vtx )
305+ f_args = FormalArguments . new ( [ vtx ] , [ ] , nil , [ ] , [ ] , [ ] , nil , nil )
306+ @changes . add_method_def_box ( genv , cpath , false , :"#{ member } =" , f_args , [ writer_ret ] )
307+ end
308+ end
309+
310+ # initialize
311+ init_vtxs = @members . map do |member |
312+ ive = genv . resolve_ivar ( cpath , false , member )
313+ vtx = Vertex . new ( self )
314+ @changes . add_edge ( genv , vtx , ive . vtx )
315+ vtx
316+ end
317+ init_ret = @changes . add_escape_box ( genv , Source . new ( genv . nil_type ) )
318+ if @kind == :struct
319+ init_f_args = FormalArguments . new ( init_vtxs , [ ] , nil , [ ] , [ ] , [ ] , nil , nil )
320+ else
321+ # Data.define uses keyword arguments
322+ init_f_args = FormalArguments . new ( [ ] , [ ] , nil , [ ] , init_vtxs , [ ] , nil , nil )
323+ end
324+ @changes . add_method_def_box ( genv , cpath , false , :initialize , init_f_args , [ init_ret ] )
325+
326+ # Struct.[] is an alias for Struct.new
327+ if @kind == :struct
328+ self_ret = @changes . add_escape_box ( genv , Source . new ( Type ::Instance . new ( genv , genv . resolve_cpath ( cpath ) , [ ] ) ) )
329+ @changes . add_method_def_box ( genv , cpath , true , :[] , init_f_args , [ self_ret ] )
330+ end
331+
332+ # Install block body (additional method definitions)
333+ if @block_body
334+ @block_body . lenv . locals [ :"*self" ] = @block_body . lenv . cref . get_self ( genv )
335+ @block_body . install ( genv )
336+ end
337+
338+ mod_val
339+ end
340+ end
211341 end
212342end
0 commit comments