Module: Rigor::Reflection

Defined in:
lib/rigor/reflection.rb,
sig/rigor/reflection.rbs

Overview

Read-side facade over Rigor's three reflection sources:

  1. Rigor::Environment::ClassRegistry — Ruby Class / Module objects (Integer, Float, Set, Pathname, …) registered at boot. Static; never changes during a rigor check run.
  2. Rigor::Environment::RbsLoader — RBS-side declarations (instance / singleton methods, class hierarchy, constants). Loaded on demand from the project's sig/ directory + the bundled stdlib RBS.
  3. Rigor::Scope discovered facts — source-side discoveries produced by Rigor::Inference::ScopeIndexer (user-defined classes / modules, in-source constants, discovered method nodes, class ivar / cvar declarations).

This module is the stable read shape the plugin API is designed against (ADR-2, docs/adr/2-extension-api.md).

The facade is read-only and additive. Existing call sites that read directly from Rigor::Scope or Rigor::Environment::RbsLoader continue to work unchanged; they migrate to the facade at their own pace. The facade performs no caching beyond what the underlying sources already provide.

Public surface (v0.0.7 first pass)

The provenance side of the API (which source family contributed each fact) is explicitly out of scope for the v0.0.7 first pass; v0.1.0's plugin API added it as a separate concern.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.class_known?(class_name, scope: Scope.empty) ⇒ Boolean

Parameters:

  • class_name (String, Symbol)
  • scope (Rigor::Scope) (defaults to: Scope.empty)

Returns:

  • (Boolean)


56
57
58
59
60
# File 'lib/rigor/reflection.rb', line 56

def class_known?(class_name, scope: Scope.empty)
  return true if scope.discovered_classes.key?(class_name.to_s)

  scope.environment.class_known?(class_name)
end

.class_ordering(lhs, rhs, scope: Scope.empty) ⇒ Symbol

Returns one of :equal, :subclass, :superclass, :disjoint, :unknown.

Returns:

  • (Symbol)

    one of :equal, :subclass, :superclass, :disjoint, :unknown.



81
82
83
# File 'lib/rigor/reflection.rb', line 81

def class_ordering(lhs, rhs, scope: Scope.empty)
  scope.environment.class_ordering(lhs, rhs)
end

.class_type_param_names(class_name, scope: nil, environment: nil) ⇒ Object

Returns the RBS-declared type parameter names for the class (e.g. [:A] for Array[A]), or [] when the class is non-generic / not in RBS. Used by the dispatcher when binding generic method types to a concrete receiver.



315
316
317
318
319
320
# File 'lib/rigor/reflection.rb', line 315

