Class: Rigor::Scope
- Inherits:
-
Object
- Object
- Rigor::Scope
- Defined in:
- lib/rigor/scope.rb
Overview
Immutable analyzer scope: holds local-variable bindings and a reference to the surrounding Environment. State changes return new scopes through explicit transition methods (#with_local). The central query is #type_of(node), the Rigor counterpart of PHPStan’s $scope->getType($node).
See docs/internal-spec/inference-engine.md for the binding contract. rubocop:disable Metrics/ClassLength,Metrics/ParameterLists
Defined Under Namespace
Classes: ChainKey, IndexedKey
Instance Attribute Summary collapse
-
#class_cvars ⇒ Object
readonly
Returns the value of attribute class_cvars.
-
#class_ivars ⇒ Object
readonly
Returns the value of attribute class_ivars.
-
#cvars ⇒ Object
readonly
Returns the value of attribute cvars.
-
#declared_types ⇒ Object
readonly
Returns the value of attribute declared_types.
-
#discovered_classes ⇒ Object
readonly
Returns the value of attribute discovered_classes.
-
#discovered_def_nodes ⇒ Object
readonly
Returns the value of attribute discovered_def_nodes.
-
#discovered_includes ⇒ Object
readonly
Returns the value of attribute discovered_includes.
-
#discovered_method_visibilities ⇒ Object
readonly
Returns the value of attribute discovered_method_visibilities.
-
#discovered_methods ⇒ Object
readonly
Returns the value of attribute discovered_methods.
-
#discovered_superclasses ⇒ Object
readonly
Returns the value of attribute discovered_superclasses.
-
#environment ⇒ Object
readonly
Returns the value of attribute environment.
-
#fact_store ⇒ Object
readonly
Returns the value of attribute fact_store.
-
#globals ⇒ Object
readonly
Returns the value of attribute globals.
-
#in_source_constants ⇒ Object
readonly
Returns the value of attribute in_source_constants.
-
#indexed_narrowings ⇒ Object
readonly
Returns the value of attribute indexed_narrowings.
-
#ivars ⇒ Object
readonly
Returns the value of attribute ivars.
-
#locals ⇒ Object
readonly
Returns the value of attribute locals.
-
#method_chain_narrowings ⇒ Object
readonly
Returns the value of attribute method_chain_narrowings.
-
#program_globals ⇒ Object
readonly
Returns the value of attribute program_globals.
-
#self_type ⇒ Object
readonly
Returns the value of attribute self_type.
-
#source_path ⇒ Object
readonly
Returns the value of attribute source_path.
Class Method Summary collapse
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#class_cvars_for(class_name) ⇒ Object
Slice 7 phase 6 — class-level cvar accumulator (same shape as ‘class_ivars` but populated from `Prism::ClassVariableWriteNode` writes, and seeded on BOTH instance and singleton method bodies because Ruby cvars are visible from each).
-
#class_ivars_for(class_name) ⇒ Object
Slice 7 phase 2 — class-level ivar accumulator.
- #cvar(name) ⇒ Object
-
#discovered_method?(class_name, method_name, kind) ⇒ Boolean
Slice 7 phase 12 — in-source method discovery.
-
#discovered_method_visibility(class_name, method_name) ⇒ Object
v0.1.2 — per-class table mapping ‘method_name (Symbol) → :public | :private | :protected`.
-
#evaluate(node, tracer: nil) ⇒ Object
Statement-level evaluation: returns the pair ‘[type, scope’]‘ where `type` is what the node produces and `scope’‘ is the scope observable after the node has run.
- #facts_for(target: nil, bucket: nil) ⇒ Object
- #global(name) ⇒ Object
- #hash ⇒ Object
-
#includes_of(class_name) ⇒ Object
ADR-24 slice 2 — per-class/module table mapping a fully qualified user class or module to the list of module names it ‘include`s / `prepend`s, AS WRITTEN at the mixin call.
- #indexed_narrowing(receiver_kind, receiver_name, key) ⇒ Object
-
#initialize(environment:, locals:, fact_store: Analysis::FactStore.empty, self_type: nil, declared_types: EMPTY_DECLARED_TYPES, ivars: EMPTY_VAR_BINDINGS, cvars: EMPTY_VAR_BINDINGS, globals: EMPTY_VAR_BINDINGS, class_ivars: EMPTY_CLASS_BINDINGS, class_cvars: EMPTY_CLASS_BINDINGS, program_globals: EMPTY_VAR_BINDINGS, discovered_classes: EMPTY_VAR_BINDINGS, in_source_constants: EMPTY_VAR_BINDINGS, discovered_methods: EMPTY_CLASS_BINDINGS, discovered_def_nodes: EMPTY_CLASS_BINDINGS, discovered_method_visibilities: EMPTY_CLASS_BINDINGS, discovered_superclasses: EMPTY_CLASS_BINDINGS, discovered_includes: EMPTY_CLASS_BINDINGS, indexed_narrowings: EMPTY_INDEXED_NARROWINGS, method_chain_narrowings: EMPTY_CHAIN_NARROWINGS, source_path: nil) ⇒ Scope
constructor
A new instance of Scope.
-
#ivar(name) ⇒ Object
Slice 7 phase 1 — instance/class/global variable bindings.
-
#join(other) ⇒ Object
Joins this scope with another at a control-flow merge point.
- #local(name) ⇒ Object
- #local_facts(name, bucket: nil) ⇒ Object
-
#method_chain_narrowing(receiver_kind, receiver_name, method_name) ⇒ Object
Closes the “stable receiver method-chain narrowing” gap (ROADMAP § Future cycles / Type-language / engine — “Method-call receiver narrowing across stable receivers”; 2026-05-28 Redmine survey).
-
#superclass_of(class_name) ⇒ Object
ADR-24 slice 2 — per-class table mapping a fully qualified user-class name to its superclass name AS WRITTEN at the ‘class Foo < Bar` declaration (`“Bar”`, possibly a qualified `“A::B”`).
-
#top_level_def_for(method_name) ⇒ Object
v0.0.3 A — top-level def lookup for implicit-self calls.
- #type_of(node, tracer: nil) ⇒ Object
-
#user_def_for(class_name, method_name) ⇒ Object
v0.0.2 #5 — per-class table mapping ‘method_name (Symbol) → Prism::DefNode`.
- #with_class_cvars(table) ⇒ Object
- #with_class_ivars(table) ⇒ Object
- #with_cvar(name, type) ⇒ Object
-
#with_declared_types(table) ⇒ Object
Slice A-declarations.
-
#with_discovered_classes(table) ⇒ Object
Slice 7 phase 7 — in-source class discovery.
- #with_discovered_def_nodes(table) ⇒ Object
- #with_discovered_includes(table) ⇒ Object
- #with_discovered_method_visibilities(table) ⇒ Object
- #with_discovered_methods(table) ⇒ Object
- #with_discovered_superclasses(table) ⇒ Object
- #with_fact(fact) ⇒ Object
- #with_global(name, type) ⇒ Object
-
#with_in_source_constants(table) ⇒ Object
Slice 7 phase 9 — in-source constant-value tracking.
- #with_indexed_narrowing(receiver_kind, receiver_name, key, type) ⇒ Object
- #with_ivar(name, type) ⇒ Object
- #with_local(name, type) ⇒ Object
- #with_method_chain_narrowing(receiver_kind, receiver_name, method_name, type) ⇒ Object
-
#with_program_globals(table) ⇒ Object
Slice 7 phase 6 — program-level globals accumulator.
-
#with_self_type(type) ⇒ Object
Slice A-engine.
-
#with_source_path(path) ⇒ Object
ADR-11 per-call-site assertion gating prerequisite.
- #without_indexed_narrowing(receiver_kind, receiver_name, key) ⇒ Object
- #without_indexed_narrowings_for(receiver_kind, receiver_name) ⇒ Object
- #without_method_chain_narrowing(receiver_kind, receiver_name, method_name) ⇒ Object
- #without_method_chain_narrowings_for(receiver_kind, receiver_name) ⇒ Object
Constructor Details
#initialize(environment:, locals:, fact_store: Analysis::FactStore.empty, self_type: nil, declared_types: EMPTY_DECLARED_TYPES, ivars: EMPTY_VAR_BINDINGS, cvars: EMPTY_VAR_BINDINGS, globals: EMPTY_VAR_BINDINGS, class_ivars: EMPTY_CLASS_BINDINGS, class_cvars: EMPTY_CLASS_BINDINGS, program_globals: EMPTY_VAR_BINDINGS, discovered_classes: EMPTY_VAR_BINDINGS, in_source_constants: EMPTY_VAR_BINDINGS, discovered_methods: EMPTY_CLASS_BINDINGS, discovered_def_nodes: EMPTY_CLASS_BINDINGS, discovered_method_visibilities: EMPTY_CLASS_BINDINGS, discovered_superclasses: EMPTY_CLASS_BINDINGS, discovered_includes: EMPTY_CLASS_BINDINGS, indexed_narrowings: EMPTY_INDEXED_NARROWINGS, method_chain_narrowings: EMPTY_CHAIN_NARROWINGS, source_path: nil) ⇒ Scope
Returns a new instance of Scope.
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 |
# File 'lib/rigor/scope.rb', line 77 def initialize( environment:, locals:, fact_store: Analysis::FactStore.empty, self_type: nil, declared_types: EMPTY_DECLARED_TYPES, ivars: EMPTY_VAR_BINDINGS, cvars: EMPTY_VAR_BINDINGS, globals: EMPTY_VAR_BINDINGS, class_ivars: EMPTY_CLASS_BINDINGS, class_cvars: EMPTY_CLASS_BINDINGS, program_globals: EMPTY_VAR_BINDINGS, discovered_classes: EMPTY_VAR_BINDINGS, in_source_constants: EMPTY_VAR_BINDINGS, discovered_methods: EMPTY_CLASS_BINDINGS, discovered_def_nodes: EMPTY_CLASS_BINDINGS, discovered_method_visibilities: EMPTY_CLASS_BINDINGS, discovered_superclasses: EMPTY_CLASS_BINDINGS, discovered_includes: EMPTY_CLASS_BINDINGS, indexed_narrowings: EMPTY_INDEXED_NARROWINGS, method_chain_narrowings: EMPTY_CHAIN_NARROWINGS, source_path: nil ) @environment = environment @locals = locals @fact_store = fact_store @self_type = self_type @declared_types = declared_types @ivars = ivars @cvars = cvars @globals = globals @class_ivars = class_ivars @class_cvars = class_cvars @program_globals = program_globals @discovered_classes = discovered_classes @in_source_constants = in_source_constants @discovered_methods = discovered_methods @discovered_def_nodes = discovered_def_nodes @discovered_method_visibilities = discovered_method_visibilities @discovered_superclasses = discovered_superclasses @discovered_includes = discovered_includes @indexed_narrowings = indexed_narrowings @method_chain_narrowings = method_chain_narrowings @source_path = source_path freeze end |
Instance Attribute Details
#class_cvars ⇒ Object (readonly)
Returns the value of attribute class_cvars.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def class_cvars @class_cvars end |
#class_ivars ⇒ Object (readonly)
Returns the value of attribute class_ivars.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def class_ivars @class_ivars end |
#cvars ⇒ Object (readonly)
Returns the value of attribute cvars.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def cvars @cvars end |
#declared_types ⇒ Object (readonly)
Returns the value of attribute declared_types.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def declared_types @declared_types end |
#discovered_classes ⇒ Object (readonly)
Returns the value of attribute discovered_classes.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_classes @discovered_classes end |
#discovered_def_nodes ⇒ Object (readonly)
Returns the value of attribute discovered_def_nodes.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_def_nodes @discovered_def_nodes end |
#discovered_includes ⇒ Object (readonly)
Returns the value of attribute discovered_includes.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_includes @discovered_includes end |
#discovered_method_visibilities ⇒ Object (readonly)
Returns the value of attribute discovered_method_visibilities.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_method_visibilities @discovered_method_visibilities end |
#discovered_methods ⇒ Object (readonly)
Returns the value of attribute discovered_methods.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_methods @discovered_methods end |
#discovered_superclasses ⇒ Object (readonly)
Returns the value of attribute discovered_superclasses.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def discovered_superclasses @discovered_superclasses end |
#environment ⇒ Object (readonly)
Returns the value of attribute environment.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def environment @environment end |
#fact_store ⇒ Object (readonly)
Returns the value of attribute fact_store.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def fact_store @fact_store end |
#globals ⇒ Object (readonly)
Returns the value of attribute globals.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def globals @globals end |
#in_source_constants ⇒ Object (readonly)
Returns the value of attribute in_source_constants.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def in_source_constants @in_source_constants end |
#indexed_narrowings ⇒ Object (readonly)
Returns the value of attribute indexed_narrowings.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def indexed_narrowings @indexed_narrowings end |
#ivars ⇒ Object (readonly)
Returns the value of attribute ivars.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def ivars @ivars end |
#locals ⇒ Object (readonly)
Returns the value of attribute locals.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def locals @locals end |
#method_chain_narrowings ⇒ Object (readonly)
Returns the value of attribute method_chain_narrowings.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def method_chain_narrowings @method_chain_narrowings end |
#program_globals ⇒ Object (readonly)
Returns the value of attribute program_globals.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def program_globals @program_globals end |
#self_type ⇒ Object (readonly)
Returns the value of attribute self_type.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def self_type @self_type end |
#source_path ⇒ Object (readonly)
Returns the value of attribute source_path.
19 20 21 |
# File 'lib/rigor/scope.rb', line 19 def source_path @source_path end |
Class Method Details
.empty(environment: Environment.default, source_path: nil) ⇒ Object
71 72 73 74 |
# File 'lib/rigor/scope.rb', line 71 def empty(environment: Environment.default, source_path: nil) new(environment: environment, locals: {}.freeze, fact_store: Analysis::FactStore.empty, source_path: source_path) end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/rigor/scope.rb', line 521 def ==(other) other.is_a?(Scope) && environment.equal?(other.environment) && @locals == other.locals && fact_store == other.fact_store && self_type == other.self_type && @ivars == other.ivars && @cvars == other.cvars && @globals == other.globals && @indexed_narrowings == other.indexed_narrowings && @method_chain_narrowings == other.method_chain_narrowings end |
#class_cvars_for(class_name) ⇒ Object
Slice 7 phase 6 — class-level cvar accumulator (same shape as ‘class_ivars` but populated from `Prism::ClassVariableWriteNode` writes, and seeded on BOTH instance and singleton method bodies because Ruby cvars are visible from each).
249 250 251 252 253 |
# File 'lib/rigor/scope.rb', line 249 def class_cvars_for(class_name) return EMPTY_VAR_BINDINGS if class_name.nil? @class_cvars[class_name.to_s] || EMPTY_VAR_BINDINGS end |
#class_ivars_for(class_name) ⇒ Object
Slice 7 phase 2 — class-level ivar accumulator. Keyed by the qualified class name (e.g. ‘“Rigor::Scope”`); the value is a `Hash[Symbol, Type::t]` of every ivar that appears as a write target inside any def body of that class. `StatementEvaluator#build_method_entry_scope` seeds the method body’s ‘ivars` map from this table so a `def get; @x; end` reads the type written in a sibling `def init; @x = 1; end`.
‘ScopeIndexer` populates the table once at index time through a separate pre-pass over the program. The map is frozen and shared by structural reference across every derived scope.
235 236 237 238 239 |
# File 'lib/rigor/scope.rb', line 235 def class_ivars_for(class_name) return EMPTY_VAR_BINDINGS if class_name.nil? @class_ivars[class_name.to_s] || EMPTY_VAR_BINDINGS end |
#cvar(name) ⇒ Object
198 199 200 |
# File 'lib/rigor/scope.rb', line 198 def cvar(name) @cvars[name.to_sym] end |
#discovered_method?(class_name, method_name, kind) ⇒ Boolean
Slice 7 phase 12 — in-source method discovery. Maps a qualified class name to a ‘Hash[Symbol, Symbol]` of `method_name => :instance | :singleton`. Populated by `ScopeIndexer` from every `Prism::DefNode` and recognised `define_method` invocation inside class/module bodies. The `rigor check` undefined-method and wrong-arity rules consult this map to suppress diagnostics for methods the user has defined dynamically, even when no RBS sig describes them.
305 306 307 308 309 310 |
# File 'lib/rigor/scope.rb', line 305 def discovered_method?(class_name, method_name, kind) table = @discovered_methods[class_name.to_s] return false unless table table[method_name.to_sym] == kind end |
#discovered_method_visibility(class_name, method_name) ⇒ Object
v0.1.2 — per-class table mapping ‘method_name (Symbol) →:public | :private | :protected`. Populated by `ScopeIndexer` for every `def` it sees inside a class body, with the visibility taken from the surrounding `private` / `protected` / `public` modifier state plus any post-hoc `private :name, …` named-argument calls. Consumed by the `def.method-visibility-mismatch` rule so explicit-non-self calls to a private method surface a diagnostic.
394 395 396 397 398 399 |
# File 'lib/rigor/scope.rb', line 394 def discovered_method_visibility(class_name, method_name) table = @discovered_method_visibilities[class_name.to_s] return nil unless table table[method_name.to_sym] end |
#evaluate(node, tracer: nil) ⇒ Object
Statement-level evaluation: returns the pair ‘[type, scope’]‘ where `type` is what the node produces and `scope’‘ is the scope observable after the node has run. The receiver scope is never mutated. See Inference::StatementEvaluator for the catalogue of nodes that thread scope; everything else defers to #type_of and returns the receiver scope unchanged.
496 497 498 |
# File 'lib/rigor/scope.rb', line 496 def evaluate(node, tracer: nil) Inference::StatementEvaluator.new(scope: self, tracer: tracer).evaluate(node) end |
#facts_for(target: nil, bucket: nil) ⇒ Object
478 479 480 |
# File 'lib/rigor/scope.rb', line 478 def facts_for(target: nil, bucket: nil) fact_store.facts_for(target: target, bucket: bucket) end |
#global(name) ⇒ Object
202 203 204 |
# File 'lib/rigor/scope.rb', line 202 def global(name) @globals[name.to_sym] end |
#hash ⇒ Object
535 536 537 |
# File 'lib/rigor/scope.rb', line 535 def hash [Scope, environment.object_id, @locals, fact_store, self_type, @ivars, @cvars, @globals].hash end |
#includes_of(class_name) ⇒ Object
ADR-24 slice 2 — per-class/module table mapping a fully qualified user class or module to the list of module names it ‘include`s / `prepend`s, AS WRITTEN at the mixin call. Populated by `ScopeIndexer` (per-file plus the cross-file pre-pass) and consumed by `ExpressionTyper#resolve_user_def_through_ancestors` so an implicit-self call resolves against an included module’s ‘def`s, not just the superclass chain. As-written names are resolved to qualified classes at walk time.
377 378 379 |
# File 'lib/rigor/scope.rb', line 377 def includes_of(class_name) @discovered_includes[class_name.to_s] || [] end |
#indexed_narrowing(receiver_kind, receiver_name, key) ⇒ Object
Closes the “‘params ||= []; params << x`” precision gap (ROADMAP § Type-language / engine — indexed-collection narrowing through `Hash ||= default`). After `receiver ||= default`, the next read at `receiver` is known non-nil; recording the post-`||=` type keyed on `(receiver_kind, receiver_name, literal_key)` lets the ExpressionTyper’s ‘[]` dispatch hand back the narrowed type. Receiver-rebind and `[]=`/mutator invalidation rules are documented at the call sites in `Inference::StatementEvaluator`.
415 416 417 |
# File 'lib/rigor/scope.rb', line 415 def indexed_narrowing(receiver_kind, receiver_name, key) @indexed_narrowings[indexed_key(receiver_kind, receiver_name, key)] end |
#ivar(name) ⇒ Object
Slice 7 phase 1 — instance/class/global variable bindings. ‘ivar(name)` / `cvar(name)` / `global(name)` return the type currently bound for the named variable, or `nil` when the variable has not been written in the analyzed slice of the program. The first cut tracks bindings only within a single method body (each `def` enters with a fresh binding map), so reads in other methods of the same class fall through to `Dynamic`. Cross-method ivar/cvar inference is a follow-up slice.
194 195 196 |
# File 'lib/rigor/scope.rb', line 194 def ivar(name) @ivars[name.to_sym] end |
#join(other) ⇒ Object
Joins this scope with another at a control-flow merge point. The joined scope is bound to every local that BOTH branches bind, with the type widened to the union of both sides. Names bound in only one branch are dropped from the joined scope; the eventual statement-level evaluator (Slice 3 phase 2) is responsible for nil-injecting half-bound names where the language semantics demand it. The two scopes MUST share the same Environment.
507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/rigor/scope.rb', line 507 def join(other) raise ArgumentError, "join requires a Rigor::Scope, got #{other.class}" unless other.is_a?(Scope) unless environment.equal?(other.environment) raise ArgumentError, "join requires both scopes to share the same Environment" end joined_locals = join_bindings(locals, other.locals) joined_ivars = join_bindings(ivars, other.ivars) joined_cvars = join_bindings(cvars, other.cvars) joined_globals = join_bindings(globals, other.globals) build_joined_scope(joined_locals, joined_ivars, joined_cvars, joined_globals, other) end |
#local(name) ⇒ Object
123 124 125 |
# File 'lib/rigor/scope.rb', line 123 def local(name) @locals[name.to_sym] end |
#local_facts(name, bucket: nil) ⇒ Object
482 483 484 |
# File 'lib/rigor/scope.rb', line 482 def local_facts(name, bucket: nil) facts_for(target: Analysis::FactStore::Target.local(name), bucket: bucket) end |
#method_chain_narrowing(receiver_kind, receiver_name, method_name) ⇒ Object
Closes the “stable receiver method-chain narrowing” gap (ROADMAP § Future cycles / Type-language / engine —“Method-call receiver narrowing across stable receivers”; 2026-05-28 Redmine survey). After ‘if x.last.is_a?(Array)` the dominated body’s ‘x.last` reads MUST observe the truthy-narrowed type; the same chain reaching the falsey edge observes the negative narrowing.
Address shape mirrors #indexed_narrowing: stable root variable + no-arg single-hop method name. See ChainKey for the precise contract.
452 453 454 |
# File 'lib/rigor/scope.rb', line 452 def method_chain_narrowing(receiver_kind, receiver_name, method_name) @method_chain_narrowings[chain_key(receiver_kind, receiver_name, method_name)] end |
#superclass_of(class_name) ⇒ Object
ADR-24 slice 2 — per-class table mapping a fully qualified user-class name to its superclass name AS WRITTEN at the ‘class Foo < Bar` declaration (`“Bar”`, possibly a qualified `“A::B”`). Populated by `ScopeIndexer` — per-file plus the cross-file project pre-pass — and consumed by `ExpressionTyper#try_user_method_inference` to walk the superclass chain when an implicit-self call does not resolve against the enclosing class’s own defs. The as-written name is resolved to a qualified class at walk time against the call’s lexical nesting.
360 361 362 |
# File 'lib/rigor/scope.rb', line 360 def superclass_of(class_name) @discovered_superclasses[class_name.to_s] end |
#top_level_def_for(method_name) ⇒ Object
v0.0.3 A — top-level def lookup for implicit-self calls. Returns the ‘Prism::DefNode` for a top-level (or DSL-block-nested, outside any class body) `def <method_name>` in the file, or nil. The sentinel key is owned by `Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY`; consumers should treat its presence as an opaque implementation detail and go through this accessor.
339 340 341 342 343 344 |
# File 'lib/rigor/scope.rb', line 339 def top_level_def_for(method_name) table = @discovered_def_nodes[Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY] return nil unless table table[method_name.to_sym] end |
#type_of(node, tracer: nil) ⇒ Object
486 487 488 |
# File 'lib/rigor/scope.rb', line 486 def type_of(node, tracer: nil) Inference::ExpressionTyper.new(scope: self, tracer: tracer).type_of(node) end |
#user_def_for(class_name, method_name) ⇒ Object
v0.0.2 #5 — per-class table mapping ‘method_name (Symbol) → Prism::DefNode`. Populated by `ScopeIndexer` alongside `discovered_methods` for instance-side defs only (singleton-side and `define_method`-introduced methods do not contribute a static body the engine can re-type). Consumed by `ExpressionTyper` to do inter-procedural return-type inference when the receiver class is user-defined and has no RBS sig.
325 326 327 328 329 330 |
# File 'lib/rigor/scope.rb', line 325 def user_def_for(class_name, method_name) table = @discovered_def_nodes[class_name.to_s] return nil unless table table[method_name.to_sym] end |
#with_class_cvars(table) ⇒ Object
255 256 257 |
# File 'lib/rigor/scope.rb', line 255 def with_class_cvars(table) rebuild(class_cvars: table) end |
#with_class_ivars(table) ⇒ Object
241 242 243 |
# File 'lib/rigor/scope.rb', line 241 def with_class_ivars(table) rebuild(class_ivars: table) end |
#with_cvar(name, type) ⇒ Object
214 215 216 |
# File 'lib/rigor/scope.rb', line 214 def with_cvar(name, type) rebuild(cvars: @cvars.merge(name.to_sym => type).freeze) end |
#with_declared_types(table) ⇒ Object
Slice A-declarations. Returns a scope that carries an identity-comparing Hash of ‘Prism::Node => Rigor::Type` overrides. `ExpressionTyper#type_of(node)` MUST consult `declared_types` before any other dispatch and return the recorded type as-is when present. The table is populated by `ScopeIndexer` for declaration-position nodes (the `constant_path` of `Prism::ModuleNode` and `Prism::ClassNode`) so a `module Foo` / `class Bar` header types as `Singleton[<qualified path>]` instead of falling through to `Dynamic`. The table is shared by structural reference across every derived scope so `with_local` / `with_fact` / `with_self_type` carry it transparently.
181 182 183 |
# File 'lib/rigor/scope.rb', line 181 def with_declared_types(table) rebuild(declared_types: table) end |
#with_discovered_classes(table) ⇒ Object
Slice 7 phase 7 — in-source class discovery. Maps a qualified class name (e.g. ‘“Account”`) to its `Type::Singleton` so references to user-defined classes in the analyzed files resolve through `ExpressionTyper#resolve_constant_name` even when no RBS decl exists. Populated once at index time by `ScopeIndexer` from every `Prism::ClassNode` and `Prism::ModuleNode` it walks.
277 278 279 |
# File 'lib/rigor/scope.rb', line 277 def with_discovered_classes(table) rebuild(discovered_classes: table) end |
#with_discovered_def_nodes(table) ⇒ Object
346 347 348 |
# File 'lib/rigor/scope.rb', line 346 def with_discovered_def_nodes(table) rebuild(discovered_def_nodes: table) end |
#with_discovered_includes(table) ⇒ Object
381 382 383 |
# File 'lib/rigor/scope.rb', line 381 def with_discovered_includes(table) rebuild(discovered_includes: table) end |
#with_discovered_method_visibilities(table) ⇒ Object
401 402 403 |
# File 'lib/rigor/scope.rb', line 401 def with_discovered_method_visibilities(table) rebuild(discovered_method_visibilities: table) end |
#with_discovered_methods(table) ⇒ Object
312 313 314 |
# File 'lib/rigor/scope.rb', line 312 def with_discovered_methods(table) rebuild(discovered_methods: table) end |
#with_discovered_superclasses(table) ⇒ Object
364 365 366 |
# File 'lib/rigor/scope.rb', line 364 def with_discovered_superclasses(table) rebuild(discovered_superclasses: table) end |
#with_fact(fact) ⇒ Object
145 146 147 |
# File 'lib/rigor/scope.rb', line 145 def with_fact(fact) rebuild(fact_store: fact_store.with_fact(fact)) end |
#with_global(name, type) ⇒ Object
218 219 220 |
# File 'lib/rigor/scope.rb', line 218 def with_global(name, type) rebuild(globals: @globals.merge(name.to_sym => type).freeze) end |
#with_in_source_constants(table) ⇒ Object
Slice 7 phase 9 — in-source constant-value tracking. Maps a qualified constant name (e.g. ‘“BUCKETS”` or `“Rigor::Analysis::FactStore::BUCKETS”`) to the type of the rvalue assigned at its `Prism::ConstantWriteNode` / `Prism::ConstantPathWriteNode`. Populated by `ScopeIndexer` once at index time. `ExpressionTyper#resolve_constant_name` consults this map after class lookups so an in-source constant assignment overrides any RBS-declared constant of the same qualified name (matching Ruby’s runtime precedence: a constant defined in user code is the authoritative value).
292 293 294 |
# File 'lib/rigor/scope.rb', line 292 def with_in_source_constants(table) rebuild(in_source_constants: table) end |
#with_indexed_narrowing(receiver_kind, receiver_name, key, type) ⇒ Object
419 420 421 422 423 424 |
# File 'lib/rigor/scope.rb', line 419 def with_indexed_narrowing(receiver_kind, receiver_name, key, type) new_table = @indexed_narrowings.merge( indexed_key(receiver_kind, receiver_name, key) => type ).freeze rebuild(indexed_narrowings: new_table) end |
#with_ivar(name, type) ⇒ Object
206 207 208 209 210 211 212 |
# File 'lib/rigor/scope.rb', line 206 def with_ivar(name, type) new_indexed_narrowings = drop_indexed_narrowings_for(:ivar, name) new_chain_narrowings = drop_chain_narrowings_for(:ivar, name) rebuild(ivars: @ivars.merge(name.to_sym => type).freeze, indexed_narrowings: new_indexed_narrowings, method_chain_narrowings: new_chain_narrowings) end |
#with_local(name, type) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rigor/scope.rb', line 127 def with_local(name, type) new_locals = @locals.merge(name.to_sym => type).freeze new_fact_store = fact_store.invalidate_target(Analysis::FactStore::Target.local(name)) # Rebinding `name` invalidates every "after `receiver[key] # ||= default`" narrowing keyed on it — the slot at `name[*]` # is reachable through the old binding only, so the # next read against the new binding does not inherit the # earlier non-nil guarantee. The same logic applies to # method-chain narrowings: `x.last` after `x = something_new` # is a call on the new binding and any prior `is_a?`-driven # narrowing keyed on `(local, :x, :last)` no longer holds. new_indexed_narrowings = drop_indexed_narrowings_for(:local, name) new_chain_narrowings = drop_chain_narrowings_for(:local, name) rebuild(locals: new_locals, fact_store: new_fact_store, indexed_narrowings: new_indexed_narrowings, method_chain_narrowings: new_chain_narrowings) end |
#with_method_chain_narrowing(receiver_kind, receiver_name, method_name, type) ⇒ Object
456 457 458 459 460 461 |
# File 'lib/rigor/scope.rb', line 456 def with_method_chain_narrowing(receiver_kind, receiver_name, method_name, type) new_table = @method_chain_narrowings.merge( chain_key(receiver_kind, receiver_name, method_name) => type ).freeze rebuild(method_chain_narrowings: new_table) end |
#with_program_globals(table) ⇒ Object
Slice 7 phase 6 — program-level globals accumulator. Globals are process-wide in Ruby, so the analyzer carries a single map (‘Hash[Symbol, Type]`) keyed by the variable name and seeded into every method body (instance and singleton) plus the top-level program scope. `ScopeIndexer` populates it from a single program-wide pre-pass.
265 266 267 |
# File 'lib/rigor/scope.rb', line 265 def with_program_globals(table) rebuild(program_globals: table) end |
#with_self_type(type) ⇒ Object
Slice A-engine. Returns a scope with ‘self_type` set to `type`, preserving locals and facts. `StatementEvaluator` injects this at class-body and method-body boundaries; `ExpressionTyper` consults it when typing `Prism::SelfNode` and implicit-self `Prism::CallNode` receivers.
154 155 156 |
# File 'lib/rigor/scope.rb', line 154 def with_self_type(type) rebuild(self_type: type) end |
#with_source_path(path) ⇒ Object
ADR-11 per-call-site assertion gating prerequisite. The analyzer’s per-file boundary stamps the current source file’s path onto the seed scope; nested rebuilds carry the value through so plugin hooks like ‘flow_contribution_for` can resolve “which file does this call site belong to?” without thread-locals.
164 165 166 |
# File 'lib/rigor/scope.rb', line 164 def with_source_path(path) rebuild(source_path: path) end |
#without_indexed_narrowing(receiver_kind, receiver_name, key) ⇒ Object
426 427 428 429 430 431 432 |
# File 'lib/rigor/scope.rb', line 426 def without_indexed_narrowing(receiver_kind, receiver_name, key) lookup = indexed_key(receiver_kind, receiver_name, key) return self unless @indexed_narrowings.key?(lookup) new_table = @indexed_narrowings.reject { |k, _| k == lookup }.freeze rebuild(indexed_narrowings: new_table) end |
#without_indexed_narrowings_for(receiver_kind, receiver_name) ⇒ Object
434 435 436 437 438 439 |
# File 'lib/rigor/scope.rb', line 434 def without_indexed_narrowings_for(receiver_kind, receiver_name) new_table = drop_indexed_narrowings_for(receiver_kind, receiver_name) return self if new_table.equal?(@indexed_narrowings) rebuild(indexed_narrowings: new_table) end |
#without_method_chain_narrowing(receiver_kind, receiver_name, method_name) ⇒ Object
463 464 465 466 467 468 469 |
# File 'lib/rigor/scope.rb', line 463 def without_method_chain_narrowing(receiver_kind, receiver_name, method_name) lookup = chain_key(receiver_kind, receiver_name, method_name) return self unless @method_chain_narrowings.key?(lookup) new_table = @method_chain_narrowings.reject { |k, _| k == lookup }.freeze rebuild(method_chain_narrowings: new_table) end |
#without_method_chain_narrowings_for(receiver_kind, receiver_name) ⇒ Object
471 472 473 474 475 476 |
# File 'lib/rigor/scope.rb', line 471 def without_method_chain_narrowings_for(receiver_kind, receiver_name) new_table = drop_chain_narrowings_for(receiver_kind, receiver_name) return self if new_table.equal?(@method_chain_narrowings) rebuild(method_chain_narrowings: new_table) end |