Merge pull request #5233 from Microsoft/relaxedUseBeforeDef
allow forward references to block scoped variables from functions
This commit is contained in:
Коммит
124447763b
|
@ -383,20 +383,72 @@ namespace ts {
|
|||
// return undefined if we can't find a symbol.
|
||||
}
|
||||
|
||||
/** Returns true if node1 is defined before node 2**/
|
||||
function isDefinedBefore(node1: Node, node2: Node): boolean {
|
||||
let file1 = getSourceFileOfNode(node1);
|
||||
let file2 = getSourceFileOfNode(node2);
|
||||
if (file1 === file2) {
|
||||
return node1.pos <= node2.pos;
|
||||
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;
|
||||
}
|
||||
|
||||
const sourceFiles = host.getSourceFiles();
|
||||
return indexOf(sourceFiles, declarationFile) <= indexOf(sourceFiles, useFile);
|
||||
}
|
||||
|
||||
if (!compilerOptions.outFile && !compilerOptions.out) {
|
||||
return true;
|
||||
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(<VariableDeclaration>declaration, usage);
|
||||
}
|
||||
|
||||
let sourceFiles = host.getSourceFiles();
|
||||
return sourceFiles.indexOf(file1) <= sourceFiles.indexOf(file2);
|
||||
// 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 = (<ForInStatement | ForOfStatement>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 &&
|
||||
(<PropertyDeclaration>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
|
||||
|
@ -627,34 +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 = !isDefinedBefore(declaration, errorLocation);
|
||||
if (!isUsedBeforeDeclaration) {
|
||||
// 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 = <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 = (<ForInStatement | ForOfStatement>variableDeclaration.parent.parent).expression;
|
||||
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container);
|
||||
}
|
||||
}
|
||||
if (isUsedBeforeDeclaration) {
|
||||
if (!isBlockScopedNameDeclaredBeforeUse(<Declaration>getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) {
|
||||
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
|
||||
}
|
||||
}
|
||||
|
@ -13354,7 +13379,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
// illegal case: forward reference
|
||||
if (!isDefinedBefore(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;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
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 (4 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;
|
||||
}
|
||||
|
||||
function foo2() {
|
||||
let a = function () { return x; }
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo3() {
|
||||
class X {
|
||||
m() { return x;}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo4() {
|
||||
let y = class {
|
||||
m() { return x; }
|
||||
};
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo5() {
|
||||
let x = () => y;
|
||||
let y = () => x;
|
||||
}
|
||||
|
||||
function foo6() {
|
||||
function f() {
|
||||
return x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo7() {
|
||||
class A {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo8() {
|
||||
let y = class {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo9() {
|
||||
let y = class {
|
||||
static a = x;
|
||||
~
|
||||
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo10() {
|
||||
class A {
|
||||
static a = x;
|
||||
~
|
||||
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo11() {
|
||||
function f () {
|
||||
let y = class {
|
||||
static a = x;
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo12() {
|
||||
function f () {
|
||||
let y = class {
|
||||
a;
|
||||
constructor() {
|
||||
this.a = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
//// [blockScopedVariablesUseBeforeDef.ts]
|
||||
function foo0() {
|
||||
let a = x;
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo1() {
|
||||
let a = () => x;
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo2() {
|
||||
let a = function () { return x; }
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo3() {
|
||||
class X {
|
||||
m() { return x;}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo4() {
|
||||
let y = class {
|
||||
m() { return x; }
|
||||
};
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo5() {
|
||||
let x = () => y;
|
||||
let y = () => x;
|
||||
}
|
||||
|
||||
function foo6() {
|
||||
function f() {
|
||||
return x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo7() {
|
||||
class A {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo8() {
|
||||
let y = class {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo9() {
|
||||
let y = class {
|
||||
static a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo10() {
|
||||
class A {
|
||||
static a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo11() {
|
||||
function f () {
|
||||
let y = class {
|
||||
static a = x;
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo12() {
|
||||
function f () {
|
||||
let y = class {
|
||||
a;
|
||||
constructor() {
|
||||
this.a = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo13() {
|
||||
let a = {
|
||||
get a() { return x }
|
||||
}
|
||||
let x
|
||||
}
|
||||
|
||||
function foo14() {
|
||||
let a = {
|
||||
a: x
|
||||
}
|
||||
let x
|
||||
}
|
||||
|
||||
//// [blockScopedVariablesUseBeforeDef.js]
|
||||
function foo0() {
|
||||
var a = x;
|
||||
var x;
|
||||
}
|
||||
function foo1() {
|
||||
var a = function () { return x; };
|
||||
var x;
|
||||
}
|
||||
function foo2() {
|
||||
var a = function () { return x; };
|
||||
var x;
|
||||
}
|
||||
function foo3() {
|
||||
var X = (function () {
|
||||
function X() {
|
||||
}
|
||||
X.prototype.m = function () { return x; };
|
||||
return X;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo4() {
|
||||
var y = (function () {
|
||||
function class_1() {
|
||||
}
|
||||
class_1.prototype.m = function () { return x; };
|
||||
return class_1;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo5() {
|
||||
var x = function () { return y; };
|
||||
var y = function () { return x; };
|
||||
}
|
||||
function foo6() {
|
||||
function f() {
|
||||
return x;
|
||||
}
|
||||
var x;
|
||||
}
|
||||
function foo7() {
|
||||
var A = (function () {
|
||||
function A() {
|
||||
this.a = x;
|
||||
}
|
||||
return A;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo8() {
|
||||
var y = (function () {
|
||||
function class_2() {
|
||||
this.a = x;
|
||||
}
|
||||
return class_2;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo9() {
|
||||
var y = (function () {
|
||||
function class_3() {
|
||||
}
|
||||
class_3.a = x;
|
||||
return class_3;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo10() {
|
||||
var A = (function () {
|
||||
function A() {
|
||||
}
|
||||
A.a = x;
|
||||
return A;
|
||||
})();
|
||||
var x;
|
||||
}
|
||||
function foo11() {
|
||||
function f() {
|
||||
var y = (function () {
|
||||
function class_4() {
|
||||
}
|
||||
class_4.a = x;
|
||||
return class_4;
|
||||
})();
|
||||
}
|
||||
var x;
|
||||
}
|
||||
function foo12() {
|
||||
function f() {
|
||||
var y = (function () {
|
||||
function class_5() {
|
||||
this.a = x;
|
||||
}
|
||||
return class_5;
|
||||
})();
|
||||
}
|
||||
var x;
|
||||
}
|
||||
function foo13() {
|
||||
var a = {
|
||||
get a() { return x; }
|
||||
};
|
||||
var x;
|
||||
}
|
||||
function foo14() {
|
||||
var a = {
|
||||
a: x
|
||||
};
|
||||
var x;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
tests/cases/compiler/file1.ts(2,1): error TS2448: Block-scoped variable 'c' used before its declaration.
|
||||
|
||||
|
||||
==== tests/cases/compiler/file1.ts (1 errors) ====
|
||||
|
||||
c;
|
||||
~
|
||||
!!! error TS2448: Block-scoped variable 'c' used before its declaration.
|
||||
|
||||
==== tests/cases/compiler/file2.ts (0 errors) ====
|
||||
const c = 0;
|
|
@ -0,0 +1,9 @@
|
|||
=== tests/cases/compiler/file1.ts ===
|
||||
|
||||
c;
|
||||
>c : Symbol(c, Decl(file2.ts, 0, 5))
|
||||
|
||||
=== tests/cases/compiler/file2.ts ===
|
||||
const c = 0;
|
||||
>c : Symbol(c, Decl(file2.ts, 0, 5))
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
=== tests/cases/compiler/file1.ts ===
|
||||
|
||||
c;
|
||||
>c : number
|
||||
|
||||
=== tests/cases/compiler/file2.ts ===
|
||||
const c = 0;
|
||||
>c : number
|
||||
>0 : number
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
tests/cases/compiler/file1.ts(2,1): error TS2448: Block-scoped variable 'l' used before its declaration.
|
||||
|
||||
|
||||
==== tests/cases/compiler/file1.ts (1 errors) ====
|
||||
|
||||
l;
|
||||
~
|
||||
!!! error TS2448: Block-scoped variable 'l' used before its declaration.
|
||||
|
||||
==== tests/cases/compiler/file2.ts (0 errors) ====
|
||||
const l = 0;
|
|
@ -0,0 +1,9 @@
|
|||
=== tests/cases/compiler/file1.ts ===
|
||||
|
||||
l;
|
||||
>l : Symbol(l, Decl(file2.ts, 0, 5))
|
||||
|
||||
=== tests/cases/compiler/file2.ts ===
|
||||
const l = 0;
|
||||
>l : Symbol(l, Decl(file2.ts, 0, 5))
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
=== tests/cases/compiler/file1.ts ===
|
||||
|
||||
l;
|
||||
>l : number
|
||||
|
||||
=== tests/cases/compiler/file2.ts ===
|
||||
const l = 0;
|
||||
>l : number
|
||||
>0 : number
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// @target: ES5
|
||||
function foo0() {
|
||||
let a = x;
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo1() {
|
||||
let a = () => x;
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo2() {
|
||||
let a = function () { return x; }
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo3() {
|
||||
class X {
|
||||
m() { return x;}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo4() {
|
||||
let y = class {
|
||||
m() { return x; }
|
||||
};
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo5() {
|
||||
let x = () => y;
|
||||
let y = () => x;
|
||||
}
|
||||
|
||||
function foo6() {
|
||||
function f() {
|
||||
return x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo7() {
|
||||
class A {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo8() {
|
||||
let y = class {
|
||||
a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo9() {
|
||||
let y = class {
|
||||
static a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo10() {
|
||||
class A {
|
||||
static a = x;
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo11() {
|
||||
function f () {
|
||||
let y = class {
|
||||
static a = x;
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo12() {
|
||||
function f () {
|
||||
let y = class {
|
||||
a;
|
||||
constructor() {
|
||||
this.a = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
let x;
|
||||
}
|
||||
|
||||
function foo13() {
|
||||
let a = {
|
||||
get a() { return x }
|
||||
}
|
||||
let x
|
||||
}
|
||||
|
||||
function foo14() {
|
||||
let a = {
|
||||
a: x
|
||||
}
|
||||
let x
|
||||
}
|
Загрузка…
Ссылка в новой задаче