def class_type_param_names(class_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return [] if loader.nil?

  loader.class_type_param_names(class_name.to_s)
end

.constant_type_for(constant_name, scope: Scope.empty) ⇒ Object

Returns the type of the named constant. Joins in-source constants (recorded by ScopeIndexer) and RBS-side constants. In-source wins on collision because the user's source is the authoritative declaration.

This is the flat lookup keyed on the literal constant_name. Callers that need Ruby's lexical constant resolution (walking the enclosing class path, and folding in registry classes / source-discovered classes as Singleton / class types) use resolve_constant_type instead.



105
106
107
108
109
110
111
# File 'lib/rigor/reflection.rb', line 105

def constant_type_for(constant_name, scope: Scope.empty)
  key = constant_name.to_s
  in_source = scope.in_source_constants[key]
  return in_source if in_source

  scope.environment.constant_for_name(constant_name)
end

.discovered_class?(class_name, scope: Scope.empty) ⇒ Boolean

Returns true when the analyzed source contains a class / module declaration for the given name. Does NOT consult the RBS loader (use class_known? for the union).

Returns:

  • (Boolean)

    true when the analyzed source contains a class / module declaration for the given name. Does NOT consult the RBS loader (use class_known? for the union).



336
337
338
# File 'lib/rigor/reflection.rb', line 336

def discovered_class?(class_name, scope: Scope.empty)
  scope.discovered_classes.key?(class_name.to_s)
end

.discovered_method?(class_name, method_name, kind: :instance, scope: Scope.empty) ⇒ Boolean

Returns true when the ScopeIndexer recorded a def for the given method on the given class with the matching kind.

Parameters:

  • kind (:instance, :singleton) (defaults to: :instance)

Returns:

  • (Boolean)

    true when the ScopeIndexer recorded a def for the given method on the given class with the matching kind.



343
344
345
# File 'lib/rigor/reflection.rb', line 343

def discovered_method?(class_name, method_name, kind: :instance, scope: Scope.empty)
  scope.discovered_method?(class_name, method_name, kind)
end

.instance_definition(class_name, scope: nil, environment: nil) ⇒ Object

Returns the full RBS instance-side class definition (RBS::Definition), used by callers that walk the method table or member list. Returns nil when the class is not in RBS or when the loader cannot build a definition (e.g. constant aliases, malformed signatures).



293
294
295
296
297
298
299
300
# File 'lib/rigor/reflection.rb', line 293

def instance_definition(class_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return nil if loader.nil?

  loader.instance_definition(class_name.to_s)
rescue ::RBS::BaseError
  nil
end

.instance_method_definition(class_name, method_name, scope: nil, environment: nil) ⇒ Object

Returns the RBS RBS::Definition::Method for the instance method, or nil when the class or method is not in RBS. The source-side discovered-method facts are reachable through discovered_method?; a future slice will unify the two under a MethodDefinition carrier.



273
274
275
276
277
278
# File 'lib/rigor/reflection.rb', line 273

def instance_method_definition(class_name, method_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return nil if loader.nil?

  loader.instance_method(class_name: class_name.to_s, method_name: method_name.to_sym)
end

.nominal_for_name(class_name, scope: Scope.empty) ⇒ Object

Returns the Rigor::Type::Nominal for the class name, or nil when no source knows the class.



87
88
89
# File 'lib/rigor/reflection.rb', line 87

def nominal_for_name(class_name, scope: Scope.empty)
  scope.environment.nominal_for_name(class_name)
end

.rbs_class_known?(class_name, scope: nil, environment: nil) ⇒ Boolean

RBS-only variant of class_known?. Use when the caller needs to know specifically whether RBS has a definition for the class, independent of any source-discovered class Foo; end declarations. The diagnostic-rule code paths that walk RBS method tables to decide whether to flag a missing method use this variant; otherwise the source-discovered class would suppress the rule even when no RBS sig actually proves the method exists.

The kwarg accepts either scope: or environment:. The latter is for call sites that don't carry a Scope (most are bottom-half dispatcher code paths called with only an environment).

Returns:

  • (Boolean)


72
73
74
75
76
77
# File 'lib/rigor/reflection.rb', line 72

def rbs_class_known?(class_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return false if loader.nil?

  loader.class_known?(class_name)
end

.resolve_constant_type(name, scope: Scope.empty) ⇒ Object

Resolves a constant reference to its type through Ruby's lexical constant lookup: the most-qualified candidate first (the enclosing class path joined to name), then progressively less-qualified, then the bare name. Each candidate consults, in order, the class registry (yielding a Singleton[C]), source-discovered classes, in-source value constants, and finally RBS-side constants — in-source value constants winning over RBS because the user's source is authoritative for its own constants. Returns the matched Rigor::Type, or nil when no source knows the constant.

This is the shared owner of the lexical-constant resolution: Inference::ExpressionTyper reads it to type a constant read, and Inference::Narrowing reads it to recognise a value-pinned Constant[Regexp] match-predicate operand.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/rigor/reflection.rb', line 124

def resolve_constant_type(name, scope: Scope.empty)
  prefix = enclosing_class_path(scope)

  # Step 1 — `Module.nesting`, innermost first. Each entry contributes only its OWN constants.
  walker = prefix
  while walker && !walker.empty?
    hit = constant_type_at("#{walker}::#{name}", scope)
    return hit if hit

    idx = walker.rindex("::")
    walker = idx ? walker[0, idx] : nil
  end

  # Step 2 (#354) — the ancestors of the innermost cresting scope, which Ruby consults BEFORE
  # falling back to the top level. Skipping this step did not merely lose a resolution: when the
  # same name also exists at top level, step 3 answered a lookup Ruby gives to the ancestor, so
  # `KEY` inside `class Sub < Base` typed as the top-level constant rather than `Base::KEY` — a
  # wrong type on correct code, which outranks any worst-case reading (AGENTS.md
  # § "Implementation Guidelines"). Only project ancestors are walked; an RBS-known superclass
  # contributes no name here (see {.ancestor_constant_scopes}).
  if prefix
    ancestor_constant_scopes(prefix, scope).each do |ancestor|
      hit = constant_type_at("#{ancestor}::#{name}", scope)
      return hit if hit
    end
  end

  # Step 3 — the bare name (top level).
  constant_type_at(name, scope)
end

.singleton_definition(class_name, scope: nil, environment: nil) ⇒ Object

Returns the full RBS singleton-side class definition.



303
304
305
306
307
308
309
310
# File 'lib/rigor/reflection.rb', line 303

def singleton_definition(class_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return nil if loader.nil?

  loader.singleton_definition(class_name.to_s)
rescue ::RBS::BaseError
  nil
end

.singleton_for_name(class_name, scope: Scope.empty) ⇒ Object

Returns the Rigor::Type::Singleton for the class name's class object, or nil when no source knows the class.



93
94
95
# File 'lib/rigor/reflection.rb', line 93

def singleton_for_name(class_name, scope: Scope.empty)
  scope.environment.singleton_for_name(class_name)
end

.singleton_method_definition(class_name, method_name, scope: nil, environment: nil) ⇒ Object

Returns the RBS RBS::Definition::Method for the singleton (class-side) method, or nil.



282
283
284
285
286
287
# File 'lib/rigor/reflection.rb', line 282

def singleton_method_definition(class_name, method_name, scope: nil, environment: nil)
  loader = rbs_loader_for(scope, environment)
  return nil if loader.nil?

  loader.singleton_method(class_name: class_name.to_s, method_name: method_name.to_sym)
end

Instance Method Details

#self?.class_known?Boolean

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Boolean)


3
# File 'sig/rigor/reflection.rbs', line 3

def self?.class_known?: (String | Symbol class_name, ?scope: Scope) -> bool

#self?.class_orderingSymbol

Parameters:

  • lhs (String, Symbol)
  • rhs (String, Symbol)
  • scope: (Scope)

Returns:

  • (Symbol)


5
# File 'sig/rigor/reflection.rbs', line 5

def self?.class_ordering: (String | Symbol lhs, String | Symbol rhs, ?scope: Scope) -> Symbol

#self?.class_type_param_namesArray[Symbol]

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Array[Symbol])


14
# File 'sig/rigor/reflection.rbs', line 14

def self?.class_type_param_names: (String | Symbol class_name, ?scope: Scope) -> Array[Symbol]

#self?.constant_type_forType::t?

Parameters:

  • constant_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Type::t, nil)


8
# File 'sig/rigor/reflection.rbs', line 8

def self?.constant_type_for: (String | Symbol constant_name, ?scope: Scope) -> Type::t?

#self?.discovered_class?Boolean

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Boolean)


15
# File 'sig/rigor/reflection.rbs', line 15

def self?.discovered_class?: (String | Symbol class_name, ?scope: Scope) -> bool

#self?.discovered_method?Boolean

Parameters:

  • class_name (String, Symbol)
  • method_name (String, Symbol)
  • kind: (Symbol)
  • scope: (Scope)

Returns:

  • (Boolean)


16
# File 'sig/rigor/reflection.rbs', line 16

def self?.discovered_method?: (String | Symbol class_name, String | Symbol method_name, ?kind: Symbol, ?scope: Scope) -> bool

#self?.instance_definitionObject

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Object)


