From 426c2dbb001a8fc4b2c9fcaeaab10dea9c09ffc3 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 26 Feb 2026 17:02:59 +0000 Subject: [PATCH] Notify StaticReads when constant values are defined or removed When a constant value (not a module/class) is defined or removed across files, the StaticRead objects that track constant resolution were not being notified. This caused cross-file constant references to remain untyped when the defining file was loaded after the referencing file. --- lib/typeprof/core/ast/const.rb | 11 ++++--- lib/typeprof/core/ast/sig_decl.rb | 11 ++++--- lib/typeprof/core/env/value_entity.rb | 14 +++++++++ scenario/const/cross_file_value.rb | 44 +++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 scenario/const/cross_file_value.rb diff --git a/lib/typeprof/core/ast/const.rb b/lib/typeprof/core/ast/const.rb index e492417fb..6ae0c513c 100644 --- a/lib/typeprof/core/ast/const.rb +++ b/lib/typeprof/core/ast/const.rb @@ -83,9 +83,10 @@ def define0(genv) @cpath.define(genv) if @cpath @rhs.define(genv) if @rhs if @static_cpath - mod = genv.resolve_const(@static_cpath) - mod.add_def(self) - mod + cdef = genv.resolve_const(@static_cpath) + cdef.on_const_added(genv, @static_cpath) + cdef.add_def(self) + cdef else nil end @@ -102,7 +103,9 @@ def define_copy(genv) def undefine0(genv) if @static_cpath - genv.resolve_const(@static_cpath).remove_def(self) + cdef = genv.resolve_const(@static_cpath) + cdef.remove_def(self) + cdef.on_const_removed(genv, @static_cpath) end @rhs.undefine(genv) if @rhs @cpath.undefine(genv) if @cpath diff --git a/lib/typeprof/core/ast/sig_decl.rb b/lib/typeprof/core/ast/sig_decl.rb index be64da7fe..d4f1ca772 100644 --- a/lib/typeprof/core/ast/sig_decl.rb +++ b/lib/typeprof/core/ast/sig_decl.rb @@ -425,9 +425,10 @@ def attrs = { cpath: } def define0(genv) @type.define(genv) - mod = genv.resolve_const(@cpath) - mod.add_decl(self) - mod + cdef = genv.resolve_const(@cpath) + cdef.on_const_added(genv, @cpath) + cdef.add_decl(self) + cdef end def define_copy(genv) @@ -438,7 +439,9 @@ def define_copy(genv) end def undefine0(genv) - genv.resolve_const(@cpath).remove_decl(self) + cdef = genv.resolve_const(@cpath) + cdef.remove_decl(self) + cdef.on_const_removed(genv, @cpath) @type.undefine(genv) end diff --git a/lib/typeprof/core/env/value_entity.rb b/lib/typeprof/core/env/value_entity.rb index 00c918fd3..6bf20a0a8 100644 --- a/lib/typeprof/core/env/value_entity.rb +++ b/lib/typeprof/core/env/value_entity.rb @@ -28,5 +28,19 @@ def remove_def(def_) def exist? !@decls.empty? || !@defs.empty? end + + def on_const_added(genv, cpath) + unless exist? + parent_mod = genv.resolve_cpath(cpath[0..-2]) + genv.add_static_eval_queue(:inner_modules_changed, [parent_mod, cpath[-1]]) + end + end + + def on_const_removed(genv, cpath) + unless exist? + parent_mod = genv.resolve_cpath(cpath[0..-2]) + genv.add_static_eval_queue(:inner_modules_changed, [parent_mod, cpath[-1]]) + end + end end end diff --git a/scenario/const/cross_file_value.rb b/scenario/const/cross_file_value.rb new file mode 100644 index 000000000..1721e9794 --- /dev/null +++ b/scenario/const/cross_file_value.rb @@ -0,0 +1,44 @@ +## update: test0.rb +module Foo + X = 1 +end + +## update: test1.rb +module Foo + class Bar + def get_x + X + end + end +end + +## assert: test1.rb +module Foo + class Bar + def get_x: -> Integer + end +end + +## update: test0.rb +module Foo + # X is removed +end + +## assert: test1.rb +module Foo + class Bar + def get_x: -> untyped + end +end + +## update: test0.rb +module Foo + X = "hello" +end + +## assert: test1.rb +module Foo + class Bar + def get_x: -> String + end +end