Module: Rigor::Inference::MethodDispatcher

Defined in:
lib/rigor/inference/method_dispatcher.rb,
lib/rigor/inference/method_dispatcher/rbs_dispatch.rb,
lib/rigor/inference/method_dispatcher/shape_dispatch.rb,
lib/rigor/inference/method_dispatcher/constant_folding.rb,
lib/rigor/inference/method_dispatcher/overload_selector.rb

Overview

Coordinates method dispatch for the inference engine.

Given ‘(receiver_type, method_name, arg_types, block_type, environment)`, the dispatcher returns the inferred result type or `nil` when no rule matches. `nil` is a deliberately blunt “I don’t know” signal: callers (today only ‘ExpressionTyper`) own the fail-soft fallback and decide whether to record a `FallbackTracer` event.

Tiers (in order):

  1. ConstantFolding: executes the Ruby operation directly when the receiver and argument are ‘Constant` carriers and the method is on the curated whitelist. Slice 2.

  2. ShapeDispatch: returns the precise element/value type for a curated catalogue of ‘Tuple`/`HashShape` element-access methods (`first`, `last`, `[]` with a static integer/key, `fetch`, `dig`, `size`/`length`/`count`). Slice 5 phase 2.

  3. RbsDispatch: looks up the receiver’s class in the RBS environment carried by the scope and translates the method’s return type into a Rigor::Type. Slice 4.

‘ShapeDispatch` deliberately runs above RbsDispatch so the precise per-position/per-key answer wins over the projected `Array#[]`/`Hash#fetch` answer; it falls through (`nil`) when the call cannot be proved against the static shape, in which case the projection answer from RbsDispatch applies.

The dispatcher’s public signature reserves space for ‘block_type:` and ADR-2 plugin extensions (later slices), so call sites added now do not have to be rewritten when those tiers arrive.

Defined Under Namespace

Modules: ConstantFolding, OverloadSelector, RbsDispatch, ShapeDispatch

Class Method Summary collapse

Class Method Details

.constant_metaclass(value) ⇒ Object



176
177
178
179
180
181
# File 'lib/rigor/inference/method_dispatcher.rb', line 176

def constant_metaclass(value)
  CONSTANT_METACLASSES.each do |klass, name|
    return Type::Combinator.singleton_of(name) if value.is_a?(klass)
  end
  nil
end

.dispatch(receiver_type:, method_name:, arg_types:, block_type: nil, environment: nil) ⇒ Rigor::Type?

Returns inferred result type, or ‘nil` for “no rule”.

Parameters:

  • receiver_type (Rigor::Type, nil)

    type of the receiver expression, or ‘nil` for an implicit-self call.

  • method_name (Symbol)
  • arg_types (Array<Rigor::Type>)

    positional argument types.

  • block_type (Rigor::Type, nil) (defaults to: nil)

    inferred return type of the accompanying ‘do … end` / `{ … }` block (Slice 6 phase C sub-phase 2). When non-nil, the dispatcher prefers an overload that declares a block, and binds the method’s block-return type variable to ‘block_type` so a return type like `Array` resolves to `Array`.

  • environment (Rigor::Environment, nil) (defaults to: nil)

    required for RBS-backed dispatch; when nil only constant folding can fire.

Returns:

  • (Rigor::Type, nil)

    inferred result type, or ‘nil` for “no rule”.



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
# File 'lib/rigor/inference/method_dispatcher.rb', line 56

def dispatch(receiver_type:, method_name:, arg_types:, block_type: nil, environment: nil)
  return nil if receiver_type.nil?

  meta_result = try_meta_introspection(receiver_type, method_name)
  return meta_result if meta_result

  constant_result = ConstantFolding.try_fold(
    receiver: receiver_type,
    method_name: method_name,
    args: arg_types
  )
  return constant_result if constant_result

  shape_result = ShapeDispatch.try_dispatch(
    receiver: receiver_type,
    method_name: method_name,
    args: arg_types
  )
  return shape_result if shape_result

  rbs_result = RbsDispatch.try_dispatch(
    receiver: receiver_type,
    method_name: method_name,
    args: arg_types,
    environment: environment,
    block_type: block_type
  )
  return rbs_result if rbs_result

  # Slice 7 phase 10 — user-class ancestor fallback. When
  # the receiver is `Nominal[T]` or `Singleton[T]` for a
  # class not in the RBS environment (typically a
  # user-defined class), retry the dispatch against the
  # implicit ancestor: `Nominal[Object]` for instance
  # receivers and `Singleton[Object]` for singleton
  # receivers. This resolves Kernel intrinsics
  # (`require`, `raise`, `puts`, ...) and Module/Class
  # introspection (`attr_reader`, `private`, ...) on
  # user classes without requiring the user to author
  # their own RBS.
  try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type)
