Module: Rigor::Inference::ReceiverAlias

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

Overview

Which variables can a receiver EXPRESSION evaluate to?

A receiver-fact invalidation (see MutationWidening) has to name the binding it invalidates, and the overwhelmingly common receiver — a bare arr / @arr read — names exactly one. But a receiver may also select among variables without naming any of them:

(kind == :required ? required : optional)[key] = info

The mutation lands on whichever of required / optional the ternary picked, so BOTH are possible targets and both must forget their literal shape. Reading only the syntactic head left both hashes carrying the empty HashShape the literal {} wrote, and a downstream .empty? then constant-folded into a false flow.always-truthy-condition (#277).

The recursion covers only the forms whose value IS one of the sub-expressions: if / unless (including the ternary spelling), the short-circuit operators, and the transparent wrappers. Anything else — an index read (declared[kind] << key), a call result, a literal — names an object no binding can be attributed to and yields [], which is what every receiver form outside the single-read case already contributed. The walk is depth-capped so a pathological nest cannot make receiver classification unbounded.

Constant Summary collapse

WALK_DEPTH_CAP =

Deep enough for any hand-written selection; a nest beyond it degrades to "names no binding".

6

Class Method Summary collapse

Class Method Details

.branches(first, second, depth) ⇒ Object



52
53
54
# File 'lib/rigor/inference/receiver_alias.rb', line 52

def branches(first, second, depth)
  candidates(first, depth + 1) + candidates(second, depth + 1)
end

.candidates(node, depth = 0) ⇒ Array<Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode>

Returns every variable read the expression can evaluate to; empty when it can evaluate to none.

Parameters:

  • node (Prism::Node, nil)

    the receiver expression.

  • depth (Integer) (defaults to: 0)

    recursion depth, internal.

Returns:

  • (Array<Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode>)

    every variable read the expression can evaluate to; empty when it can evaluate to none.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rigor/inference/receiver_alias.rb', line 37

def candidates(node, depth = 0)
  return [] if node.nil? || depth > WALK_DEPTH_CAP

  case node
  when Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode then [node]
  when Prism::ParenthesesNode then candidates(node.body, depth + 1)
  when Prism::StatementsNode then candidates(node.body.last, depth + 1)
  when Prism::ElseNode then candidates(node.statements, depth + 1)
  when Prism::IfNode then branches(node.statements, node.subsequent, depth)
  when Prism::UnlessNode then branches(node.statements, node.else_clause, depth)
  when Prism::OrNode, Prism::AndNode then branches(node.left, node.right, depth)
  else []
  end
end