Skip to content

Commit 48b2434

Browse files
committed
use isBlockScopedNameDeclaredBeforeUse for block scoped variables and enums
1 parent eb3b91c commit 48b2434

File tree

4 files changed

+182
-99
lines changed

4 files changed

+182
-99
lines changed

src/compiler/checker.ts

Lines changed: 64 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -383,42 +383,72 @@ namespace ts {
383383
// return undefined if we can't find a symbol.
384384
}
385385

386-
const enum RelativeLocation {
387-
Unknown,
388-
SameFileLocatedBefore,
389-
SameFileLocatedAfter,
390-
DifferentFilesLocatedBefore,
391-
DifferentFilesLocatedAfter,
392-
}
393-
394-
function isLocatedBefore(origin: Node, target: Node): boolean {
395-
switch (getRelativeLocation(origin, target)) {
396-
// unknown is returned with nodes are in different files and order cannot be determined based on compilation settings
397-
// optimistically assume this is ok
398-
case RelativeLocation.Unknown:
399-
case RelativeLocation.SameFileLocatedBefore:
400-
case RelativeLocation.DifferentFilesLocatedBefore:
386+
function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean {
387+
const declarationFile = getSourceFileOfNode(declaration);
388+
const useFile = getSourceFileOfNode(usage);
389+
if (declarationFile !== useFile) {
390+
if (modulekind || (!compilerOptions.outFile && !compilerOptions.out)) {
391+
// nodes are in different files and order cannot be determines
401392
return true;
402-
default:
403-
return false;
393+
}
394+
395+
const sourceFiles = host.getSourceFiles();
396+
return indexOf(sourceFiles, declarationFile) <= indexOf(sourceFiles, useFile);
404397
}
405-
}
406398

407-
/** gets relative location of target comparing to origin **/
408-
function getRelativeLocation(origin: Node, target: Node): RelativeLocation {
409-
let file1 = getSourceFileOfNode(origin);
410-
let file2 = getSourceFileOfNode(target);
411-
if (file1 === file2) {
412-
return origin.pos > target.pos ? RelativeLocation.SameFileLocatedBefore : RelativeLocation.SameFileLocatedAfter;
399+
if (declaration.pos <= usage.pos) {
400+
// declaration is before usage
401+
// still might be illegal if usage is in the initializer of the variable declaration
402+
return declaration.kind !== SyntaxKind.VariableDeclaration ||
403+
!isImmediatelyUsedInInitializerOfBlockScopedVariable(<VariableDeclaration>declaration, usage);
413404
}
414405

415-
if (modulekind || (!compilerOptions.outFile && !compilerOptions.out)) {
416-
// nodes are in different files and order cannot be determines
417-
return RelativeLocation.Unknown;
406+
// declaration is after usage
407+
// can be legal if usage is deferred (i.e. inside function or in initializer of instance property)
408+
return isUsedInFunctionOrNonStaticProperty(declaration, usage);
409+
410+
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
411+
const container = getEnclosingBlockScopeContainer(declaration);
412+
413+
if (declaration.parent.parent.kind === SyntaxKind.VariableStatement ||
414+
declaration.parent.parent.kind === SyntaxKind.ForStatement) {
415+
// variable statement/for statement case,
416+
// use site should not be inside variable declaration (initializer of declaration or binding element)
417+
return isSameScopeDescendentOf(usage, declaration, container);
418+
}
419+
else if (declaration.parent.parent.kind === SyntaxKind.ForOfStatement ||
420+
declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
421+
// ForIn/ForOf case - use site should not be used in expression part
422+
let expression = (<ForInStatement | ForOfStatement>declaration.parent.parent).expression;
423+
return isSameScopeDescendentOf(usage, expression, container);
424+
}
418425
}
419426

420-
let sourceFiles = host.getSourceFiles();
421-
return sourceFiles.indexOf(file1) > sourceFiles.indexOf(file2) ? RelativeLocation.DifferentFilesLocatedBefore : RelativeLocation.DifferentFilesLocatedAfter;
427+
function isUsedInFunctionOrNonStaticProperty(declaration: Declaration, usage: Node): boolean {
428+
const container = getEnclosingBlockScopeContainer(declaration);
429+
let current = usage;
430+
while (current) {
431+
if (current === container) {
432+
return false;
433+
}
434+
435+
if (isFunctionLike(current)) {
436+
return true;
437+
}
438+
439+
const initializerOfNonStaticProperty = current.parent &&
440+
current.parent.kind === SyntaxKind.PropertyDeclaration &&
441+
(current.parent.flags & NodeFlags.Static) === 0 &&
442+
(<PropertyDeclaration>current.parent).initializer === current;
443+
444+
if (initializerOfNonStaticProperty) {
445+
return true;
446+
}
447+
448+
current = current.parent;
449+
}
450+
return false;
451+
}
422452
}
423453

424454
// 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 {
649679

650680
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
651681

652-
// first check if usage is lexically located after the declaration
653-
let isUsedBeforeDeclaration = false;
654-
switch (getRelativeLocation(declaration, errorLocation)) {
655-
case RelativeLocation.DifferentFilesLocatedBefore:
656-
isUsedBeforeDeclaration = true;
657-
break;
658-
case RelativeLocation.SameFileLocatedBefore:
659-
// try to detect if forward reference to block scoped variable is inside function
660-
// such forward references are permitted (they are still technically can be incorrect (i.e. in case of IIFEs)
661-
// but detecting these case is more complicated task)
662-
const declarationContainer = getEnclosingBlockScopeContainer(declaration);
663-
let current = errorLocation;
664-
while (current) {
665-
if (current === declarationContainer) {
666-
isUsedBeforeDeclaration = true;
667-
break;
668-
}
669-
670-
if (isFunctionLike(current)) {
671-
break;
672-
}
673-
674-
const isInitializerOfNonStaticProperty =
675-
current.parent &&
676-
current.parent.kind === SyntaxKind.PropertyDeclaration &&
677-
(current.parent.flags & NodeFlags.Static) === 0 &&
678-
(<PropertyDeclaration>current.parent).initializer === current;
679-
680-
if (isInitializerOfNonStaticProperty) {
681-
break;
682-
}
683-
current = current.parent;
684-
}
685-
break;
686-
case RelativeLocation.SameFileLocatedAfter:
687-
// lexical check succeeded however code still can be illegal.
688-
// - block scoped variables cannot be used in its initializers
689-
// let x = x; // illegal but usage is lexically after definition
690-
// - in ForIn/ForOf statements variable cannot be contained in expression part
691-
// for (let x in x)
692-
// for (let x of x)
693-
694-
// climb up to the variable declaration skipping binding patterns
695-
let variableDeclaration = <VariableDeclaration>getAncestor(declaration, SyntaxKind.VariableDeclaration);
696-
let container = getEnclosingBlockScopeContainer(variableDeclaration);
697-
698-
if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement ||
699-
variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) {
700-
// variable statement/for statement case,
701-
// use site should not be inside variable declaration (initializer of declaration or binding element)
702-
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container);
703-
}
704-
else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement ||
705-
variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) {
706-
// ForIn/ForOf case - use site should not be used in expression part
707-
let expression = (<ForInStatement | ForOfStatement>variableDeclaration.parent.parent).expression;
708-
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container);
709-
}
710-
break;
711-
}
712-
if (isUsedBeforeDeclaration) {
682+
if (!isBlockScopedNameDeclaredBeforeUse(<Declaration>getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) {
713683
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
714684
}
715685
}
@@ -13233,6 +13203,8 @@ namespace ts {
1323313203
let nodeLinks = getNodeLinks(node);
1323413204

1323513205
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
13206+
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
13207+
1323613208
let enumSymbol = getSymbolOfNode(node);
1323713209
let enumType = getDeclaredTypeOfSymbol(enumSymbol);
1323813210
let autoValue = 0; // set to undefined when enum member is non-constant
@@ -13270,8 +13242,6 @@ namespace ts {
1327013242
getNodeLinks(member).enumMemberValue = autoValue++;
1327113243
}
1327213244
}
13273-
13274-
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
1327513245
}
1327613246

