From 184ce98bf63153c8d4cf9edc25ebd6bb6d7e8538 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 26 Mar 2015 10:51:07 -0700 Subject: [PATCH] Simplify temporary name generation logic --- src/compiler/emitter.ts | 335 +++++++++++++++------------------------- 1 file changed, 125 insertions(+), 210 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 02531593de6..0358106057a 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -12,12 +12,12 @@ module ts { return isExternalModule(sourceFile) || isDeclarationFile(sourceFile); } - // flag enum used to request and track usages of few dedicated temp variables - // enum values are used to set/check bit values and thus should not have bit collisions. - const enum TempVariableKind { - auto = 0, - _i = 1, - _n = 2, + // Flags enum to track count of temp variables and a few dedicated names + const enum TempFlags { + Auto = 0x00000000, // No preferred name + CountMask = 0x0FFFFFFF, // Temp variable counter + _i = 0x10000000, // Use/preference flag for '_i' + _n = 0x20000000, // Use/preference flag for '_n' } // @internal @@ -99,11 +99,10 @@ module ts { let extendsEmitted = false; let decorateEmitted = false; - let tempCount = 0; + let tempFlags = 0; let tempVariables: Identifier[]; let tempParameters: Identifier[]; let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; - let predefinedTempsInUse = TempVariableKind.auto; let exportSpecifiers: Map; let exportEquals: ExportAssignment; let hasExportStars: boolean; @@ -168,6 +167,96 @@ module ts { emit(sourceFile); } + function isUniqueName(name: string): boolean { + return !resolver.hasGlobalName(name) && + !hasProperty(currentSourceFile.identifiers, name) && + (!generatedNameSet || !hasProperty(generatedNameSet, name)) + } + + // Return the next available name in the pattern _a ... _z, _0, _1, ... + // TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + // Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + function makeTempVariableName(flags: TempFlags): string { + if (flags && !(tempFlags & flags)) { + var name = flags === TempFlags._i ? "_i" : "_n" + if (isUniqueName(name)) { + tempFlags |= flags; + return name; + } + } + while (true) { + let count = tempFlags & TempFlags.CountMask; + let ch = CharacterCodes.a + count; + let name = count < 26 ? "_" + String.fromCharCode(ch) : "_" + (count - 26); + tempFlags++; + if (ch !== CharacterCodes.i && ch !== CharacterCodes.n && isUniqueName(name)) { + return name; + } + } + } + + // Generate a name that is unique within the current file and doesn't conflict with any names + // in global scope. The name is formed by adding an '_n' suffix to the specified base name, + // where n is a positive integer. Note that names generated by makeTempVariableName and + // makeUniqueName are guaranteed to never conflict. + function makeUniqueName(baseName: string): string { + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + let i = 1; + while (true) { + let generatedName = baseName + i; + if (isUniqueName(generatedName)) { + return (generatedNameSet || (generatedNameSet = {}))[generatedName] = generatedName; + } + i++; + } + } + + function assignGeneratedName(node: Node, name: string) { + (nodeToGeneratedName || (nodeToGeneratedName = []))[getNodeId(node)] = unescapeIdentifier(name); + } + + function generateNameForFunctionOrClassDeclaration(node: Declaration) { + if (!node.name) { + assignGeneratedName(node, makeUniqueName("default")); + } + } + + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + if (node.name.kind === SyntaxKind.Identifier) { + let name = node.name.text; + // Use module/enum name itself if it is unique, otherwise make a unique variation + assignGeneratedName(node, isUniqueLocalName(name, node) ? name : makeUniqueName(name)); + } + } + + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + let expr = getExternalModuleName(node); + let baseName = expr.kind === SyntaxKind.StringLiteral ? + escapeIdentifier(makeIdentifierFromModuleName((expr).text)) : "module"; + assignGeneratedName(node, makeUniqueName(baseName)); + } + + function generateNameForImportDeclaration(node: ImportDeclaration) { + if (node.importClause) { + generateNameForImportOrExportDeclaration(node); + } + } + + function generateNameForExportDeclaration(node: ExportDeclaration) { + if (node.moduleSpecifier) { + generateNameForImportOrExportDeclaration(node); + } + } + + function generateNameForExportAssignment(node: ExportAssignment) { + if (node.expression && node.expression.kind !== SyntaxKind.Identifier) { + assignGeneratedName(node, makeUniqueName("default")); + } + } + function generateNameForNode(node: Node) { switch (node.kind) { case SyntaxKind.FunctionDeclaration: @@ -190,166 +279,6 @@ module ts { case SyntaxKind.ExportAssignment: generateNameForExportAssignment(node); break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleBlock: - forEach((node).statements, generateNameForNode); - break; - } - } - - function isUniqueName(name: string): boolean { - return !resolver.hasGlobalName(name) && - !hasProperty(currentSourceFile.identifiers, name) && - (!generatedNameSet || !hasProperty(generatedNameSet, name)) - } - - // in cases like - // for (var x of []) { - // _i; - // } - // we should be able to detect if let _i was shadowed by some temp variable that was allocated in scope - function nameConflictsWithSomeTempVariable(name: string): boolean { - // temp variable names always start with '_' - if (name.length < 2 || name.charCodeAt(0) !== CharacterCodes._) { - return false; - } - - if (name === "_i") { - return !!(predefinedTempsInUse & TempVariableKind._i); - } - - if (name === "_n") { - return !!(predefinedTempsInUse & TempVariableKind._n); - } - - if (name.length === 2 && name.charCodeAt(1) >= CharacterCodes.a && name.charCodeAt(1) <= CharacterCodes.z) { - // handles _a .. _z - let n = name.charCodeAt(1) - CharacterCodes.a; - return n < tempCount; - } - else { - // handles _1, _2... - let n = +name.substring(1); - return !isNaN(n) && n >= 0 && n < (tempCount - 26); - } - } - - // This function generates a name using the following pattern: - // _a .. _h, _j ... _z, _0, _1, ... - // It is guaranteed that generated name will not shadow any existing user-defined names, - // however it can hide another name generated by this function higher in the scope. - // NOTE: names generated by 'makeTempVariableName' and 'makeUniqueName' will never conflict. - // see comment for 'makeTempVariableName' for more information. - function makeTempVariableName(location: Node, tempVariableKind: TempVariableKind): string { - let tempName: string; - if (tempVariableKind !== TempVariableKind.auto && !(predefinedTempsInUse & tempVariableKind)) { - tempName = tempVariableKind === TempVariableKind._i ? "_i" : "_n"; - if (!resolver.resolvesToSomeValue(location, tempName)) { - predefinedTempsInUse |= tempVariableKind; - return tempName; - } - } - - do { - // Note: we avoid generating _i and _n as those are common names we want in other places. - var char = CharacterCodes.a + tempCount; - if (char !== CharacterCodes.i && char !== CharacterCodes.n) { - if (tempCount < 26) { - tempName = "_" + String.fromCharCode(char); - } - else { - tempName = "_" + (tempCount - 26); - } - } - - tempCount++; - } - while (resolver.resolvesToSomeValue(location, tempName)); - - return tempName; - } - - // Generates a name that is unique within current file and does not collide with - // any names in global scope. - // NOTE: names generated by 'makeTempVariableName' and 'makeUniqueName' will never conflict - // because of the way how these names are generated - // - makeUniqueName builds a name by picking a base name (which should not be empty string) - // and appending suffix '_' - // - makeTempVariableName creates a name using the following pattern: - // _a .. _h, _j ... _z, _0, _1, ... - // This means that names from 'makeTempVariableName' will have only one underscore at the beginning - // and names from 'makeUniqieName' will have at least one underscore in the middle - // so they will never collide. - function makeUniqueName(baseName: string): string { - Debug.assert(!!baseName); - - // Find the first unique 'name_n', where n is a positive number - if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { - baseName += "_"; - } - - let i = 1; - let generatedName: string; - while (true) { - generatedName = baseName + i; - if (isUniqueName(generatedName)) { - break; - } - i++; - } - - if (!generatedNameSet) { - generatedNameSet = {}; - } - return generatedNameSet[generatedName] = generatedName; - } - - function renameNode(node: Node, name: string): string { - var nodeId = getNodeId(node); - - if (!nodeToGeneratedName) { - nodeToGeneratedName = []; - } - - return nodeToGeneratedName[nodeId] = unescapeIdentifier(name); - } - - function generateNameForFunctionOrClassDeclaration(node: Declaration) { - if (!node.name) { - renameNode(node, makeUniqueName("default")); - } - } - - function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { - if (node.name.kind === SyntaxKind.Identifier) { - let name = node.name.text; - // Use module/enum name itself if it is unique, otherwise make a unique variation - renameNode(node, isUniqueLocalName(name, node) ? name : makeUniqueName(name)); - } - } - - function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { - let expr = getExternalModuleName(node); - let baseName = expr.kind === SyntaxKind.StringLiteral ? - escapeIdentifier(makeIdentifierFromModuleName((expr).text)) : "module"; - renameNode(node, makeUniqueName(baseName)); - } - - function generateNameForImportDeclaration(node: ImportDeclaration) { - if (node.importClause) { - generateNameForImportOrExportDeclaration(node); - } - } - - function generateNameForExportDeclaration(node: ExportDeclaration) { - if (node.moduleSpecifier) { - generateNameForImportOrExportDeclaration(node); - } - } - - function generateNameForExportAssignment(node: ExportAssignment) { - if (node.expression && node.expression.kind !== SyntaxKind.Identifier) { - renameNode(node, makeUniqueName("default")); } } @@ -726,9 +655,9 @@ module ts { } // Create a temporary variable with a unique unused name. - function createTempVariable(location: Node, tempVariableKind = TempVariableKind.auto): Identifier { + function createTempVariable(flags: TempFlags): Identifier { let result = createSynthesizedNode(SyntaxKind.Identifier); - result.text = makeTempVariableName(location, tempVariableKind); + result.text = makeTempVariableName(flags); return result; } @@ -739,8 +668,8 @@ module ts { tempVariables.push(name); } - function createAndRecordTempVariable(location: Node, tempVariableKind?: TempVariableKind): Identifier { - let temp = createTempVariable(location, tempVariableKind); + function createAndRecordTempVariable(flags: TempFlags): Identifier { + let temp = createTempVariable(flags); recordTempDeclaration(temp); return temp; @@ -982,7 +911,7 @@ module ts { } function emitDownlevelTaggedTemplate(node: TaggedTemplateExpression) { - let tempVariable = createAndRecordTempVariable(node); + let tempVariable = createAndRecordTempVariable(TempFlags.Auto); write("("); emit(tempVariable); write(" = "); @@ -1178,7 +1107,7 @@ module ts { return; } - let generatedVariable = createTempVariable(node); + let generatedVariable = createTempVariable(TempFlags.Auto); generatedName = generatedVariable.text; recordTempDeclaration(generatedVariable); computedPropertyNamesToGeneratedNames[node.id] = generatedName; @@ -1425,7 +1354,7 @@ module ts { function createDownlevelObjectLiteralWithComputedProperties(originalObjectLiteral: ObjectLiteralExpression, firstComputedPropertyIndex: number): ParenthesizedExpression { // For computed properties, we need to create a unique handle to the object // literal so we can modify it without risking internal assignments tainting the object. - let tempVar = createAndRecordTempVariable(originalObjectLiteral); + let tempVar = createAndRecordTempVariable(TempFlags.Auto); // Hold onto the initial non-computed properties in a new object literal, // then create the rest through property accesses on the temp variable. @@ -1790,7 +1719,7 @@ module ts { emit(node); return node; } - let temp = createAndRecordTempVariable(node); + let temp = createAndRecordTempVariable(TempFlags.Auto); write("("); emit(temp); @@ -2234,10 +2163,10 @@ module ts { // // we don't want to emit a temporary variable for the RHS, just use it directly. let rhsIsIdentifier = node.expression.kind === SyntaxKind.Identifier; - let counter = createTempVariable(node, TempVariableKind._i); - let rhsReference = rhsIsIdentifier ? node.expression : createTempVariable(node); + let counter = createTempVariable(TempFlags._i); + let rhsReference = rhsIsIdentifier ? node.expression : createTempVariable(TempFlags.Auto); - var cachedLength = compilerOptions.cacheDownlevelForOfLength ? createTempVariable(node, TempVariableKind._n) : undefined; + var cachedLength = compilerOptions.cacheDownlevelForOfLength ? createTempVariable(TempFlags._n) : undefined; // This is the let keyword for the counter and rhsReference. The let keyword for // the LHS will be emitted inside the body. @@ -2322,7 +2251,7 @@ module ts { else { // It's an empty declaration list. This can only happen in an error case, if the user wrote // for (let of []) {} - emitNodeWithoutSourceMap(createTempVariable(node)); + emitNodeWithoutSourceMap(createTempVariable(TempFlags.Auto)); write(" = "); emitNodeWithoutSourceMap(rhsIterationValue); } @@ -2581,7 +2510,7 @@ module ts { // In case the root is a synthesized node, we need to pass lowestNonSynthesizedAncestor // as the location for determining uniqueness of the variable we are about to // generate. - let identifier = createTempVariable(lowestNonSynthesizedAncestor || root); + let identifier = createTempVariable(TempFlags.Auto); if (!isDeclaration) { recordTempDeclaration(identifier); } @@ -2860,11 +2789,7 @@ module ts { ? blockScopeContainer : blockScopeContainer.parent; - var hasConflictsInEnclosingScope = - resolver.resolvesToSomeValue(parent, (node).text) || - nameConflictsWithSomeTempVariable((node).text); - - if (hasConflictsInEnclosingScope) { + if (resolver.resolvesToSomeValue(parent, (node).text)) { let variableId = resolver.getBlockScopedVariableId(node); if (!blockScopedVariableToGeneratedName) { blockScopedVariableToGeneratedName = []; @@ -2900,7 +2825,7 @@ module ts { function emitParameter(node: ParameterDeclaration) { if (languageVersion < ScriptTarget.ES6) { if (isBindingPattern(node.name)) { - let name = createTempVariable(node); + let name = createTempVariable(TempFlags.Auto); if (!tempParameters) { tempParameters = []; } @@ -2954,7 +2879,7 @@ module ts { if (languageVersion < ScriptTarget.ES6 && hasRestParameters(node)) { let restIndex = node.parameters.length - 1; let restParam = node.parameters[restIndex]; - let tempName = createTempVariable(node, TempVariableKind._i).text; + let tempName = createTempVariable(TempFlags._i).text; writeLine(); emitLeadingComments(restParam); emitStart(restParam); @@ -3087,15 +3012,12 @@ module ts { } function emitSignatureAndBody(node: FunctionLikeDeclaration) { - let saveTempCount = tempCount; + let saveTempFlags = tempFlags; let saveTempVariables = tempVariables; let saveTempParameters = tempParameters; - let savePredefinedTempsInUse = predefinedTempsInUse; - - tempCount = 0; + tempFlags = 0; tempVariables = undefined; tempParameters = undefined; - predefinedTempsInUse = TempVariableKind.auto; // When targeting ES6, emit arrow function natively in ES6 if (shouldEmitAsArrowFunction(node)) { @@ -3122,8 +3044,7 @@ module ts { emitExportMemberAssignment(node); } - predefinedTempsInUse = savePredefinedTempsInUse; - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; } @@ -3410,14 +3331,12 @@ module ts { } function emitConstructor(node: ClassDeclaration, baseTypeNode: TypeReferenceNode) { - let saveTempCount = tempCount; + let saveTempFlags = tempFlags; let saveTempVariables = tempVariables; let saveTempParameters = tempParameters; - let savePredefinedTempsInUse = predefinedTempsInUse; - tempCount = 0; + tempFlags = 0; tempVariables = undefined; tempParameters = undefined; - predefinedTempsInUse = TempVariableKind.auto; // Check if we have property assignment inside class declaration. // If there is property assignment, we need to emit constructor whether users define it or not @@ -3527,8 +3446,7 @@ module ts { emitTrailingComments(ctor); } - predefinedTempsInUse = savePredefinedTempsInUse; - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; } @@ -3695,11 +3613,11 @@ module ts { write("_super"); } write(") {"); - let saveTempCount = tempCount; + let saveTempFlags = tempFlags; let saveTempVariables = tempVariables; let saveTempParameters = tempParameters; let saveComputedPropertyNamesToGeneratedNames = computedPropertyNamesToGeneratedNames; - tempCount = 0; + tempFlags = 0; tempVariables = undefined; tempParameters = undefined; computedPropertyNamesToGeneratedNames = undefined; @@ -3726,7 +3644,7 @@ module ts { }); write(";"); emitTempDeclarations(/*newLine*/ true); - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; computedPropertyNamesToGeneratedNames = saveComputedPropertyNamesToGeneratedNames; @@ -4093,17 +4011,14 @@ module ts { emitEnd(node.name); write(") "); if (node.body.kind === SyntaxKind.ModuleBlock) { - let saveTempCount = tempCount; + let saveTempFlags = tempFlags; let saveTempVariables = tempVariables; - let savePredefinedTempsInUse = predefinedTempsInUse; - tempCount = 0; + tempFlags = 0; tempVariables = undefined; - predefinedTempsInUse = TempVariableKind.auto; emit(node.body); - predefinedTempsInUse = savePredefinedTempsInUse; - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; } else {