Module: Audition::Rewriters::Memoization

Defined in:
lib/audition/rewriters.rb

Overview

Rewrites singleton-scope ivar state to Ractor-local storage:

@x ||= expr   ->  Ractor.store_if_absent(:"Klass/@x") { expr }
@x = expr     ->  Ractor.current[:"Klass/@x"] = expr
@x            ->  Ractor.current[:"Klass/@x"]

Only when every reference in singleton scope is visible in this file, none sit directly in the class body, and no compound writes exist. Caveat (why this is unsafe): each Ractor computes its own copy, and store_if_absent treats a stored nil as present where ||= would recompute.

Defined Under Namespace

Classes: SingletonIvars

Class Method Summary collapse

Class Method Details

.edit_for(op, key) ⇒ Object



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/audition/rewriters.rb', line 117

def self.edit_for(op, key)
  node = op[:node]
  case op[:kind]
  when :or_write
    value = node.value.location.slice
    Autofix.new(
      start_offset: node.location.start_offset,
      end_offset: node.location.end_offset,
      replacement:
        "Ractor.store_if_absent(#{key}) { #{value} }",
      safety: :unsafe
    )
  when :write
    Autofix.new(
      start_offset: node.name_loc.start_offset,
      end_offset: node.name_loc.end_offset,
      replacement: "Ractor.current[#{key}]",
      safety: :unsafe
    )
  when :read
    Autofix.new(
      start_offset: node.location.start_offset,
      end_offset: node.location.end_offset,
      replacement: "Ractor.current[#{key}]",
      safety: :unsafe
    )
  end
end

.plan(file, findings) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/audition/rewriters.rb', line 97

def self.plan(file, findings)
  return [] if findings.none? { |f| f.check == "class-level-state" }

  collector = SingletonIvars.new
  collector.visit(file.root)
  edits = []
  collector.groups.each do |(namespace, name), ops|
    next if namespace.empty?

    kinds = ops.map { |op| op[:kind] }
    next unless kinds.include?(:or_write)
    next if kinds.include?(:other)
    next if ops.any? { |op| op[:body] }

    key = %(:"#{namespace}/#{name}")
    ops.each { |op| edits << edit_for(op, key) }
  end
  edits
end