Skip to content

Commit e4108f3

Browse files
committed
Improve handling of scoping for function declarations and catch params
Issue #620, issue #548 This is relatively ad-hoc and weird, but so is annex B.3.5, so maybe that's okay. It's very possible that I missed some circumstances.
1 parent 1a8edd9 commit e4108f3

File tree

9 files changed

+81
-190
lines changed

9 files changed

+81
-190
lines changed

bin/test262.whitelist

Lines changed: 2 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,3 @@
1-
annexB/language/function-code/block-decl-func-no-skip-try.js (default)
2-
annexB/language/function-code/block-decl-func-skip-early-err-block.js (default)
3-
annexB/language/function-code/block-decl-func-skip-early-err.js (default)
4-
annexB/language/function-code/block-decl-func-skip-early-err-switch.js (default)
5-
annexB/language/function-code/block-decl-func-skip-early-err-try.js (default)
6-
annexB/language/function-code/if-decl-else-decl-a-func-no-skip-try.js (default)
7-
annexB/language/function-code/if-decl-else-decl-a-func-skip-early-err-block.js (default)
8-
annexB/language/function-code/if-decl-else-decl-a-func-skip-early-err.js (default)
9-
annexB/language/function-code/if-decl-else-decl-a-func-skip-early-err-switch.js (default)
10-
annexB/language/function-code/if-decl-else-decl-a-func-skip-early-err-try.js (default)
11-
annexB/language/function-code/if-decl-else-decl-b-func-no-skip-try.js (default)
12-
annexB/language/function-code/if-decl-else-decl-b-func-skip-early-err-block.js (default)
13-
annexB/language/function-code/if-decl-else-decl-b-func-skip-early-err.js (default)
14-
annexB/language/function-code/if-decl-else-decl-b-func-skip-early-err-switch.js (default)
15-
annexB/language/function-code/if-decl-else-decl-b-func-skip-early-err-try.js (default)
16-
annexB/language/function-code/if-decl-else-stmt-func-no-skip-try.js (default)
17-
annexB/language/function-code/if-decl-else-stmt-func-skip-early-err-block.js (default)
18-
annexB/language/function-code/if-decl-else-stmt-func-skip-early-err.js (default)
19-
annexB/language/function-code/if-decl-else-stmt-func-skip-early-err-switch.js (default)
20-
annexB/language/function-code/if-decl-else-stmt-func-skip-early-err-try.js (default)
21-
annexB/language/function-code/if-decl-no-else-func-no-skip-try.js (default)
22-
annexB/language/function-code/if-decl-no-else-func-skip-early-err-block.js (default)
23-
annexB/language/function-code/if-decl-no-else-func-skip-early-err.js (default)
24-
annexB/language/function-code/if-decl-no-else-func-skip-early-err-switch.js (default)
25-
annexB/language/function-code/if-decl-no-else-func-skip-early-err-try.js (default)
26-
annexB/language/function-code/if-stmt-else-decl-func-no-skip-try.js (default)
27-
annexB/language/function-code/if-stmt-else-decl-func-skip-early-err-block.js (default)
28-
annexB/language/function-code/if-stmt-else-decl-func-skip-early-err.js (default)
29-
annexB/language/function-code/if-stmt-else-decl-func-skip-early-err-switch.js (default)
30-
annexB/language/function-code/if-stmt-else-decl-func-skip-early-err-try.js (default)
31-
annexB/language/function-code/switch-case-func-no-skip-try.js (default)
32-
annexB/language/function-code/switch-case-func-skip-early-err-block.js (default)
33-
annexB/language/function-code/switch-case-func-skip-early-err.js (default)
34-
annexB/language/function-code/switch-case-func-skip-early-err-switch.js (default)
35-
annexB/language/function-code/switch-case-func-skip-early-err-try.js (default)
36-
annexB/language/function-code/switch-dflt-func-no-skip-try.js (default)
37-
annexB/language/function-code/switch-dflt-func-skip-early-err-block.js (default)
38-
annexB/language/function-code/switch-dflt-func-skip-early-err.js (default)
39-
annexB/language/function-code/switch-dflt-func-skip-early-err-switch.js (default)
40-
annexB/language/function-code/switch-dflt-func-skip-early-err-try.js (default)
41-
annexB/language/global-code/block-decl-global-no-skip-try.js (default)
42-
annexB/language/global-code/block-decl-global-skip-early-err-block.js (default)
43-
annexB/language/global-code/block-decl-global-skip-early-err.js (default)
44-
annexB/language/global-code/block-decl-global-skip-early-err-switch.js (default)
45-
annexB/language/global-code/block-decl-global-skip-early-err-try.js (default)
46-
annexB/language/global-code/if-decl-else-decl-a-global-no-skip-try.js (default)
47-
annexB/language/global-code/if-decl-else-decl-a-global-skip-early-err-block.js (default)
48-
annexB/language/global-code/if-decl-else-decl-a-global-skip-early-err.js (default)
49-
annexB/language/global-code/if-decl-else-decl-a-global-skip-early-err-switch.js (default)
50-
annexB/language/global-code/if-decl-else-decl-a-global-skip-early-err-try.js (default)
51-
annexB/language/global-code/if-decl-else-decl-b-global-no-skip-try.js (default)
52-
annexB/language/global-code/if-decl-else-decl-b-global-skip-early-err-block.js (default)
53-
annexB/language/global-code/if-decl-else-decl-b-global-skip-early-err.js (default)
54-
annexB/language/global-code/if-decl-else-decl-b-global-skip-early-err-switch.js (default)
55-
annexB/language/global-code/if-decl-else-decl-b-global-skip-early-err-try.js (default)
56-
annexB/language/global-code/if-decl-else-stmt-global-no-skip-try.js (default)
57-
annexB/language/global-code/if-decl-else-stmt-global-skip-early-err-block.js (default)
58-
annexB/language/global-code/if-decl-else-stmt-global-skip-early-err.js (default)
59-
annexB/language/global-code/if-decl-else-stmt-global-skip-early-err-switch.js (default)
60-
annexB/language/global-code/if-decl-else-stmt-global-skip-early-err-try.js (default)
61-
annexB/language/global-code/if-decl-no-else-global-no-skip-try.js (default)
62-
annexB/language/global-code/if-decl-no-else-global-skip-early-err-block.js (default)
63-
annexB/language/global-code/if-decl-no-else-global-skip-early-err.js (default)
64-
annexB/language/global-code/if-decl-no-else-global-skip-early-err-switch.js (default)
65-
annexB/language/global-code/if-decl-no-else-global-skip-early-err-try.js (default)
66-
annexB/language/global-code/if-stmt-else-decl-global-no-skip-try.js (default)
67-
annexB/language/global-code/if-stmt-else-decl-global-skip-early-err-block.js (default)
68-
annexB/language/global-code/if-stmt-else-decl-global-skip-early-err.js (default)
69-
annexB/language/global-code/if-stmt-else-decl-global-skip-early-err-switch.js (default)
70-
annexB/language/global-code/if-stmt-else-decl-global-skip-early-err-try.js (default)
71-
annexB/language/global-code/switch-case-global-no-skip-try.js (default)
72-
annexB/language/global-code/switch-case-global-skip-early-err-block.js (default)
73-
annexB/language/global-code/switch-case-global-skip-early-err.js (default)
74-
annexB/language/global-code/switch-case-global-skip-early-err-switch.js (default)
75-
annexB/language/global-code/switch-case-global-skip-early-err-try.js (default)
76-
annexB/language/global-code/switch-dflt-global-no-skip-try.js (default)
77-
annexB/language/global-code/switch-dflt-global-skip-early-err-block.js (default)
78-
annexB/language/global-code/switch-dflt-global-skip-early-err.js (default)
79-
annexB/language/global-code/switch-dflt-global-skip-early-err-switch.js (default)
80-
annexB/language/global-code/switch-dflt-global-skip-early-err-try.js (default)
81-
annexB/language/statements/try/catch-redeclared-for-in-var.js (default)
82-
annexB/language/statements/try/catch-redeclared-for-in-var.js (strict mode)
83-
annexB/language/statements/try/catch-redeclared-for-var.js (default)
84-
annexB/language/statements/try/catch-redeclared-for-var.js (strict mode)
85-
annexB/language/statements/try/catch-redeclared-var-statement-captured.js (default)
86-
annexB/language/statements/try/catch-redeclared-var-statement-captured.js (strict mode)
87-
annexB/language/statements/try/catch-redeclared-var-statement.js (default)
88-
annexB/language/statements/try/catch-redeclared-var-statement.js (strict mode)
891
language/block-scope/syntax/redeclaration/async-function-declaration-attempt-to-redeclare-with-async-function-declaration.js (default)
902
language/block-scope/syntax/redeclaration/async-function-declaration-attempt-to-redeclare-with-async-function-declaration.js (strict mode)
913
language/block-scope/syntax/redeclaration/async-function-declaration-attempt-to-redeclare-with-class-declaration.js (default)
@@ -261,7 +173,6 @@ language/statements/class/syntax/early-errors/class-definition-evaluation-block-
261173
language/statements/class/syntax/early-errors/class-definition-evaluation-scriptbody-duplicate-binding.js (default)
262174
language/statements/class/syntax/early-errors/class-definition-evaluation-scriptbody-duplicate-binding.js (strict mode)
263175
language/statements/const/syntax/const-declaring-let-split-across-two-lines.js (default)
264-
language/statements/do-while/labelled-fn-stmt.js (default)
265176
language/statements/for/head-let-bound-names-in-stmt.js (default)
266177
language/statements/for/head-let-bound-names-in-stmt.js (strict mode)
267178
language/statements/for-in/head-const-bound-names-in-stmt.js (default)
@@ -270,15 +181,8 @@ language/statements/for-in/head-const-bound-names-let.js (default)
270181
language/statements/for-in/head-let-bound-names-in-stmt.js (default)
271182
language/statements/for-in/head-let-bound-names-in-stmt.js (strict mode)
272183
language/statements/for-in/head-let-bound-names-let.js (default)
273-
language/statements/for-in/labelled-fn-stmt-const.js (default)
274-
language/statements/for-in/labelled-fn-stmt-let.js (default)
275-
language/statements/for-in/labelled-fn-stmt-lhs.js (default)
276-
language/statements/for-in/labelled-fn-stmt-var.js (default)
277184
language/statements/for-in/let-block-with-newline.js (default)
278185
language/statements/for-in/let-identifier-with-newline.js (default)
279-
language/statements/for/labelled-fn-stmt-expr.js (default)
280-
language/statements/for/labelled-fn-stmt-let.js (default)
281-
language/statements/for/labelled-fn-stmt-var.js (default)
282186
language/statements/for/let-block-with-newline.js (default)
283187
language/statements/for/let-identifier-with-newline.js (default)
284188
language/statements/for-of/head-const-bound-names-in-stmt.js (default)
@@ -287,10 +191,6 @@ language/statements/for-of/head-const-bound-names-let.js (default)
287191
language/statements/for-of/head-let-bound-names-in-stmt.js (default)
288192
language/statements/for-of/head-let-bound-names-in-stmt.js (strict mode)
289193
language/statements/for-of/head-let-bound-names-let.js (default)
290-
language/statements/for-of/labelled-fn-stmt-const.js (default)
291-
language/statements/for-of/labelled-fn-stmt-let.js (default)
292-
language/statements/for-of/labelled-fn-stmt-lhs.js (default)
293-
language/statements/for-of/labelled-fn-stmt-var.js (default)
294194
language/statements/for-of/let-block-with-newline.js (default)
295195
language/statements/for-of/let-identifier-with-newline.js (default)
296196
language/statements/for-await-of/let-block-with-newline.js (default)
@@ -394,11 +294,11 @@ language/statements/switch/syntax/redeclaration/var-declaration-attempt-to-redec
394294
language/statements/switch/syntax/redeclaration/var-declaration-attempt-to-redeclare-with-function-declaration.js (strict mode)
395295
language/statements/switch/syntax/redeclaration/var-declaration-attempt-to-redeclare-with-generator-declaration.js (default)
396296
language/statements/switch/syntax/redeclaration/var-declaration-attempt-to-redeclare-with-generator-declaration.js (strict mode)
397-
language/statements/while/labelled-fn-stmt.js (default)
398297
language/statements/while/let-block-with-newline.js (default)
399298
language/statements/while/let-identifier-with-newline.js (default)
400-
language/statements/with/labelled-fn-stmt.js (default)
401299
language/statements/with/let-block-with-newline.js (default)
402300
language/statements/with/let-identifier-with-newline.js (default)
301+
language/statements/try/early-catch-var.js (default)
302+
language/statements/try/early-catch-var.js (strict mode)
403303
language/white-space/mongolian-vowel-separator.js (default)
404304
language/white-space/mongolian-vowel-separator.js (strict mode)

