Merge pull request #6553 from Microsoft/handleNestedBlockScopedName

handle block scoped binding in nested blocks
This commit is contained in:
Vladimir Matveev
2016-01-27 11:28:59 -08:00
78 changed files with 4636 additions and 121 deletions
+65 -55
View File
@@ -7183,7 +7183,7 @@ namespace ts {
checkCollisionWithCapturedSuperVariable(node, node);
checkCollisionWithCapturedThisVariable(node, node);
checkBlockScopedBindingCapturedInLoop(node, symbol);
checkNestedBlockScopedBinding(node, symbol);
return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
}
@@ -7200,7 +7200,7 @@ namespace ts {
return false;
}
function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void {
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
if (languageVersion >= ScriptTarget.ES6 ||
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) {
@@ -7212,40 +7212,31 @@ namespace ts {
// 2. walk from the declaration up to the boundary of lexical environment and check
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
let container: Node;
if (symbol.flags & SymbolFlags.Class) {
// get parent of class declaration
container = getClassLikeDeclarationOfSymbol(symbol).parent;
}
else {
// nesting structure:
// (variable declaration or binding element) -> variable declaration list -> container
container = symbol.valueDeclaration;
while (container.kind !== SyntaxKind.VariableDeclarationList) {
container = container.parent;
}
// get the parent of variable declaration list
container = container.parent;
if (container.kind === SyntaxKind.VariableStatement) {
// if parent is variable statement - get its parent
container = container.parent;
}
}
const inFunction = isInsideFunction(node.parent, container);
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
const usedInFunction = isInsideFunction(node.parent, container);
let current = container;
let containedInIterationStatement = false;
while (current && !nodeStartsNewLexicalEnvironment(current)) {
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
if (inFunction) {
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithBlockScopedBindingCapturedInFunction;
}
// mark value declaration so during emit they can have a special handling
getNodeLinks(<VariableDeclaration>symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
containedInIterationStatement = true;
break;
}
current = current.parent;
}
if (containedInIterationStatement) {
if (usedInFunction) {
// mark iteration statement as containing block-scoped binding captured in some function
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
}
// set 'declared inside loop' bit on the block-scoped binding
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
}
if (usedInFunction) {
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding;
}
}
function captureLexicalThis(node: Node, container: Node): void {
@@ -15652,42 +15643,61 @@ namespace ts {
return symbol && symbol.flags & SymbolFlags.Alias ? getDeclarationOfAliasSymbol(symbol) : undefined;
}
function isStatementWithLocals(node: Node) {
switch (node.kind) {
case SyntaxKind.Block:
case SyntaxKind.CaseBlock:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return true;
}
return false;
}
function isNestedRedeclarationSymbol(symbol: Symbol): boolean {
function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean {
if (symbol.flags & SymbolFlags.BlockScoped) {
const links = getSymbolLinks(symbol);
if (links.isNestedRedeclaration === undefined) {
if (links.isDeclaratonWithCollidingName === undefined) {
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
links.isNestedRedeclaration = isStatementWithLocals(container) &&
!!resolveName(container.parent, symbol.name, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
if (isStatementWithLocals(container)) {
const nodeLinks = getNodeLinks(symbol.valueDeclaration);
if (!!resolveName(container.parent, symbol.name, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined)) {
// redeclaration - always should be renamed
links.isDeclaratonWithCollidingName = true;
}
else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) {
// binding is captured in the function
// should be renamed if:
// - binding is not top level - top level bindings never collide with anything
// AND
// - binding is not declared in loop, should be renamed to avoid name reuse across siblings
// let a, b
// { let x = 1; a = () => x; }
// { let x = 100; b = () => x; }
// console.log(a()); // should print '1'
// console.log(b()); // should print '100'
// OR
// - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body
// * variables from initializer are passed to rewritted loop body as parameters so they are not captured directly
// * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus
// they will not collide with anything
const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop;
const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false);
const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false);
links.isDeclaratonWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock));
}
else {
links.isDeclaratonWithCollidingName = false;
}
}
}
return links.isNestedRedeclaration;
return links.isDeclaratonWithCollidingName;
}
return false;
}
// When resolved as an expression identifier, if the given node references a nested block scoped entity with
// a name that hides an existing name, return the declaration of that entity. Otherwise, return undefined.
function getReferencedNestedRedeclaration(node: Identifier): Declaration {
// a name that either hides an existing name or might hide it when compiled downlevel,
// return the declaration of that entity. Otherwise, return undefined.
function getReferencedDeclarationWithCollidingName(node: Identifier): Declaration {
const symbol = getReferencedValueSymbol(node);
return symbol && isNestedRedeclarationSymbol(symbol) ? symbol.valueDeclaration : undefined;
return symbol && isSymbolOfDeclarationWithCollidingName(symbol) ? symbol.valueDeclaration : undefined;
}
// Return true if the given node is a declaration of a nested block scoped entity with a name that hides an
// existing name.
function isNestedRedeclaration(node: Declaration): boolean {
return isNestedRedeclarationSymbol(getSymbolOfNode(node));
// Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an
// existing name or might hide a name when compiled downlevel
function isDeclarationWithCollidingName(node: Declaration): boolean {
return isSymbolOfDeclarationWithCollidingName(getSymbolOfNode(node));
}
function isValueAliasDeclaration(node: Node): boolean {
@@ -15888,8 +15898,8 @@ namespace ts {
return {
getReferencedExportContainer,
getReferencedImportDeclaration,
getReferencedNestedRedeclaration,
isNestedRedeclaration,
getReferencedDeclarationWithCollidingName,
isDeclarationWithCollidingName,
isValueAliasDeclaration,
hasGlobalName,
isReferencedAliasDeclaration,
+54 -28
View File
@@ -1530,7 +1530,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
if (languageVersion !== ScriptTarget.ES6) {
const declaration = resolver.getReferencedNestedRedeclaration(node);
const declaration = resolver.getReferencedDeclarationWithCollidingName(node);
if (declaration) {
write(getGeneratedNameForNode(declaration.name));
return;
@@ -1546,7 +1546,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
function isNameOfNestedRedeclaration(node: Identifier) {
function isNameOfNestedBlockScopedRedeclarationOrCapturedBinding(node: Identifier) {
if (languageVersion < ScriptTarget.ES6) {
const parent = node.parent;
switch (parent.kind) {
@@ -1554,7 +1554,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.VariableDeclaration:
return (<Declaration>parent).name === node && resolver.isNestedRedeclaration(<Declaration>parent);
return (<Declaration>parent).name === node && resolver.isDeclarationWithCollidingName(<Declaration>parent);
}
}
return false;
@@ -1576,7 +1576,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
else if (isExpressionIdentifier(node)) {
emitExpressionIdentifier(node);
}
else if (isNameOfNestedRedeclaration(node)) {
else if (isNameOfNestedBlockScopedRedeclarationOrCapturedBinding(node)) {
write(getGeneratedNameForNode(node));
}
else if (nodeIsSynthesized(node)) {
@@ -2891,7 +2891,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
function shouldConvertLoopBody(node: IterationStatement): boolean {
return languageVersion < ScriptTarget.ES6 &&
(resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithBlockScopedBindingCapturedInFunction) !== 0;
(resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithCapturedBlockScopedBinding) !== 0;
}
function emitLoop(node: IterationStatement, loopEmitter: (n: IterationStatement, convertedLoop: ConvertedLoop) => void): void {
@@ -3045,7 +3045,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
function collectNames(name: Identifier | BindingPattern): void {
if (name.kind === SyntaxKind.Identifier) {
const nameText = isNameOfNestedRedeclaration(<Identifier>name) ? getGeneratedNameForNode(name) : (<Identifier>name).text;
const nameText = isNameOfNestedBlockScopedRedeclarationOrCapturedBinding(<Identifier>name) ? getGeneratedNameForNode(name) : (<Identifier>name).text;
loopParameters.push(nameText);
}
else {
@@ -4065,22 +4065,56 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
else {
let initializer = node.initializer;
if (!initializer && languageVersion < ScriptTarget.ES6) {
if (!initializer &&
languageVersion < ScriptTarget.ES6 &&
// for names - binding patterns that lack initializer there is no point to emit explicit initializer
// since downlevel codegen for destructuring will fail in the absence of initializer so all binding elements will say uninitialized
node.name.kind === SyntaxKind.Identifier) {
// downlevel emit for non-initialized let bindings defined in loops
// for (...) { let x; }
// should be
// for (...) { var <some-uniqie-name> = void 0; }
// this is necessary to preserve ES6 semantic in scenarios like
// for (...) { let x; console.log(x); x = 1 } // assignment on one iteration should not affect other iterations
const isLetDefinedInLoop =
(resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) &&
(getCombinedFlagsForIdentifier(<Identifier>node.name) & NodeFlags.Let);
const container = getEnclosingBlockScopeContainer(node);
const flags = resolver.getNodeCheckFlags(node);
// NOTE: default initialization should not be added to let bindings in for-in\for-of statements
if (isLetDefinedInLoop &&
node.parent.parent.kind !== SyntaxKind.ForInStatement &&
node.parent.parent.kind !== SyntaxKind.ForOfStatement) {
// nested let bindings might need to be initialized explicitly to preserve ES6 semantic
// { let x = 1; }
// { let x; } // x here should be undefined. not 1
// NOTES:
// Top level bindings never collide with anything and thus don't require explicit initialization.
// As for nested let bindings there are two cases:
// - nested let bindings that were not renamed definitely should be initialized explicitly
// { let x = 1; }
// { let x; if (some-condition) { x = 1}; if (x) { /*1*/ } }
// Without explicit initialization code in /*1*/ can be executed even if some-condition is evaluated to false
// - renaming introduces fresh name that should not collide with any existing names, however renamed bindings sometimes also should be
// explicitly initialized. One particular case: non-captured binding declared inside loop body (but not in loop initializer)
// let x;
// for (;;) {
// let x;
// }
// in downlevel codegen inner 'x' will be renamed so it won't collide with outer 'x' however it will should be reset on every iteration
// as if it was declared anew.
// * Why non-captured binding - because if loop contains block scoped binding captured in some function then loop body will be rewritten
// to have a fresh scope on every iteration so everything will just work.
// * Why loop initializer is excluded - since we've introduced a fresh name it already will be undefined.
const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding;
const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop;
const emittedAsTopLevel =
isBlockScopedContainerTopLevel(container) ||
(isCapturedInFunction && isDeclaredInLoop && container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false));
const emittedAsNestedLetDeclaration =
getCombinedNodeFlags(node) & NodeFlags.Let &&
!emittedAsTopLevel;
const emitExplicitInitializer =
emittedAsNestedLetDeclaration &&
container.kind !== SyntaxKind.ForInStatement &&
container.kind !== SyntaxKind.ForOfStatement &&
(
!resolver.isDeclarationWithCollidingName(node) ||
(isDeclaredInLoop && !isCapturedInFunction && !isIterationStatement(container, /*lookInLabeledStatements*/ false))
);
if (emitExplicitInitializer) {
initializer = createVoidZero();
}
}
@@ -4115,14 +4149,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}
}
function getCombinedFlagsForIdentifier(node: Identifier): NodeFlags {
if (!node.parent || (node.parent.kind !== SyntaxKind.VariableDeclaration && node.parent.kind !== SyntaxKind.BindingElement)) {
return 0;
}
return getCombinedNodeFlags(node.parent);
}
function isES6ExportedDeclaration(node: Node) {
return !!(node.flags & NodeFlags.Export) &&
modulekind === ModuleKind.ES6 &&
+7 -6
View File
@@ -1903,8 +1903,8 @@ namespace ts {
hasGlobalName(name: string): boolean;
getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration;
getReferencedImportDeclaration(node: Identifier): Declaration;
getReferencedNestedRedeclaration(node: Identifier): Declaration;
isNestedRedeclaration(node: Declaration): boolean;
getReferencedDeclarationWithCollidingName(node: Identifier): Declaration;
isDeclarationWithCollidingName(node: Declaration): boolean;
isValueAliasDeclaration(node: Node): boolean;
isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean;
isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean;
@@ -2040,7 +2040,7 @@ namespace ts {
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
resolvedExports?: SymbolTable; // Resolved exports of module
exportsChecked?: boolean; // True if exports of external module have been checked
isNestedRedeclaration?: boolean; // True if symbol is block scoped redeclaration
isDeclaratonWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
bindingElement?: BindingElement; // Binding element associated with property symbol
exportsSomeValue?: boolean; // true if module exports some value (not just types)
}
@@ -2066,9 +2066,10 @@ namespace ts {
// Values for enum members have been computed, and any errors have been reported for them.
EnumValuesComputed = 0x00004000,
BlockScopedBindingInLoop = 0x00008000,
LexicalModuleMergesWithClass = 0x00010000, // Instantiated lexical module declaration is merged with a previous class declaration.
LoopWithBlockScopedBindingCapturedInFunction = 0x00020000, // Loop that contains block scoped variable captured in closure
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
}
/* @internal */
+19
View File
@@ -151,6 +151,18 @@ namespace ts {
return <SourceFile>node;
}
export function isStatementWithLocals(node: Node) {
switch (node.kind) {
case SyntaxKind.Block:
case SyntaxKind.CaseBlock:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return true;
}
return false;
}
export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
Debug.assert(line >= 0);
return getLineStarts(sourceFile)[line];
@@ -256,6 +268,13 @@ namespace ts {
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
}
export function isBlockScopedContainerTopLevel(node: Node): boolean {
return node.kind === SyntaxKind.SourceFile ||
node.kind === SyntaxKind.ModuleDeclaration ||
isFunctionLike(node) ||
isFunctionBlock(node);
}
export function isGlobalScopeAugmentation(module: ModuleDeclaration): boolean {
return !!(module.flags & NodeFlags.GlobalAugmentation);
}