Module: Rigor::Inference::FlowTracer
- Defined in:
- lib/rigor/inference/flow_tracer.rb
Overview
Thread-local event recorder behind ‘rigor trace`: while a block runs under FlowTracer.record, the inference engine emits a flat, ordered event stream describing HOW it typed the program — expression enter/result pairs, scope binds, union formation, and method-dispatch outcomes. The CLI replays that stream as a terminal animation (or dumps it as JSON); the engine itself never reads the events back, so recording is purely observational and MUST NOT change any inferred type.
Modelled on Analysis::DependencyRecorder: thread-local state, a module-level activation count so the disabled fast path (FlowTracer.active?) is a plain integer read, and a frozen snapshot for consumers. The instrumented hot paths (‘ExpressionTyper#type_of`, `Scope#with_local`, `Type::Combinator.union`, `MethodDispatcher.dispatch`) each guard their emit behind FlowTracer.active?, so a normal (non-tracing) run pays one integer comparison.
Defined Under Namespace
Class Method Summary collapse
-
.active? ⇒ Boolean
Plain integer read (GVL-atomic) — the disabled fast path.
-
.bind(name, type) ⇒ Object
‘Scope#with_local` — the moment a local enters the scope.
- .describe(type) ⇒ Object
-
.dispatch(receiver:, method_name:, args:, result:, location: nil) ⇒ Object
‘MethodDispatcher.dispatch` — resolution or the fail-soft `nil` (“no rule matched”; the caller will widen to `Dynamic`).
- .location_hash(loc) ⇒ Object
-
.record ⇒ Object
Activates recording on the current thread for the duration of the block and returns the frozen event list.
-
.trace_node(node) ⇒ Object
Brackets one expression-typing recursion.
-
.union(members, result) ⇒ Object
‘Type::Combinator.union` — the moment branch types merge (including degenerate collapses like `1 | 1 → 1`).
Class Method Details
.active? ⇒ Boolean
Plain integer read (GVL-atomic) — the disabled fast path.
119 120 121 |
# File 'lib/rigor/inference/flow_tracer.rb', line 119 def active? @active_count.positive? end |
.bind(name, type) ⇒ Object
‘Scope#with_local` — the moment a local enters the scope.
134 135 136 |
# File 'lib/rigor/inference/flow_tracer.rb', line 134 def bind(name, type) Thread.current[KEY]&.emit(:bind, data: { name: name.to_s, type: describe(type) }) end |
.describe(type) ⇒ Object
164 165 166 167 168 169 |
# File 'lib/rigor/inference/flow_tracer.rb', line 164 def describe(type) return "nil" if type.nil? return type.describe(:short) if type.respond_to?(:describe) type.inspect end |
.dispatch(receiver:, method_name:, args:, result:, location: nil) ⇒ Object
‘MethodDispatcher.dispatch` — resolution or the fail-soft `nil` (“no rule matched”; the caller will widen to `Dynamic`).
149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/rigor/inference/flow_tracer.rb', line 149 def dispatch(receiver:, method_name:, args:, result:, location: nil) recorder = Thread.current[KEY] return unless recorder recorder.emit( :dispatch, location: location && location_hash(location), data: { receiver: describe(receiver), method: method_name.to_s, args: args.map { |a| describe(a) }.freeze, type: result && describe(result), resolved: !result.nil? } ) end |
.location_hash(loc) ⇒ Object
171 172 173 174 175 176 177 |
# File 'lib/rigor/inference/flow_tracer.rb', line 171 def location_hash(loc) { start_line: loc.start_line, start_column: loc.start_column, end_line: loc.end_line, end_column: loc.end_column, start_offset: loc.start_offset, end_offset: loc.end_offset } end |
.record ⇒ Object
Activates recording on the current thread for the duration of the block and returns the frozen event list. Nests safely; restores the previous recorder on exit.
106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/rigor/inference/flow_tracer.rb', line 106 def record previous = Thread.current[KEY] recorder = Recorder.new Thread.current[KEY] = recorder @mutex.synchronize { @active_count += 1 } yield recorder.events.freeze ensure Thread.current[KEY] = previous @mutex.synchronize { @active_count -= 1 } end |
.trace_node(node) ⇒ Object
Brackets one expression-typing recursion. Falls through to the bare block when the current thread is not recording (another thread may have flipped active?).
126 127 128 129 130 131 |
# File 'lib/rigor/inference/flow_tracer.rb', line 126 def trace_node(node, &) recorder = Thread.current[KEY] return yield unless recorder recorder.node(node, &) end |
.union(members, result) ⇒ Object
‘Type::Combinator.union` — the moment branch types merge (including degenerate collapses like `1 | 1 → 1`).
140 141 142 143 144 145 |
# File 'lib/rigor/inference/flow_tracer.rb', line 140 def union(members, result) Thread.current[KEY]&.emit( :union, data: { members: members.map { |m| describe(m) }.freeze, type: describe(result) } ) end |