src/expression.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {types as tt} from "./tokentype"
2020
import {Parser} from "./state"
2121
import {DestructuringErrors} from "./parseutil"
2222
import {lineBreak} from "./whitespace"
23-
import {SCOPE_FUNCTION, SCOPE_ARROW, SCOPE_ASYNC, SCOPE_GENERATOR} from "./scopeflags"
23+
import {SCOPE_FUNCTION, SCOPE_ARROW, SCOPE_ASYNC, SCOPE_GENERATOR, BIND_OUTSIDE, BIND_VAR} from "./scopeflags"
2424

2525
const pp = Parser.prototype
2626

@@ -323,7 +323,7 @@ pp.parseExprAtom = function(refDestructuringErrors) {
323323
let startPos = this.start, startLoc = this.startLoc, containsEsc = this.containsEsc
324324
let id = this.parseIdent(this.type !== tt.name)
325325
if (this.options.ecmaVersion >= 8 && !containsEsc && id.name === "async" && !this.canInsertSemicolon() && this.eat(tt._function))
326-
return this.parseFunction(this.startNodeAt(startPos, startLoc), false, false, true)
326+
return this.parseFunction(this.startNodeAt(startPos, startLoc), 0, false, true)
327327
if (canBeArrow && !this.canInsertSemicolon()) {
328328
if (this.eat(tt.arrow))
329329
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], false)
@@ -374,7 +374,7 @@ pp.parseExprAtom = function(refDestructuringErrors) {
374374
case tt._function:
375375
node = this.startNode()
376376
this.next()
377-
return this.parseFunction(node, false)
377+
return this.parseFunction(node, 0)
378378

379379
case tt._class:
380380
return this.parseClass(this.startNode(), false)
@@ -768,10 +768,8 @@ pp.parseFunctionBody = function(node, isArrowFunction) {
768768
}
769769
this.exitScope()
770770

771-
if (this.strict && node.id) {
772-
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
773-
this.checkLVal(node.id, "none")
774-
}
771+
// Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
772+
if (this.strict && node.id) this.checkLVal(node.id, BIND_OUTSIDE)
775773
this.strict = oldStrict
776774
}
777775

