From 6b72817b2c5c2ddf5647b0b2d487a5007fa10dd3 Mon Sep 17 00:00:00 2001 From: Takumi Shotoku Date: Sat, 29 Aug 2026 19:21:40 +0900 Subject: [PATCH] Fix crash when calling attr_* methods with keyword arguments `MethodDefBox#normalize_keyword_hash_argument_for_def` expects the keyword interface on the method definition node, which the attr_* meta nodes did not fully implement. So `attr_reader :foo` plus `x.foo(k: 1)` crashed with `NoMethodError`. Now the keywords are packed into a hash argument, as for any method without keyword parameters. --- lib/typeprof/core/ast/meta.rb | 13 +++++++++++++ scenario/meta/attr_kwargs.rb | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 scenario/meta/attr_kwargs.rb diff --git a/lib/typeprof/core/ast/meta.rb b/lib/typeprof/core/ast/meta.rb index 8452d9862..1e4b67f6b 100644 --- a/lib/typeprof/core/ast/meta.rb +++ b/lib/typeprof/core/ast/meta.rb @@ -69,6 +69,7 @@ def rest_positionals = nil def req_keywords = [] def opt_keywords = [] def rest_keywords = nil + def no_keywords = false def mname_code_range(name) idx = @args.index(name.to_sym) # TODO: support string args @@ -101,6 +102,12 @@ def initialize(raw_node, lenv) def attrs = { singleton:, args: } + # Interface expected by MethodDefBox + def req_keywords = [] + def opt_keywords = [] + def rest_keywords = nil + def no_keywords = false + def mname_code_range(name) idx = @args.index(name.to_sym) node = @raw_node.arguments.arguments[idx].location @@ -160,6 +167,12 @@ def initialize(raw_node, lenv) def attrs = { singleton:, args: } + # Interface expected by MethodDefBox + def req_keywords = [] + def opt_keywords = [] + def rest_keywords = nil + def no_keywords = false + def mname_code_range(name) idx = @args.index(name.to_sym) # TODO: support string args node = @raw_node.arguments.arguments[idx].location diff --git a/scenario/meta/attr_kwargs.rb b/scenario/meta/attr_kwargs.rb new file mode 100644 index 000000000..eefa1a042 --- /dev/null +++ b/scenario/meta/attr_kwargs.rb @@ -0,0 +1,23 @@ +## update +class Foo + attr_reader :a + attr_accessor :b + attr_writer :c + alias set_c c= +end +foo = Foo.new +foo.a(k: 1) +foo.b(k: 1) +foo.set_c(k: 1) + +## assert +class Foo + def a: -> untyped + def b: -> untyped + def b=: (untyped) -> untyped + def c=: ({ k: Integer }) -> { k: Integer } +end + +## diagnostics +(8,4)-(8,5): wrong number of arguments (1 for 0) +(9,4)-(9,5): wrong number of arguments (1 for 0)