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:
Huáng Jùnliàng 2021-06-01 10:35:19 -04:00 committed by GitHub
parent cbad50ac1d
commit b281fe352c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 46 deletions

View File

@ -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();

View File

@ -11,7 +11,7 @@ import * as N from "../../types";
// Reference implementation: https://github.com/facebook/flow/blob/23aeb2a2ef6eb4241ce178fde5d8f17c5f747fb5/src/typing/env.ml#L536-L584 // Reference implementation: https://github.com/facebook/flow/blob/23aeb2a2ef6eb4241ce178fde5d8f17c5f747fb5/src/typing/env.ml#L536-L584
class FlowScope extends Scope { class FlowScope extends Scope {
// declare function foo(): type; // declare function foo(): type;
declareFunctions: string[] = []; declareFunctions: Set<string> = new Set();
} }
export default class FlowScopeHandler extends ScopeHandler<FlowScope> { 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) { if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) {
this.checkRedeclarationInScope(scope, name, bindingType, pos); this.checkRedeclarationInScope(scope, name, bindingType, pos);
this.maybeExportDefined(scope, name); this.maybeExportDefined(scope, name);
scope.declareFunctions.push(name); scope.declareFunctions.add(name);
return; return;
} }
@ -40,8 +40,8 @@ export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) { if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) {
return ( return (
!scope.declareFunctions.includes(name) && !scope.declareFunctions.has(name) &&
(scope.lexical.includes(name) || scope.functions.includes(name)) (scope.lexical.has(name) || scope.functions.has(name))
); );
} }
@ -49,7 +49,7 @@ export default class FlowScopeHandler extends ScopeHandler<FlowScope> {
} }
checkLocalExport(id: N.Identifier) { checkLocalExport(id: N.Identifier) {
if (this.scopeStack[0].declareFunctions.indexOf(id.name) === -1) { if (!this.scopeStack[0].declareFunctions.has(id.name)) {
super.checkLocalExport(id); super.checkLocalExport(id);
} }
} }

View File

