Class: Ryac::Pipeline::ConstantAliaser
- Includes:
- RenamePatcher
- Defined in:
- lib/ryac/pipeline/constant_aliaser.rb,
sig/ryac/pipeline/constant_aliaser.rbs
Overview
Constant aliasing via source patching.
Instance Method Summary collapse
- #build_renamed_via_user_prefix(node, analysis) ⇒ String?
- #collect(ctx, patches) ⇒ void
- #collect_patches(node, patches, analysis) ⇒ void
- #finish(ctx) ⇒ void
- #get_short_cpath(cpath) ⇒ String
-
#initialize(rename_classes: false) ⇒ ConstantAliaser
constructor
A new instance of ConstantAliaser.
- #mark_constant_children(node) ⇒ void
- #needs_analysis? ⇒ Boolean
- #patch_class_node(node, patches, analysis) ⇒ Object
- #patch_constant_path_write(node, patches, analysis) ⇒ Object
-
#patch_constant_ref(node, patches, analysis) ⇒ Object
The patch_* helpers are called for their side effects from the walk_prism block, whose declared result is Symbol? (:skip_children or nil) — untyped rather than void keeps the block body compatible.
- #patch_constant_write(node, patches, analysis) ⇒ Object
- #patch_def_receiver(receiver, patches, analysis) ⇒ Object
-
#patch_definition_path(node, patches, class_cpath, analysis) ⇒ void
A definition's written path names the class relative to its lexical nesting, and the enclosing modules are renamed right along with it — so the renamed spelling only needs the written segments' short names.
- #patch_module_node(node, patches, analysis) ⇒ Object
-
#render_short_cpath(cpath, from: 0) ⇒ String
Each prefix of the cpath may carry its own short name; render from
fromonward, falling back to the original segment where none was assigned.
Methods included from RenamePatcher
#patch_assoc, #patch_block_param_node, #patch_block_params, #patch_def_params, #patch_for_node, #patch_keyword_hash, #patch_keyword_params, #patch_keyword_rest, #patch_multi_target_params, #patch_named_param, #patch_optional_params, #patch_post_params, #patch_required_params, #patch_rest_param, #patch_variable, #patch_variable_name_only, #prism_location_key, #walk_prism
Methods inherited from Stage
#analysis, #analysis_options, #call, #fixpoint?, new
Methods included from SourcePatcher
#apply_patches, #mk, #src, #verify_parses
Constructor Details
#initialize(rename_classes: false) ⇒ ConstantAliaser
Returns a new instance of ConstantAliaser.
13 14 15 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 13 def initialize(rename_classes: false) @rename_classes = rename_classes end |
Instance Method Details
#build_renamed_via_user_prefix(node, analysis) ⇒ String?
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 192 def build_renamed_via_user_prefix(node, analysis) parent_node = node.parent return nil unless parent_node parent_key = prism_location_key(parent_node) parent_resolved = analysis.const_resolution_map[parent_key] return nil unless parent_resolved if @constant_mapping.user_defined_path?(parent_resolved) parent_short = if parent_node.is_a?(Prism::ConstantReadNode) @constant_mapping.short_name_for_path(parent_resolved) || parent_node.name.to_s else get_short_cpath(parent_resolved) end "#{parent_short}::#{node.name}" elsif parent_node.is_a?(Prism::ConstantPathNode) parent_renamed = build_renamed_via_user_prefix(parent_node, analysis) parent_renamed ? "#{parent_renamed}::#{node.name}" : nil end end |
#collect(ctx, patches) ⇒ void
This method returns an undefined value.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 17 def collect(ctx, patches) analysis = analysis(ctx) @class_module_cpath_offsets = Set.new # Declared optional on AnalysisResult but always present by the time # constant patches are collected. @constant_mapping = analysis.constant_mapping #: ConstantRenameMapping # Short-name assignment is this stage's policy — it happens exactly # once, before any constant patch is emitted, and no other stage # reads the mapping. if @constant_mapping generator = NameGenerator.new([], upcase: true) @constant_mapping.assign_short_names(generator, skip_class_modules: !@rename_classes) end collect_patches(ctx.ast, patches, analysis) end |
#collect_patches(node, patches, analysis) ⇒ void
This method returns an undefined value.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 46 def collect_patches(node, patches, analysis) walk_prism(node) do |subnode| case subnode when Prism::ConstantReadNode patch_constant_ref(subnode, patches, analysis) when Prism::ConstantPathNode patch_constant_ref(subnode, patches, analysis) next :skip_children when Prism::ConstantWriteNode patch_constant_write(subnode, patches, analysis) when Prism::ConstantTargetNode # `A, B = ...` — Prism models each target as a ConstantTargetNode, # not a ConstantWriteNode. TypeProf still reports one constant per # target at the same location, so without this the references get # renamed while the definitions keep their original names. patch_constant_write(subnode, patches, analysis) when Prism::ConstantPathWriteNode patch_constant_path_write(subnode, patches, analysis) when Prism::ClassNode patch_class_node(subnode, patches, analysis) when Prism::ModuleNode patch_module_node(subnode, patches, analysis) when Prism::DefNode patch_def_receiver(subnode.receiver, patches, analysis) if subnode.receiver && !subnode.receiver.is_a?(Prism::SelfNode) end end end |
#finish(ctx) ⇒ void
This method returns an undefined value.
33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 33 def finish(ctx) mapping = @constant_mapping return unless mapping prefix_decls = mapping.generate_prefix_declarations ctx.preamble = [ctx.preamble, prefix_decls.join(';')].reject(&:empty?).join(';') if prefix_decls.any? alias_decls = mapping.generate_alias_declarations ctx.aliases = [ctx.aliases, alias_decls.join(';')].reject(&:empty?).join(';') if alias_decls.any? end |
#get_short_cpath(cpath) ⇒ String
213 214 215 216 217 218 219 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 213 def get_short_cpath(cpath) if @constant_mapping.user_defined_path?(cpath) render_short_cpath(cpath) else cpath.map(&:to_s).join('::') end end |
#mark_constant_children(node) ⇒ void
This method returns an undefined value.
182 183 184 185 186 187 188 189 190 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 182 def mark_constant_children(node) case node when Prism::ConstantPathNode @class_module_cpath_offsets << node.location.start_offset mark_constant_children(node.parent) if node.parent when Prism::ConstantReadNode @class_module_cpath_offsets << node.location.start_offset end end |
#needs_analysis? ⇒ Boolean
11 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 11 def needs_analysis? = true |
#patch_class_node(node, patches, analysis) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 130 def patch_class_node(node, patches, analysis) key = prism_location_key(node) patch_definition_path(node, patches, analysis.class_cpath_map[key], analysis) if node.superclass superclass_path = analysis.superclass_resolution_map[key] superclass_path ||= analysis.const_resolution_map[prism_location_key(node.superclass)] if superclass_path short = get_short_cpath(superclass_path) sc_loc = node.superclass.location patches << { start: sc_loc.start_offset, end: sc_loc.end_offset, replacement: short } @class_module_cpath_offsets << sc_loc.start_offset mark_constant_children(node.superclass) end end end |
#patch_constant_path_write(node, patches, analysis) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 118 def patch_constant_path_write(node, patches, analysis) key = prism_location_key(node) static_cpath = analysis.const_write_cpath_map[key] return unless static_cpath path_str = render_short_cpath(static_cpath) target_loc = node.target.location patches << { start: target_loc.start_offset, end: target_loc.end_offset, replacement: path_str } @class_module_cpath_offsets << target_loc.start_offset mark_constant_children(node.target) end |
#patch_constant_ref(node, patches, analysis) ⇒ Object
The patch_* helpers are called for their side effects from the walk_prism block, whose declared result is Symbol? (:skip_children or nil) — untyped rather than void keeps the block body compatible.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'sig/ryac/pipeline/constant_aliaser.rbs', line 22 def patch_constant_ref(node, patches, analysis) return if @class_module_cpath_offsets.include?(node.location.start_offset) key = prism_location_key(node) resolved_cpath = analysis.const_resolution_map[key] full_path = analysis.const_full_path_map[key] prefix_alias = full_path && @constant_mapping&.short_name_for_prefix(full_path) if resolved_cpath && @constant_mapping&.user_defined_path?(resolved_cpath) short = if node.is_a?(Prism::ConstantReadNode) # Bare name reference (e.g., CONST) — use only the leaf short name. # Expanding to a fully qualified path would break constants defined # in `class << self` (metaclass constants are not accessible as Foo::X). @constant_mapping.short_name_for_path(resolved_cpath) || node.name.to_s else get_short_cpath(resolved_cpath) end loc = node.location patches << { start: loc.start_offset, end: loc.end_offset, replacement: short } elsif node.is_a?(Prism::ConstantPathNode) && resolved_cpath && (short = build_renamed_via_user_prefix(node, analysis)) loc = node.location patches << { start: loc.start_offset, end: loc.end_offset, replacement: short } elsif prefix_alias short = "#{prefix_alias}::#{node.name}" loc = node.location patches << { start: loc.start_offset, end: loc.end_offset, replacement: short } end end |
#patch_constant_write(node, patches, analysis) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 104 def patch_constant_write(node, patches, analysis) key = prism_location_key(node) static_cpath = analysis.const_write_cpath_map[key] return unless static_cpath short_name = @constant_mapping.short_name_for_path(static_cpath) return unless short_name # A ConstantTargetNode is entirely the name; a ConstantWriteNode also # spans `= value`, so only its name_loc belongs to us. name_loc = node.is_a?(Prism::ConstantTargetNode) ? node.location : node.name_loc patches << { start: name_loc.start_offset, end: name_loc.end_offset, replacement: short_name } end |
#patch_def_receiver(receiver, patches, analysis) ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 221 def patch_def_receiver(receiver, patches, analysis) return unless @constant_mapping @class_module_cpath_offsets.add(receiver.location.start_offset) key = prism_location_key(receiver) resolved_cpath = case receiver when Prism::ConstantReadNode analysis.const_resolution_map[key] || [receiver.name] when Prism::ConstantPathNode analysis.const_resolution_map[key] end return unless resolved_cpath && @constant_mapping.user_defined_path?(resolved_cpath) short = get_short_cpath(resolved_cpath) loc = receiver.location patches << { start: loc.start_offset, end: loc.end_offset, replacement: short } end |
#patch_definition_path(node, patches, class_cpath, analysis) ⇒ void
This method returns an undefined value.
A definition's written path names the class relative to its lexical
nesting, and the enclosing modules are renamed right along with it —
so the renamed spelling only needs the written segments' short names.
Re-qualifying with the full path (module A; class A::B) costs bytes
and says nothing the nesting doesn't already say: class B inside
module A creates and reopens exactly A::B.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 157 def patch_definition_path(node, patches, class_cpath, analysis) return unless class_cpath # class_cpath exists only when path_segments succeeded on this node, # and in the absolute case it equals the segments outright. segments, = Nesting.path_segments(node.constant_path) #: [Array[Symbol], bool] rendered = render_short_cpath(class_cpath, from: class_cpath.size - segments.size) if rendered != node.constant_path.slice cpath_loc = node.constant_path.location patches << { start: cpath_loc.start_offset, end: cpath_loc.end_offset, replacement: rendered } end @class_module_cpath_offsets << node.constant_path.location.start_offset mark_constant_children(node.constant_path) end |
#patch_module_node(node, patches, analysis) ⇒ Object
147 148 149 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 147 def patch_module_node(node, patches, analysis) patch_definition_path(node, patches, analysis.class_cpath_map[prism_location_key(node)], analysis) end |
#render_short_cpath(cpath, from: 0) ⇒ String
Each prefix of the cpath may carry its own short name; render from
from onward, falling back to the original segment where none was
assigned.
176 177 178 179 180 |
# File 'lib/ryac/pipeline/constant_aliaser.rb', line 176 def render_short_cpath(cpath, from: 0) (from...cpath.size).map { |i| @constant_mapping.short_name_for_path(cpath[0..i]) || cpath[i].to_s }.join('::') end |