From 5f2588f018040f5fcd2a5cb9e31d90fd72b4c597 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sun, 15 Feb 2015 18:44:25 -0800 Subject: [PATCH] show error if block scoped variable declared in the loop is captured in closure --- src/compiler/checker.ts | 45 +++++++++++++++++++ .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 ++ src/compiler/types.ts | 1 + 4 files changed, 51 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4fd925947f4..211331579cd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4905,10 +4905,55 @@ module ts { checkCollisionWithCapturedSuperVariable(node, node); checkCollisionWithCapturedThisVariable(node, node); + checkBlockScopedBindingCapturedInLoop(node, symbol); return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node); } + function isNameScopeBoundary(n: Node): boolean { + return isAnyFunction(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile; + } + + function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void { + if (languageVersion >= ScriptTarget.ES6 || (symbol.flags & SymbolFlags.BlockScopedVariable) === 0) { + return; + } + + // - check if binding is used in some function + // (stop the walk when reaching container of binding declaration) + // - if first check succeeded - check if variable is declared inside the loop + + // var decl -> var decl list -> parent + var container = (symbol.valueDeclaration).parent.parent; + if (container.kind === SyntaxKind.VariableStatement) { + container = container.parent; + } + + var inFunction = false; + var current = node.parent; + while (current && current !== container) { + if (isAnyFunction(current)) { + inFunction = true; + break; + } + current = current.parent; + } + + if (!inFunction) { + return; + } + + var current: Node = container; + while (current && !isNameScopeBoundary(current)) { + if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { + getNodeLinks(current).flags |= NodeCheckFlags.BlockScopedBindingCapturedInLoop; + grammarErrorOnFirstToken(current, Diagnostics.Code_in_the_loop_captures_block_scoped_variable_0_in_closure_This_is_natively_supported_in_ECMAScript_6_or_higher, declarationNameToString(node)); + break; + } + current = current.parent; + } + } + function captureLexicalThis(node: Node, container: Node): void { var classNode = container.parent && container.parent.kind === SyntaxKind.ClassDeclaration ? container.parent : undefined; getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index c6061e3570a..a6e8f25e0a2 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -382,6 +382,7 @@ module ts { Property_0_does_not_exist_on_const_enum_1: { code: 4088, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." }, let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 4089, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." }, Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1: { code: 4090, category: DiagnosticCategory.Error, key: "Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{1}'." }, + Code_in_the_loop_captures_block_scoped_variable_0_in_closure_This_is_natively_supported_in_ECMAScript_6_or_higher: { code: 4091, category: DiagnosticCategory.Error, key: "Code in the loop captures block-scoped variable '{0}' in closure. This is natively supported in ECMAScript 6 or higher." }, The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." }, 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." }, Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index fdeec58a705..f3622218eef 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1521,6 +1521,10 @@ "category": "Error", "code": 4090 }, + "Code in the loop captures block-scoped variable '{0}' in closure. This is natively supported in ECMAScript 6 or higher.": { + "category": "Error", + "code": 4091 + }, "The current host does not support the '{0}' option.": { "category": "Error", "code": 5001 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b4e848f1e52..940a4a6e5a4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1262,6 +1262,7 @@ module ts { // Values for enum members have been computed, and any errors have been reported for them. EnumValuesComputed = 0x00000080, + BlockScopedBindingCapturedInLoop = 0x00000100, } export interface NodeLinks {