Skip to content

Commit 8abf4ff

Browse files
committed
Merge pull request #2161 from Microsoft/letConstES5Minus
Downlevel emit for let\const
2 parents 419d379 + 3b3a94c commit 8abf4ff

File tree

118 files changed

+2958
-326
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+2958
-326
lines changed

src/compiler/binder.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ module ts {
6767

6868
if (!file.locals) {
6969
file.locals = {};
70-
container = blockScopeContainer = file;
70+
container = file;
71+
setBlockScopeContainer(file, /*cleanLocals*/ false);
7172
bind(file);
7273
file.symbolCount = symbolCount;
7374
}
@@ -77,6 +78,13 @@ module ts {
7778
return new Symbol(flags, name);
7879
}
7980

81+
function setBlockScopeContainer(node: Node, cleanLocals: boolean) {
82+
blockScopeContainer = node;
83+
if (cleanLocals) {
84+
blockScopeContainer.locals = undefined;
85+
}
86+
}
87+
8088
function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolKind: SymbolFlags) {
8189
symbol.flags |= symbolKind;
8290
if (!symbol.declarations) symbol.declarations = [];
@@ -236,7 +244,13 @@ module ts {
236244
}
237245

238246
if (isBlockScopeContainer) {
239-
blockScopeContainer = node;
247+
// in incremental scenarios we might reuse nodes that already have locals being allocated
248+
// during the bind step these locals should be dropped to prevent using stale data.
249+
// locals should always be dropped unless they were previously initialized by the binder
250+
// these cases are:
251+
// - node has locals (symbolKind & HasLocals) !== 0
252+
// - node is a source file
253+
setBlockScopeContainer(node, /*cleanLocals*/ (symbolKind & SymbolFlags.HasLocals) === 0 && node.kind !== SyntaxKind.SourceFile);
240254
}
241255

242256
forEachChild(node, bind);

src/compiler/checker.ts

+98-29
Original file line numberDiff line numberDiff line change
@@ -5075,10 +5075,63 @@ module ts {
50755075

50765076
checkCollisionWithCapturedSuperVariable(node, node);
50775077
checkCollisionWithCapturedThisVariable(node, node);
5078+
checkBlockScopedBindingCapturedInLoop(node, symbol);
50785079

50795080
return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
50805081
}
50815082

5083+
function isInsideFunction(node: Node, threshold: Node): boolean {
5084+
var current = node;
5085+
while (current && current !== threshold) {
5086+
if (isAnyFunction(current)) {
5087+
return true;
5088+
}
5089+
current = current.parent;
5090+
}
5091+
5092+
return false;
5093+
}
5094+
5095+
function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void {
5096+
if (languageVersion >= ScriptTarget.ES6 ||
5097+
(symbol.flags & SymbolFlags.BlockScopedVariable) === 0 ||
5098+
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) {
5099+
return;
5100+
}
5101+
5102+
// - check if binding is used in some function
5103+
// (stop the walk when reaching container of binding declaration)
5104+
// - if first check succeeded - check if variable is declared inside the loop
5105+
5106+
// nesting structure:
5107+
// (variable declaration or binding element) -> variable declaration list -> container
5108+
var container: Node = symbol.valueDeclaration;
5109+
while (container.kind !== SyntaxKind.VariableDeclarationList) {
5110+
container = container.parent;
5111+
}
5112+
// get the parent of variable declaration list
5113+
container = container.parent;
5114+
if (container.kind === SyntaxKind.VariableStatement) {
5115+
// if parent is variable statement - get its parent
5116+
container = container.parent;
5117+
}
5118+
5119+
var inFunction = isInsideFunction(node.parent, container);
5120+
5121+
var current = container;
5122+
while (current && !nodeStartsNewLexicalEnvironment(current)) {
5123+
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
5124+
if (inFunction) {
5125+
grammarErrorOnFirstToken(current, Diagnostics.Loop_contains_block_scoped_variable_0_referenced_by_a_function_in_the_loop_This_is_only_supported_in_ECMAScript_6_or_higher, declarationNameToString(node));
5126+
}
5127+
// mark value declaration so during emit they can have a special handling
5128+
getNodeLinks(<VariableDeclaration>symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
5129+
break;
5130+
}
5131+
current = current.parent;
5132+
}
5133+
}
5134+
50825135
function captureLexicalThis(node: Node, container: Node): void {
50835136
var classNode = container.parent && container.parent.kind === SyntaxKind.ClassDeclaration ? container.parent : undefined;
50845137
getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis;
@@ -10467,25 +10520,8 @@ module ts {
1046710520
}
1046810521

1046910522
function makeUniqueName(baseName: string): string {
10470-
// First try '_name'
10471-
if (baseName.charCodeAt(0) !== CharacterCodes._) {
10472-
var baseName = "_" + baseName;
10473-
if (!isExistingName(baseName)) {
10474-
return generatedNames[baseName] = baseName;
10475-
}
10476-
}
10477-
// Find the first unique '_name_n', where n is a positive number
10478-
if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) {
10479-
baseName += "_";
10480-
}
10481-
var i = 1;
10482-
while (true) {
10483-
name = baseName + i;
10484-
if (!isExistingName(name)) {
10485-
return generatedNames[name] = name;
10486-
}
10487-
i++;
10488-
}
10523+
var name = generateUniqueName(baseName, isExistingName);
10524+
return generatedNames[name] = name;
1048910525
}
1049010526

1049110527
function assignGeneratedName(node: Node, name: string) {
@@ -10686,6 +10722,46 @@ module ts {
1068610722
!hasProperty(getGeneratedNamesForSourceFile(getSourceFile(location)), name);
1068710723
}
1068810724

10725+
function getBlockScopedVariableId(n: Identifier): number {
10726+
Debug.assert(!nodeIsSynthesized(n));
10727+
10728+
// ignore name parts of property access expressions
10729+
if (n.parent.kind === SyntaxKind.PropertyAccessExpression &&
10730+
(<PropertyAccessExpression>n.parent).name === n) {
10731+
return undefined;
10732+
}
10733+
10734+
// ignore property names in object binding patterns
10735+
if (n.parent.kind === SyntaxKind.BindingElement &&
10736+
(<BindingElement>n.parent).propertyName === n) {
10737+
return undefined;
10738+
}
10739+
10740+
// for names in variable declarations and binding elements try to short circuit and fetch symbol from the node
10741+
var declarationSymbol: Symbol =
10742+
(n.parent.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>n.parent).name === n) ||
10743+
n.parent.kind === SyntaxKind.BindingElement
10744+
? getSymbolOfNode(n.parent)
10745+
: undefined;
10746+
10747+
var symbol = declarationSymbol ||
10748+
getNodeLinks(n).resolvedSymbol ||
10749+
resolveName(n, n.text, SymbolFlags.BlockScopedVariable | SymbolFlags.Import, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined);
10750+
10751+
var isLetOrConst =
10752+
symbol &&
10753+
(symbol.flags & SymbolFlags.BlockScopedVariable) &&
10754+
symbol.valueDeclaration.parent.kind !== SyntaxKind.CatchClause;
10755+
10756+
if (isLetOrConst) {
10757+
// side-effect of calling this method:
10758+
// assign id to symbol if it was not yet set
10759+
getSymbolLinks(symbol);
10760+
return symbol.id;
10761+
}
10762+
return undefined;
10763+
}
10764+
1068910765
function createResolver(): EmitResolver {
1069010766
return {
1069110767
getGeneratedNameForNode,
@@ -10702,6 +10778,7 @@ module ts {
1070210778
isEntityNameVisible,
1070310779
getConstantValue,
1070410780
isUnknownIdentifier,
10781+
getBlockScopedVariableId,
1070510782
};
1070610783
}
1070710784

@@ -11443,7 +11520,8 @@ module ts {
1144311520
if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) {
1144411521
return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer);
1144511522
}
11446-
if (isConst(node)) {
11523+
// const declarations should not be initialized in for-in for-of statements
11524+
if (isConst(node) && node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) {
1144711525
return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized);
1144811526
}
1144911527
}
@@ -11485,15 +11563,6 @@ module ts {
1148511563
if (!declarationList.declarations.length) {
1148611564
return grammarErrorAtPos(getSourceFileOfNode(declarationList), declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty);
1148711565
}
11488-
11489-
if (languageVersion < ScriptTarget.ES6) {
11490-
if (isLet(declarationList)) {
11491-
return grammarErrorOnFirstToken(declarationList, Diagnostics.let_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher);
11492-
}
11493-
else if (isConst(declarationList)) {
11494-
return grammarErrorOnFirstToken(declarationList, Diagnostics.const_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher);
11495-
}
11496-
}
1149711566
}
1149811567