12
# File 'sig/rigor/reflection.rbs', line 12

def self?.instance_definition: (String | Symbol class_name, ?scope: Scope) -> untyped

#self?.instance_method_definitionObject

Parameters:

  • class_name (String, Symbol)
  • method_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Object)


10
# File 'sig/rigor/reflection.rbs', line 10

def self?.instance_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped

#self?.nominal_for_nameType::Nominal?

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:



6
# File 'sig/rigor/reflection.rbs', line 6

def self?.nominal_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Nominal?

#self?.rbs_class_known?Boolean

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Boolean)


4
# File 'sig/rigor/reflection.rbs', line 4

def self?.rbs_class_known?: (String | Symbol class_name, ?scope: Scope) -> bool

#self?.resolve_constant_typeType::t?

Parameters:

  • name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Type::t, nil)


9
# File 'sig/rigor/reflection.rbs', line 9

def self?.resolve_constant_type: (String | Symbol name, ?scope: Scope) -> Type::t?

#self?.singleton_definitionObject

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Object)


13
# File 'sig/rigor/reflection.rbs', line 13

def self?.singleton_definition: (String | Symbol class_name, ?scope: Scope) -> untyped

#self?.singleton_for_nameType::Singleton?

Parameters:

  • class_name (String, Symbol)
  • scope: (Scope)

Returns:



7
# File 'sig/rigor/reflection.rbs', line 7

def self?.singleton_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Singleton?

#self?.singleton_method_definitionObject

Parameters:

  • class_name (String, Symbol)
  • method_name (String, Symbol)
  • scope: (Scope)

Returns:

  • (Object)


11
# File 'sig/rigor/reflection.rbs', line 11

def self?.singleton_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped