Summary
The foreach sugar form
foreach a(lb:ub[:step]) { ... }
is desugared at parse time to foreach a = a(lb:ub[:step]). This gives a single name a two distinct meanings:
- the
with-declared bounded variable (a), which supplies the loop bounds; and
- the induction variable visible inside the loop body.
Problem
The two meanings are currently disambiguated by convention rather than by structure:
- the bound is read from the type system (the
BoundedType upper bound), while
- the value in the body resolves to the induction variable via scope shadowing.
This works today, but it is fragile. Any future feature that needs to read the bound of a from inside the loop body would silently resolve to the induction variable instead, because that is what the name a maps to in that scope.
Proposal
Canonicalize (normalize) the sugar form into an explicit, fresh induction variable:
foreach __iv_a = a(lb:ub[:step]) { ... }
and rewrite references to a inside the loop body to __iv_a. This makes the separation structural: the source variable a always denotes the bound, and __iv_a always denotes the induction variable.
This mirrors what code generation already does at emit time (it introduces an __iv_<name> remapping for with-in matchers). The proposal is to lift that remapping into AST normalization so that every consumer (all backends, and any future passes) benefits uniformly instead of each one re-deriving the same convention.
Example
with {i} in [12] {
foreach i(2:) {
// inside the body, i is the induction variable
}
// outside the loop, i(-2) reads the bound (12) -> 10
}
Scope
- General language/normalization feature; applies to all backends.
- A lowering/normalization refactor with no observable behavior change for existing programs.
Acceptance criteria
foreach a(...) and foreach c = a(...) both lower to an explicit __iv_<name> induction variable.
- Body references to the source name are rewritten to the induction variable.
- Negative-index bound accesses such as
a(-1) continue to resolve against the bounded type.
- Existing tests pass unchanged.
Summary
The
foreachsugar formis desugared at parse time to
foreach a = a(lb:ub[:step]). This gives a single nameatwo distinct meanings:with-declared bounded variable (a), which supplies the loop bounds; andProblem
The two meanings are currently disambiguated by convention rather than by structure:
BoundedTypeupper bound), whileThis works today, but it is fragile. Any future feature that needs to read the bound of
afrom inside the loop body would silently resolve to the induction variable instead, because that is what the nameamaps to in that scope.Proposal
Canonicalize (normalize) the sugar form into an explicit, fresh induction variable:
and rewrite references to
ainside the loop body to__iv_a. This makes the separation structural: the source variableaalways denotes the bound, and__iv_aalways denotes the induction variable.This mirrors what code generation already does at emit time (it introduces an
__iv_<name>remapping forwith-in matchers). The proposal is to lift that remapping into AST normalization so that every consumer (all backends, and any future passes) benefits uniformly instead of each one re-deriving the same convention.Example
Scope
Acceptance criteria
foreach a(...)andforeach c = a(...)both lower to an explicit__iv_<name>induction variable.a(-1)continue to resolve against the bounded type.