From aed646597ff603348af1f89482faae1051d881fb Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 24 Feb 2026 18:02:25 +0000 Subject: [PATCH 1/2] Lazily compute code_units_cache in FileContext Instead of eagerly computing the UTF-16 code units cache during parsing, store the Prism source and defer computation until first access. This avoids expensive String#encode calls when code_range positions are not immediately needed. --- lib/typeprof/core/ast.rb | 2 +- lib/typeprof/core/env.rb | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/typeprof/core/ast.rb b/lib/typeprof/core/ast.rb index 6264a095c..46cbf9275 100644 --- a/lib/typeprof/core/ast.rb +++ b/lib/typeprof/core/ast.rb @@ -11,7 +11,7 @@ def self.parse_rb(path, src) raise unless raw_scope.type == :program_node prism_source = result.source - file_context = FileContext.new(path, prism_source.code_units_cache(Encoding::UTF_16LE), result.comments) + file_context = FileContext.new(path, prism_source, result.comments) cref = CRef::Toplevel lenv = LocalEnv.new(file_context, cref, {}, []) diff --git a/lib/typeprof/core/env.rb b/lib/typeprof/core/env.rb index 41da1edfb..6da8d0b2d 100644 --- a/lib/typeprof/core/env.rb +++ b/lib/typeprof/core/env.rb @@ -305,12 +305,17 @@ class Hash[K, V] end class FileContext - attr_reader :path, :code_units_cache, :comments - def initialize(path, code_units_cache = nil, comments = nil) + attr_reader :path, :comments + def initialize(path, prism_source = nil, comments = nil) @path = path - @code_units_cache = code_units_cache + @prism_source = prism_source + @code_units_cache = nil @comments = comments end + + def code_units_cache + @code_units_cache ||= @prism_source&.code_units_cache(Encoding::UTF_16LE) + end end class LocalEnv From e3f580e4569062784bf7398ce744583f25654083 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 24 Feb 2026 18:03:32 +0000 Subject: [PATCH 2/2] Lazily allocate @diagnostics Set in AST nodes Initialize @diagnostics as the frozen empty set instance to avoid allocating a Hash for every AST node. Most nodes never receive diagnostics, so the Set is only created on first use via add_diagnostic. --- lib/typeprof/core/ast/base.rb | 6 ++++-- lib/typeprof/core/util.rb | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/typeprof/core/ast/base.rb b/lib/typeprof/core/ast/base.rb index 915fa6be1..cd1fbf8dd 100644 --- a/lib/typeprof/core/ast/base.rb +++ b/lib/typeprof/core/ast/base.rb @@ -9,7 +9,7 @@ def initialize(raw_node, lenv) @ret = nil @changes = ChangeSet.new(self, nil) - @diagnostics = Set.empty + @diagnostics = Set::EMPTY end attr_reader :lenv @@ -104,6 +104,7 @@ def install_copy(genv) @changes.reuse(self) (@prev_node || raise).diagnostics.each do |diag| diag.reuse(self) + @diagnostics = Set.empty if @diagnostics.equal?(Set::EMPTY) @diagnostics << diag end each_subnode do |subnode| @@ -129,11 +130,12 @@ def narrowings end def add_diagnostic(diag) + @diagnostics = Set.empty if @diagnostics.equal?(Set::EMPTY) @diagnostics << diag end def remove_diagnostic(diag) - @diagnostics.delete(diag) + @diagnostics.delete(diag) unless @diagnostics.equal?(Set::EMPTY) end def diff(prev_node) diff --git a/lib/typeprof/core/util.rb b/lib/typeprof/core/util.rb index 66fb3c829..7c73b3859 100644 --- a/lib/typeprof/core/util.rb +++ b/lib/typeprof/core/util.rb @@ -81,5 +81,7 @@ def pretty_print(q) end q.text "]" end + + EMPTY = empty.freeze end end