Skip to content

fix(laurel): local variable no longer shadows output parameter#642

Open
fabiomadge wants to merge 1 commit intostrata-org:mainfrom
fabiomadge:fix/567-local-var-shadows-output-param
Open

fix(laurel): local variable no longer shadows output parameter#642
fabiomadge wants to merge 1 commit intostrata-org:mainfrom
fabiomadge:fix/567-local-var-shadows-output-param

Conversation

@fabiomadge
Copy link
Contributor

@fabiomadge fabiomadge commented Mar 24, 2026

Fixes #567.

Problem

When a Laurel procedure declares a local variable with the same name as an output parameter (e.g. var result: int := x + x in a procedure with returns (result: int)), the generated Core emits init result inside the $body block, which shadows the output parameter. The return statement then assigns the local to itself, and the output parameter is never written.

Fix

In translateStmt for LocalVariable: when the variable name matches an output parameter, emit set/havoc instead of init, since the variable already exists in Core as the output parameter.

  • init name type (some expr)set name expr
  • init name type nonehavoc name
  • init before call → dropped (the call writes to the existing variable directly)

Before

procedure double (x : int) returns (result : int)
{
  $body: {
    var result : int := x + x;   // shadows output param
    result := result;            // self-assignment on local
    exit $body;
  }
};

After

procedure double (x : int) returns (result : int)
{
  $body: {
    result := x + x;             // assigns to output param
    result := result;            // harmless self-assignment
    exit $body;
  }
};

Tests

New test T18_LocalVarShadowsOutput covers five cases:

  1. Expression initializer with auto-generated result name
  2. Expression initializer with user-named output param
  3. No initializer (havoc path)
  4. Procedure call as initializer
  5. Output param used as scratch, then overwritten by return

…a-org#567)

When a local variable declaration has the same name as an output
parameter (e.g. `var result` in a procedure with auto-generated
`returns (result : int)`), emit `set`/`havoc` instead of `init`
in the generated Core. This avoids shadowing the output parameter,
which caused the return value to be silently lost.

The fix uses a local `intro` helper that checks `isOutputParam` once
and replaces `init` with the appropriate Core statement:
- `init name type (some expr)` → `set name expr`
- `init name type none` → `havoc name`
- `init` before `call` → dropped (call writes directly)
@fabiomadge fabiomadge requested a review from a team March 24, 2026 01:08
@fabiomadge fabiomadge changed the title fix(laurel): local variable no longer shadows output parameter (#567) fix(laurel): local variable no longer shadows output parameter Mar 24, 2026
Copy link
Contributor

@keyboardDrummer keyboardDrummer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes Laurel in a way that I think is confusing.

What happens if I do:

procedure double(x: int) returns (result: int)
  ensures result == 1
{
  result := 1;
  var result: int := 23;
  return;
};

Would that fail?

Instead of what this PR does, I'd rather add a feature to Laurel that enables anonymous return variables, so we can replace the code that creates a "result" return variable.

We could also consider adding an option that disallows shadowing of return parameters.

Copy link
Contributor Author

@fabiomadge fabiomadge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification fails for the example you provided, as result becomes 23. I don't think there is any situation where a Laurel user should see Core type-checking errors. I don't understand what value anonymous return values add to a language not primarily targeted at humans, to make them worth the added complexity. I'm fine with preventing users from redeclaring return values, but that seems like a different PR to me. We should close this PR if you object to the change.

@keyboardDrummer
Copy link
Contributor

Verification fails for the example you provided, as result becomes 23.

That seems wrong

I don't think there is any situation where a Laurel user should see Core type-checking errors.

Agreed. Laurel's resolution phase should prevent that.

I don't understand what value anonymous return values add to a language not primarily targeted at humans, to make them worth the added complexity.

Because then the languages building on top of Laurel don't have to come up with names for the output parameters when there is no name in the user program, which gives them the problem of having to come up with a unique name, a problem that Laurel can solve for them.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Variable shadowing in Laurel -> Strata Core translation

3 participants