Class: Ryac::Pipeline::MethodRenamer

Inherits:
Stage
  • Object
show all
Includes:
RenamePatcher
Defined in:
lib/ryac/pipeline/method_renamer.rb,
sig/ryac/pipeline/method_renamer.rbs

Overview

Method renaming: renames method definitions and call sites.

policy: :aggressive (default) renames every group the exclusion passes leave standing, folding unresolved same-name calls and blind defs into the groups they probably belong to. :safe renames only groups whose every caller type inference actually resolved — an unresolved call excludes its name, an uncalled def keeps its name (it is either dead or someone outside the program calls it), and a name that appears inside a string literal keeps its spelling (eval and send-by-string read strings, not the syntax tree).

Constant Summary collapse

SEND_METHODS =

Returns:

  • (Array[Symbol])
%i[send __send__ public_send].freeze

Instance Method Summary collapse

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, #call, #finish, #fixpoint?, new

Methods included from SourcePatcher

#apply_patches, #mk, #src, #verify_parses

Constructor Details

#initialize(policy: :aggressive) ⇒ MethodRenamer

Returns a new instance of MethodRenamer.

Parameters:

  • policy: (Symbol) (defaults to: :aggressive)


18
19
20
# File 'lib/ryac/pipeline/method_renamer.rb', line 18

def initialize(policy: :aggressive)
  @policy = policy
end

Instance Method Details

#analysis_options ⇒ Hash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


22
# File 'lib/ryac/pipeline/method_renamer.rb', line 22

def analysis_options = { method_policy: @policy }

#collect(ctx, patches) ⇒ void

This method returns an undefined value.

Parameters:



26
27
28
29
30
31
32
33
# File 'lib/ryac/pipeline/method_renamer.rb', line 26

def collect(ctx, patches)
  analysis = analysis(ctx)
  rename_map = analysis.rename_map
  method_alias_map = analysis.method_alias_map
  @negated_transforms = {}
  @source_bytes = analysis.source.content
  collect_patches(ctx.ast, patches, analysis, rename_map, method_alias_map)
end

#collect_patches(node, patches, analysis, rename_map, method_alias_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node)
  • patches (Array[patch_entry])
  • analysis (AnalysisResult)
  • rename_map (Hash[location_key, String])
  • method_alias_map (Hash[location_key, Symbol])


37
38
39
40
41
42
43
# File 'lib/ryac/pipeline/method_renamer.rb', line 37

def collect_patches(node, patches, analysis, rename_map, method_alias_map)
  # @type var callback: ^(Prism::Node) -> void
  callback = proc { |subnode|
    handle_node(subnode, patches, analysis, rename_map, method_alias_map)
  }
  walk_prism(node, &callback)
end

#handle_node(subnode, patches, analysis, rename_map, method_alias_map) ⇒ void

This method returns an undefined value.

Parameters:

  • subnode (Prism::Node)
  • patches (Array[patch_entry])
  • analysis (AnalysisResult)
  • rename_map (Hash[location_key, String])
  • method_alias_map (Hash[location_key, Symbol])


45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/ryac/pipeline/method_renamer.rb', line 45

def handle_node(subnode, patches, analysis, rename_map, method_alias_map)
  case subnode
  when Prism::DefNode
    patch_def_name(subnode, patches, rename_map)

  when Prism::CallNode
    patch_call_node(subnode, patches, rename_map, method_alias_map, analysis)

  when Prism::CallOperatorWriteNode,
       Prism::CallOrWriteNode,
       Prism::CallAndWriteNode
    patch_call_operator_write(subnode, patches, rename_map, method_alias_map)
  end
end

#needs_analysis? ⇒ Boolean

Returns:

  • (Boolean)


24
# File 'lib/ryac/pipeline/method_renamer.rb', line 24

def needs_analysis? = true

#patch_call_node(node, patches, rename_map, method_alias_map, analysis) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallNode)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])
  • method_alias_map (Hash[location_key, Symbol])
  • analysis (AnalysisResult)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/ryac/pipeline/method_renamer.rb', line 69

