Class: Rigor::Inference::BlockParameterBinder

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/inference/block_parameter_binder.rb,
sig/rigor/inference.rbs

Overview

Builds the entry scope of a block body by translating the block's parameter list into a name -> Rigor::Type map.

The binder is the symmetric counterpart of MethodParameterBinder for Prism::BlockNode. The expected parameter types come from the receiving method's RBS signature (MethodDispatcher.expected_block_param_types); parameters that the signature does not cover (or that the binder cannot match by position) default to Dynamic[Top]. The default is the Slice 1 fail-soft answer for unknown values, so a block whose receiving method has no signature still binds every name into the scope (a block body whose Local x reads return Dynamic[Top] instead of falling through to the unbound-local Dynamic[Top] event is the same observable type, but the binding presence is what later slices need to attach narrowing facts to).

MultiTargetNode parameters (|(a, b), c|) are bound by delegating each destructuring slot to MultiTargetBinder, so a Tuple-shaped expected element type projects element-wise into the inner locals (Slice 6 phase C sub-phase 2). Numbered parameters (_1, _2, ...) are bound from Prism::NumberedParametersNode using the same per-position expected_param_types: array, so [1, 2, 3].each { _1 + _2 } sees _1/_2 typed identically to their explicit |x, y| counterparts.

The it implicit parameter (Ruby 3.4+) is bound from Prism::ItParametersNode. It is the single-argument cousin of _1: the binder produces { it: expected_param_types[0] } so the body's Prism::ItLocalVariableReadNode lookup sees the same type as the explicit |x| form would.

Block-local declarations after ; (e.g., |x; y, z|) are still skipped — they are explicitly block-local, so the outer scope MUST NOT observe them and the binder leaves them unbound.

See docs/internal-spec/inference-engine.md for the binding contract.

Instance Method Summary collapse

Constructor Details

#initialize(expected_param_types: []) ⇒ BlockParameterBinder

Returns a new instance of BlockParameterBinder.

Parameters:

  • expected_param_types (Array<Rigor::Type>) (defaults to: [])

    positional block parameter types in order. Indices the binder cannot fill from this array (because the array is shorter than the parameter list, or because the slot is a kind we do not pull from the array) default to Dynamic[Top].

  • expected_param_types: (Array[Type::t]) (defaults to: [])


40
41
42
# File 'lib/rigor/inference/block_parameter_binder.rb', line 40

def initialize(expected_param_types: [])
  @expected_param_types = expected_param_types
end

Instance Method Details

#bind(block_node) ⇒ Hash{Symbol => Rigor::Type}

Returns ordered map from parameter name to bound type. Anonymous parameters are skipped; MultiTargetNode destructuring slots delegate to MultiTargetBinder and contribute every named local in declaration order. Numbered-parameter forms (_1, _2, ...) bind :_1, :_2, ... up to the maximum the block body refers to.

Parameters:

  • block_node (Prism::BlockNode)

Returns:

  • (Hash{Symbol => Rigor::Type})

    ordered map from parameter name to bound type. Anonymous parameters are skipped; MultiTargetNode destructuring slots delegate to MultiTargetBinder and contribute every named local in declaration order. Numbered-parameter forms (_1, _2, ...) bind :_1, :_2, ... up to the maximum the block body refers to.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rigor/inference/block_parameter_binder.rb', line 49

def bind(block_node)
  params_root = block_node.parameters
  return {} if params_root.nil?

  case params_root
  when Prism::NumberedParametersNode
    bind_numbered_parameters(params_root)
  when Prism::ItParametersNode
    bind_it_parameter
  when Prism::BlockParametersNode
    bind_block_parameters(params_root)
  else
    {}
  end
end

#bind_block_parameters(params_root) ⇒ Hash[Symbol, Type::t]

Parameters:

  • params_root (Object)

Returns:

  • (Hash[Symbol, Type::t])


85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rigor/inference/block_parameter_binder.rb', line 85

def bind_block_parameters(params_root)
  params_node = params_root.parameters
  return {} if params_node.nil?

  apply_auto_splat(params_node)

  bindings = {}
  bind_positionals(params_node, bindings, 0)
  bind_rest(params_node, bindings)
  bind_keywords(params_node, bindings)
  bind_keyword_rest(params_node, bindings)
  bind_block_param(params_node, bindings)
  bindings
end

#bind_numbered_parameters(numbered_node) ⇒ Hash[Symbol, Type::t]

|_1, _2| numbered-parameter form. Prism exposes the implicit count through NumberedParametersNode#maximum (the highest _N referenced in the body); we materialise bindings for :_1 through :_maximum so the block body's LocalVariableReadNode lookups see the same types as the equivalent explicit |x, y| form would.

Parameters:

  • numbered_node (Object)

Returns:

  • (Hash[Symbol, Type::t])


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

def bind_numbered_parameters(numbered_node)
  bindings = {}
  numbered_node.maximum.times do |i|
    bindings[:"_#{i + 1}"] = positional_type_at(i)
  end
  bindings
end

#positional_type_at(index) ⇒ Type::t

Parameters:

  • index (Integer)

Returns:

  • (Type::t)


209
210
211
# File 'lib/rigor/inference/block_parameter_binder.rb', line 209

def positional_type_at(index)
  @expected_param_types[index] || Type::Combinator.untyped
end