Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use set in parser scope #13408

Merged
merged 3 commits into from Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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