def patch_call_node(node, patches, rename_map, method_alias_map, analysis)
  # Detect negated transforms: !receiver.empty? → receiver!=[]
  # The ! CallNode is visited before its children, so we mark the inner
  # node here and negate the transform when it's applied later.
  if node.name == :"!" && node.receiver.is_a?(Prism::CallNode)
    inner = node.receiver
    inner_key = prism_location_key(inner)
    transform = analysis.method_transform_map[inner_key]
    if transform&.start_with?('==') && inner.call_operator_loc && !inner.safe_navigation?
      @negated_transforms[inner_key] = true
      patches << { start: node.location.start_offset, end: inner.location.start_offset, replacement: '' }
    end
  end

  key = prism_location_key(node)

  # Meta calls are structural, not plain calls: attr declarations are
  # rewritten wholesale by AttrDeclShorten, and include must keep its
  # name. Renaming or transforming them here would corrupt them.
  return if analysis.meta_node_map[key]

  # Structural transforms (e.g. .first → [0], .empty? → ==[])
  transform = analysis.method_transform_map[key]
  if transform && node.call_operator_loc && !node.safe_navigation?
    replacement = @negated_transforms[key] ? "!#{transform[1..]}" : transform
    end_offset = node.location.end_offset
    # Consume trailing spaces around ternary ? when no longer needed
    # e.g. .empty? ? "x" → ==[]?"x" since ] is not a name char
    if @source_bytes.getbyte(end_offset) == 0x20 && @source_bytes.getbyte(end_offset + 1) == 0x3F # ' ' and '?'
      end_offset += 2 # consume space + ternary ?
      end_offset += 1 if @source_bytes.getbyte(end_offset) == 0x20 # consume space after ?
      replacement = "#{replacement}?"
    end
    # call_operator_loc was checked at the top of this branch
    patches << { start: node.call_operator_loc.start_offset, end: end_offset, replacement: replacement } # steep:ignore NoMethod
    return
  elsif transform && !node.call_operator_loc && node.receiver.is_a?(Prism::CallNode)
    # A comparison-shape transform (.size==0 → ==[]), registered on
    # the operator call whose receiver is the size query: the patch
    # swallows the query and the comparison together.
    receiver_end = node.receiver.receiver.location.end_offset # steep:ignore NoMethod
    patches << { start: receiver_end, end: node.location.end_offset, replacement: transform }
    return
  end

  return unless node.message_loc

  short = rename_map[key]

  # send/public_send/__send__: patch the symbol argument, not the method name
  if short && SEND_METHODS.include?(node.name)
    patch_send_symbol(node, patches, short)
    return
  end

  alias_name = method_alias_map[key]&.to_s
  replacement = short || alias_name
  return unless replacement

  if replacement.end_with?('=') && !replacement.end_with?('==') &&
     node.name.to_s.end_with?('=') && !node.name.to_s.end_with?('==')
    msg_slice = node.message_loc.slice
    replacement = replacement.chomp('=') unless msg_slice.end_with?('=')
  end

  end_offset = node.message_loc.end_offset
  # The compactor separates a ?/!-ending name from an =-starting
  # operator with one protective space. A rename that drops the ?/!
  # must take the space with it — left behind, re-minification removes
  # it and the self-host fixed point drifts by a byte per site.
  if node.name.to_s.end_with?('?', '!') && !replacement.end_with?('?', '!') &&
     @source_bytes.getbyte(end_offset) == 0x20 && @source_bytes.getbyte(end_offset + 1) == 0x3D # ' ' then '='
    end_offset += 1
  end
  patches << { start: node.message_loc.start_offset, end: end_offset, replacement: replacement }
end

#patch_call_operator_write(node, patches, rename_map, method_alias_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallOperatorWriteNode, Prism::CallOrWriteNode, Prism::CallAndWriteNode)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])
  • method_alias_map (Hash[location_key, Symbol])


156
157
158
159
160
161
162
163
# File 'lib/ryac/pipeline/method_renamer.rb', line 156

def patch_call_operator_write(node, patches, rename_map, method_alias_map)
  return unless node.message_loc
  key = prism_location_key(node)
  short = rename_map[key] || method_alias_map[key]&.to_s
  return unless short
  short = short.chomp('=') if short.end_with?('=') && !short.end_with?('==')
  patches << { start: node.message_loc.start_offset, end: node.message_loc.end_offset, replacement: short }
end

#patch_def_name(node, patches, rename_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::DefNode)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])


60
61
62
63
64
65
66
67
# File 'lib/ryac/pipeline/method_renamer.rb', line 60

def patch_def_name(node, patches, rename_map)
  key = prism_location_key(node)
  short = rename_map[key]
  return unless short

  name_loc = node.name_loc
  patches << { start: name_loc.start_offset, end: name_loc.end_offset, replacement: short }
end

#patch_send_symbol(node, patches, short_name) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallNode)
  • patches (Array[patch_entry])
  • short_name (String)


148
149
150
151
152
153
154
# File 'lib/ryac/pipeline/method_renamer.rb', line 148

def patch_send_symbol(node, patches, short_name)
  first_arg = node.arguments&.arguments&.first
  return unless first_arg.is_a?(Prism::SymbolNode)

  loc = first_arg.location
  patches << { start: loc.start_offset, end: loc.end_offset, replacement: ":#{short_name}" }
end