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-softnil("no rule matched"; the caller will widen toDynamic[Top]). - .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 like1 | 1 → 1).
Class Method Details
.active? ⇒ Boolean
Plain integer read (GVL-atomic) — the disabled fast path.
110 111 112 |
# File 'lib/rigor/inference/flow_tracer.rb', line 110 def active? @active_count.positive? end |
.bind(name, type) ⇒ Object
Scope#with_local — the moment a local enters the scope.
124 125 126 |
# File 'lib/rigor/inference/flow_tracer.rb', line 124 def bind(name, type) Thread.current[KEY]&.emit(:bind, data: { name: name.to_s, type: describe(type) }) end |
.describe(type) ⇒ Object
153 154 155 156 157 158 |
# File 'lib/rigor/inference/flow_tracer.rb', line 153 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[Top]).
138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/rigor/inference/flow_tracer.rb', line 138 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
160 161 162 163 164 165 166 |
# File 'lib/rigor/inference/flow_tracer.rb', line 160 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.
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rigor/inference/flow_tracer.rb', line 97 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?).
116 117 118 119 120 121 |
# File 'lib/rigor/inference/flow_tracer.rb', line 116 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).
129 130 131 132 133 134 |
# File 'lib/rigor/inference/flow_tracer.rb', line 129 def union(members, result) Thread.current[KEY]&.emit( :union, data: { members: members.map { |m| describe(m) }.freeze, type: describe(result) } ) end |