Skip to content

codegen: var x; in a loop body is reset to undefined each iteration instead of keeping its value #6876

Description

@proggeramlug

Summary

var x; re-declared inside a loop body is reset to undefined on each iteration. var is function-scoped and hoisted, so re-executing the declaration is a runtime no-op — the binding must keep its value.

This is the mirror image of #6871 (which was about lexical let not being reset). It is a pre-existing defect, present before and after the #6871 fix.

Repro

function varKeepsValue(): string {
  const out: string[] = [];
  for (let i = 0; i < 3; i++) {
    var v: string | undefined;
    if (i === 0) v = "kept";
    out.push(v === undefined ? "u" : v);
  }
  return out.join(" ");
}
console.log(varKeepsValue());
node --experimental-strip-types   kept kept kept
perry 0.5.1239                    kept u u          <-- wrong

Verified against released perry 0.5.1239 (i.e. before #6871's fix landed), so this is not a regression from that change — #6871 deliberately excludes var and neither fixes nor worsens this.

Notes

Per ECMA-262, a var declaration's binding is created once at function entry (FunctionDeclarationInstantiation, initialized to undefined); the declaration site itself only performs the assignment when one is written. var v; with no initializer is therefore a no-op every time control reaches it, including on a loop back-edge.

Likely relevant: HIR emits Stmt::Let { init: None } for the hoisted binding, and stmt/let_stmt.rs has a redeclaration path (if ctx.locals.contains_key(&id)) whose no-init arm clears the slot to undefined when the id is in ctx.tdz_boxes. That arm's comment already states the intended semantics — "A redeclaration with no initializer (var x;) keeps the prior value, matching JS" — so the reset is coming from somewhere that path does not intend. I did not chase it further.

Test coverage

test-files/test_gap_uninit_let_loop_reset.ts (added by #6871) documents this case in a comment rather than asserting it, so it does not bake in a known failure. Once fixed, the assertion can be restored there:

console.log(varKeepsValue());   // kept kept kept

Environment

  • perry 0.5.1239, macOS arm64

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions