Merge pull request #2514 from Microsoft/simplifyTempLogic

Simplify temporary name generation logic
This commit is contained in:
Anders Hejlsberg 2015-03-27 10:24:25 -07:00
commit 4894fee7d3
3 changed files with 139 additions and 222 deletions

View file

@ -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<string>;
let nodeToGeneratedName: string[];
let generatedNameSet: Map<string> = {};
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<ExportSpecifier[]>;
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((<LiteralExpression>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(<ExportAssignment>node);
break;
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleBlock:
forEach((<SourceFile | ModuleBlock>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 '_<number>'
// - 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((<LiteralExpression>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 = <Identifier>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 ? <Identifier>node.expression : createTempVariable(node);
let counter = createTempVariable(TempFlags._i);
let rhsReference = rhsIsIdentifier ? <Identifier>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, (<Identifier>node).text) ||
nameConflictsWithSomeTempVariable((<Identifier>node).text);
if (hasConflictsInEnclosingScope) {
if (resolver.resolvesToSomeValue(parent, (<Identifier>node).text)) {
let variableId = resolver.getBlockScopedVariableId(<Identifier>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 {

View file

@ -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];
}
}

View file

@ -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);
}