diff --git a/source/units/Goccia.Evaluator.pas b/source/units/Goccia.Evaluator.pas index cdf9a4a6..1235ab28 100644 --- a/source/units/Goccia.Evaluator.pas +++ b/source/units/Goccia.Evaluator.pas @@ -2758,6 +2758,19 @@ procedure EvalDeclarationInstantiation(const AProgram: TGocciaProgram; Exit(True); Result := False; end; + // A duplicate lexical name, or a lexical/var conflict, is a static Script + // early error (ECMA-262 §16.1.1). Raise TGocciaSyntaxError directly — like + // RejectEvalControlFlow and the parser's redeclaration error, and unlike + // ThrowSyntaxError, which raises a throwable TGocciaThrowValue — so a caller + // distinguishing early errors from runtime abrupt completions (notably + // ShadowRealm.prototype.evaluate, which maps the former to a caller-realm + // SyntaxError and the latter to a TypeError) classifies it correctly. + procedure RaiseAlreadyDeclared(const AName: string); + begin + raise TGocciaSyntaxError.Create( + Format(SErrorIdentifierAlreadyDeclared, [AName]), 0, 0, '', nil, + SSuggestAlreadyDeclared); + end; begin VarNames := TStringList.Create; LexNames := TStringList.Create; @@ -2777,16 +2790,14 @@ procedure EvalDeclarationInstantiation(const AProgram: TGocciaProgram; CollectTopLevelEvalLexicalNames(AProgram.Body, LexNames); for I := 0 to LexNames.Count - 1 do if VarNames.IndexOf(LexNames[I]) >= 0 then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, [LexNames[I]]), - SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(LexNames[I]); if not AStrictEval then begin if AVarScope.ScopeKind = skGlobal then for I := 0 to VarNames.Count - 1 do if AVarScope.HasLexicalDeclaration(VarNames[I]) then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, - [VarNames[I]]), SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(VarNames[I]); ScopeCursor := ALexicalScope; while Assigned(ScopeCursor) and (ScopeCursor <> AVarScope) do @@ -2795,30 +2806,26 @@ procedure EvalDeclarationInstantiation(const AProgram: TGocciaProgram; for I := 0 to VarNames.Count - 1 do if ScopeCursor.ContainsOwnLexicalBinding(VarNames[I]) or ScopeCursor.ContainsOwnVarBinding(VarNames[I]) then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, - [VarNames[I]]), SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(VarNames[I]); ScopeCursor := ScopeCursor.Parent; end; if AVarScope.ScopeKind <> skGlobal then for I := 0 to VarNames.Count - 1 do if AVarScope.ContainsOwnLexicalBinding(VarNames[I]) then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, - [VarNames[I]]), SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(VarNames[I]); end; if (not AStrictEval) then begin for I := 0 to VarNames.Count - 1 do if RejectsVarDeclarationName(VarNames[I]) then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, - [VarNames[I]]), SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(VarNames[I]); end; if (not AStrictEval) and ARejectArgumentsVarDeclaration and (VarNames.IndexOf(IDENTIFIER_ARGUMENTS) >= 0) then - ThrowSyntaxError(Format(SErrorIdentifierAlreadyDeclared, - [IDENTIFIER_ARGUMENTS]), SSuggestAlreadyDeclared); + RaiseAlreadyDeclared(IDENTIFIER_ARGUMENTS); CollectEvalFunctionDeclarations(AProgram.Body, FunctionDeclarations); for I := FunctionDeclarations.Count - 1 downto 0 do diff --git a/tests/built-ins/ShadowRealm/evaluate.js b/tests/built-ins/ShadowRealm/evaluate.js index ac7a83fb..d1a1dca9 100644 --- a/tests/built-ins/ShadowRealm/evaluate.js +++ b/tests/built-ins/ShadowRealm/evaluate.js @@ -140,6 +140,18 @@ describe("ShadowRealm.prototype.evaluate", () => { // Re-declaring the same top-level lexical name must not clash. expect(realm.evaluate("const scoped = 2; scoped")).toBe(2); }); + + test("throws a SyntaxError when a top-level name is both lexically and var declared", () => { + const realm = new ShadowRealm(); + expect(() => realm.evaluate("let y; var y;")).toThrow(SyntaxError); + expect(() => realm.evaluate("var y; let y;")).toThrow(SyntaxError); + }); + + test("allows distinct top-level lexical and var names", () => { + const realm = new ShadowRealm(); + expect(realm.evaluate("var a = 1; let b = 2; a + b")).toBe(3); + expect(realm.evaluate("let c = 4; var d = 5; c + d")).toBe(9); + }); }); describe("ShadowRealm wrapped functions", () => {