diff --git a/lib/typeprof/core/ast/sig_decl.rb b/lib/typeprof/core/ast/sig_decl.rb index d040f512..324f514e 100644 --- a/lib/typeprof/core/ast/sig_decl.rb +++ b/lib/typeprof/core/ast/sig_decl.rb @@ -505,20 +505,20 @@ def attrs = { cpath:, class_scope: } def define0(genv) @type.define(genv) - mod = genv.resolve_ivar(cpath, @class_scope, @var) - mod.add_decl(self) - mod + mod = genv.resolve_cpath(cpath) + mod.add_ivar_decl(genv, @class_scope, @var, self) end def define_copy(genv) - mod = genv.resolve_ivar(cpath, @class_scope, @var) - mod.add_decl(self) - mod.remove_decl(@prev_node) + mod = genv.resolve_cpath(cpath) + mod.add_ivar_decl(genv, @class_scope, @var, self) + mod.remove_ivar_decl(genv, @class_scope, @var, @prev_node) super(genv) end def undefine0(genv) - genv.resolve_ivar(cpath, @class_scope, @var).remove_decl(self) + mod = genv.resolve_cpath(cpath) + mod.remove_ivar_decl(genv, @class_scope, @var, self) @type.undefine(genv) end diff --git a/lib/typeprof/core/env/module_entity.rb b/lib/typeprof/core/env/module_entity.rb index 6ad0a971..2783f612 100644 --- a/lib/typeprof/core/env/module_entity.rb +++ b/lib/typeprof/core/env/module_entity.rb @@ -436,6 +436,19 @@ def get_ivar(singleton, name) @ivars[singleton][name] ||= ValueEntity.new end + def add_ivar_decl(genv, singleton, name, decl) + ive = get_ivar(singleton, name) + ive.add_decl(decl) + ive.on_decl_changed(genv) + ive + end + + def remove_ivar_decl(genv, singleton, name, decl) + ive = get_ivar(singleton, name) + ive.remove_decl(decl) + ive.on_decl_changed(genv) + end + def get_cvar(name) @cvars[name] ||= ValueEntity.new end diff --git a/lib/typeprof/core/env/value_entity.rb b/lib/typeprof/core/env/value_entity.rb index 6bf20a0a..27f95bfb 100644 --- a/lib/typeprof/core/env/value_entity.rb +++ b/lib/typeprof/core/env/value_entity.rb @@ -17,6 +17,14 @@ def remove_decl(decl) @decls.delete(decl) || raise end + # Re-run all read boxes that depend on this entity. Used when a + # declaration is added or removed so that dependents (e.g. an + # IVarReadBox that previously fell back to the inferred type) can + # observe the new state. + def on_decl_changed(genv) + @read_boxes.each {|box| genv.add_run(box) } + end + def add_def(def_) @defs << def_ end diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index 91fb388d..86822ebd 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -1280,23 +1280,44 @@ def run0(genv, changes) singleton = @singleton cur_ive = mod.get_ivar(singleton, @name) target_vtx = nil + target_decls = nil genv.each_direct_superclass(mod, singleton) do |mod, singleton| ive = mod.get_ivar(singleton, @name) + # Subscribe to every visited ive so that, if one later acquires an + # RBS declaration, this box is re-run and switches to the declared + # type instead of the inferred one. + changes.add_depended_value_entity(ive) if ive.exist? target_vtx = ive.vtx + target_decls = ive.decls unless ive.decls.empty? + break if target_decls end end - edges = [] - if target_vtx + + if target_decls + # When declarations exist, return declared types instead of assigned types + target_decls.each do |decl| + subst = {} + if decl.cpath + decl_mod = genv.resolve_cpath(decl.cpath) + if decl_mod.type_params && !decl_mod.type_params.empty? + subst = decl_mod.type_params.to_h do |param, _default_ty| + [param, Vertex.new(@node)] + end + end + end + vtx = decl.type.covariant_vertex(genv, changes, subst) + changes.add_edge(genv, vtx, @ret) + end + elsif target_vtx + edges = [] if target_vtx != cur_ive.vtx edges << [cur_ive.vtx, @proxy] << [@proxy, target_vtx] end edges << [target_vtx, @ret] - else - # TODO: error? - end - edges.each do |src, dst| - changes.add_edge(genv, src, dst) + edges.each do |src, dst| + changes.add_edge(genv, src, dst) + end end end end diff --git a/scenario/rbs/ivar_decl_late_load.rb b/scenario/rbs/ivar_decl_late_load.rb new file mode 100644 index 00000000..37ae3483 --- /dev/null +++ b/scenario/rbs/ivar_decl_late_load.rb @@ -0,0 +1,21 @@ +## update: test.rb +class Foo + def initialize + @x = nil + end + + def get_x + @x + end +end + +## update: test.rbs +class Foo + @x: Integer +end + +## assert: test.rb +class Foo + def initialize: -> void + def get_x: -> Integer +end diff --git a/scenario/rbs/ivar_decl_priority.rb b/scenario/rbs/ivar_decl_priority.rb new file mode 100644 index 00000000..ceab1189 --- /dev/null +++ b/scenario/rbs/ivar_decl_priority.rb @@ -0,0 +1,34 @@ +## update: test.rbs +class Base +end + +class Child < Base + @x: Integer +end + +## update: test.rb +class Base + def initialize + @x = nil + end +end + +class Child < Base + def initialize + super + @x = 1 + end + + def get_x + @x + end +end + +## assert +class Base + def initialize: -> void +end +class Child < Base + def initialize: -> void + def get_x: -> Integer +end