diff --git a/lib/typeprof/core/ast/module.rb b/lib/typeprof/core/ast/module.rb index 8d97aabfd..cf125acce 100644 --- a/lib/typeprof/core/ast/module.rb +++ b/lib/typeprof/core/ast/module.rb @@ -89,7 +89,22 @@ class ClassNode < ModuleBaseNode def initialize(raw_node, lenv, use_result) super(raw_node, lenv, raw_node.constant_path, false, raw_node.body, use_result) raw_superclass = raw_node.superclass - @superclass_cpath = raw_superclass ? AST.create_node(raw_superclass, lenv) : nil + if raw_superclass + # In Ruby, the superclass expression is evaluated before the class constant + # is created. When the superclass is a bare constant with the same name as + # the class being defined (e.g., `class Foo < Foo` inside a module), use the + # outer scope to avoid resolving to the class itself. + if @static_cpath && lenv.cref.outer && + raw_superclass.type == :constant_read_node && + raw_superclass.name == @static_cpath.last + slenv = LocalEnv.new(lenv.file_context, lenv.cref.outer, {}, []) + @superclass_cpath = AST.create_node(raw_superclass, slenv) + else + @superclass_cpath = AST.create_node(raw_superclass, lenv) + end + else + @superclass_cpath = nil + end end attr_reader :superclass_cpath @@ -113,6 +128,21 @@ def undefine0(genv) def install0(genv) @superclass_cpath.install(genv) if @superclass_cpath + if @static_cpath && @superclass_cpath + const_read = @superclass_cpath.static_ret + if const_read && const_read.cpath + super_mod = genv.resolve_cpath(const_read.cpath) + self_mod = genv.resolve_cpath(@static_cpath) + mod = super_mod + while mod + if mod == self_mod + @changes.add_diagnostic(:code_range, "circular inheritance", @superclass_cpath) + break + end + mod = mod.superclass + end + end + end super(genv) end end diff --git a/scenario/class/circular.rb b/scenario/class/circular.rb index cfd30da14..5eefeee0c 100644 --- a/scenario/class/circular.rb +++ b/scenario/class/circular.rb @@ -16,3 +16,6 @@ class B # failed to identify its superclass end module M end + +## diagnostics +(4,10)-(4,11): circular inheritance diff --git a/scenario/class/circular_mutual.rb b/scenario/class/circular_mutual.rb new file mode 100644 index 000000000..9e00aa5b5 --- /dev/null +++ b/scenario/class/circular_mutual.rb @@ -0,0 +1,23 @@ +## update +class Foo +end + +class Bar + class Baz < Foo + end + class Foo < Baz + end +end + +## assert +class Foo +end +class Bar + class Bar::Baz < Bar::Foo + end + class Bar::Foo # failed to identify its superclass + end +end + +## diagnostics +(7,14)-(7,17): circular inheritance diff --git a/scenario/regressions/superclass-self-reference.rb b/scenario/regressions/superclass-self-reference.rb new file mode 100644 index 000000000..9fa1ecd3f --- /dev/null +++ b/scenario/regressions/superclass-self-reference.rb @@ -0,0 +1,19 @@ +## update: model.rb +class Foo + def value = 1 +end + +module Bar + class Foo < Foo + end +end + +## update: test.rb +def call + Bar::Foo.new.value +end + +## assert: test.rb +class Object + def call: -> Integer +end