From c79decc418c0432a1f11804691e38bd74043e3ac Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Mon, 28 Sep 2026 20:36:38 +0900 Subject: [PATCH] Tell super outside a method from super in a define_method block A bare `super` without enclosing method arguments reported "implicit argument passing of super is not supported here" wherever it was, and `super()` outside a method reported nothing. Ruby raises "super called outside of method" at the top level, in a class body and in a block there, and rejects only the bare `super` in a define_method block. CRef now records whether it is inside a method body, which a define_method block is although its method name is unknown, so the two cases are reported with Ruby's wording. This also lets a define_method block keep the enclosing method's forward_args, which it had dropped to reject a bare `super`, so `...` in the block no longer reports the super diagnostic: def self.m(...) define_method(:x) { pr(...) } end Co-Authored-By: Claude Opus 5.5 --- lib/typeprof/core/ast/call.rb | 22 +++++++++----- lib/typeprof/core/env.rb | 7 +++-- scenario/diagnostics/super-outside-method.rb | 32 ++++++++++++++++++-- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/typeprof/core/ast/call.rb b/lib/typeprof/core/ast/call.rb index 4c1f0bd0..ca60d021 100644 --- a/lib/typeprof/core/ast/call.rb +++ b/lib/typeprof/core/ast/call.rb @@ -6,11 +6,10 @@ def initialize(raw_node, lenv, method_body: false) super(raw_node, lenv) @tbl = raw_node.locals - @method_body = method_body # A `super` in a block calls the super of the enclosing method, but a # block given to define_method is the body of another method, whose name # is not known here - ncref = CRef.new(lenv.cref.cpath, :instance, method_body ? nil : lenv.cref.mid, lenv.cref) + ncref = CRef.new(lenv.cref.cpath, :instance, method_body ? nil : lenv.cref.mid, lenv.cref, in_method: method_body || lenv.cref.in_method) # A `return` in a block exits the enclosing method, so the body writes into # its return boxes. A lambda's `return` exits the lambda, so it gets its own. nlenv = LocalEnv.new(lenv.file_context, ncref, {}, lambda? ? [] : lenv.return_boxes) @@ -64,8 +63,9 @@ def formal_names def install0(genv) blenv = @body.lenv - # Ruby rejects a bare `super` in a define_method block at runtime - blenv.forward_args = @lenv.forward_args unless @method_body + # `...` in a define_method block still forwards the enclosing method's + # arguments; a bare `super` there is rejected in CallBaseNode#install0 + blenv.forward_args = @lenv.forward_args # An outer block may have shadowed the same name already; its entry is # the method's variable, so it must win over the outer block's own one blenv.shadowed_vars = @lenv.locals.slice(*@tbl).merge(@lenv.shadowed_vars) @@ -228,10 +228,18 @@ def install0(genv) recv = NilFilter.new(genv, self, recv, false).next_vtx end - if @forwarding_arguments + if @mid == :"*super" + if !@lenv.cref.in_method + @changes.add_diagnostic(:code_range, "super called outside of method") + elsif @forwarding_arguments && !@lenv.cref.mid + # A define_method block: the arguments of the enclosing method, if any, + # are not the ones a bare `super` would pass + @changes.add_diagnostic(:code_range, "implicit argument passing of super from method defined by define_method() is not supported") + end + end + + if @forwarding_arguments && (@mid != :"*super" || @lenv.cref.mid) forward_args = @lenv.forward_args - # `...` needs a method definition, so only a bare `super` can reach here - @changes.add_diagnostic(:code_range, "implicit argument passing of super is not supported here") unless forward_args end if forward_args diff --git a/lib/typeprof/core/env.rb b/lib/typeprof/core/env.rb index d1b8ce83..479310d6 100644 --- a/lib/typeprof/core/env.rb +++ b/lib/typeprof/core/env.rb @@ -450,14 +450,17 @@ def use_strict_const_scope end class CRef - def initialize(cpath, scope_level, mid, outer) + # in_method: whether this is inside a method body, which a define_method + # block is even though its method name (mid) is not known + def initialize(cpath, scope_level, mid, outer, in_method: !mid.nil?) @cpath = cpath @scope_level = scope_level @mid = mid @outer = outer + @in_method = in_method end - attr_reader :cpath, :scope_level, :mid, :outer + attr_reader :cpath, :scope_level, :mid, :outer, :in_method def get_self(genv) case @scope_level diff --git a/scenario/diagnostics/super-outside-method.rb b/scenario/diagnostics/super-outside-method.rb index 142f4d32..8b4f8029 100644 --- a/scenario/diagnostics/super-outside-method.rb +++ b/scenario/diagnostics/super-outside-method.rb @@ -5,7 +5,7 @@ ## diagnostics (1,0)-(1,13): undefined method: Object#define_method -(2,2)-(2,7): implicit argument passing of super is not supported here +(2,2)-(2,7): implicit argument passing of super from method defined by define_method() is not supported ## update class Foo @@ -26,4 +26,32 @@ def self.make end ## diagnostics -(7,36)-(7,41): implicit argument passing of super is not supported here +(7,36)-(7,41): implicit argument passing of super from method defined by define_method() is not supported + +## update +super +super() + +class C + super + [1].each { super() } +end + +## diagnostics +(1,0)-(1,5): super called outside of method +(2,0)-(2,7): super called outside of method +(5,2)-(5,7): super called outside of method +(6,13)-(6,20): super called outside of method + +## update +def pr(*a, **k) = a + +class C + def self.m(...) + define_method(:x) { pr(...) } + end + + m(1, 2) +end + +## diagnostics