@ -14,22 +14,22 @@ import {
import * as N from "../../types"; import * as N from "../../types";
class TypeScriptScope extends Scope { class TypeScriptScope extends Scope {
types: string[] = []; types: Set<string> = new Set();
// enums (which are also in .types) // enums (which are also in .types)
enums: string[] = []; enums: Set<string> = new Set();
// const enums (which are also in .enums and .types) // 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 (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, // namespaces and ambient functions (or classes) are too difficult to track,
// especially without type analysis. // especially without type analysis.
// We need to track them anyway, to avoid "X is not defined" errors // We need to track them anyway, to avoid "X is not defined" errors
// when exporting them. // when exporting them.
exportOnlyBindings: string[] = []; exportOnlyBindings: Set<string> = new Set();
} }
// See https://github.com/babel/babel/pull/9766#discussion_r268920730 for an // 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(); const scope = this.currentScope();
if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) { if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) {
this.maybeExportDefined(scope, name); this.maybeExportDefined(scope, name);
scope.exportOnlyBindings.push(name); scope.exportOnlyBindings.add(name);
return; return;
} }
@ -56,11 +56,11 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
this.checkRedeclarationInScope(scope, name, bindingType, pos); this.checkRedeclarationInScope(scope, name, bindingType, pos);
this.maybeExportDefined(scope, name); 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_ENUM) scope.enums.add(name);
if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.push(name); if (bindingType & BIND_FLAGS_TS_CONST_ENUM) scope.constEnums.add(name);
if (bindingType & BIND_FLAGS_CLASS) scope.classes.push(name); if (bindingType & BIND_FLAGS_CLASS) scope.classes.add(name);
} }
isRedeclaredInScope( isRedeclaredInScope(
@ -68,18 +68,18 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
name: string, name: string,
bindingType: BindingTypes, bindingType: BindingTypes,
): boolean { ): boolean {
if (scope.enums.indexOf(name) > -1) { if (scope.enums.has(name)) {
if (bindingType & BIND_FLAGS_TS_ENUM) { if (bindingType & BIND_FLAGS_TS_ENUM) {
// Enums can be merged with other enums if they are both // Enums can be merged with other enums if they are both
// const or both non-const. // const or both non-const.
const isConst = !!(bindingType & BIND_FLAGS_TS_CONST_ENUM); 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 isConst !== wasConst;
} }
return true; return true;
} }
if (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) { if (bindingType & BIND_FLAGS_CLASS && scope.classes.has(name)) {
if (scope.lexical.indexOf(name) > -1) { if (scope.lexical.has(name)) {
// Classes can be merged with interfaces // Classes can be merged with interfaces
return !!(bindingType & BIND_KIND_VALUE); return !!(bindingType & BIND_KIND_VALUE);
} else { } else {
@ -87,7 +87,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
return false; return false;
} }
} }
if (bindingType & BIND_KIND_TYPE && scope.types.indexOf(name) > -1) { if (bindingType & BIND_KIND_TYPE && scope.types.has(name)) {
return true; return true;
} }
@ -95,9 +95,11 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
} }
checkLocalExport(id: N.Identifier) { checkLocalExport(id: N.Identifier) {
const topLevelScope = this.scopeStack[0];
const { name } = id;
if ( if (
this.scopeStack[0].types.indexOf(id.name) === -1 && !topLevelScope.types.has(name) &&
this.scopeStack[0].exportOnlyBindings.indexOf(id.name) === -1 !topLevelScope.exportOnlyBindings.has(name)
) { ) {
super.checkLocalExport(id); super.checkLocalExport(id);
} }

View File

@ -21,13 +21,13 @@ import { Errors, type raiseFunction } from "../parser/error";
// Start an AST node, attaching a start offset. // Start an AST node, attaching a start offset.
export class Scope { export class Scope {
flags: ScopeFlags; declare flags: ScopeFlags;
// A list of var-declared names in the current lexical scope // A set of var-declared names in the current lexical scope
var: string[] = []; var: Set<string> = new Set();
// A list of lexically-declared names in the current lexical scope // A set of lexically-declared names in the current lexical scope
lexical: string[] = []; lexical: Set<string> = new Set();
// A list of lexically-declared FunctionDeclaration names in the current lexical scope // A set of lexically-declared FunctionDeclaration names in the current lexical scope
functions: string[] = []; functions: Set<string> = new Set();
constructor(flags: ScopeFlags) { constructor(flags: ScopeFlags) {
this.flags = flags; this.flags = flags;
@ -104,9 +104,9 @@ export default class ScopeHandler<IScope: Scope = Scope> {
this.checkRedeclarationInScope(scope, name, bindingType, pos); this.checkRedeclarationInScope(scope, name, bindingType, pos);
if (bindingType & BIND_SCOPE_FUNCTION) { if (bindingType & BIND_SCOPE_FUNCTION) {
scope.functions.push(name); scope.functions.add(name);
} else { } else {
scope.lexical.push(name); scope.lexical.add(name);
} }
if (bindingType & BIND_SCOPE_LEXICAL) { 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) { for (let i = this.scopeStack.length - 1; i >= 0; --i) {
scope = this.scopeStack[i]; scope = this.scopeStack[i];
this.checkRedeclarationInScope(scope, name, bindingType, pos); this.checkRedeclarationInScope(scope, name, bindingType, pos);
scope.var.push(name); scope.var.add(name);
this.maybeExportDefined(scope, name); this.maybeExportDefined(scope, name);
if (scope.flags & SCOPE_VAR) break; if (scope.flags & SCOPE_VAR) break;
@ -153,38 +153,41 @@ export default class ScopeHandler<IScope: Scope = Scope> {
if (bindingType & BIND_SCOPE_LEXICAL) { if (bindingType & BIND_SCOPE_LEXICAL) {
return ( return (
scope.lexical.indexOf(name) > -1 || scope.lexical.has(name) ||
scope.functions.indexOf(name) > -1 || scope.functions.has(name) ||
scope.var.indexOf(name) > -1 scope.var.has(name)
); );
} }
if (bindingType & BIND_SCOPE_FUNCTION) { if (bindingType & BIND_SCOPE_FUNCTION) {
return ( return (
scope.lexical.indexOf(name) > -1 || scope.lexical.has(name) ||
(!this.treatFunctionsAsVarInScope(scope) && (!this.treatFunctionsAsVarInScope(scope) && scope.var.has(name))
scope.var.indexOf(name) > -1)
); );
} }
return ( return (
(scope.lexical.indexOf(name) > -1 && (scope.lexical.has(name) &&
!(scope.flags & SCOPE_SIMPLE_CATCH && scope.lexical[0] === name)) || !(
(!this.treatFunctionsAsVarInScope(scope) && scope.flags & SCOPE_SIMPLE_CATCH &&
scope.functions.indexOf(name) > -1) scope.lexical.values().next().value === name
)) ||
(!this.treatFunctionsAsVarInScope(scope) && scope.functions.has(name))
); );
} }
checkLocalExport(id: N.Identifier) { checkLocalExport(id: N.Identifier) {
const { name } = id;
const topLevelScope = this.scopeStack[0];
if ( if (
this.scopeStack[0].lexical.indexOf(id.name) === -1 && !topLevelScope.lexical.has(name) &&
this.scopeStack[0].var.indexOf(id.name) === -1 && !topLevelScope.var.has(name) &&
// In strict mode, scope.functions will always be empty. // In strict mode, scope.functions will always be empty.
// Modules are strict by default, but the `scriptMode` option // Modules are strict by default, but the `scriptMode` option
// can overwrite this behavior. // 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);
} }
} }