diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 03b30755cf2..71a35432685 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 @@ -92,18 +92,17 @@ module ts { let currentSourceFile: SourceFile; - let generatedNameSet: Map; - let nodeToGeneratedName: string[]; + let generatedNameSet: Map = {}; + let nodeToGeneratedName: string[] = []; let blockScopedVariableToGeneratedName: string[]; let computedPropertyNamesToGeneratedNames: string[]; 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,98 @@ module ts { emit(sourceFile); } + function isUniqueName(name: string): boolean { + return !resolver.hasGlobalName(name) && + !hasProperty(currentSourceFile.identifiers, name) && + !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; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + let name = count < 26 ? "_" + String.fromCharCode(CharacterCodes.a + count) : "_" + (count - 26); + if (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[generatedName] = generatedName; + } + i++; + } + } + + function assignGeneratedName(node: Node, name: string) { + 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,175 +281,15 @@ 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")); } } function getGeneratedNameForNode(node: Node) { let nodeId = getNodeId(node); - if (!nodeToGeneratedName || !nodeToGeneratedName[nodeId]) { + if (!nodeToGeneratedName[nodeId]) { generateNameForNode(node); } - return nodeToGeneratedName ? nodeToGeneratedName[nodeId] : undefined; + return nodeToGeneratedName[nodeId]; } function initializeEmitterWithSourceMaps() { @@ -726,9 +657,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 +670,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 +913,7 @@ module ts { } function emitDownlevelTaggedTemplate(node: TaggedTemplateExpression) { - let tempVariable = createAndRecordTempVariable(node); + let tempVariable = createAndRecordTempVariable(TempFlags.Auto); write("("); emit(tempVariable); write(" = "); @@ -1178,7 +1109,7 @@ module ts { return; } - let generatedVariable = createTempVariable(node); + let generatedVariable = createTempVariable(TempFlags.Auto); generatedName = generatedVariable.text; recordTempDeclaration(generatedVariable); computedPropertyNamesToGeneratedNames[node.id] = generatedName; @@ -1425,7 +1356,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 +1721,7 @@ module ts { emit(node); return node; } - let temp = createAndRecordTempVariable(node); + let temp = createAndRecordTempVariable(TempFlags.Auto); write("("); emit(temp); @@ -2234,10 +2165,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 +2253,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 +2512,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 +2791,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 +2827,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 +2881,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 +3014,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 +3046,7 @@ module ts { emitExportMemberAssignment(node); } - predefinedTempsInUse = savePredefinedTempsInUse; - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; } @@ -3410,14 +3333,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 +3448,7 @@ module ts { emitTrailingComments(ctor); } - predefinedTempsInUse = savePredefinedTempsInUse; - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; } @@ -3695,11 +3615,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 +3646,7 @@ module ts { }); write(";"); emitTempDeclarations(/*newLine*/ true); - tempCount = saveTempCount; + tempFlags = saveTempFlags; tempVariables = saveTempVariables; tempParameters = saveTempParameters; computedPropertyNamesToGeneratedNames = saveComputedPropertyNamesToGeneratedNames; @@ -4089,17 +4009,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 { diff --git a/tests/baselines/reference/ES5For-of21.js b/tests/baselines/reference/ES5For-of21.js index 9356514ea6b..d7e4db63c3b 100644 --- a/tests/baselines/reference/ES5For-of21.js +++ b/tests/baselines/reference/ES5For-of21.js @@ -4,9 +4,9 @@ for (let v of []) { } //// [ES5For-of21.js] -for (var _i = 0, _a = []; _i < _a.length; _i++) { - var v = _a[_i]; - for (var _b = 0, _c = []; _b < _c.length; _b++) { - var _i_1 = _c[_b]; +for (var _a = 0, _b = []; _a < _b.length; _a++) { + var v = _b[_a]; + for (var _c = 0, _d = []; _c < _d.length; _c++) { + var _i = _d[_c]; } } diff --git a/tests/baselines/reference/ES5For-of22.js b/tests/baselines/reference/ES5For-of22.js index 63608f36e51..87b084d52ac 100644 --- a/tests/baselines/reference/ES5For-of22.js +++ b/tests/baselines/reference/ES5For-of22.js @@ -5,12 +5,12 @@ for (var x of [1, 2, 3]) { } //// [ES5For-of22.js] -for (var _i = 0, _a = [ +for (var _i = 0, _b = [ 1, 2, 3 -]; _i < _a.length; _i++) { - var x = _a[_i]; - var _a_1 = 0; +]; _i < _b.length; _i++) { + var x = _b[_i]; + var _a = 0; console.log(x); }