From 1e39130f41a702e3f8ff53d939c87115bb53c5d8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 3 Jun 2015 06:57:20 -0700 Subject: [PATCH] Unified logic for renamed block scoped locals and other generated names --- src/compiler/checker.ts | 47 ++++++++-- src/compiler/emitter.ts | 195 ++++++++++++++-------------------------- src/compiler/types.ts | 6 +- 3 files changed, 112 insertions(+), 136 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6257031afbd..78dbe9077c6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11821,6 +11821,38 @@ module ts { } } + 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 getIsNestedRedeclaration(symbol: Symbol): boolean { + let links = getSymbolLinks(symbol); + if (links.isNestedRedeclaration === undefined) { + let container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + links.isNestedRedeclaration = isStatementWithLocals(container) && + !!resolveName(container.parent, symbol.name, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); + } + return links.isNestedRedeclaration; + } + + function getReferencedNestedRedeclaration(node: Identifier): Declaration { + let symbol = getReferencedValueSymbol(node); + return symbol && symbol.flags & SymbolFlags.BlockScoped && getIsNestedRedeclaration(symbol) ? symbol.valueDeclaration : undefined; + } + + function isNestedRedeclaration(node: Declaration): boolean { + let symbol = getSymbolOfNode(node); + return symbol.flags & SymbolFlags.BlockScoped && getIsNestedRedeclaration(symbol); + } + function isValueAliasDeclaration(node: Node): boolean { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: @@ -11924,8 +11956,10 @@ module ts { /** Serializes an EntityName (with substitutions) to an appropriate JS constructor value. Used by the __metadata decorator. */ function serializeEntityName(node: EntityName, getGeneratedNameForNode: (Node: Node) => string, fallbackPath?: string[]): string { if (node.kind === SyntaxKind.Identifier) { - var substitution = getExpressionNameSubstitution(node, getGeneratedNameForNode); - var text = substitution || (node).text; + // TODO(andersh): Fix this + // var substitution = getExpressionNameSubstitution(node, getGeneratedNameForNode); + // var text = substitution || (node).text; + var text = (node).text; if (fallbackPath) { fallbackPath.push(text); } @@ -12140,11 +12174,6 @@ module ts { return hasProperty(globals, name); } - function resolvesToSomeValue(location: Node, name: string): boolean { - Debug.assert(!nodeIsSynthesized(location), "resolvesToSomeValue called with a synthesized location"); - return !!resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); - } - function getReferencedValueSymbol(reference: Identifier): Symbol { return getNodeLinks(reference).resolvedSymbol || resolveName(reference, reference.text, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, @@ -12198,9 +12227,10 @@ module ts { function createResolver(): EmitResolver { return { - getExpressionNameSubstitution, getReferencedExportContainer, getReferencedImportDeclaration, + getReferencedNestedRedeclaration, + isNestedRedeclaration, isValueAliasDeclaration, hasGlobalName, isReferencedAliasDeclaration, @@ -12214,7 +12244,6 @@ module ts { isSymbolAccessible, isEntityNameVisible, getConstantValue, - resolvesToSomeValue, collectLinkedAliases, getBlockScopedVariableId, getReferencedValueDeclaration, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 2414c88a7a9..96dec48d82c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -125,7 +125,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { let generatedNameSet: Map = {}; let nodeToGeneratedName: string[] = []; - let blockScopedVariableToGeneratedName: string[]; let computedPropertyNamesToGeneratedNames: string[]; let extendsEmitted = false; @@ -268,6 +267,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { function generateNameForNode(node: Node) { switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName((node).text); case SyntaxKind.ModuleDeclaration: case SyntaxKind.EnumDeclaration: return generateNameForModuleOrEnum(node); @@ -648,19 +649,19 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { sourceMapDir = getDirectoryPath(normalizePath(jsFilePath)); } - function emitNodeWithSourceMap(node: Node, allowGeneratedIdentifiers?: boolean) { + function emitNodeWithSourceMap(node: Node) { if (node) { if (nodeIsSynthesized(node)) { - return emitNodeWithoutSourceMap(node, /*allowGeneratedIdentifiers*/ false); + return emitNodeWithoutSourceMap(node); } if (node.kind != SyntaxKind.SourceFile) { recordEmitNodeStartSpan(node); - emitNodeWithoutSourceMap(node, allowGeneratedIdentifiers); + emitNodeWithoutSourceMap(node); recordEmitNodeEndSpan(node); } else { recordNewSourceFileStart(node); - emitNodeWithoutSourceMap(node, /*allowGeneratedIdentifiers*/ false); + emitNodeWithoutSourceMap(node); } } } @@ -1187,6 +1188,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.ReturnStatement: + case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.SpreadElementExpression: case SyntaxKind.SwitchStatement: case SyntaxKind.TaggedTemplateExpression: @@ -1235,52 +1237,54 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } } else if (languageVersion < ScriptTarget.ES6) { - let declaration = resolver.getReferencedImportDeclaration(node); - if (declaration) { - if (declaration.kind === SyntaxKind.ImportClause) { + let alias = resolver.getReferencedImportDeclaration(node); + if (alias) { + if (alias.kind === SyntaxKind.ImportClause) { // Identifier references default import - write(getGeneratedNameForNode(declaration.parent)); + write(getGeneratedNameForNode(alias.parent)); write(languageVersion === ScriptTarget.ES3 ? '["default"]' : ".default"); } else { // Identifier references named import - write(getGeneratedNameForNode(declaration.parent.parent.parent)); + write(getGeneratedNameForNode(alias.parent.parent.parent)); write("."); - writeTextOfNode(currentSourceFile, (declaration).propertyName || (declaration).name); + writeTextOfNode(currentSourceFile, (alias).propertyName || (alias).name); } return; } + let local = resolver.getReferencedNestedRedeclaration(node); + if (local) { + write(getGeneratedNameForNode(local.name)); + return; + } } writeTextOfNode(currentSourceFile, node); } - function getGeneratedNameForIdentifier(node: Identifier): string { - if (nodeIsSynthesized(node) || !blockScopedVariableToGeneratedName) { - return undefined; - } - - var variableId = resolver.getBlockScopedVariableId(node) - if (variableId === undefined) { - return undefined; - } - - return blockScopedVariableToGeneratedName[variableId]; - } - - function emitIdentifier(node: Identifier, allowGeneratedIdentifiers: boolean) { - if (allowGeneratedIdentifiers) { - let generatedName = getGeneratedNameForIdentifier(node); - if (generatedName) { - write(generatedName); - return; + function isNameOfNestedRedeclaration(node: Identifier) { + if (languageVersion < ScriptTarget.ES6) { + let parent = node.parent; + switch (parent.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableDeclaration: + return (parent).name === node && resolver.isNestedRedeclaration(parent); } } + return false; + } + + function emitIdentifier(node: Identifier) { if (!node.parent) { write(node.text); } else if (isExpressionIdentifier(node)) { emitExpressionIdentifier(node); } + else if (isNameOfNestedRedeclaration(node)) { + write(getGeneratedNameForNode(node)); + } else { writeTextOfNode(currentSourceFile, node); } @@ -1326,7 +1330,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { function emitBindingElement(node: BindingElement) { if (node.propertyName) { - emit(node.propertyName, /*allowGeneratedIdentifiers*/ false); + emit(node.propertyName); write(": "); } if (node.dotDotDotToken) { @@ -1679,7 +1683,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { write("*"); } - emit(node.name, /*allowGeneratedIdentifiers*/ false); + emit(node.name); if (languageVersion < ScriptTarget.ES6) { write(": function "); } @@ -1687,43 +1691,34 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } function emitPropertyAssignment(node: PropertyDeclaration) { - emit(node.name, /*allowGeneratedIdentifiers*/ false); + emit(node.name); write(": "); emit(node.initializer); } + // Return true if identifier resolves to an exported member of a namespace + function isNamespaceExportReference(node: Identifier) { + let container = resolver.getReferencedExportContainer(node); + return container && container.kind !== SyntaxKind.SourceFile; + } + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { - emit(node.name, /*allowGeneratedIdentifiers*/ false); - // If short-hand property has a prefix, then regardless of the target version, we will emit it as normal property assignment. For example: - // module m { - // export let y; - // } - // module m { - // export let obj = { y }; - // } - // The short-hand property in obj need to emit as such ... = { y : m.y } regardless of the TargetScript version - if (languageVersion < ScriptTarget.ES6) { + // The name property of a short-hand property assignment is considered an expression position, so here + // we manually emit the identifier to avoid rewriting. + writeTextOfNode(currentSourceFile, node.name); + // If emitting pre-ES6 code, or if the name requires rewriting when resolved as an expression identifier, + // we emit a normal property assignment. For example: + // module m { + // export let y; + // } + // module m { + // let obj = { y }; + // } + // Here we need to emit obj = { y : m.y } regardless of the output target. + if (languageVersion < ScriptTarget.ES6 || isNamespaceExportReference(node.name)) { // Emit identifier as an identifier write(": "); - var generatedName = getGeneratedNameForIdentifier(node.name); - if (generatedName) { - write(generatedName); - } - else { - // Even though this is stored as identifier treat it as an expression - // Short-hand, { x }, is equivalent of normal form { x: x } - emitExpressionIdentifier(node.name); - } - } - else { - let container = resolver.getReferencedExportContainer(node.name); - if (container && container.kind !== SyntaxKind.SourceFile) { - // Emit identifier as an identifier - write(": "); - // Even though this is stored as identifier treat it as an expression - // Short-hand, { x }, is equivalent of normal form { x: x } - emitExpressionIdentifier(node.name); - } + emit(node.name); } } @@ -1776,7 +1771,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { let indentedBeforeDot = indentIfOnDifferentLines(node, node.expression, node.dotToken); write("."); let indentedAfterDot = indentIfOnDifferentLines(node, node.dotToken, node.name); - emit(node.name, /*allowGeneratedIdentifiers*/ false); + emit(node.name); decreaseIndentIf(indentedBeforeDot, indentedAfterDot); } @@ -2778,8 +2773,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { write(", "); } - renameNonTopLevelLetAndConst(name); - const isVariableDeclarationOrBindingElement = name.parent && (name.parent.kind === SyntaxKind.VariableDeclaration || name.parent.kind === SyntaxKind.BindingElement); @@ -2847,11 +2840,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } function createPropertyAccessForDestructuringProperty(object: Expression, propName: Identifier | LiteralExpression): Expression { - if (propName.kind !== SyntaxKind.Identifier) { - return createElementAccessExpression(object, propName); + let syntheticName = createSynthesizedNode(propName.kind); + syntheticName.text = propName.text; + if (syntheticName.kind !== SyntaxKind.Identifier) { + return createElementAccessExpression(object, syntheticName); } - - return createPropertyAccessExpression(object, propName); + return createPropertyAccessExpression(object, syntheticName); } function createSliceCall(value: Expression, sliceIndex: number): CallExpression { @@ -2873,8 +2867,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } for (let p of properties) { if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) { - // TODO(andersh): Computed property support - let propName = ((p).name); + let propName = (p).name; emitDestructuringAssignment((p).initializer || propName, createPropertyAccessForDestructuringProperty(value, propName)); } } @@ -2988,8 +2981,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { } } else { - renameNonTopLevelLetAndConst(node.name); - let initializer = node.initializer; if (!initializer && languageVersion < ScriptTarget.ES6) { @@ -3049,54 +3040,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { return getCombinedNodeFlags(node.parent); } - function renameNonTopLevelLetAndConst(node: Node): void { - // do not rename if - // - language version is ES6+ - // - node is synthesized - // - node is not identifier (can happen when tree is malformed) - // - node is definitely not name of variable declaration. - // it still can be part of parameter declaration, this check will be done next - if (languageVersion >= ScriptTarget.ES6 || - nodeIsSynthesized(node) || - node.kind !== SyntaxKind.Identifier || - (node.parent.kind !== SyntaxKind.VariableDeclaration && node.parent.kind !== SyntaxKind.BindingElement)) { - return; - } - - let combinedFlags = getCombinedFlagsForIdentifier(node); - if (((combinedFlags & NodeFlags.BlockScoped) === 0) || combinedFlags & NodeFlags.Export) { - // do not rename exported or non-block scoped variables - return; - } - - // here it is known that node is a block scoped variable - let list = getAncestor(node, SyntaxKind.VariableDeclarationList); - if (list.parent.kind === SyntaxKind.VariableStatement) { - let isSourceFileLevelBinding = list.parent.parent.kind === SyntaxKind.SourceFile; - let isModuleLevelBinding = list.parent.parent.kind === SyntaxKind.ModuleBlock; - let isFunctionLevelBinding = - list.parent.parent.kind === SyntaxKind.Block && isFunctionLike(list.parent.parent.parent); - if (isSourceFileLevelBinding || isModuleLevelBinding || isFunctionLevelBinding) { - return; - } - } - - let blockScopeContainer = getEnclosingBlockScopeContainer(node); - let parent = blockScopeContainer.kind === SyntaxKind.SourceFile - ? blockScopeContainer - : blockScopeContainer.parent; - - if (resolver.resolvesToSomeValue(parent, (node).text)) { - let variableId = resolver.getBlockScopedVariableId(node); - if (!blockScopedVariableToGeneratedName) { - blockScopedVariableToGeneratedName = []; - } - - let generatedName = makeUniqueName((node).text); - blockScopedVariableToGeneratedName[variableId] = generatedName; - } - } - function isES6ExportedDeclaration(node: Node) { return !!(node.flags & NodeFlags.Export) && languageVersion >= ScriptTarget.ES6 && @@ -3260,7 +3203,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { function emitAccessor(node: AccessorDeclaration) { write(node.kind === SyntaxKind.GetAccessor ? "get " : "set "); - emit(node.name, /*allowGeneratedIdentifiers*/ false); + emit(node.name); emitSignatureAndBody(node); } @@ -5786,7 +5729,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { emitLeadingComments(node.endOfFileToken); } - function emitNodeWithoutSourceMap(node: Node, allowGeneratedIdentifiers?: boolean): void { + function emitNodeWithoutSourceMap(node: Node): void { if (!node) { return; } @@ -5800,7 +5743,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { emitLeadingComments(node); } - emitJavaScriptWorker(node, allowGeneratedIdentifiers); + emitJavaScriptWorker(node); if (emitComments) { emitTrailingComments(node); @@ -5850,11 +5793,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { return true; } - function emitJavaScriptWorker(node: Node, allowGeneratedIdentifiers: boolean = true) { + function emitJavaScriptWorker(node: Node) { // Check if the node can be emitted regardless of the ScriptTarget switch (node.kind) { case SyntaxKind.Identifier: - return emitIdentifier(node, allowGeneratedIdentifiers); + return emitIdentifier(node); case SyntaxKind.Parameter: return emitParameter(node); case SyntaxKind.MethodDeclaration: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e64d6afad8e..a8478a82797 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1398,6 +1398,8 @@ module ts { hasGlobalName(name: string): boolean; getReferencedExportContainer(node: Identifier): SourceFile | ModuleDeclaration | EnumDeclaration; getReferencedImportDeclaration(node: Identifier): ImportClause | ImportSpecifier; + getReferencedNestedRedeclaration(node: Identifier): Declaration; + isNestedRedeclaration(node: Declaration): boolean; isValueAliasDeclaration(node: Node): boolean; isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean; isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean; @@ -1412,7 +1414,6 @@ module ts { isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult; // Returns the constant value this property access resolves to, or 'undefined' for a non-constant getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; - resolvesToSomeValue(location: Node, name: string): boolean; getBlockScopedVariableId(node: Identifier): number; getReferencedValueDeclaration(reference: Identifier): Declaration; serializeTypeOfNode(node: Node, getGeneratedNameForNode: (Node: Node) => string): string | string[]; @@ -1494,6 +1495,8 @@ module ts { HasExports = Class | Enum | Module, HasMembers = Class | Interface | TypeLiteral | ObjectLiteral, + BlockScoped = BlockScopedVariable | Class | Enum, + IsContainer = HasLocals | HasExports | HasMembers, PropertyOrAccessor = Property | Accessor, Export = ExportNamespace | ExportType | ExportValue, @@ -1523,6 +1526,7 @@ module ts { unionType?: UnionType; // Containing union type for union 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 } /* @internal */