SIGN IN SIGN UP

ast: index refs rooted at a local variable

The rule indexer already handles a local that stands in for a whole value:
`x := input.foo; x == "a"` discriminates, because resolveVarToRef tracks
var-to-ref associations from earlier expressions in the body. But a local used as
the *head* of a ref does not. For

	p := 1 if { x := input; x.foo == "a" }
	p := 2 if { x := input; x.foo == "b" }

eqOperandsToRefAndValue takes `__local0__.foo` as a Ref and hands it straight to
isValidIndexRef, which requires RootDocumentNames.Contains(ref[0]). `__local0__`
is neither input nor data, so nothing is indexed and both rules are evaluated --
where the same policy written as `input.foo == "a"` indexes fine. Which spelling
the author reaches for should not decide whether the rule is indexable.

Resolve the head variable and splice the remainder onto what it resolves to,
reusing the existing resolveVarToRef.

The equality path is the only one that needs this. `in` and glob.match never
receive a ref operand: RewriteDynamicTerms hoists those to a variable first, so
`"a" in x.foo` becomes `__local1__ = __local0__.foo; "a" in __local1__` and
glob.match's third operand is always a bare variable, as the comment on
updateGlobMatch says. Both therefore benefit from this change via the assignment
that does the hoisting, and resolveAndValidateRef needs no equivalent -- its Ref
branch is unreachable with a locally-rooted ref.

Function formals are deliberately untouched. resolveVarToRef maps a formal to an
`args[i]` ref, which isValidIndexRef rejects since RootDocumentNames holds only
data and input; bare-formal indexing works today by bypassing that validation
entirely. Making `args[i].foo` indexable would need the resolver to resolve
sub-paths of args, which is a separate question.

resolveVarToRef only sees indices recorded from earlier expressions in the body.
That is a sufficient condition rather than a limitation: a body that uses a local
ahead of binding it cannot reach the indexer at all. CheckSafetyRuleBodies runs
seven stages earlier, and reorderBodyForSafety either moves the binding ahead of
the use or the variable is unsafe and compilation fails. RewriteDynamicTerms,
which runs after that reordering, likewise emits each generated assignment
immediately before the expression that consumes it.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
S
Stephan Renatus committed
aebe258fc8789c2e5b7cbe5130eaa38a4f8eec9f
Parent: 3d8c901
Committed by Stephan Renatus <s_renatus@apple.com> on 8/28/2026, 7:28:21 AM