Skip to content

Commit 1244477

Browse files
committed
Merge pull request #5233 from Microsoft/relaxedUseBeforeDef
allow forward references to block scoped variables from functions
2 parents 99d448a + 0465f1b commit 1244477

10 files changed

+540
-61
lines changed

src/compiler/checker.ts

+64-39
Original file line numberDiff line numberDiff line change
@@ -383,20 +383,72 @@ namespace ts {
383383
// return undefined if we can't find a symbol.
384384
}
385385

386-
/** Returns true if node1 is defined before node 2**/
387-
function isDefinedBefore(node1: Node, node2: Node): boolean {
388-
let file1 = getSourceFileOfNode(node1);
389-
let file2 = getSourceFileOfNode(node2);
390-
if (file1 === file2) {
391-
return node1.pos <= node2.pos;
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
392+
return true;
393+
}
394+
395+
const sourceFiles = host.getSourceFiles();
396+
return indexOf(sourceFiles, declarationFile) <= indexOf(sourceFiles, useFile);
392397
}
393398

394-
if (!compilerOptions.outFile && !compilerOptions.out) {
395-
return true;
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);
404+
}
405+
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+
}
396425
}
397426

398-
let sourceFiles = host.getSourceFiles();
399-
return sourceFiles.indexOf(file1) <= sourceFiles.indexOf(file2);
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+
}
400452
}
401453

402454
// Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and
@@ -627,34 +679,7 @@ namespace ts {
627679

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

630-
// first check if usage is lexically located after the declaration
631-
let isUsedBeforeDeclaration = !isDefinedBefore(declaration, errorLocation);
632-
if (!isUsedBeforeDeclaration) {
633-
// lexical check succeeded however code still can be illegal.
634-
// - block scoped variables cannot be used in its initializers
635-
// let x = x; // illegal but usage is lexically after definition
636-
// - in ForIn/ForOf statements variable cannot be contained in expression part
637-
// for (let x in x)
638-
// for (let x of x)
639-
640-
// climb up to the variable declaration skipping binding patterns
641-
let variableDeclaration = <VariableDeclaration>getAncestor(declaration, SyntaxKind.VariableDeclaration);
642-
let container = getEnclosingBlockScopeContainer(variableDeclaration);
643-
644-
if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement ||
645-
variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) {
646-
// variable statement/for statement case,
647-
// use site should not be inside variable declaration (initializer of declaration or binding element)
648-
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container);
649-
}
650-
else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement ||
651-
variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) {
652-
// ForIn/ForOf case - use site should not be used in expression part
653-
let expression = (<ForInStatement | ForOfStatement>variableDeclaration.parent.parent).expression;
654-
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container);
655-
}
656-
}
657-
if (isUsedBeforeDeclaration) {
682+
if (!isBlockScopedNameDeclaredBeforeUse(<Declaration>getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) {
658683
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
659684
}
660685
}
@@ -13354,7 +13379,7 @@ namespace ts {
1335413379
}
1335513380

1335613381
// illegal case: forward reference
13357-
if (!isDefinedBefore(propertyDecl, member)) {
13382+
if (!isBlockScopedNameDeclaredBeforeUse(propertyDecl, member)) {
1335813383
reportError = false;
1335913384
error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
1336013385
return undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
6+
7+
==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (4 errors) ====
8+
function foo0() {
9+
let a = x;
10+
~
11+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
12+
let x;
13+
}
14+
15+
function foo1() {
16+
let a = () => x;
17+
let x;
18+
}
19+
20+
function foo2() {
21+
let a = function () { return x; }
22+
let x;
23+
}
24+
25+
function foo3() {
26+
class X {
27+
m() { return x;}
28+
}
29+
let x;
30+
}
31+
32+
function foo4() {
33+
let y = class {
34+
m() { return x; }
35+
};
36+
let x;
37+
}
38+
39+
function foo5() {
40+
let x = () => y;
41+
let y = () => x;
42+
}
43+
44+
function foo6() {
45+
function f() {
46+
return x;
47+
}
48+
let x;
49+
}
50+
51+
function foo7() {
52+
class A {
53+
a = x;
54+
}
55+
let x;
56+
}
57+
58+
function foo8() {
59+
let y = class {
60+
a = x;
61+
}
62+
let x;
63+
}
64+
65+
function foo9() {
66+
let y = class {
67+
static a = x;
68+
~
69+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
70+
}
71+
let x;
72+
}
73+
74+
function foo10() {
75+
class A {
76+
static a = x;
77+
~
78+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
79+
}
80+
let x;
81+
}
82+
83+
function foo11() {
84+
function f () {
85+
let y = class {
86+
static a = x;
87+
}
88+
}
89+
let x;
90+
}
91+
92+
function foo12() {
93+
function f () {
94+
let y = class {
95+
a;
96+
constructor() {
97+
this.a = x;
98+
}
99+
}
100+
}
101+
let x;
102+
}
103+
104+
function foo13() {
105+
let a = {
106+
get a() { return x }
107+
}
108+
let x
109+
}
110+
111+
function foo14() {
112+
let a = {
113+
a: x
114+
~
115+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
116+
}
117+
let x
118+
}

0 commit comments

Comments
 (0)