1327713247
function computeConstantValueForEnumMemberInitializer(initializer: Expression, enumType: Type, enumIsConst: boolean, ambient: boolean): number {
@@ -13411,12 +13381,13 @@ namespace ts {
1341113381
}
1341213382

1341313383
// illegal case: forward reference
13414-
if (isLocatedBefore(propertyDecl, member)) {
13384+
if (!isBlockScopedNameDeclaredBeforeUse(propertyDecl, member)) {
1341513385
reportError = false;
1341613386
error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
1341713387
return undefined;
1341813388
}
1341913389

13390+
computeEnumMemberValues(<EnumDeclaration>propertyDecl.parent);
1342013391
return <number>getNodeLinks(propertyDecl).enumMemberValue;
1342113392
}
1342213393
}

tests/baselines/reference/blockScopedVariablesUseBeforeDef.errors.txt

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(53,20): error TS2448: Block-scoped variable 'x' used before its declaration.
2-
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(60,20): error TS2448: Block-scoped variable 'x' used before its declaration.
1+
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(2,13): error TS2448: Block-scoped variable 'x' used before its declaration.
2+
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(58,20): error TS2448: Block-scoped variable 'x' used before its declaration.
3+
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(65,20): error TS2448: Block-scoped variable 'x' used before its declaration.
4+
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(100,12): error TS2448: Block-scoped variable 'x' used before its declaration.
5+
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.
36

