From a0bcd7eabfa2c7a9706e0aff3cd752fd95c6864b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 14 Feb 2015 00:48:46 -0800 Subject: [PATCH] initial revision of downlevel compilation for let/const bindings --- src/compiler/checker.ts | 37 +++++-- src/compiler/emitter.ts | 238 +++++++++++++++++++++++++++++++++++----- src/compiler/types.ts | 1 + 3 files changed, 237 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cccd74e3907..68eb3e3e960 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -264,7 +264,7 @@ module ts { Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (symbol.flags & meaning) { return symbol; - } + } if (symbol.flags & SymbolFlags.Import) { var target = resolveImport(symbol); @@ -10207,7 +10207,30 @@ module ts { } function isUnknownIdentifier(location: Node, name: string): boolean { - return !resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + return !resolveName(location, name, SymbolFlags.Value | SymbolFlags.Import, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + } + + function getBlockScopedVariableId(n: Identifier): number { + Debug.assert(n.parent !== undefined); + + // ignore name parts of property access expressions + if (n.parent.kind === SyntaxKind.PropertyAccessExpression && + (n.parent).name === n) { + return undefined; + } + + // for names in variable declarations and binding elements try to short circuit and fetch symbol from the node + var declarationSymbol: Symbol = + (n.parent.kind === SyntaxKind.VariableDeclaration && (n.parent).name === n) || + n.parent.kind === SyntaxKind.BindingElement + ? getSymbolOfNode(n.parent) + : undefined; + + var symbol = declarationSymbol || + getNodeLinks(n).resolvedSymbol || + resolveName(n, n.text, SymbolFlags.BlockScopedVariable | SymbolFlags.Import, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + + return symbol && symbol.flags & SymbolFlags.BlockScopedVariable ? symbol.id : undefined; } function createResolver(): EmitResolver { @@ -10226,6 +10249,7 @@ module ts { isEntityNameVisible, getConstantValue, isUnknownIdentifier, + getBlockScopedVariableId, }; } @@ -10953,15 +10977,6 @@ module ts { if (!declarationList.declarations.length) { return grammarErrorAtPos(getSourceFileOfNode(declarationList), declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); } - - if (languageVersion < ScriptTarget.ES6) { - if (isLet(declarationList)) { - return grammarErrorOnFirstToken(declarationList, Diagnostics.let_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); - } - else if (isConst(declarationList)) { - return grammarErrorOnFirstToken(declarationList, Diagnostics.const_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); - } - } } function allowLetAndConstDeclarations(parent: Node): boolean { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6a2338268e8..a6681b032ce 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -21,6 +21,12 @@ module ts { diagnosticMessage: DiagnosticMessage; typeName?: DeclarationName; } + + interface ScopeFrame { + names: Map; + previous: ScopeFrame; + } + type GetSymbolAccessibilityDiagnostic = (symbolAccesibilityResult: SymbolAccessiblityResult) => SymbolAccessibilityDiagnostic; interface EmitTextWriterWithSymbolWriter extends EmitTextWriter, SymbolWriter { @@ -353,7 +359,6 @@ module ts { var enclosingDeclaration: Node; var currentSourceFile: SourceFile; var reportedDeclarationError = false; - var emitJsDocComments = compilerOptions.removeComments ? function (declaration: Node) { } : writeJsDocComments; var emit = compilerOptions.stripInternal ? stripInternal : emitNode; @@ -1557,6 +1562,11 @@ module ts { var currentSourceFile: SourceFile; + var lastFrame: ScopeFrame; + var currentScopeNames: Map; + + var generatedBlockScopeNames: string[]; + var extendsEmitted = false; var tempCount = 0; var tempVariables: Identifier[]; @@ -1629,6 +1639,82 @@ module ts { writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM); return; + function enterScope(): boolean { + var names = currentScopeNames; + currentScopeNames = undefined; + if (names) { + lastFrame = { names, previous: lastFrame }; + return true; + } + return false; + } + + function exitScope(popFrame: boolean): void { + if (popFrame) { + currentScopeNames = lastFrame.names; + lastFrame = lastFrame.previous; + } + else { + currentScopeNames = undefined; + } + } + + function makeUniqueName(location: Node, baseName: string): string { + if (!isExistingName(location, baseName)) { + // use current name as is + return setGeneratedName(baseName); + } + + // First try '_name' + if (baseName.charCodeAt(0) !== CharacterCodes._) { + var baseName = "_" + baseName; + if (!isExistingName(location, baseName)) { + return setGeneratedName(baseName); + } + } + // Find the first unique '_name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + var i = 1; + while (true) { + name = baseName + i; + if (!isExistingName(location, name)) { + return setGeneratedName(name); + } + i++; + } + } + + function setGeneratedName(name: string): string { + if (!currentScopeNames) { + currentScopeNames = {}; + } + + return currentScopeNames[name] = name; + } + + + function isExistingName(location: Node, name: string) { + if (!resolver.isUnknownIdentifier(location, name)) { + return true; + } + + if (currentScopeNames && hasProperty(currentScopeNames, name)) { + return true; + } + + var frame = lastFrame; + while (frame) { + if (hasProperty(frame.names, name)) { + return true; + } + frame = frame.previous; + } + + return false; + } + function initializeEmitterWithSourceMaps() { var sourceMapDir: string; // The directory in which sourcemap will be @@ -1990,7 +2076,7 @@ module ts { function createTempVariable(location: Node, forLoopVariable?: boolean): Identifier { var name = forLoopVariable ? "_i" : undefined; while (true) { - if (name && resolver.isUnknownIdentifier(location, name)) { + if (name && !isExistingName(location, name)) { break; } // _a .. _h, _j ... _z, _0, _1, ... @@ -2338,7 +2424,20 @@ module ts { writeTextOfNode(currentSourceFile, node); } + function getBlockScopedVariableId(node: Identifier): number { + // return undefined for synthesized nodes + return node.parent && resolver.getBlockScopedVariableId(node); + } + function emitIdentifier(node: Identifier) { + var symbolId = getBlockScopedVariableId(node); + if (symbolId !== undefined && generatedBlockScopeNames) { + var text = generatedBlockScopeNames[symbolId]; + if (text) { + write(text); + return; + } + } if (!node.parent) { write(node.text); } @@ -2881,6 +2980,32 @@ module ts { emitEmbeddedStatement(node.statement); } + function emitStartOfVariableDeclarationList(decl: Node, startPos?: number): void { + var tokenKind = SyntaxKind.VarKeyword; + if (decl && languageVersion >= ScriptTarget.ES6) { + if (isLet(decl)) { + tokenKind = SyntaxKind.LetKeyword; + } + else if (isConst(decl)) { + tokenKind = SyntaxKind.ConstKeyword; + } + } + + if (startPos !== undefined) { + emitToken(tokenKind, startPos); + } + else { + switch (tokenKind) { + case SyntaxKind.VarKeyword: + return write("var "); + case SyntaxKind.LetKeyword: + return write("let "); + case SyntaxKind.ConstKeyword: + return write("const "); + } + } + } + function emitForStatement(node: ForStatement) { var endPos = emitToken(SyntaxKind.ForKeyword, node.pos); write(" "); @@ -2888,17 +3013,9 @@ module ts { if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { var variableDeclarationList = node.initializer; var declarations = variableDeclarationList.declarations; - if (declarations[0] && isLet(declarations[0])) { - emitToken(SyntaxKind.LetKeyword, endPos); - } - else if (declarations[0] && isConst(declarations[0])) { - emitToken(SyntaxKind.ConstKeyword, endPos); - } - else { - emitToken(SyntaxKind.VarKeyword, endPos); - } + emitStartOfVariableDeclarationList(declarations[0], endPos); write(" "); - emitCommaList(variableDeclarationList.declarations); + emitCommaList(declarations); } else if (node.initializer) { emit(node.initializer); @@ -2919,12 +3036,7 @@ module ts { var variableDeclarationList = node.initializer; if (variableDeclarationList.declarations.length >= 1) { var decl = variableDeclarationList.declarations[0]; - if (isLet(decl)) { - emitToken(SyntaxKind.LetKeyword, endPos); - } - else { - emitToken(SyntaxKind.VarKeyword, endPos); - } + emitStartOfVariableDeclarationList(decl, endPos); write(" "); emit(decl); } @@ -3075,6 +3187,8 @@ module ts { if (emitCount++) { write(", "); } + + renameNonTopLevelLetAndConst(name); if (name.parent && (name.parent.kind === SyntaxKind.VariableDeclaration || name.parent.kind === SyntaxKind.BindingElement)) { emitModuleMemberName(name.parent); } @@ -3184,7 +3298,7 @@ module ts { emitDestructuringAssignment(e, createElementAccess(value, createNumericLiteral(i))); } else { - if (i === elements.length - 1) { + if (i === elements.length - 1 && (e).expression.kind === SyntaxKind.Identifier) { value = ensureIdentifier(value); emitAssignment((e).expression, value); write(".slice(" + i + ")"); @@ -3286,22 +3400,75 @@ module ts { } } else { + renameNonTopLevelLetAndConst(node.name); emitModuleMemberName(node); emitOptional(" = ", node.initializer); } } + function getEnclosingBlockScopeContainer(node: Node): Node { + var current = node; + while (current) { + if (isAnyFunction(current)) { + return current.parent; + } + switch (current.kind) { + case SyntaxKind.CatchClause: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.SwitchKeyword: + return current.parent; + case SyntaxKind.Block: + if (isAnyFunction(current.parent)) { + return current.parent.parent; + } + else { + return current.parent; + } + case SyntaxKind.SourceFile: + return current; + } + + current = current.parent; + } + } + + function renameNonTopLevelLetAndConst(node: Node): void { + // do not rename if + // - language version is ES6+ + // - node is synthesized (does not have a parent) + // - 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 || + !node.parent || + (node.parent.kind !== SyntaxKind.VariableDeclaration && node.parent.kind !== SyntaxKind.BindingElement)) { + return; + } + + var combinedFlags = getCombinedNodeFlags(node.parent); + 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 + var list = getAncestor(node, SyntaxKind.VariableDeclarationList); + if (list.parent.kind === SyntaxKind.VariableStatement && list.parent.parent.kind === SyntaxKind.SourceFile) { + // do not rename variables that are defined on source file level + return; + } + + var generatedName = makeUniqueName(getEnclosingBlockScopeContainer(node), (node).text); + var symbolId = resolver.getBlockScopedVariableId(node); + if (!generatedBlockScopeNames) { + generatedBlockScopeNames = []; + } + generatedBlockScopeNames[symbolId] = generatedName; + } + function emitVariableStatement(node: VariableStatement) { if (!(node.flags & NodeFlags.Export)) { - if (isLet(node.declarationList)) { - write("let "); - } - else if (isConst(node.declarationList)) { - write("const "); - } - else { - write("var "); - } + emitStartOfVariableDeclarationList(node.declarationList); } emitCommaList(node.declarationList.declarations); write(";"); @@ -3472,6 +3639,8 @@ module ts { tempVariables = undefined; tempParameters = undefined; + var popFrame = enterScope() + // When targeting ES6, emit arrow function natively in ES6 if (shouldEmitAsArrowFunction(node)) { emitSignatureParametersForArrow(node); @@ -3563,6 +3732,9 @@ module ts { emitEnd(node); write(";"); } + + exitScope(popFrame); + tempCount = saveTempCount; tempVariables = saveTempVariables; tempParameters = saveTempParameters; @@ -3773,6 +3945,9 @@ module ts { tempCount = 0; tempVariables = undefined; tempParameters = undefined; + + var popFrame = enterScope(); + // Emit the constructor overload pinned comments forEach(node.members, member => { if (member.kind === SyntaxKind.Constructor && !(member).body) { @@ -3833,6 +4008,9 @@ module ts { if (ctor) { emitTrailingComments(ctor); } + + exitScope(popFrame); + tempCount = saveTempCount; tempVariables = saveTempVariables; tempParameters = saveTempParameters; @@ -3962,7 +4140,11 @@ module ts { var saveTempVariables = tempVariables; tempCount = 0; tempVariables = undefined; + var popFrame = enterScope(); + emit(node.body); + + exitScope(popFrame); tempCount = saveTempCount; tempVariables = saveTempVariables; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 12c5af21e12..b4e848f1e52 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1138,6 +1138,7 @@ module ts { // Returns the constant value this property access resolves to, or 'undefined' for a non-constant getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isUnknownIdentifier(location: Node, name: string): boolean; + getBlockScopedVariableId(node: Identifier): number; } export const enum SymbolFlags {