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[X] (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 method_names.

On a match the helper returns the instance type of the receiver class (Nominal[X]) — 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



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

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.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rigor/inference/macro_block_self_type.rb', line 36

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. Entries arrive in
  # (plugin registration, declaration) order; the method-name membership 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)


68
69
70
71
72
73
74
75
# File 'lib/rigor/inference/macro_block_self_type.rb', line 68

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[X]) 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.



62
63
64
65
66
# File 'lib/rigor/inference/macro_block_self_type.rb', line 62

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

  receiver_type.class_name
end