Skip to content

fix(aiken-lang): stop when clause variables receiving each other's values - #1463

Open
logicalmechanism wants to merge 2 commits into
aiken-lang:mainfrom
logicalmechanism:fix/when-hoisted-clause-bindings
Open

logicalmechanism wants to merge 2 commits into
aiken-lang:mainfrom
logicalmechanism:fix/when-hoisted-clause-bindings

Conversation

@logicalmechanism

Copy link
Copy Markdown
Contributor

Variables bound by a when clause can silently receive each other's values.

pub type Action { Deposit Withdraw Update }

fn allowed(action: Action, old: Option<Int>, new: Option<Int>) -> Bool {
  when (action, old, new) is {
    (Deposit, None, _) -> True
    (Withdraw, _, None) -> True
    (_, Some(before), Some(after)) -> after >= before
    _ -> False
  }
}

On current main, allowed(Withdraw, Some(100), Some(60)) returns True and allowed(Withdraw, Some(100), Some(120)) returns False. The same inputs with Update or Deposit are correct.

Cause: a clause body is hoisted into a function shared by every leaf that reaches it, and takes its variables by position. The parameter order came from the first leaf built, but each leaf passed the variables in the order its own row collected them, which depends on which column was specialized first on that branch. Above, the Deposit branch tests old first and the Withdraw branch tests new first.

Fix: every leaf now passes the order recorded for the clause. The comment saying the list could simply be overwritten is replaced, since that assumption was the bug.

Effect on scripts: output only changes when a clause's leaves disagreed on the order. That includes scripts where the swap had no visible effect, such as a + b, so those can get a new hash. The changelog entry says so.

Checked:

  • The three new tests fail on main and pass with this change.
  • The acceptance tests and stdlib v4 (135 projects) produce identical test results and compiled code with and without it.
  • Across 504 random when programs with bound variables, checked against an independent matcher, it fixed 4, caused no new failures, and changed the hash of only those 4.

🤖 Generated with Claude Code

logicalmechanism and others added 2 commits November 9, 2026 09:16
…t order

The body of a `when` clause is hoisted into a function shared by every
leaf that reaches it, and receives the clause's variables by position.
The parameter order came from the first leaf built, but each leaf passed
the variables in the order its own row collected them, which depends on
the order columns were specialized on that branch. When two branches
differed, the variables received each other's values.

Make every leaf pass the order recorded for the clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a regression test shaped like a state transition check, where the
swap inverts the comparison on one branch only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant