fix(semantic): fix function redeclaration errors

The problem here we face here is that TypeScript does not consider Annex
B.3.2, which makes implementation a bit more difficult.

This implementation conforms to test262.
This commit is contained in:
Boshen 2023-03-17 10:01:22 +08:00
parent 5b2474600f
commit 9ed682a38e
No known key found for this signature in database
GPG key ID: 6AC90C77AAAA6ABC
6 changed files with 23 additions and 241 deletions

View file

@ -2,7 +2,7 @@
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use oxc_ast::{syntax_directed_operations::BoundNames, AstKind};
use oxc_ast::{syntax_directed_operations::BoundNames, AstKind, SourceType};
use crate::{
scope::{Scope, ScopeFlags, ScopeId},
@ -80,9 +80,9 @@ impl<'a> Binder for Class<'a> {
// and the duplicate entries are only bound by FunctionDeclarations.
// https://tc39.es/ecma262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics
#[must_use]
fn function_as_var(scope: &Scope, is_script: bool) -> bool {
fn function_as_var(scope: &Scope, source_type: SourceType) -> bool {
scope.flags.intersects(ScopeFlags::Function)
|| (is_script && scope.flags.intersects(ScopeFlags::Top))
|| (source_type.is_script() && scope.flags.intersects(ScopeFlags::Top))
}
impl<'a> Binder for Function<'a> {
@ -93,35 +93,22 @@ impl<'a> Binder for Function<'a> {
if !scope.strict_mode && matches!(builder.parent_kind(), AstKind::IfStatement(_)) {
// Do not declare in if single statements,
// if (false) function f() {} else function g() { }
} else if matches!(self.r#type, FunctionType::FunctionDeclaration) {
// the visitor is already inside the function scope,
} else if self.r#type == FunctionType::FunctionDeclaration {
// The visitor is already inside the function scope,
// retrieve the parent scope for the function id to bind to.
let parent_scope_id =
builder.scope.scopes[*current_scope_id].parent().unwrap().into();
let parent_scope: &Scope = &builder.scope.scopes[parent_scope_id];
let (includes, excludes) =
if parent_scope.strict_mode || self.r#async || self.generator {
if function_as_var(parent_scope, builder.source_type.is_script()) {
(
SymbolFlags::FunctionScopedVariable | SymbolFlags::Function,
SymbolFlags::FunctionScopedVariableExcludes,
)
} else {
(
SymbolFlags::BlockScopedVariable | SymbolFlags::Function,
SymbolFlags::BlockScopedVariableExcludes,
)
}
} else if function_as_var(parent_scope, builder.source_type.is_script()) {
(
SymbolFlags::Function,
SymbolFlags::FunctionScopedVariableExcludes - SymbolFlags::Function,
)
if (parent_scope.strict_mode || self.r#async || self.generator)
&& !function_as_var(parent_scope, builder.source_type)
{
(SymbolFlags::BlockScopedVariable, SymbolFlags::BlockScopedVariableExcludes)
} else {
(
SymbolFlags::Function,
SymbolFlags::BlockScopedVariableExcludes - SymbolFlags::Function,
SymbolFlags::FunctionScopedVariable,
SymbolFlags::FunctionScopedVariableExcludes,
)
};

View file

@ -39,10 +39,9 @@ bitflags! {
const BlockScopedVariable = 1 << 1;
const Class = 1 << 5;
const CatchVariable = 1 << 6; // try {} catch(catch_variable) {}
const Function = 1 << 7;
const Variable = Self::FunctionScopedVariable.bits | Self::BlockScopedVariable.bits;
const Value = Self::Variable.bits | Self::Function.bits | Self::Class.bits;
const Value = Self::Variable.bits | Self::Class.bits;
/// Variables can be redeclared, but can not redeclare a block-scoped declaration with the
/// same name, or any other value that is not a variable, e.g. ValueModule or Class
@ -52,8 +51,7 @@ bitflags! {
/// they can not merge with anything in the value space
const BlockScopedVariableExcludes = Self::Value.bits;
const FunctionExcludes = Self::Value.bits - (Self::Function.bits | Self::Class.bits);
const ClassExcludes = Self::Value.bits - Self::Function.bits;
const ClassExcludes = Self::Value.bits;
}
}

View file

@ -1,7 +1,7 @@
Babel Summary:
AST Parsed : 2052/2071 (99.08%)
Positive Passed: 2052/2071 (99.08%)
Negative Passed: 1335/1502 (88.88%)
AST Parsed : 2054/2071 (99.18%)
Positive Passed: 2054/2071 (99.18%)
Negative Passed: 1333/1502 (88.75%)
Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js"
Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-if-body/input.js"
Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-multiple-labels/input.js"
@ -13,6 +13,8 @@ Expect Syntax Error: "annex-b/disabled/3.5-for-in-initializer/input.js"
Expect Syntax Error: "annex-b/enabled/3.1-sloppy-labeled-functions-if-body/input.js"
Expect Syntax Error: "core/categorized/invalid-fn-decl-labeled-inside-if/input.js"
Expect Syntax Error: "core/categorized/invalid-fn-decl-labeled-inside-loop/input.js"
Expect Syntax Error: "core/scope/dupl-bind-catch-func/input.js"
Expect Syntax Error: "core/scope/dupl-bind-func-var-sloppy/input.js"
Expect Syntax Error: "es2015/class-methods/direct-super-in-object-method/input.js"
Expect Syntax Error: "es2015/destructuring/error-operator-for-default/input.js"
Expect Syntax Error: "es2015/for-of/invalid-let-as-identifier/input.js"
@ -218,28 +220,6 @@ Expect to Parse: "core/opts/allowNewTargetOutsideFunction-true/input.js"
· ──────────
╰────
help: new.target is only allowed in constructors and functions invoked using thew `new` operator
Expect to Parse: "core/scope/dupl-bind-func-var/input.js"
× Identifier `foo` has already been declared
╭─[core/scope/dupl-bind-func-var/input.js:1:1]
1 │ function foo() {}
· ─┬─
· ╰── `foo` has already been declared here
2 │ var foo = 1;
· ─┬─
· ╰── It can not be redeclared here
╰────
Expect to Parse: "core/scope/dupl-bind-gen-gen-script/input.js"
× Identifier `foo` has already been declared
╭─[core/scope/dupl-bind-gen-gen-script/input.js:1:1]
1 │ function *foo() {};
· ─┬─
· ╰── `foo` has already been declared here
2 │ function *foo() {};
· ─┬─
· ╰── It can not be redeclared here
╰────
Expect to Parse: "core/uncategorised/327/input.js"
× TS1108: A 'return' statement can only be used within a function body
@ -904,18 +884,6 @@ Expect to Parse: "typescript/types/const-type-parameters/input.ts"
· ╰── `foo` has already been declared here
╰────
× Identifier `foo` has already been declared
╭─[core/scope/dupl-bind-catch-func/input.js:1:1]
1 │ try {
2 │ } catch (foo) {
· ─┬─
· ╰── `foo` has already been declared here
3 │ function foo() {}
· ─┬─
· ╰── It can not be redeclared here
4 │ }
╰────
× Identifier `foo` has already been declared
╭─[core/scope/dupl-bind-catch-let/input.js:1:1]
1 │ try {
@ -1046,18 +1014,6 @@ Expect to Parse: "typescript/types/const-type-parameters/input.ts"
· ╰── It can not be redeclared here
╰────
× Identifier `foo` has already been declared
╭─[core/scope/dupl-bind-func-var-sloppy/input.js:1:1]
1 │ {
2 │ function foo() {}
· ─┬─
· ╰── `foo` has already been declared here
3 │ var foo = 1;
· ─┬─
· ╰── It can not be redeclared here
4 │ }
╰────
× Identifier `f` has already been declared
╭─[core/scope/dupl-bind-gen-func/input.js:1:1]
1 │ { function* f() {} function f() {} }

View file

@ -1,31 +1,7 @@
Test262 Summary:
AST Parsed : 44015/44024 (99.98%)
Positive Passed: 44015/44024 (99.98%)
AST Parsed : 44022/44024 (100.00%)
Positive Passed: 44022/44024 (100.00%)
Negative Passed: 3915/3915 (100.00%)
Expect to Parse: "language/block-scope/syntax/redeclaration-global/allowed-to-redeclare-function-declaration-with-var.js"
× Identifier `f` has already been declared
╭─[language/block-scope/syntax/redeclaration-global/allowed-to-redeclare-function-declaration-with-var.js:8:1]
8 │ ---*/
9 │ function f() {} var f;
· ┬ ┬
· │ ╰── It can not be redeclared here
· ╰── `f` has already been declared here
10 │
╰────
Expect to Parse: "language/destructuring/binding/syntax/recursive-array-and-object-patterns.js"
× Identifier `fn4` has already been declared
╭─[language/destructuring/binding/syntax/recursive-array-and-object-patterns.js:22:1]
22 │
23 │ function fn4([], [[]], [[[[[[[[[x]]]]]]]]]) {}
· ─┬─
· ╰── `fn4` has already been declared here
24 │
25 │ function fn4([[x, y, ...z]]) {}
· ─┬─
· ╰── It can not be redeclared here
╰────
Expect to Parse: "language/expressions/class/decorator/syntax/class-valid/decorator-member-expr-private-identifier.js"
× Unexpected token
@ -35,17 +11,6 @@ Expect to Parse: "language/expressions/class/decorator/syntax/class-valid/decora
· ──
45 │ @#_
╰────
Expect to Parse: "language/global-code/decl-func-dup.js"
× Identifier `f` has already been declared
╭─[language/global-code/decl-func-dup.js:8:1]
8 │ ---*/
9 │ function f() { return 1; } function f() { return 2; }
· ┬ ┬
· │ ╰── It can not be redeclared here
· ╰── `f` has already been declared here
10 │
╰────
Expect to Parse: "language/statements/class/decorator/syntax/class-valid/decorator-member-expr-private-identifier.js"
× Unexpected token
@ -55,83 +20,6 @@ Expect to Parse: "language/statements/class/decorator/syntax/class-valid/decorat
· ──
46 │ @#_
╰────
Expect to Parse: "language/statements/function/S13_A6_T1.js"
× Identifier `__func` has already been declared
╭─[language/statements/function/S13_A6_T1.js:11:1]
11 │
12 │ function __func(){return 1};
· ───┬──
· ╰── `__func` has already been declared here
13 │
14 │ var __store__func = __func;
15 │
16 │ var __1 = __func();
17 │
18 │ function __func(){return 'A'};
· ───┬──
· ╰── It can not be redeclared here
19 │
╰────
Expect to Parse: "language/statements/function/S13_A6_T2.js"
× Identifier `__func` has already been declared
╭─[language/statements/function/S13_A6_T2.js:24:1]
24 │
25 │ function __func(){return "FIRST";};
· ───┬──
· ╰── `__func` has already been declared here
26 │
27 │ //////////////////////////////////////////////////////////////////////////////
28 │ //CHECK#2
29 │ __result = __func();
30 │ if (__result !== "SECOND") {
31 │ throw new Test262Error('#2: __result === "SECOND". Actual: __result ==='+__result);
32 │ }
33 │ //
34 │ //////////////////////////////////////////////////////////////////////////////
35 │
36 │ function __func(){return "SECOND";};
· ───┬──
· ╰── It can not be redeclared here
╰────
Expect to Parse: "language/statements/function/S14_A5_T1.js"
× Identifier `__func` has already been declared
╭─[language/statements/function/S14_A5_T1.js:21:1]
21 │
22 │ function __func(){return "ascii"};
· ───┬──
· ╰── `__func` has already been declared here
23 │ function \u005f\u005f\u0066\u0075\u006e\u0063(){return "unicode"};//__func in unicode
· ──────────────────┬─────────────────
· ╰── It can not be redeclared here
24 │ function __\u0066\u0075\u006e\u0063(){return "both"};//__func in unicode
╰────
× Identifier `__func` has already been declared
╭─[language/statements/function/S14_A5_T1.js:21:1]
21 │
22 │ function __func(){return "ascii"};
· ───┬──
· ╰── `__func` has already been declared here
23 │ function \u005f\u005f\u0066\u0075\u006e\u0063(){return "unicode"};//__func in unicode
24 │ function __\u0066\u0075\u006e\u0063(){return "both"};//__func in unicode
· ─────────────┬────────────
· ╰── It can not be redeclared here
╰────
Expect to Parse: "language/statements/function/S14_A5_T2.js"
× Identifier `__func` has already been declared
╭─[language/statements/function/S14_A5_T2.js:21:1]
21 │
22 │ function __func(){return "ascii"};
· ───┬──
· ╰── `__func` has already been declared here
23 │ function \u005f\u005f\u0066\u0075\u006e\u0063(){return "unicode"};//__func in unicode
· ──────────────────┬─────────────────
· ╰── It can not be redeclared here
╰────
× '0'-prefixed octal literals and octal escape sequences are deprecated
╭─[annexB/language/expressions/template-literal/legacy-octal-escape-sequence-strict.js:17:1]

@ -1 +1 @@
Subproject commit 7f292bf2a19aa14ed69a55e646111af9533d8f1c
Subproject commit b8c71685f910230a9dca864d403e370a27ea1041

View file

@ -1,7 +1,7 @@
TypeScript Summary:
AST Parsed : 2323/2340 (99.27%)
Positive Passed: 2323/2340 (99.27%)
Negative Passed: 681/2532 (26.90%)
Negative Passed: 680/2532 (26.86%)
Expect Syntax Error: "Symbols/ES5SymbolProperty2.ts"
Expect Syntax Error: "Symbols/ES5SymbolProperty6.ts"
Expect Syntax Error: "additionalChecks/noPropertyAccessFromIndexSignature1.ts"
@ -829,6 +829,7 @@ Expect Syntax Error: "externalModules/verbatimModuleSyntaxInternalImportEquals.t
Expect Syntax Error: "fixSignatureCaching.ts"
Expect Syntax Error: "functions/functionImplementationErrors.ts"
Expect Syntax Error: "functions/functionImplementations.ts"
Expect Syntax Error: "functions/functionNameConflicts.ts"
Expect Syntax Error: "functions/functionOverloadCompatibilityWithVoid01.ts"
Expect Syntax Error: "functions/functionOverloadErrors.ts"
Expect Syntax Error: "functions/parameterInitializersForwardReferencing.2.ts"
@ -8267,54 +8268,6 @@ Expect to Parse: "salsa/privateIdentifierExpando.ts"
15 │ }
╰────
× Identifier `fn1` has already been declared
╭─[functions/functionNameConflicts.ts:4:1]
4 │ module M {
5 │ function fn1() { }
· ─┬─
· ╰── `fn1` has already been declared here
6 │ var fn1;
· ─┬─
· ╰── It can not be redeclared here
7 │
╰────
× Identifier `fn2` has already been declared
╭─[functions/functionNameConflicts.ts:7:1]
7 │
8 │ var fn2;
· ─┬─
· ╰── `fn2` has already been declared here
9 │ function fn2() { }
· ─┬─
· ╰── It can not be redeclared here
10 │ }
╰────
× Identifier `fn3` has already been declared
╭─[functions/functionNameConflicts.ts:11:1]
11 │
12 │ function fn3() { }
· ─┬─
· ╰── `fn3` has already been declared here
13 │ var fn3;
· ─┬─
· ╰── It can not be redeclared here
14 │
╰────
× Identifier `fn5` has already been declared
╭─[functions/functionNameConflicts.ts:18:1]
18 │
19 │ function fn5() { }
· ─┬─
· ╰── `fn5` has already been declared here
20 │ var fn5;
· ─┬─
· ╰── It can not be redeclared here
21 │ }
╰────
× Rest element must be last element
╭─[functions/functionOverloadErrorsSyntax.ts:8:1]
8 │ //Function overload signature with rest param followed by non-optional parameter