Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions lib/typeprof/core/ast/call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions lib/typeprof/core/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -455,14 +455,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
Expand Down
32 changes: 30 additions & 2 deletions scenario/diagnostics/super-outside-method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Loading