Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions source/units/Goccia.Evaluator.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions tests/built-ins/ShadowRealm/evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Loading