1149911568
function allowLetAndConstDeclarations(parent: Node): boolean {

src/compiler/diagnosticInformationMap.generated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ module ts {
397397
Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2: { code: 4077, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using name '{1}' from private module '{2}'." },
398398
Parameter_0_of_exported_function_has_or_is_using_private_name_1: { code: 4078, category: DiagnosticCategory.Error, key: "Parameter '{0}' of exported function has or is using private name '{1}'." },
399399
Exported_type_alias_0_has_or_is_using_private_name_1: { code: 4081, category: DiagnosticCategory.Error, key: "Exported type alias '{0}' has or is using private name '{1}'." },
400+
Loop_contains_block_scoped_variable_0_referenced_by_a_function_in_the_loop_This_is_only_supported_in_ECMAScript_6_or_higher: { code: 4091, category: DiagnosticCategory.Error, key: "Loop contains block-scoped variable '{0}' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher." },
400401
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
401402
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
402403
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,10 @@
15811581
"category": "Error",
15821582
"code": 4081
15831583
},
1584+
"Loop contains block-scoped variable '{0}' referenced by a function in the loop. This is only supported in ECMAScript 6 or higher.": {
1585+
"category": "Error",
1586+
"code": 4091
1587+
},
15841588
"The current host does not support the '{0}' option.": {
15851589
"category": "Error",
15861590
"code": 5001

0 commit comments

Comments
 (0)