From 29f05c17cb8d9c581b4465569982ce7d66b94987 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 3 Apr 2026 15:27:01 +0900 Subject: [PATCH] Add attr_writer support Implement AttrWriterMetaNode to handle attr_writer declarations, generating setter methods that write to instance variables. Previously attr_writer was silently ignored. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/typeprof/core/ast.rb | 2 ++ lib/typeprof/core/ast/meta.rb | 56 +++++++++++++++++++++++++++++++++++ scenario/misc/attr_writer.rb | 17 +++++++++++ 3 files changed, 75 insertions(+) create mode 100644 scenario/misc/attr_writer.rb diff --git a/lib/typeprof/core/ast.rb b/lib/typeprof/core/ast.rb index d1ec4ef8c..f4ec8f526 100644 --- a/lib/typeprof/core/ast.rb +++ b/lib/typeprof/core/ast.rb @@ -278,6 +278,8 @@ def self.create_node(raw_node, lenv, use_result = true, allow_meta = false) return IncludeMetaNode.new(raw_node, lenv) when :attr_reader return AttrReaderMetaNode.new(raw_node, lenv) + when :attr_writer + return AttrWriterMetaNode.new(raw_node, lenv) when :attr_accessor return AttrAccessorMetaNode.new(raw_node, lenv) end diff --git a/lib/typeprof/core/ast/meta.rb b/lib/typeprof/core/ast/meta.rb index 2da05462f..c4aa0b641 100644 --- a/lib/typeprof/core/ast/meta.rb +++ b/lib/typeprof/core/ast/meta.rb @@ -86,6 +86,62 @@ def install0(genv) end end + class AttrWriterMetaNode < Node + def initialize(raw_node, lenv) + super(raw_node, lenv) + @args = [] + raw_node.arguments.arguments.each do |raw_arg| + @args << raw_arg.value.to_sym if raw_arg.type == :symbol_node + end + end + + attr_reader :args + + def attrs = { args: } + + def mname_code_range(name) + idx = @args.index(name.to_sym) + node = @raw_node.arguments.arguments[idx].location + @lenv.code_range_from_node(node) + end + + def define0(genv) + @args.map do |arg| + mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }") + mod.add_def(self) + mod + end + end + + def define_copy(genv) + @args.map do |arg| + mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }") + mod.add_def(self) + mod.remove_def(@prev_node) + mod + end + super(genv) + end + + def undefine0(genv) + @args.each do |arg| + mod = genv.resolve_ivar(lenv.cref.cpath, false, :"@#{ arg }") + mod.remove_def(self) + end + end + + def install0(genv) + @args.zip(@static_ret) do |arg, ive| + vtx = Vertex.new(self) + @changes.add_edge(genv, vtx, ive.vtx) + ret_box = @changes.add_escape_box(genv, vtx) + f_args = FormalArguments.new([vtx], [], nil, [], [], [], nil, nil) + @changes.add_method_def_box(genv, @lenv.cref.cpath, false, :"#{ arg }=", f_args, [ret_box]) + end + Source.new + end + end + class AttrAccessorMetaNode < Node def initialize(raw_node, lenv) super(raw_node, lenv) diff --git a/scenario/misc/attr_writer.rb b/scenario/misc/attr_writer.rb new file mode 100644 index 000000000..8a39fc9cd --- /dev/null +++ b/scenario/misc/attr_writer.rb @@ -0,0 +1,17 @@ +## update +class Foo + attr_writer :x + + def get_x + @x + end +end + +Foo.new.x = 1 +Foo.new.get_x + +## assert +class Foo + def x=: (Integer) -> Integer + def get_x: -> Integer +end