Module: Rigor::Inference::ScopeIndexer
- Defined in:
- lib/rigor/inference/scope_indexer.rb
Overview
Builds a per-node scope index for a Prism program by running ‘Rigor::Inference::StatementEvaluator` over the root and recording the entry scope visible at every node. Expression-interior nodes the evaluator does not specialise (call receivers, arguments, array/hash elements, …) inherit their nearest statement-y ancestor’s recorded scope, so a downstream caller that looks up the scope for any Prism node in the tree always gets the scope that was effectively visible at that point.
The CLI commands ‘rigor type-of` and `rigor type-scan` consume the index so that local-variable bindings established earlier in the program are visible to the typer when probing later nodes. Without the index, both commands would type every node under an empty scope and miss the constant-folding / dispatch precision that Slice 3 phase 2’s StatementEvaluator unlocks.
The returned object is an identity-comparing Hash:
“‘ruby index = Rigor::Inference::ScopeIndexer.index(program, default_scope: Scope.empty) index #=> the Rigor::Scope visible at that node “`
Nodes that are not part of the program subtree (e.g. synthesised virtual nodes that the caller looks up after the fact) yield the ‘default_scope`. The returned Hash is mutable in principle but callers MUST treat it as read-only; the indexer itself never exposes a way to update it past construction. rubocop:disable Metrics/ModuleLength
Constant Summary collapse
- TOP_LEVEL_DEF_KEY =
v0.0.3 A — sentinel key under which ‘record_def_node` files DefNodes that live outside any class / module body (top-level helpers, `def`s nested inside DSL blocks like `RSpec.describe … do; def helper; end`). Looked up by `Scope#top_level_def_for` to give implicit-self calls priority over RBS dispatch when the file defines a same-named local method.
"<toplevel>"
Class Method Summary collapse
-
.build_class_cvar_index(root, default_scope) ⇒ Object
Slice 7 phase 6 — class-cvar pre-pass.
-
.build_class_ivar_index(root, default_scope) ⇒ Object
Slice 7 phase 2.
-
.build_declaration_artifacts(root) ⇒ Object
Walks the program once for ‘Prism::ModuleNode` and `Prism::ClassNode`, recording the `Singleton` type for the outermost `constant_path` node of each declaration.
-
.build_discovered_def_nodes(root) ⇒ Object
v0.0.2 #5 — instance-side def-node recording.
-
.build_discovered_methods(root) ⇒ Object
Slice 7 phase 12 — in-source method discovery pre-pass.
-
.build_in_source_constants(root, default_scope) ⇒ Object
Slice 7 phase 9 — in-source constant value pre-pass.
-
.build_program_global_index(root, default_scope) ⇒ Object
Slice 7 phase 6 — program-global pre-pass.
- .collect_def_cvar_writes(def_node, qualified_prefix, default_scope, accumulator) ⇒ Object
- .collect_def_ivar_writes(def_node, qualified_prefix, default_scope, accumulator) ⇒ Object
- .data_constant_receiver?(node) ⇒ Boolean
-
.data_define_call?(node) ⇒ Boolean
Recognises ‘Data.define(*Symbol)` and `Data.define(*Symbol) do …
- .gather_cvar_writes(node, scope, class_name, accumulator) ⇒ Object
- .gather_global_writes(node, scope, accumulator) ⇒ Object
- .gather_ivar_writes(node, scope, class_name, accumulator) ⇒ Object
-
.index(root, default_scope:) ⇒ Hash{Prism::Node => Rigor::Scope}
Build the scope index for a Prism program subtree.
- .literal_method_name(node) ⇒ Object
-
.propagate(node, table, parent_scope) ⇒ Object
Walks ‘node`’s subtree DFS and fills in scope entries for every Prism node the StatementEvaluator did not visit (i.e. expression- interior nodes like the receiver/args of a CallNode).
- .propagate_if_branches(node, table, current_scope) ⇒ Object
- .propagate_unless_branches(node, table, current_scope) ⇒ Object
- .qualified_name_for(constant_path_node) ⇒ Object
- .record_class_or_module?(node, qualified_prefix, identity_table, discovered) ⇒ Boolean
- .record_constant_write(node, qualified_prefix, default_scope, accumulator, base_name) ⇒ Object
- .record_cvar_write(node, scope, class_name, accumulator) ⇒ Object
-
.record_data_define_constant?(node, qualified_prefix, identity_table, discovered) ⇒ Boolean
Recognises ‘Const = Data.define(*Symbol) [do … end]` and registers `Const` (qualified by the surrounding class/module path) as a discovered class.
- .record_declarations(node, qualified_prefix, identity_table, discovered) ⇒ Object
- .record_def_method(def_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
- .record_def_node(def_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
- .record_define_method(call_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
- .record_global_write(node, scope, accumulator) ⇒ Object
- .record_ivar_write(node, scope, class_name, accumulator) ⇒ Object
- .render_constant_path(node) ⇒ Object
- .walk_class_cvars(node, qualified_prefix, default_scope, accumulator) ⇒ Object
- .walk_class_ivars(node, qualified_prefix, default_scope, accumulator) ⇒ Object
-
.walk_constant_writes(node, qualified_prefix, default_scope, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity.
-
.walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity.
-
.walk_methods(node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
Class Method Details
.build_class_cvar_index(root, default_scope) ⇒ Object
Slice 7 phase 6 — class-cvar pre-pass. Same shape as the ivar pre-pass but collects ‘Prism::ClassVariableWriteNode` writes inside ANY def body (instance or singleton) of the enclosing class, because Ruby cvars are shared across both facets. The resulting table is seeded into both instance and singleton method bodies through `Scope#class_cvars_for`.
203 204 205 206 207 |
# File 'lib/rigor/inference/scope_indexer.rb', line 203 def build_class_cvar_index(root, default_scope) accumulator = {} walk_class_cvars(root, [], default_scope, accumulator) accumulator.transform_values(&:freeze).freeze end |
.build_class_ivar_index(root, default_scope) ⇒ Object
Slice 7 phase 2. Builds the class-level ivar accumulator by walking every ‘Prism::ClassNode` / `Prism::ModuleNode` body, descending into each nested `Prism::DefNode`, and typing every `Prism::InstanceVariableWriteNode` rvalue under a scope that carries the appropriate `self_type` for that def (singleton vs instance). The rvalue is typed with NO local bindings — the pre-pass lacks statement-level threading — so `@x = 1` records `Constant` but `@x = some_local + 1` records `Dynamic` (since `some_local` is unbound at pre-pass time). Multiple writes to the same ivar union via `Type::Combinator.union`.
131 132 133 134 135 |
# File 'lib/rigor/inference/scope_indexer.rb', line 131 def build_class_ivar_index(root, default_scope) accumulator = {} walk_class_ivars(root, [], default_scope, accumulator) accumulator.transform_values(&:freeze).freeze end |
.build_declaration_artifacts(root) ⇒ Object
Walks the program once for ‘Prism::ModuleNode` and `Prism::ClassNode`, recording the `Singleton` type for the outermost `constant_path` node of each declaration. Inner segments of a `class Foo::Bar::Baz` path remain real references (resolved through the ordinary lexical walk), so we annotate ONLY the topmost path node. Nested declarations contribute their fully qualified path: `class A::B; class C; …` produces `A::B` for the outer and `A::B::C` for the inner.
467 468 469 470 471 472 |
# File 'lib/rigor/inference/scope_indexer.rb', line 467 def build_declaration_artifacts(root) identity_table = {}.compare_by_identity discovered = {} record_declarations(root, [], identity_table, discovered) [identity_table.freeze, discovered.freeze] end |
.build_discovered_def_nodes(root) ⇒ Object
v0.0.2 #5 — instance-side def-node recording. Walks class bodies the same way as ‘build_discovered_methods` but records the actual `Prism::DefNode` for each instance method so `ExpressionTyper` can re-type the body at the call site for inter-procedural return inference. Singleton methods and `define_method` calls are intentionally skipped: the inference path needs a statically introspectable body, and singleton dispatch has its own complications (Class / Module ancestry) the first-iteration rule does not yet model.
390 391 392 393 394 |
# File 'lib/rigor/inference/scope_indexer.rb', line 390 def build_discovered_def_nodes(root) accumulator = {} walk_def_nodes(root, [], false, accumulator) accumulator.transform_values(&:freeze).freeze end |
.build_discovered_methods(root) ⇒ Object
Slice 7 phase 12 — in-source method discovery pre-pass. Walks every class/module body and records the methods introduced via ‘Prism::DefNode` (instance + singleton) and via recognised `define_method(:name) { … }` calls. The returned table maps qualified class name to a `Hash[Symbol, :instance | :singleton]`.
337 338 339 340 341 |
# File 'lib/rigor/inference/scope_indexer.rb', line 337 def build_discovered_methods(root) accumulator = {} walk_methods(root, [], false, accumulator) accumulator.transform_values(&:freeze).freeze end |
.build_in_source_constants(root, default_scope) ⇒ Object
Slice 7 phase 9 — in-source constant value pre-pass. Walks the entire program (top-level AND inside class / module / def bodies) for ‘Prism::ConstantWriteNode` and `Prism::ConstantPathWriteNode`, types each rvalue, and accumulates by qualified name. Constants defined inside a class body are qualified with the surrounding class path; constants written via a path (`Foo::BAR = …`) use the rendered path as-is.
289 290 291 292 293 |
# File 'lib/rigor/inference/scope_indexer.rb', line 289 def build_in_source_constants(root, default_scope) accumulator = {} walk_constant_writes(root, [], default_scope, accumulator) accumulator.freeze end |
.build_program_global_index(root, default_scope) ⇒ Object
Slice 7 phase 6 — program-global pre-pass. Globals are process-wide so the accumulator is a flat ‘Hash[Symbol, Type::t]` populated from every `Prism::GlobalVariableWriteNode` in the program (top-level AND inside method bodies). The same accumulator is seeded into every method body and the top-level scope.
261 262 263 264 265 |
# File 'lib/rigor/inference/scope_indexer.rb', line 261 def build_program_global_index(root, default_scope) accumulator = {} gather_global_writes(root, default_scope, accumulator) accumulator.freeze end |
.collect_def_cvar_writes(def_node, qualified_prefix, default_scope, accumulator) ⇒ Object
230 231 232 233 234 235 236 |
# File 'lib/rigor/inference/scope_indexer.rb', line 230 def collect_def_cvar_writes(def_node, qualified_prefix, default_scope, accumulator) return if def_node.body.nil? || qualified_prefix.empty? class_name = qualified_prefix.join("::") body_scope = default_scope.with_self_type(Type::Combinator.nominal_of(class_name)) gather_cvar_writes(def_node.body, body_scope, class_name, accumulator) end |
.collect_def_ivar_writes(def_node, qualified_prefix, default_scope, accumulator) ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/rigor/inference/scope_indexer.rb', line 158 def collect_def_ivar_writes(def_node, qualified_prefix, default_scope, accumulator) return if def_node.body.nil? || qualified_prefix.empty? class_name = qualified_prefix.join("::") self_type = if def_node.receiver.is_a?(Prism::SelfNode) Type::Combinator.singleton_of(class_name) else Type::Combinator.nominal_of(class_name) end body_scope = default_scope.with_self_type(self_type) gather_ivar_writes(def_node.body, body_scope, class_name, accumulator) end |
.data_constant_receiver?(node) ⇒ Boolean
534 535 536 537 538 539 540 541 |
# File 'lib/rigor/inference/scope_indexer.rb', line 534 def data_constant_receiver?(node) case node when Prism::ConstantReadNode node.name == :Data when Prism::ConstantPathNode node.parent.nil? && node.name == :Data end end |
.data_define_call?(node) ⇒ Boolean
Recognises ‘Data.define(*Symbol)` and `Data.define(*Symbol) do … end` at constant-write rvalue position. The receiver MUST be the bare `Data` constant (or `::Data`); other receivers (a local variable, a method call return) are rejected because their identity is not statically known.
525 526 527 528 529 530 531 532 |
# File 'lib/rigor/inference/scope_indexer.rb', line 525 def data_define_call?(node) return false unless node.is_a?(Prism::CallNode) return false unless node.name == :define return false unless data_constant_receiver?(node.receiver) args = node.arguments&.arguments || [] args.all?(Prism::SymbolNode) end |
.gather_cvar_writes(node, scope, class_name, accumulator) ⇒ Object
238 239 240 241 242 243 244 245 |
# File 'lib/rigor/inference/scope_indexer.rb', line 238 def gather_cvar_writes(node, scope, class_name, accumulator) return unless node.is_a?(Prism::Node) record_cvar_write(node, scope, class_name, accumulator) if node.is_a?(Prism::ClassVariableWriteNode) return if IVAR_BARRIER_NODES.any? { |klass| node.is_a?(klass) } node.compact_child_nodes.each { |c| gather_cvar_writes(c, scope, class_name, accumulator) } end |
.gather_global_writes(node, scope, accumulator) ⇒ Object
267 268 269 270 271 272 |
# File 'lib/rigor/inference/scope_indexer.rb', line 267 def gather_global_writes(node, scope, accumulator) return unless node.is_a?(Prism::Node) record_global_write(node, scope, accumulator) if node.is_a?(Prism::GlobalVariableWriteNode) node.compact_child_nodes.each { |c| gather_global_writes(c, scope, accumulator) } end |
.gather_ivar_writes(node, scope, class_name, accumulator) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/rigor/inference/scope_indexer.rb', line 176 def gather_ivar_writes(node, scope, class_name, accumulator) return unless node.is_a?(Prism::Node) record_ivar_write(node, scope, class_name, accumulator) if node.is_a?(Prism::InstanceVariableWriteNode) # Don't recurse into nested defs, classes, or modules; their # ivars belong to their own enclosing class. return if IVAR_BARRIER_NODES.any? { |klass| node.is_a?(klass) } node.compact_child_nodes.each { |c| gather_ivar_writes(c, scope, class_name, accumulator) } end |
.index(root, default_scope:) ⇒ Hash{Prism::Node => Rigor::Scope}
Build the scope index for a Prism program subtree.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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 |
# File 'lib/rigor/inference/scope_indexer.rb', line 53 def index(root, default_scope:) # rubocop:disable Metrics/AbcSize # Slice A-declarations. Build the declaration overrides # first so every scope handed to the StatementEvaluator # already carries the table; structural sharing through # `Scope#with_local` / `#with_fact` / `#with_self_type` # propagates it across every derived scope. declared_types, discovered_classes = build_declaration_artifacts(root) seeded_scope = default_scope .with_declared_types(declared_types) .with_discovered_classes(discovered_classes) # Slice 7 phase 2. Pre-pass over every class/module body # to collect the per-class ivar accumulator. Seeded after # declared_types so the rvalue typer in the pre-pass can # see declaration overrides. class_ivars = build_class_ivar_index(root, seeded_scope) seeded_scope = seeded_scope.with_class_ivars(class_ivars) # Slice 7 phase 6. Same pre-pass shape for cvars (per # class) and globals (program-wide). Globals are also # materialised into the top-level scope's `globals` map # so reads at the top level (and in CLI probes that do # not enter a method body) observe the precise type # without consulting the accumulator on every lookup. class_cvars = build_class_cvar_index(root, seeded_scope) seeded_scope = seeded_scope.with_class_cvars(class_cvars) program_globals = build_program_global_index(root, seeded_scope) seeded_scope = seeded_scope.with_program_globals(program_globals) program_globals.each { |name, type| seeded_scope = seeded_scope.with_global(name, type) } # Slice 7 phase 9. In-source constant value tracking. # Walks every ConstantWriteNode/ConstantPathWriteNode in # the program and types its rvalue under a scope that # carries the surrounding qualified prefix as # `self_type`, so the rvalue typer sees in-class # references resolve correctly. Multiple writes to the # same qualified name union via `Type::Combinator.union`. in_source_constants = build_in_source_constants(root, seeded_scope) seeded_scope = seeded_scope.with_in_source_constants(in_source_constants) # Slice 7 phase 12. In-source method discovery. Walks # every class/module body for `Prism::DefNode` and # recognised `define_method` calls and records the # introduced method names. `rigor check` consults the # table to suppress false positives for methods the # user has defined but no RBS sig describes. discovered_methods = build_discovered_methods(root) seeded_scope = seeded_scope.with_discovered_methods(discovered_methods) # v0.0.2 #5 — also record the def node itself for # instance methods so the engine can re-type the body # when a call site dispatches against a user-defined # method without an RBS sig. discovered_def_nodes = build_discovered_def_nodes(root) seeded_scope = seeded_scope.with_discovered_def_nodes(discovered_def_nodes) table = {}.compare_by_identity table.default = seeded_scope on_enter = ->(node, scope) { table[node] = scope unless table.key?(node) } StatementEvaluator.new(scope: seeded_scope, on_enter: on_enter).evaluate(root) propagate(root, table, seeded_scope) table end |
.literal_method_name(node) ⇒ Object
452 453 454 455 456 |
# File 'lib/rigor/inference/scope_indexer.rb', line 452 def literal_method_name(node) return nil unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode) node.unescaped&.to_sym end |
.propagate(node, table, parent_scope) ⇒ Object
Walks ‘node`’s subtree DFS and fills in scope entries for every Prism node the StatementEvaluator did not visit (i.e. expression- interior nodes like the receiver/args of a CallNode). Those nodes inherit their nearest recorded ancestor’s scope.
‘IfNode` / `UnlessNode` are special-cased: the truthy and falsey branches each get their predicate’s narrowed scope before recursing. This handles expression-position conditionals (e.g. ‘cache = if cond; t; else; e; end` and conditionals nested as call arguments) which are typed by ExpressionTyper without going through `eval_if`’s narrowing path.
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 |
# File 'lib/rigor/inference/scope_indexer.rb', line 573 def propagate(node, table, parent_scope) return unless node.is_a?(Prism::Node) current_scope = if table.key?(node) table[node] else table[node] = parent_scope parent_scope end case node when Prism::IfNode propagate_if_branches(node, table, current_scope) when Prism::UnlessNode propagate_unless_branches(node, table, current_scope) else node.compact_child_nodes.each { |child| propagate(child, table, current_scope) } end end |
.propagate_if_branches(node, table, current_scope) ⇒ Object
594 595 596 597 598 599 |
# File 'lib/rigor/inference/scope_indexer.rb', line 594 def propagate_if_branches(node, table, current_scope) truthy_scope, falsey_scope = Narrowing.predicate_scopes(node.predicate, current_scope) propagate(node.predicate, table, current_scope) if node.predicate propagate(node.statements, table, truthy_scope) if node.statements propagate(node.subsequent, table, falsey_scope) if node.subsequent end |
.propagate_unless_branches(node, table, current_scope) ⇒ Object
601 602 603 604 605 606 |
# File 'lib/rigor/inference/scope_indexer.rb', line 601 def propagate_unless_branches(node, table, current_scope) truthy_scope, falsey_scope = Narrowing.predicate_scopes(node.predicate, current_scope) propagate(node.predicate, table, current_scope) if node.predicate propagate(node.statements, table, falsey_scope) if node.statements propagate(node.else_clause, table, truthy_scope) if node.else_clause end |
.qualified_name_for(constant_path_node) ⇒ Object
543 544 545 546 547 548 549 550 |
# File 'lib/rigor/inference/scope_indexer.rb', line 543 def qualified_name_for(constant_path_node) case constant_path_node when Prism::ConstantReadNode constant_path_node.name.to_s when Prism::ConstantPathNode render_constant_path(constant_path_node) end end |
.record_class_or_module?(node, qualified_prefix, identity_table, discovered) ⇒ Boolean
489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/rigor/inference/scope_indexer.rb', line 489 def record_class_or_module?(node, qualified_prefix, identity_table, discovered) name = qualified_name_for(node.constant_path) return false unless name full = (qualified_prefix + [name]).join("::") singleton = Type::Combinator.singleton_of(full) identity_table[node.constant_path] = singleton discovered[full] = singleton child_prefix = qualified_prefix + [name] record_declarations(node.body, child_prefix, identity_table, discovered) if node.body true end |
.record_constant_write(node, qualified_prefix, default_scope, accumulator, base_name) ⇒ Object
320 321 322 323 324 325 326 327 328 329 |
# File 'lib/rigor/inference/scope_indexer.rb', line 320 def record_constant_write(node, qualified_prefix, default_scope, accumulator, base_name) full = qualified_prefix.empty? ? base_name : "#{qualified_prefix.join('::')}::#{base_name}" body_scope = default_scope unless qualified_prefix.empty? body_scope = body_scope.with_self_type(Type::Combinator.singleton_of(qualified_prefix.join("::"))) end rvalue_type = body_scope.type_of(node.value) existing = accumulator[full] accumulator[full] = existing ? Type::Combinator.union(existing, rvalue_type) : rvalue_type end |
.record_cvar_write(node, scope, class_name, accumulator) ⇒ Object
247 248 249 250 251 252 253 |
# File 'lib/rigor/inference/scope_indexer.rb', line 247 def record_cvar_write(node, scope, class_name, accumulator) rvalue_type = scope.type_of(node.value) accumulator[class_name] ||= {} existing = accumulator[class_name][node.name] accumulator[class_name][node.name] = existing ? Type::Combinator.union(existing, rvalue_type) : rvalue_type end |
.record_data_define_constant?(node, qualified_prefix, identity_table, discovered) ⇒ Boolean
Recognises ‘Const = Data.define(*Symbol) [do … end]` and registers `Const` (qualified by the surrounding class/module path) as a discovered class. `Const.new(…)` then resolves to a fresh `Nominal` via `meta_new`, instead of the un-narrowed `Dynamic` returned by the default `Class#new` envelope.
The Data.define block body, if present, is recursed into so any nested class/module declarations in the override block (rare but legal) still feed the discovered table.
511 512 513 514 515 516 517 518 |
# File 'lib/rigor/inference/scope_indexer.rb', line 511 def record_data_define_constant?(node, qualified_prefix, identity_table, discovered) return false unless data_define_call?(node.value) full = (qualified_prefix + [node.name.to_s]).join("::") discovered[full] = Type::Combinator.singleton_of(full) record_declarations(node.value, qualified_prefix, identity_table, discovered) true end |
.record_declarations(node, qualified_prefix, identity_table, discovered) ⇒ Object
474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/rigor/inference/scope_indexer.rb', line 474 def record_declarations(node, qualified_prefix, identity_table, discovered) return unless node.is_a?(Prism::Node) case node when Prism::ModuleNode, Prism::ClassNode return if record_class_or_module?(node, qualified_prefix, identity_table, discovered) when Prism::ConstantWriteNode return if record_data_define_constant?(node, qualified_prefix, identity_table, discovered) end node.compact_child_nodes.each do |child| record_declarations(child, qualified_prefix, identity_table, discovered) end end |
.record_def_method(def_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
371 372 373 374 375 376 377 378 |
# File 'lib/rigor/inference/scope_indexer.rb', line 371 def record_def_method(def_node, qualified_prefix, in_singleton_class, accumulator) return if qualified_prefix.empty? class_name = qualified_prefix.join("::") kind = def_node.receiver.is_a?(Prism::SelfNode) || in_singleton_class ? :singleton : :instance accumulator[class_name] ||= {} accumulator[class_name][def_node.name] = kind end |
.record_def_node(def_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
431 432 433 434 435 436 437 |
# File 'lib/rigor/inference/scope_indexer.rb', line 431 def record_def_node(def_node, qualified_prefix, in_singleton_class, accumulator) return if def_node.receiver.is_a?(Prism::SelfNode) || in_singleton_class class_name = qualified_prefix.empty? ? TOP_LEVEL_DEF_KEY : qualified_prefix.join("::") accumulator[class_name] ||= {} accumulator[class_name][def_node.name] = def_node end |
.record_define_method(call_node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/rigor/inference/scope_indexer.rb', line 439 def record_define_method(call_node, qualified_prefix, in_singleton_class, accumulator) return if qualified_prefix.empty? return if call_node.arguments.nil? || call_node.arguments.arguments.empty? first_arg = call_node.arguments.arguments.first method_name = literal_method_name(first_arg) return if method_name.nil? class_name = qualified_prefix.join("::") accumulator[class_name] ||= {} accumulator[class_name][method_name] = in_singleton_class ? :singleton : :instance end |
.record_global_write(node, scope, accumulator) ⇒ Object
274 275 276 277 278 279 |
# File 'lib/rigor/inference/scope_indexer.rb', line 274 def record_global_write(node, scope, accumulator) rvalue_type = scope.type_of(node.value) existing = accumulator[node.name] accumulator[node.name] = existing ? Type::Combinator.union(existing, rvalue_type) : rvalue_type end |
.record_ivar_write(node, scope, class_name, accumulator) ⇒ Object
188 189 190 191 192 193 194 |
# File 'lib/rigor/inference/scope_indexer.rb', line 188 def record_ivar_write(node, scope, class_name, accumulator) rvalue_type = scope.type_of(node.value) accumulator[class_name] ||= {} existing = accumulator[class_name][node.name] accumulator[class_name][node.name] = existing ? Type::Combinator.union(existing, rvalue_type) : rvalue_type end |
.render_constant_path(node) ⇒ Object
552 553 554 555 556 557 558 559 560 |
# File 'lib/rigor/inference/scope_indexer.rb', line 552 def render_constant_path(node) prefix = case node.parent when Prism::ConstantReadNode then "#{node.parent.name}::" when Prism::ConstantPathNode then "#{render_constant_path(node.parent)}::" else "" end "#{prefix}#{node.name}" end |
.walk_class_cvars(node, qualified_prefix, default_scope, accumulator) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/rigor/inference/scope_indexer.rb', line 209 def walk_class_cvars(node, qualified_prefix, default_scope, accumulator) return unless node.is_a?(Prism::Node) case node when Prism::ClassNode, Prism::ModuleNode name = qualified_name_for(node.constant_path) if name child_prefix = qualified_prefix + [name] walk_class_cvars(node.body, child_prefix, default_scope, accumulator) if node.body return end when Prism::DefNode collect_def_cvar_writes(node, qualified_prefix, default_scope, accumulator) return end node.compact_child_nodes.each do |child| walk_class_cvars(child, qualified_prefix, default_scope, accumulator) end end |
.walk_class_ivars(node, qualified_prefix, default_scope, accumulator) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/rigor/inference/scope_indexer.rb', line 137 def walk_class_ivars(node, qualified_prefix, default_scope, accumulator) return unless node.is_a?(Prism::Node) case node when Prism::ClassNode, Prism::ModuleNode name = qualified_name_for(node.constant_path) if name child_prefix = qualified_prefix + [name] walk_class_ivars(node.body, child_prefix, default_scope, accumulator) if node.body return end when Prism::DefNode collect_def_ivar_writes(node, qualified_prefix, default_scope, accumulator) return end node.compact_child_nodes.each do |child| walk_class_ivars(child, qualified_prefix, default_scope, accumulator) end end |
.walk_constant_writes(node, qualified_prefix, default_scope, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/rigor/inference/scope_indexer.rb', line 295 def walk_constant_writes(node, qualified_prefix, default_scope, accumulator) # rubocop:disable Metrics/CyclomaticComplexity return unless node.is_a?(Prism::Node) case node when Prism::ClassNode, Prism::ModuleNode name = qualified_name_for(node.constant_path) if name child_prefix = qualified_prefix + [name] walk_constant_writes(node.body, child_prefix, default_scope, accumulator) if node.body return end when Prism::ConstantWriteNode record_constant_write(node, qualified_prefix, default_scope, accumulator, node.name.to_s) return when Prism::ConstantPathWriteNode full = qualified_name_for(node.target) record_constant_write(node, [], default_scope, accumulator, full) if full return end node.compact_child_nodes.each do |child| walk_constant_writes(child, qualified_prefix, default_scope, accumulator) end end |
.walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/rigor/inference/scope_indexer.rb', line 396 def walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator) # rubocop:disable Metrics/CyclomaticComplexity return unless node.is_a?(Prism::Node) case node when Prism::ClassNode, Prism::ModuleNode name = qualified_name_for(node.constant_path) if name child_prefix = qualified_prefix + [name] walk_def_nodes(node.body, child_prefix, false, accumulator) if node.body return end when Prism::SingletonClassNode if node.expression.is_a?(Prism::SelfNode) && node.body walk_def_nodes(node.body, qualified_prefix, true, accumulator) return end when Prism::DefNode record_def_node(node, qualified_prefix, in_singleton_class, accumulator) return end node.compact_child_nodes.each do |child| walk_def_nodes(child, qualified_prefix, in_singleton_class, accumulator) end end |
.walk_methods(node, qualified_prefix, in_singleton_class, accumulator) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/rigor/inference/scope_indexer.rb', line 343 def walk_methods(node, qualified_prefix, in_singleton_class, accumulator) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity return unless node.is_a?(Prism::Node) case node when Prism::ClassNode, Prism::ModuleNode name = qualified_name_for(node.constant_path) if name child_prefix = qualified_prefix + [name] walk_methods(node.body, child_prefix, false, accumulator) if node.body return end when Prism::SingletonClassNode if node.expression.is_a?(Prism::SelfNode) && node.body walk_methods(node.body, qualified_prefix, true, accumulator) return end when Prism::DefNode record_def_method(node, qualified_prefix, in_singleton_class, accumulator) return when Prism::CallNode record_define_method(node, qualified_prefix, in_singleton_class, accumulator) if node.name == :define_method end node.compact_child_nodes.each do |child| walk_methods(child, qualified_prefix, in_singleton_class, accumulator) end end |