Class: Ryac::ConstantRenameMapping
- Inherits:
-
Object
- Object
- Ryac::ConstantRenameMapping
- Defined in:
- lib/ryac/analysis/constant/rename_mapping.rb,
sig/ryac/analysis/constant/rename_mapping.rbs
Overview
Tracks the mapping between original and short constant names Uses static_cpath (full qualified path) as key to distinguish constants with same name in different modules. Also tracks external prefix aliases (absorbed from ExternalPrefixAliaser).
Instance Attribute Summary collapse
-
#mappings ⇒ Hash[Array[Symbol], ConstantInfo]
readonly
Returns the value of attribute mappings.
-
#used_short_names ⇒ Set[String]
readonly
Returns the value of attribute used_short_names.
Instance Method Summary collapse
-
#add_definition_with_path(static_cpath, definition_type:) ⇒ void
Add a constant definition using TypeProf's static_cpath.
-
#add_external_prefix(prefix_path, usage_count:) ⇒ void
Add an external prefix reference count (e.g., [:TypeProf, :Core, :AST] with count 20).
-
#assign_short_names(name_generator, skip_class_modules: false) ⇒ void
Freeze the mapping and assign short names.
-
#build_alias_declaration(info) ⇒ String?
Build backward alias declaration for renamed value constants.
-
#class_or_module_path?(static_cpath) ⇒ Boolean
Check if a path is a class or module definition.
-
#each_user_defined_path {|arg0| ... } ⇒ void
Iterate over user-defined constant paths.
- #empty? ⇒ Boolean
- #exclude_path(static_cpath) ⇒ void
-
#external_class_root?(cpath) ⇒ Boolean
A class/module definition whose root the program didn't create is a reopening (e.g.
class Array, ormodule Prismadding to the gem) and must not be renamed. - #finalized? ⇒ Boolean
-
#generate_alias_declarations ⇒ Array[String]
Generate backward-compatible alias declarations for renamed constants.
-
#generate_prefix_declarations ⇒ Array[String]
Generate prefix declaration statements (e.g., ["Z=TypeProf::Core::AST"]) Uses chained aliases when a sub-prefix is also aliased.
-
#has_user_defined_prefix?(full_path) ⇒ Boolean
Check if any sub-prefix of the path is user-defined.
-
#increment_usage(name) ⇒ void
Increment usage count by simple name (finds first match).
-
#increment_usage_by_path(static_cpath) ⇒ void
Increment usage count for a constant by static_cpath.
-
#initialize(boot_roots: nil, alias_surface: :full) ⇒ ConstantRenameMapping
constructor
boot_roots: the top-level constants that exist when the minified program boots (core + its own requires), used to spot reopenings of classes the program didn't create.
-
#runtime_constant?(cpath) ⇒ Boolean
Check if a constant path already exists in the Ruby runtime.
-
#set_usage_count_by_path(static_cpath, count) ⇒ void
Set usage count for a constant by static_cpath.
-
#short_name_for(name) ⇒ String?
Get short name for a constant by simple name (finds first match) Used when static_cpath is not available.
-
#short_name_for_path(static_cpath) ⇒ String?
Get short name for a constant by static_cpath.
-
#short_name_for_prefix(full_path) ⇒ String?
Get short name for the prefix of a full external path.
-
#usage_count_for_path(static_cpath) ⇒ Integer
Get usage count for a constant by static_cpath.
-
#user_defined_path?(static_cpath) ⇒ Boolean
Check if a constant is user-defined by static_cpath.
Constructor Details
#initialize(boot_roots: nil, alias_surface: :full) ⇒ ConstantRenameMapping
boot_roots: the top-level constants that exist when the minified program boots (core + its own requires), used to spot reopenings of classes the program didn't create. nil falls back to probing the minifier's own process, which over-approximates: everything the minifier has loaded — including, under self-hosting, the analyzed program itself — then looks like a reopening.
alias_surface: which renames the alias block restores — :full, every one, or :skeleton, classes and modules only. See generate_alias_declarations.
65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 65 def initialize(boot_roots: nil, alias_surface: :full) @boot_roots = boot_roots @alias_surface = alias_surface @mappings = {} # Hash<Array<Symbol>, ConstantInfo> - key is static_cpath # keyed by the last path segment @by_name = {} @used_short_names = Set.new @external_prefixes = {} @prefix_counts = Hash.new(0) # Hash<Array<Symbol>, Integer> - raw prefix reference counts @state = :empty end |
Instance Attribute Details
#mappings ⇒ Hash[Array[Symbol], ConstantInfo] (readonly)
Returns the value of attribute mappings.
53 54 55 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 53 def mappings @mappings end |
#used_short_names ⇒ Set[String] (readonly)
Returns the value of attribute used_short_names.
53 54 55 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 53 def used_short_names @used_short_names end |
Instance Method Details
#add_definition_with_path(static_cpath, definition_type:) ⇒ void
This method returns an undefined value.
Add a constant definition using TypeProf's static_cpath
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 86 def add_definition_with_path(static_cpath, definition_type:) raise "Cannot add definitions when finalized" if finalized? @state = :collecting if empty? return if @mappings.key?(static_cpath) name = static_cpath.last scope_path = static_cpath[0...-1] info = ConstantInfo.new( original_name: name, full_path: static_cpath, definition_type: definition_type, scope_path: scope_path ) @mappings[static_cpath] = info # Also index by simple name for backward compatibility (@by_name[name] ||= []) << info end |
#add_external_prefix(prefix_path, usage_count:) ⇒ void
This method returns an undefined value.
Add an external prefix reference count (e.g., [:TypeProf, :Core, :AST] with count 20)
280 281 282 283 284 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 280 def add_external_prefix(prefix_path, usage_count:) raise "Cannot add external prefix when finalized" if finalized? @state = :collecting if empty? @prefix_counts[prefix_path] += usage_count end |
#assign_short_names(name_generator, skip_class_modules: false) ⇒ void
This method returns an undefined value.
Freeze the mapping and assign short names. Unified allocation: internal constants and external prefixes are merged into a single sorted list and allocated from the same NameGenerator. This follows the src.dest two-phase model: propagation (this method) determines ALL short names before any application.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 140 def assign_short_names(name_generator, skip_class_modules: false) raise "Already finalized" if finalized? @state = :frozen prefix_counts = @prefix_counts @prefix_counts = nil existing_names = @mappings.each_value.with_object(Set.new) { |info, s| s << info.original_name.to_s } # Augment prefix counts with preamble-induced parent refs: # each prefix's declaration (e.g., C5=RuboCop::Cop) references its parent. # Adding unconditionally avoids the chicken-and-egg problem of needing to # know which children are aliased before counting parent refs. prefix_counts.keys.each do |prefix| next if prefix.size < 2 prefix_counts[prefix[0...-1]] += 1 end # Build unified allocation list: [gross_savings_estimate, :internal/:external, object] entries = [] #: Array[[Integer, Symbol, untyped]] @mappings.each_value do |info| next if skip_class_modules && info.definition_type != :value next if info.definition_type != :value && external_class_root?(info.full_path) entries << [info.original_name.to_s.size * (info.usage_count + 1), :internal, info] end prefix_counts.each do |prefix, count| prefix_string = prefix.map(&:to_s).join('::') info = ExternalPrefixInfo.new(prefix_path: prefix, prefix_string: prefix_string, usage_count: count) entries << [prefix_string.size * count, :external, info] end entries.sort_by! { |e| -e[0] } # candidate is assigned on the first iteration and only replaced after candidate = nil #: String? entries.each do |_savings, kind, info| if candidate.nil? candidate = name_generator.next_name candidate = name_generator.next_name while existing_names.include?(candidate) end cand = candidate #: String case kind when :internal # Kept names make re-minification a fixed point: the names this # pass assigns are themselves KEPT_NAME_MAX or shorter, and a # second pass must not shuffle them again. next if info.original_name.to_s.size <= NameGenerator::KEPT_NAME_MAX next unless info.original_name.to_s.size - cand.size > 0 info.short_name = cand @used_short_names << cand when :external saved_per_use = info.prefix_string.size - cand.size next unless saved_per_use > 0 declaration_cost = cand.size + 1 + info.prefix_string.size + 1 net_savings = (saved_per_use * info.usage_count) - declaration_cost next unless net_savings > 0 info.short_name = cand info.char_savings = net_savings existing_names << cand @external_prefixes[info.prefix_path] = info end candidate = nil end end |
#build_alias_declaration(info) ⇒ String?
Build backward alias declaration for renamed value constants. Class/module constants are never renamed (no short_name assigned).
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 342 def build_alias_declaration(info) path = info.full_path # LHS: short parent path + original leaf name lhs = path.each_index.map { |i| if i < path.size - 1 short_name_for_path(path[0..i]) || path[i].to_s else path[i].to_s end }.join('::') # RHS: full short path rhs = path.each_index.map { |i| short_name_for_path(path[0..i]) || path[i].to_s }.join('::') lhs == rhs ? nil : "#{lhs}=#{rhs}" end |
#class_or_module_path?(static_cpath) ⇒ Boolean
Check if a path is a class or module definition
268 269 270 271 272 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 268 def class_or_module_path?(static_cpath) return false unless static_cpath info = @mappings[static_cpath] info && (info.definition_type == :class || info.definition_type == :module) end |
#each_user_defined_path {|arg0| ... } ⇒ void
This method returns an undefined value.
Iterate over user-defined constant paths
263 264 265 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 263 def each_user_defined_path(&block) @mappings.each_key(&block) end |
#empty? ⇒ Boolean
77 78 79 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 77 def empty? @state == :empty end |
#exclude_path(static_cpath) ⇒ void
This method returns an undefined value.
127 128 129 130 131 132 133 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 127 def exclude_path(static_cpath) raise "Cannot exclude when finalized" if finalized? info = @mappings.delete(static_cpath) return unless info name = static_cpath.last @by_name[name]&.reject! { |i| i.full_path == static_cpath } end |
#external_class_root?(cpath) ⇒ Boolean
A class/module definition whose root the program didn't create is a
reopening (e.g. class Array, or module Prism adding to the gem) and
must not be renamed. With boot_roots the judgement is exact for the
program's own runtime; without it, fall back to probing this process.
321 322 323 324 325 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 321 def external_class_root?(cpath) return runtime_constant?(cpath) if @boot_roots.nil? @boot_roots.include?(cpath.first) end |
#finalized? ⇒ Boolean
81 82 83 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 81 def finalized? @state == :frozen end |
#generate_alias_declarations ⇒ Array[String]
Generate backward-compatible alias declarations for renamed constants. Returns array of strings like "OriginalName=ShortName" or "ShortParent::OriginalName=ShortParent::ShortName" for nested constants.
With the :full surface every rename stays restorable: nothing says
which original names code outside the analyzed world spells. A program
that dynamically loads files at runtime had enumerated its external
readers, and bundling them as lazy regions brought every one inside;
what remains outside is a launcher, and a launcher spells the
class/module skeleton (Optcarrot::NES.new.run), never a value
constant. That is the :skeleton surface.
221 222 223 224 225 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 221 def generate_alias_declarations renamed = @mappings.values.select(&:short_name).sort_by { |info| [info.full_path.size, info.full_path] } renamed.reject! { |info| info.definition_type == :value } if @alias_surface == :skeleton renamed.filter_map { |info| build_alias_declaration(info) } end |
#generate_prefix_declarations ⇒ Array[String]
Generate prefix declaration statements (e.g., ["Z=TypeProf::Core::AST"]) Uses chained aliases when a sub-prefix is also aliased
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 296 def generate_prefix_declarations sorted = @external_prefixes.values.sort_by { |info| [info.prefix_path.size, -info.char_savings] } alias_map = {} #: Hash[Array[Symbol], String?] sorted.map do |info| decl_rhs = info.prefix_string (info.prefix_path.size - 1).downto(2) do |len| sub = info.prefix_path[0...len] #: Array[Symbol] if alias_map.key?(sub) rest = info.prefix_path[len..] #: Array[Symbol] decl_rhs = "#{alias_map[sub]}::#{rest.map(&:to_s).join('::')}" break end end alias_map[info.prefix_path] = info.short_name "#{info.short_name}=#{decl_rhs}" end end |
#has_user_defined_prefix?(full_path) ⇒ Boolean
Check if any sub-prefix of the path is user-defined
275 276 277 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 275 def has_user_defined_prefix?(full_path) (1...full_path.size).any? { |i| user_defined_path?(full_path[0...i]) } end |
#increment_usage(name) ⇒ void
This method returns an undefined value.
Increment usage count by simple name (finds first match)
117 118 119 120 121 122 123 124 125 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 117 def increment_usage(name) raise "Cannot increment usage when finalized" if finalized? return unless @by_name.key?(name) # Increment all constants with this name @by_name[name].each do |info| info.usage_count += 1 end end |
#increment_usage_by_path(static_cpath) ⇒ void
This method returns an undefined value.
Increment usage count for a constant by static_cpath
108 109 110 111 112 113 114 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 108 def increment_usage_by_path(static_cpath) raise "Cannot increment usage when finalized" if finalized? return unless @mappings.key?(static_cpath) info = @mappings[static_cpath] info.usage_count += 1 end |
#runtime_constant?(cpath) ⇒ Boolean
Check if a constant path already exists in the Ruby runtime.
Used to detect class/module reopenings (e.g., class Array adding methods
to a built-in class) which must not be renamed.
330 331 332 333 334 335 336 337 338 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 330 def runtime_constant?(cpath) cpath.reduce(Object) do |mod, name| return false unless mod.is_a?(Module) && mod.const_defined?(name, false) mod.const_get(name, false) end true rescue false end |
#set_usage_count_by_path(static_cpath, count) ⇒ void
This method returns an undefined value.
Set usage count for a constant by static_cpath
256 257 258 259 260 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 256 def set_usage_count_by_path(static_cpath, count) raise "Cannot set usage when finalized" if finalized? info = @mappings[static_cpath] info.usage_count = count if info end |
#short_name_for(name) ⇒ String?
Get short name for a constant by simple name (finds first match) Used when static_cpath is not available
236 237 238 239 240 241 242 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 236 def short_name_for(name) return nil unless @by_name.key?(name) infos = @by_name[name] return nil if infos.empty? # Return first match (for backward compatibility) infos.first&.short_name end |
#short_name_for_path(static_cpath) ⇒ String?
Get short name for a constant by static_cpath. The parameter is nilable for the callers that pass sliced sub-paths; a nil key simply misses.
229 230 231 232 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 229 def short_name_for_path(static_cpath) info = static_cpath ? @mappings[static_cpath] : nil info&.short_name end |
#short_name_for_prefix(full_path) ⇒ String?
Get short name for the prefix of a full external path
287 288 289 290 291 292 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 287 def short_name_for_prefix(full_path) return nil if full_path.nil? || full_path.size < 2 prefix = full_path[0...-1] #: Array[Symbol] info = @external_prefixes[prefix] info&.short_name end |
#usage_count_for_path(static_cpath) ⇒ Integer
Get usage count for a constant by static_cpath
250 251 252 253 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 250 def usage_count_for_path(static_cpath) info = @mappings[static_cpath] info ? info.usage_count : 0 end |
#user_defined_path?(static_cpath) ⇒ Boolean
Check if a constant is user-defined by static_cpath
245 246 247 |
# File 'lib/ryac/analysis/constant/rename_mapping.rb', line 245 def user_defined_path?(static_cpath) @mappings.key?(static_cpath) end |