Module: Rigor::Inference::MacroBlockSelfType

Defined in:
lib/rigor/inference/macro_block_self_type.rb

Overview

ADR-16 Tier A — engine hook. Consults every registered plugin manifest’s ‘block_as_methods` entries to decide whether a block call site qualifies for `Scope#self_type` narrowing.

The match contract for a class-level DSL like Sinatra’s ‘class MyApp < Sinatra::Base; get ’/foo’ do … end; end`:

  • the call’s lexical receiver type is ‘Singleton` (the implicit-self in a class body, or an explicit `MyApp.get(…)` call);

  • the underlying class ‘X` equals or inherits from the entry’s ‘receiver_constraint`;

  • the call’s method name is in the entry’s ‘verbs`.

On a match the helper returns the instance type of the receiver class (‘Nominal`) — the narrowed `self_type` for the block body, matching Sinatra’s runtime semantics where ‘Sinatra::Base#generate_method` turns the block into an instance method of the user’s app class.

Slice 1b ships the floor only (per ADR-16 § WD13): bare-identifier method lookups inside the block resolve through the inference engine’s normal ‘self_type`-driven path, so methods declared on `Sinatra::Base` (RBS or otherwise) become visible. Precision additions —parameter-typed block params, declared per-verb argument contracts — are ceiling concerns for later slices.

Class Method Summary collapse

Class Method Details

.instance_type_for(class_name, environment) ⇒ Object



90
91
92
# File 'lib/rigor/inference/macro_block_self_type.rb', line 90

def instance_type_for(class_name, environment)
  environment.nominal_for_name(class_name) || Type::Nominal.new(class_name)
end

.narrow_self_type_for(scope:, call_node:, receiver_type:) ⇒ Rigor::Type?

Returns the narrowed self-type, or ‘nil` when no registered entry matches the call shape.

Parameters:

Returns:

  • (Rigor::Type, nil)

    the narrowed self-type, or ‘nil` when no registered entry matches the call shape.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rigor/inference/macro_block_self_type.rb', line 44

def narrow_self_type_for(scope:, call_node:, receiver_type:)
  return nil if receiver_type.nil?

  environment = scope&.environment
  registry = environment&.plugin_registry
  return nil if registry.nil? || registry.empty?

  receiver_class_name = singleton_receiver_class_name(receiver_type)
  return nil if receiver_class_name.nil?

  # ADR-52 WD1 — the verb-keyed table compiled at registry build
  # replaces the per-call plugins × block_as_methods linear scan.
  # Entries arrive in (plugin registration, declaration) order, so
  # the first ancestry match below is the same entry the previous
  # walk returned; the verb membership the old `matches?` checked
  # is guaranteed by the table key.
  entries = registry.contribution_index.block_entries_for(call_node.name)
  entries.each do |entry|
    if receiver_class_inherits_from?(receiver_class_name, entry.receiver_constraint, environment)
      return instance_type_for(receiver_class_name, environment)
    end
  end
  nil
end

.receiver_class_inherits_from?(class_name, constraint, environment) ⇒ Boolean

Returns:

  • (Boolean)


81
82
83
84
85
86
87
88
# File 'lib/rigor/inference/macro_block_self_type.rb', line 81

def receiver_class_inherits_from?(class_name, constraint, environment)
  return true if class_name == constraint

  ordering = environment.class_ordering(class_name, constraint)
  %i[equal subclass].include?(ordering)
rescue StandardError
  false
end

.singleton_receiver_class_name(receiver_type) ⇒ Object

Tier A’s match contract is intentionally narrow: class-level DSL calls (receiver is ‘Singleton`) only. Instance-receiver calls and DSL forms whose block body binds a different `self` (Concern’s ‘included do`, `instance_eval { … }`) are handled by later slices (Concern walker, Tier D, etc.) — not Tier A.



75
76
77
78
79
# File 'lib/rigor/inference/macro_block_self_type.rb', line 75

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

  receiver_type.class_name
end