47

5-
==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (2 errors) ====
8+
==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (5 errors) ====
9+
function foo0() {
10+
let a = x;
11+
~
12+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
13+
let x;
14+
}
15+
616
function foo1() {
717
let a = () => x;
818
let x;
@@ -90,4 +100,31 @@ tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(60,20): error TS2448: B
90100
}
91101
}
92102
let x;
93-
}
103+
}
104+
105+
function foo13() {
106+
let a = {
107+
get a() { return x }
108+
}
109+
let x
110+
}
111+
112+
function foo14() {
113+
let a = {
114+
a: x
115+
~
116+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
117+
}
118+
let x
119+
}
120+
121+
const enum A { X = B.Y }
122+
~~~
123+
!!! error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.
124+
125+
const enum B { Y }
126+
127+
function foo15() {
128+
const enum A1 { X = B1.Y }
129+
}
130+
const enum B1 { Y }

tests/baselines/reference/blockScopedVariablesUseBeforeDef.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
//// [blockScopedVariablesUseBeforeDef.ts]
2+
function foo0() {
3+
let a = x;
4+
let x;
5+
}
6+
27
function foo1() {
38
let a = () => x;
49
let x;
@@ -82,9 +87,36 @@ function foo12() {
8287
}
8388
}
8489
let x;
85-
}
90+
}
91+
92+
function foo13() {
93+
let a = {
94+
get a() { return x }
95+
}
96+
let x
97+
}
98+
99+
function foo14() {
100+
let a = {
101+
a: x
102+
}
103+
let x
104+
}
105+
106+
const enum A { X = B.Y }
107+
108+
const enum B { Y }
109+
110+
function foo15() {
111+
const enum A1 { X = B1.Y }
112+
}
113+
const enum B1 { Y }
86114

87115
//// [blockScopedVariablesUseBeforeDef.js]
116+
function foo0() {
117+
var a = x;
118+
var x;
119+
}
88120
function foo1() {
89121
var a = function () { return x; };
90122
var x;
@@ -179,3 +211,17 @@ function foo12() {
179211
}
180212
var x;
181213
}
214+
function foo13() {
215+
var a = {
216+
get a() { return x; }
217+
};
218+
var x;
219+
}
220+
function foo14() {
221+
var a = {
222+
a: x
223+
};
224+
var x;
225+
}
226+
function foo15() {
227+
}

tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
// @target: ES5
2+
function foo0() {
3+
let a = x;
4+
let x;
5+
}
6+
17
function foo1() {
28
let a = () => x;
39
let x;
@@ -81,4 +87,27 @@ function foo12() {
8187
}
8288
}
8389
let x;
84-
}
90+
}
91+
92+
function foo13() {
93+
let a = {
94+
get a() { return x }
95+
}
96+
let x
97+
}
98+
99+
function foo14() {
100+
let a = {
101+
a: x
102+
}
103+
let x
104+
}
105+
106+
const enum A { X = B.Y }
107+
108+
const enum B { Y }
109+
110+
function foo15() {
111+
const enum A1 { X = B1.Y }
112+
}
113+
const enum B1 { Y }

0 commit comments

Comments
 (0)