end

.expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil) ⇒ Array<Rigor::Type>

Returns the positional block parameter types declared by the receiving method’s selected RBS overload, translated into ‘Rigor::Type`. Used by the StatementEvaluator’s CallNode handler to bind block parameter names before evaluating the block body.

The probe is best-effort: it returns an empty array whenever the receiver, environment, method definition, or selected overload does not provide statically declared block parameter types. Callers MUST treat the empty array as “no information”; the binder falls back to ‘Dynamic` for every parameter slot in that case.

Parameters:

Returns:



201
202
203
204
205
206
207
208
209
210
# File 'lib/rigor/inference/method_dispatcher.rb', line 201

def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil)
  return [] if receiver_type.nil?

  RbsDispatch.block_param_types(
    receiver: receiver_type,
    method_name: method_name,
    args: arg_types,
    environment: environment
  )
end

.meta_class(receiver_type) ⇒ Object



149
150
151
152
153
154
# File 'lib/rigor/inference/method_dispatcher.rb', line 149

def meta_class(receiver_type)
  case receiver_type
  when Type::Nominal then Type::Combinator.singleton_of(receiver_type.class_name)
  when Type::Constant then constant_metaclass(receiver_type.value)
  end
end

.meta_new(receiver_type) ⇒ Object

Singleton.new` returns `Nominal` (a fresh instance), regardless of whether Foo is in RBS. This short-circuits the Class.new generic-`instance` plumbing for user classes, so a discovered-class `ScanAccumulator.new` types as `Nominal` rather than `Class`.



162
163
164
165
166
# File 'lib/rigor/inference/method_dispatcher.rb', line 162

def meta_new(receiver_type)
  return nil unless receiver_type.is_a?(Type::Singleton)

  Type::Combinator.nominal_of(receiver_type.class_name)
end

.try_meta_introspection(receiver_type, method_name) ⇒ Object

Slice 7 phase 8 — meta-introspection shortcuts. The default ‘Object#class` RBS return type is `Class`, but for a receiver of known nominal identity we can do better: `instance_of(Foo).class` is `Singleton` (the class object itself), which downstream dispatch uses to resolve `self.class.some_class_method`. The same logic answers `Foo.class` as `Singleton` (deliberate; calling `.class` on a class object yields `Class`, the metaclass). We also special-case `is_a?`- adjacent calls and the trivial `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.



142
143
144
145
146
147
# File 'lib/rigor/inference/method_dispatcher.rb', line 142

def try_meta_introspection(receiver_type, method_name)
  case method_name
  when :class then meta_class(receiver_type)
  when :new then meta_new(receiver_type)
  end
end

.try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/rigor/inference/method_dispatcher.rb', line 99

def try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type)
  return nil if environment.nil?

  fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
  return nil if fallback_receiver.nil?

  RbsDispatch.try_dispatch(
    receiver: fallback_receiver,
    method_name: method_name,
    args: arg_types,
    environment: environment,
    block_type: block_type
  )
end

.user_class_fallback_receiver(receiver_type, environment) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/rigor/inference/method_dispatcher.rb', line 114

def user_class_fallback_receiver(receiver_type, environment)
  loader = environment.rbs_loader
  return nil if loader.nil?

  case receiver_type
  when Type::Nominal
    return nil if loader.class_known?(receiver_type.class_name)

    environment.nominal_for_name("Object")
  when Type::Singleton
    return nil if loader.class_known?(receiver_type.class_name)

    environment.singleton_for_name("Class")
  end
end