diff --git a/lib/typeprof/core/ast.rb b/lib/typeprof/core/ast.rb index 43a43945..305ed720 100644 --- a/lib/typeprof/core/ast.rb +++ b/lib/typeprof/core/ast.rb @@ -436,7 +436,10 @@ def self.create_rbs_decl(raw_decl, lenv) SigInterfaceNode.new(raw_decl, lenv) when RBS::AST::Declarations::Constant SigConstNode.new(raw_decl, lenv) - when RBS::AST::Declarations::AliasDecl + when RBS::AST::Declarations::ClassAlias + SigClassAliasNode.new(raw_decl, lenv) + when RBS::AST::Declarations::ModuleAlias + SigModuleAliasNode.new(raw_decl, lenv) when RBS::AST::Declarations::TypeAlias SigTypeAliasNode.new(raw_decl, lenv) # TODO: check diff --git a/lib/typeprof/core/ast/sig_decl.rb b/lib/typeprof/core/ast/sig_decl.rb index fa9acd83..5421d14d 100644 --- a/lib/typeprof/core/ast/sig_decl.rb +++ b/lib/typeprof/core/ast/sig_decl.rb @@ -414,6 +414,52 @@ def install0(genv) end end + class SigModuleAliasBaseNode < Node + def initialize(raw_decl, lenv) + super(raw_decl, lenv) + @cpath = AST.resolve_rbs_name(raw_decl.new_name, lenv) + @old_cpath = AST.resolve_rbs_name(raw_decl.old_name, lenv) + end + + attr_reader :cpath, :old_cpath + def attrs = { cpath:, old_cpath: } + + def define0(genv) + outer = genv.resolve_cpath(@cpath[0..-2]) + cname = @cpath.last + alias_mod = outer.inner_modules[cname] ||= ModuleEntity.new(outer.cpath + [cname], outer) + target_mod = genv.resolve_cpath(@old_cpath) + alias_mod.add_alias_decl(genv, self, target_mod) + end + + def define_copy(genv) + outer = genv.resolve_cpath(@cpath[0..-2]) + alias_mod = outer.inner_modules[@cpath.last] + target_mod = genv.resolve_cpath(@old_cpath) + alias_mod.add_alias_decl(genv, self, target_mod) + alias_mod.remove_alias_decl(genv, @prev_node) + super(genv) + end + + def undefine0(genv) + outer = genv.resolve_cpath(@cpath[0..-2]) + alias_mod = outer.inner_modules[@cpath.last] + alias_mod.remove_alias_decl(genv, self) + end + + def install0(genv) + mod_val = Source.new(Type::Singleton.new(genv, genv.resolve_cpath(@cpath))) + @changes.add_edge(genv, mod_val, @static_ret.vtx) + Source.new + end + end + + class SigClassAliasNode < SigModuleAliasBaseNode + end + + class SigModuleAliasNode < SigModuleAliasBaseNode + end + class SigConstNode < Node def initialize(raw_decl, lenv) super(raw_decl, lenv) diff --git a/lib/typeprof/core/env.rb b/lib/typeprof/core/env.rb index 5b40d79d..da6955b9 100644 --- a/lib/typeprof/core/env.rb +++ b/lib/typeprof/core/env.rb @@ -221,6 +221,18 @@ def resolve_cpath(cpath) raise unless cpath # annotation cpath.each do |cname| mod = mod.inner_modules[cname] ||= ModuleEntity.new(mod.cpath + [cname], mod) + mod = follow_alias(mod) + end + mod + end + + def follow_alias(mod) + visited = nil + while mod.alias_target + visited ||= Set.empty + break if visited.include?(mod) # cycle + visited << mod + mod = mod.alias_target end mod end diff --git a/lib/typeprof/core/env/module_entity.rb b/lib/typeprof/core/env/module_entity.rb index dad5dfd0..3d5c9a45 100644 --- a/lib/typeprof/core/env/module_entity.rb +++ b/lib/typeprof/core/env/module_entity.rb @@ -10,6 +10,11 @@ def initialize(cpath, outer_module = self) @prepend_decls = [] @prepend_defs = [] + # `class Foo = Bar` / `module Foo = Bar` declarations attached to this entity. + # Maps an alias decl to the target ModuleEntity at the time of registration. + @alias_decls = {} + @alias_target = nil + @inner_modules = {} @outer_module = outer_module @@ -42,6 +47,8 @@ def initialize(cpath, outer_module = self) attr_reader :cpath attr_reader :module_decls attr_reader :module_defs + attr_reader :alias_decls + attr_reader :alias_target attr_reader :inner_modules attr_reader :outer_module @@ -79,7 +86,7 @@ def get_cname end def exist? - !@module_decls.empty? || !@module_defs.empty? + !@module_decls.empty? || !@module_defs.empty? || !@alias_decls.empty? end def on_inner_modules_changed(genv, changed_cname) @@ -176,6 +183,22 @@ def remove_module_def(genv, node) on_module_removed(genv) end + def add_alias_decl(genv, decl, target_mod) + on_module_added(genv) + @alias_decls[decl] = target_mod + @alias_target = @alias_decls.values.first + ce = @outer_module.get_const(get_cname) + ce.add_decl(decl) + ce + end + + def remove_alias_decl(genv, decl) + @outer_module.get_const(get_cname).remove_decl(decl) + @alias_decls.delete(decl) || raise + @alias_target = @alias_decls.values.first + on_module_removed(genv) + end + def add_include_decl(genv, node) @include_decls << node genv.add_static_eval_queue(:parent_modules_changed, self) diff --git a/scenario/rbs/class-module-alias-cycle.rb b/scenario/rbs/class-module-alias-cycle.rb new file mode 100644 index 00000000..3484c262 --- /dev/null +++ b/scenario/rbs/class-module-alias-cycle.rb @@ -0,0 +1,10 @@ +## update: test.rbs +module A = B +module B = A + +## update: test.rb +def test + A +end + +## diagnostics: test.rb diff --git a/scenario/rbs/class-module-alias-nested.rb b/scenario/rbs/class-module-alias-nested.rb new file mode 100644 index 00000000..c35a81b3 --- /dev/null +++ b/scenario/rbs/class-module-alias-nested.rb @@ -0,0 +1,25 @@ +## update: test.rbs +module Outer + module Inner + def self.greet: () -> String + CONST: Integer + end + module InnerAlias = Inner +end + +## update: test.rb +def test1 + Outer::InnerAlias.greet +end + +def test2 + Outer::InnerAlias::CONST +end + +## assert: test.rb +class Object + def test1: -> String + def test2: -> Integer +end + +## diagnostics: test.rb diff --git a/scenario/rbs/class-module-alias.rb b/scenario/rbs/class-module-alias.rb new file mode 100644 index 00000000..48d7f83b --- /dev/null +++ b/scenario/rbs/class-module-alias.rb @@ -0,0 +1,44 @@ +## update: test.rbs +class Foo + def foo: () -> Integer +end +class Bar = Foo +module M + def m: () -> String + CONST: Symbol +end +module N = M + +## update: test.rb +def test1 + Foo.new.foo +end + +def test2 + Bar.new.foo +end + +def test3 + N::CONST +end + +class UseN + include N + + def test4 + m + end +end + +## assert: test.rb +class Object + def test1: -> Integer + def test2: -> Integer + def test3: -> Symbol +end +class UseN + include M + def test4: -> String +end + +## diagnostics: test.rb