@@ -787,7 +785,7 @@ pp.isSimpleParamList = function(params) {
787785
pp.checkParams = function(node, allowDuplicates) {
788786
let nameHash = {}
789787
for (let param of node.params)
790-
this.checkLVal(param, "var", allowDuplicates ? null : nameHash)
788+
this.checkLVal(param, BIND_VAR, allowDuplicates ? null : nameHash)
791789
}
792790

793791
// Parses a comma-separated list of expressions, and returns them as

src/lval.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {types as tt} from "./tokentype"
22
import {Parser} from "./state"
33
import {has} from "./util"
4+
import {BIND_NONE, BIND_OUTSIDE} from "./scopeflags"
45

56
const pp = Parser.prototype
67

@@ -185,7 +186,7 @@ pp.parseMaybeDefault = function(startPos, startLoc, left) {
185186
// 'let' indicating that the lval creates a lexical ('let' or 'const') binding
186187
// 'none' indicating that the binding should be checked for illegal identifiers, but not for duplicate references
187188

188-
pp.checkLVal = function(expr, bindingType, checkClashes) {
189+
pp.checkLVal = function(expr, bindingType = BIND_NONE, checkClashes) {
189190
switch (expr.type) {
190191
case "Identifier":
191192
if (this.strict && this.reservedWordsStrictBind.test(expr.name))
@@ -195,12 +196,7 @@ pp.checkLVal = function(expr, bindingType, checkClashes) {
195196
this.raiseRecoverable(expr.start, "Argument name clash")
196197
checkClashes[expr.name] = true
197198
}
198-
if (bindingType && bindingType !== "none") {
199-
if (bindingType === "var" && !this.canDeclareVarName(expr.name) ||
200-
bindingType !== "var" && !this.canDeclareLexicalName(expr.name))
201-
this.raiseRecoverable(expr.start, `Identifier '${expr.name}' has already been declared`)
202-
this.declareName(expr.name, bindingType === "var")
203-
}
199+
if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) this.declareName(expr.name, bindingType, expr.start)
204200
break
205201

206202
case "MemberExpression":

src/scope.js

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Parser} from "./state"
2-
import {SCOPE_FUNCTION} from "./scopeflags"
2+
import {SCOPE_FUNCTION, SCOPE_SIMPLE_CATCH, BIND_LEXICAL, BIND_SIMPLE_CATCH, BIND_FUNCTION} from "./scopeflags"
33

44
const pp = Parser.prototype
55

@@ -23,39 +23,28 @@ pp.exitScope = function() {
2323
this.scopeStack.pop()
2424
}
2525

26-
/**
27-
* A name can be declared with `var` if there are no variables with the same name declared with `let`/`const`
28-
* in the current lexical scope or any of the parent lexical scopes in this function.
29-
*/
30-
pp.canDeclareVarName = function(name) {
31-
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
32-
const currentScope = this.scopeStack[i]
33-
if (currentScope.lexical.indexOf(name) !== -1) return false
34-
if (currentScope.flags & SCOPE_FUNCTION) return true
35-
}
36-
return true
37-
}
38-
39-
/**
40-
* A name can be declared with `let`/`const` if there are no variables with the same name declared with `let`/`const`
41-
* in the current scope, and there are no variables with the same name declared with `var` in the current scope or in
42-
* any child lexical scopes in this function.
43-
*/
44-
pp.canDeclareLexicalName = function(name) {
45-
const currentScope = this.currentScope()
46-
return currentScope.lexical.indexOf(name) === -1 && currentScope.var.indexOf(name) === -1
47-
}
48-
49-
pp.declareName = function(name, isVar) {
50-
if (isVar) {
51-
for (let i = this.scopeStack.length - 1; i >= 0; i--) {
52-
let scope = this.scopeStack[i]
26+
pp.declareName = function(name, bindingType, pos) {
27+
let redeclared = false
28+
if (bindingType === BIND_LEXICAL) {
29+
const scope = this.currentScope()
30+
redeclared = scope.lexical.indexOf(name) > -1 || scope.var.indexOf(name) > -1
31+
scope.lexical.push(name)
32+
} else if (bindingType === BIND_SIMPLE_CATCH) {
33+
const scope = this.currentScope()
34+
scope.lexical.push(name)
35+
} else if (bindingType === BIND_FUNCTION) {
36+
const scope = this.currentScope()
37+
redeclared = scope.lexical.indexOf(name) > -1
38+
scope.var.push(name)
39+
} else {
40+
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
41+
const scope = this.scopeStack[i]
42+
if (scope.lexical.indexOf(name) > -1 && !(scope.flags & SCOPE_SIMPLE_CATCH) && scope.lexical[0] === name) redeclared = true
5343
scope.var.push(name)
5444
if (scope.flags & SCOPE_FUNCTION) break
5545
}
56-
} else {
57-
this.currentScope().lexical.push(name)
5846
}
47+
if (redeclared) this.raiseRecoverable(pos, `Identifier '${name}' has already been declared`)
5948
}
6049

6150
pp.currentScope = function() {

src/scopeflags.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export const SCOPE_FUNCTION = 1, SCOPE_ASYNC = 2, SCOPE_GENERATOR = 4, SCOPE_ARROW = 8, SCOPE_CATCH = 16
1+
export const SCOPE_FUNCTION = 1, SCOPE_ASYNC = 2, SCOPE_GENERATOR = 4, SCOPE_ARROW = 8, SCOPE_SIMPLE_CATCH = 16
2+
3+
export const BIND_NONE = 0, BIND_VAR = 1, BIND_LEXICAL = 2, BIND_FUNCTION = 3, BIND_SIMPLE_CATCH = 4, BIND_OUTSIDE = 5

src/state.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class Parser {
115115
return this.parseTopLevel(node)
116116
}
117117

118-
get inFunction() { return this.currentFunctionScope() != this.scopeStack[0] }
118+
get inFunction() { return this.currentFunctionScope() !== this.scopeStack[0] }
119119
get inGenerator() { return (this.currentFunctionScope().flags & SCOPE_GENERATOR) > 0 }
120120
get inAsync() { return (this.currentFunctionScope().flags & SCOPE_ASYNC) > 0 }
121121
}

0 commit comments

Comments
 (0)