From 48b24343b19769c175796a5fa9f769cf2bf44f1d Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 13 Oct 2015 00:06:59 -0700 Subject: [PATCH] use isBlockScopedNameDeclaredBeforeUse for block scoped variables and enums --- src/compiler/checker.ts | 159 +++++++----------- ...lockScopedVariablesUseBeforeDef.errors.txt | 45 ++++- .../blockScopedVariablesUseBeforeDef.js | 48 +++++- .../blockScopedVariablesUseBeforeDef.ts | 31 +++- 4 files changed, 183 insertions(+), 100 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0213f594f5b..9db3d49b9af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -383,42 +383,72 @@ namespace ts { // return undefined if we can't find a symbol. } - const enum RelativeLocation { - Unknown, - SameFileLocatedBefore, - SameFileLocatedAfter, - DifferentFilesLocatedBefore, - DifferentFilesLocatedAfter, - } - - function isLocatedBefore(origin: Node, target: Node): boolean { - switch (getRelativeLocation(origin, target)) { - // unknown is returned with nodes are in different files and order cannot be determined based on compilation settings - // optimistically assume this is ok - case RelativeLocation.Unknown: - case RelativeLocation.SameFileLocatedBefore: - case RelativeLocation.DifferentFilesLocatedBefore: + function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { + const declarationFile = getSourceFileOfNode(declaration); + const useFile = getSourceFileOfNode(usage); + if (declarationFile !== useFile) { + if (modulekind || (!compilerOptions.outFile && !compilerOptions.out)) { + // nodes are in different files and order cannot be determines return true; - default: - return false; - } - } + } - /** gets relative location of target comparing to origin **/ - function getRelativeLocation(origin: Node, target: Node): RelativeLocation { - let file1 = getSourceFileOfNode(origin); - let file2 = getSourceFileOfNode(target); - if (file1 === file2) { - return origin.pos > target.pos ? RelativeLocation.SameFileLocatedBefore : RelativeLocation.SameFileLocatedAfter; + const sourceFiles = host.getSourceFiles(); + return indexOf(sourceFiles, declarationFile) <= indexOf(sourceFiles, useFile); } - if (modulekind || (!compilerOptions.outFile && !compilerOptions.out)) { - // nodes are in different files and order cannot be determines - return RelativeLocation.Unknown; + if (declaration.pos <= usage.pos) { + // declaration is before usage + // still might be illegal if usage is in the initializer of the variable declaration + return declaration.kind !== SyntaxKind.VariableDeclaration || + !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration, usage); } - let sourceFiles = host.getSourceFiles(); - return sourceFiles.indexOf(file1) > sourceFiles.indexOf(file2) ? RelativeLocation.DifferentFilesLocatedBefore : RelativeLocation.DifferentFilesLocatedAfter; + // declaration is after usage + // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) + return isUsedInFunctionOrNonStaticProperty(declaration, usage); + + function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { + const container = getEnclosingBlockScopeContainer(declaration); + + if (declaration.parent.parent.kind === SyntaxKind.VariableStatement || + declaration.parent.parent.kind === SyntaxKind.ForStatement) { + // variable statement/for statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + return isSameScopeDescendentOf(usage, declaration, container); + } + else if (declaration.parent.parent.kind === SyntaxKind.ForOfStatement || + declaration.parent.parent.kind === SyntaxKind.ForInStatement) { + // ForIn/ForOf case - use site should not be used in expression part + let expression = (declaration.parent.parent).expression; + return isSameScopeDescendentOf(usage, expression, container); + } + } + + function isUsedInFunctionOrNonStaticProperty(declaration: Declaration, usage: Node): boolean { + const container = getEnclosingBlockScopeContainer(declaration); + let current = usage; + while (current) { + if (current === container) { + return false; + } + + if (isFunctionLike(current)) { + return true; + } + + const initializerOfNonStaticProperty = current.parent && + current.parent.kind === SyntaxKind.PropertyDeclaration && + (current.parent.flags & NodeFlags.Static) === 0 && + (current.parent).initializer === current; + + if (initializerOfNonStaticProperty) { + return true; + } + + current = current.parent; + } + return false; + } } // Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and @@ -649,67 +679,7 @@ namespace ts { Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); - // first check if usage is lexically located after the declaration - let isUsedBeforeDeclaration = false; - switch (getRelativeLocation(declaration, errorLocation)) { - case RelativeLocation.DifferentFilesLocatedBefore: - isUsedBeforeDeclaration = true; - break; - case RelativeLocation.SameFileLocatedBefore: - // try to detect if forward reference to block scoped variable is inside function - // such forward references are permitted (they are still technically can be incorrect (i.e. in case of IIFEs) - // but detecting these case is more complicated task) - const declarationContainer = getEnclosingBlockScopeContainer(declaration); - let current = errorLocation; - while (current) { - if (current === declarationContainer) { - isUsedBeforeDeclaration = true; - break; - } - - if (isFunctionLike(current)) { - break; - } - - const isInitializerOfNonStaticProperty = - current.parent && - current.parent.kind === SyntaxKind.PropertyDeclaration && - (current.parent.flags & NodeFlags.Static) === 0 && - (current.parent).initializer === current; - - if (isInitializerOfNonStaticProperty) { - break; - } - current = current.parent; - } - break; - case RelativeLocation.SameFileLocatedAfter: - // lexical check succeeded however code still can be illegal. - // - block scoped variables cannot be used in its initializers - // let x = x; // illegal but usage is lexically after definition - // - in ForIn/ForOf statements variable cannot be contained in expression part - // for (let x in x) - // for (let x of x) - - // climb up to the variable declaration skipping binding patterns - let variableDeclaration = getAncestor(declaration, SyntaxKind.VariableDeclaration); - let container = getEnclosingBlockScopeContainer(variableDeclaration); - - if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement || - variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) { - // variable statement/for statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container); - } - else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement || - variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { - // ForIn/ForOf case - use site should not be used in expression part - let expression = (variableDeclaration.parent.parent).expression; - isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container); - } - break; - } - if (isUsedBeforeDeclaration) { + if (!isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); } } @@ -13233,6 +13203,8 @@ namespace ts { let nodeLinks = getNodeLinks(node); if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let enumSymbol = getSymbolOfNode(node); let enumType = getDeclaredTypeOfSymbol(enumSymbol); let autoValue = 0; // set to undefined when enum member is non-constant @@ -13270,8 +13242,6 @@ namespace ts { getNodeLinks(member).enumMemberValue = autoValue++; } } - - nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; } function computeConstantValueForEnumMemberInitializer(initializer: Expression, enumType: Type, enumIsConst: boolean, ambient: boolean): number { @@ -13411,12 +13381,13 @@ namespace ts { } // illegal case: forward reference - if (isLocatedBefore(propertyDecl, member)) { + if (!isBlockScopedNameDeclaredBeforeUse(propertyDecl, member)) { reportError = false; error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); return undefined; } + computeEnumMemberValues(propertyDecl.parent); return getNodeLinks(propertyDecl).enumMemberValue; } } diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.errors.txt b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.errors.txt index a2bae3307a1..15697e9cf61 100644 --- a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.errors.txt +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.errors.txt @@ -1,8 +1,18 @@ -tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(53,20): error TS2448: Block-scoped variable 'x' used before its declaration. -tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(60,20): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(2,13): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(58,20): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(65,20): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(100,12): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(105,20): error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums. -==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (2 errors) ==== +==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (5 errors) ==== + function foo0() { + let a = x; + ~ +!!! error TS2448: Block-scoped variable 'x' used before its declaration. + let x; + } + function foo1() { let a = () => x; let x; @@ -90,4 +100,31 @@ tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(60,20): error TS2448: B } } let x; - } \ No newline at end of file + } + + function foo13() { + let a = { + get a() { return x } + } + let x + } + + function foo14() { + let a = { + a: x + ~ +!!! error TS2448: Block-scoped variable 'x' used before its declaration. + } + let x + } + + const enum A { X = B.Y } + ~~~ +!!! error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums. + + const enum B { Y } + + function foo15() { + const enum A1 { X = B1.Y } + } + const enum B1 { Y } \ No newline at end of file diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js index 212b9d8b7b8..2030b0eed59 100644 --- a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js @@ -1,4 +1,9 @@ //// [blockScopedVariablesUseBeforeDef.ts] +function foo0() { + let a = x; + let x; +} + function foo1() { let a = () => x; let x; @@ -82,9 +87,36 @@ function foo12() { } } let x; -} +} + +function foo13() { + let a = { + get a() { return x } + } + let x +} + +function foo14() { + let a = { + a: x + } + let x +} + +const enum A { X = B.Y } + +const enum B { Y } + +function foo15() { + const enum A1 { X = B1.Y } +} +const enum B1 { Y } //// [blockScopedVariablesUseBeforeDef.js] +function foo0() { + var a = x; + var x; +} function foo1() { var a = function () { return x; }; var x; @@ -179,3 +211,17 @@ function foo12() { } var x; } +function foo13() { + var a = { + get a() { return x; } + }; + var x; +} +function foo14() { + var a = { + a: x + }; + var x; +} +function foo15() { +} diff --git a/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts b/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts index 287f5ae117e..1cade31d238 100644 --- a/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts +++ b/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts @@ -1,3 +1,9 @@ +// @target: ES5 +function foo0() { + let a = x; + let x; +} + function foo1() { let a = () => x; let x; @@ -81,4 +87,27 @@ function foo12() { } } let x; -} \ No newline at end of file +} + +function foo13() { + let a = { + get a() { return x } + } + let x +} + +function foo14() { + let a = { + a: x + } + let x +} + +const enum A { X = B.Y } + +const enum B { Y } + +function foo15() { + const enum A1 { X = B1.Y } +} +const enum B1 { Y } \ No newline at end of file