Skip to content

Commit

Permalink
Use set in parser scope (#13408)
Browse files Browse the repository at this point in the history
* chore: rename benchmark

* perf: back parser scope names storage by set

* chore: add benchmark
  • Loading branch information
JLHwung committed Jun 1, 2021
1 parent cbad50a commit b281fe3
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 46 deletions.
@@ -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();
10 changes: 5 additions & 5 deletions packages/babel-parser/src/plugins/flow/scope.js
Expand Up @@ -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> {
Expand All @@ -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;
}

Expand All @@ -40,16 +40,16 @@ 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))
);
}

return false;
}

checkLocalExport(id: N.Identifier) {
if (this.scopeStack[0].declareFunctions.indexOf(id.name) === -1) {
if (!this.scopeStack[0].declareFunctions.has(id.name)) {
super.checkLocalExport(id);
}
}
Expand Down
36 changes: 19 additions & 17 deletions packages/babel-parser/src/plugins/typescript/scope.js
Expand Up @@ -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
Expand All @@ -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;
}

Expand All @@ -56,48 +56,50 @@ 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(
scope: 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 {
// Interface can be merged with other classes or interfaces
return false;
}
}
if (bindingType & BIND_KIND_TYPE && scope.types.indexOf(name) > -1) {
if (bindingType & BIND_KIND_TYPE && scope.types.has(name)) {
return true;
}

return super.isRedeclaredInScope(...arguments);
}

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);
}
Expand Down
51 changes: 27 additions & 24 deletions packages/babel-parser/src/util/scope.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down

0 comments on commit b281fe3

Please sign in to comment.