Use set in parser scope (#13408)
* chore: rename benchmark * perf: back parser scope names storage by set * chore: add benchmark
This commit is contained in:
parent
cbad50ac1d
commit
b281fe352c
@ -0,0 +1,34 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "@babel-baseline/parser";
|
||||
import current from "../../lib/index.js";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
// All codepoints in [0x4e00, 0x9ffc] are valid identifier name per Unicode 13
|
||||
function createInput(length) {
|
||||
if (length > 0x9ffc - 0x4e00) {
|
||||
throw new Error(
|
||||
`Length greater than ${
|
||||
0x9ffc - 0x4e00
|
||||
} is not supported! Consider modify the \`createInput\`.`
|
||||
);
|
||||
}
|
||||
let source = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
source += "let " + String.fromCharCode(0x4e00 + i) + ";";
|
||||
}
|
||||
return source;
|
||||
}
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [256, 512, 1024, 2048]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} length-1 let bindings`, () => {
|
||||
implementation.parse(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline);
|
||||
benchCases("current", current);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
@ -11,7 +11,7 @@ import * as N from "../../types";
|
||||
// Reference implementation: https://github.com/facebook/flow/blob/23aeb2a2ef6eb4241ce178fde5d8f17c5f747fb5/src/typing/env.ml#L536-L584
|
||||
class FlowScope extends Scope {
|
||||
// declare function foo(): type;
|
||||
declareFunctions: string[] = [];
|
||||
declareFunctions: Set<string> = new Set();
|
||||
}
|
||||
|
||||
export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
|
||||
@ -24,7 +24,7 @@ export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
|
||||
if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) {
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
this.maybeExportDefined(scope, name);
|
||||
scope.declareFunctions.push(name);
|
||||
scope.declareFunctions.add(name);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -40,8 +40,8 @@ export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
|
||||
|
||||
if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) {
|
||||
return (
|
||||
!scope.declareFunctions.includes(name) &&
|
||||
(scope.lexical.includes(name) || scope.functions.includes(name))
|
||||
!scope.declareFunctions.has(name) &&
|
||||
(scope.lexical.has(name) || scope.functions.has(name))
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
if (this.scopeStack[0].declareFunctions.indexOf(id.name) === -1) {
|
||||
if (!this.scopeStack[0].declareFunctions.has(id.name)) {
|
||||
super.checkLocalExport(id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,22 +14,22 @@ import {
|
||||
import * as N from "../../types";
|
||||
|
||||
class TypeScriptScope extends Scope {
|
||||
types: string[] = [];
|
||||
types: Set<string> = new Set();
|
||||
|
||||
// enums (which are also in .types)
|
||||
enums: string[] = [];
|
||||
enums: Set<string> = new Set();
|
||||
|
||||
// const enums (which are also in .enums and .types)
|
||||
constEnums: string[] = [];
|
||||
constEnums: Set<string> = new Set();
|
||||
|
||||
// classes (which are also in .lexical) and interface (which are also in .types)
|
||||
classes: string[] = [];
|
||||
classes: Set<string> = new Set();
|
||||
|
||||
// namespaces and ambient functions (or classes) are too difficult to track,
|
||||
// especially without type analysis.
|
||||
// We need to track them anyway, to avoid "X is not defined" errors
|
||||
// when exporting them.
|
||||
exportOnlyBindings: string[] = [];
|
||||
exportOnlyBindings: Set<string> = new Set();
|
||||
}
|
||||
|
||||
// See https://github.com/babel/babel/pull/9766#discussion_r268920730 for an
|
||||
@ -44,7 +44,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
|
||||
const scope = this.currentScope();
|
||||
if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) {
|
||||
this.maybeExportDefined(scope, name);
|
||||
scope.exportOnlyBindings.push(name);
|
||||
scope.exportOnlyBindings.add(name);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -56,11 +56,11 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
this.maybeExportDefined(scope, name);
|
||||
}
|
||||
scope.types.push(name);
|
||||
scope.types.add(name);
|
||||
}
|
||||
if (bindingType & BIND_FLAGS_TS_ENUM) scope.enums.push(name);
|
||||
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.push(name);
|
||||
if (bindingType & BIND_FLAGS_CLASS) scope.classes.push(name);
|
||||
if (bindingType & BIND_FLAGS_TS_ENUM) scope.enums.add(name);
|
||||
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.add(name);
|
||||
if (bindingType & BIND_FLAGS_CLASS) scope.classes.add(name);
|
||||
}
|
||||
|
||||
isRedeclaredInScope(
|
||||
@ -68,18 +68,18 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
|
||||
name: string,
|
||||
bindingType: BindingTypes,
|
||||
): boolean {
|
||||
if (scope.enums.indexOf(name) > -1) {
|
||||
if (scope.enums.has(name)) {
|
||||
if (bindingType & BIND_FLAGS_TS_ENUM) {
|
||||
// Enums can be merged with other enums if they are both
|
||||
// const or both non-const.
|
||||
const isConst = !!(bindingType & BIND_FLAGS_TS_CONST_ENUM);
|
||||
const wasConst = scope.constEnums.indexOf(name) > -1;
|
||||
const wasConst = scope.constEnums.has(name);
|
||||
return isConst !== wasConst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) {
|
||||
if (scope.lexical.indexOf(name) > -1) {
|
||||
if (bindingType & BIND_FLAGS_CLASS && scope.classes.has(name)) {
|
||||
if (scope.lexical.has(name)) {
|
||||
// Classes can be merged with interfaces
|
||||
return !!(bindingType & BIND_KIND_VALUE);
|
||||
} else {
|
||||
@ -87,7 +87,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (bindingType & BIND_KIND_TYPE && scope.types.indexOf(name) > -1) {
|
||||
if (bindingType & BIND_KIND_TYPE && scope.types.has(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -95,9 +95,11 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
const topLevelScope = this.scopeStack[0];
|
||||
const { name } = id;
|
||||
if (
|
||||
this.scopeStack[0].types.indexOf(id.name) === -1 &&
|
||||
this.scopeStack[0].exportOnlyBindings.indexOf(id.name) === -1
|
||||
!topLevelScope.types.has(name) &&
|
||||
!topLevelScope.exportOnlyBindings.has(name)
|
||||
) {
|
||||
super.checkLocalExport(id);
|
||||
}
|
||||
|
||||
@ -21,13 +21,13 @@ import { Errors, type raiseFunction } from "../parser/error";
|
||||
|
||||
// Start an AST node, attaching a start offset.
|
||||
export class Scope {
|
||||
flags: ScopeFlags;
|
||||
// A list of var-declared names in the current lexical scope
|
||||
var: string[] = [];
|
||||
// A list of lexically-declared names in the current lexical scope
|
||||
lexical: string[] = [];
|
||||
// A list of lexically-declared FunctionDeclaration names in the current lexical scope
|
||||
functions: string[] = [];
|
||||
declare flags: ScopeFlags;
|
||||
// A set of var-declared names in the current lexical scope
|
||||
var: Set<string> = new Set();
|
||||
// A set of lexically-declared names in the current lexical scope
|
||||
lexical: Set<string> = new Set();
|
||||
// A set of lexically-declared FunctionDeclaration names in the current lexical scope
|
||||
functions: Set<string> = new Set();
|
||||
|
||||
constructor(flags: ScopeFlags) {
|
||||
this.flags = flags;
|
||||
@ -104,9 +104,9 @@ export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
|
||||
if (bindingType & BIND_SCOPE_FUNCTION) {
|
||||
scope.functions.push(name);
|
||||
scope.functions.add(name);
|
||||
} else {
|
||||
scope.lexical.push(name);
|
||||
scope.lexical.add(name);
|
||||
}
|
||||
|
||||
if (bindingType & BIND_SCOPE_LEXICAL) {
|
||||
@ -116,7 +116,7 @@ export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
for (let i = this.scopeStack.length - 1; i >= 0; --i) {
|
||||
scope = this.scopeStack[i];
|
||||
this.checkRedeclarationInScope(scope, name, bindingType, pos);
|
||||
scope.var.push(name);
|
||||
scope.var.add(name);
|
||||
this.maybeExportDefined(scope, name);
|
||||
|
||||
if (scope.flags & SCOPE_VAR) break;
|
||||
@ -153,38 +153,41 @@ export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
|
||||
if (bindingType & BIND_SCOPE_LEXICAL) {
|
||||
return (
|
||||
scope.lexical.indexOf(name) > -1 ||
|
||||
scope.functions.indexOf(name) > -1 ||
|
||||
scope.var.indexOf(name) > -1
|
||||
scope.lexical.has(name) ||
|
||||
scope.functions.has(name) ||
|
||||
scope.var.has(name)
|
||||
);
|
||||
}
|
||||
|
||||
if (bindingType & BIND_SCOPE_FUNCTION) {
|
||||
return (
|
||||
scope.lexical.indexOf(name) > -1 ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) &&
|
||||
scope.var.indexOf(name) > -1)
|
||||
scope.lexical.has(name) ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) && scope.var.has(name))
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(scope.lexical.indexOf(name) > -1 &&
|
||||
!(scope.flags & SCOPE_SIMPLE_CATCH && scope.lexical[0] === name)) ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) &&
|
||||
scope.functions.indexOf(name) > -1)
|
||||
(scope.lexical.has(name) &&
|
||||
!(
|
||||
scope.flags & SCOPE_SIMPLE_CATCH &&
|
||||
scope.lexical.values().next().value === name
|
||||
)) ||
|
||||
(!this.treatFunctionsAsVarInScope(scope) && scope.functions.has(name))
|
||||
);
|
||||
}
|
||||
|
||||
checkLocalExport(id: N.Identifier) {
|
||||
const { name } = id;
|
||||
const topLevelScope = this.scopeStack[0];
|
||||
if (
|
||||
this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
|
||||
this.scopeStack[0].var.indexOf(id.name) === -1 &&
|
||||
!topLevelScope.lexical.has(name) &&
|
||||
!topLevelScope.var.has(name) &&
|
||||
// In strict mode, scope.functions will always be empty.
|
||||
// Modules are strict by default, but the `scriptMode` option
|
||||
// can overwrite this behavior.
|
||||
this.scopeStack[0].functions.indexOf(id.name) === -1
|
||||
!topLevelScope.functions.has(name)
|
||||
) {
|
||||
this.undefinedExports.set(id.name, id.start);
|
||||
this.undefinedExports.set(name, id.start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user