Merge pull request #5233 from Microsoft/relaxedUseBeforeDef

allow forward references to block scoped variables from functions
This commit is contained in:
Vladimir Matveev 2015-10-13 14:19:01 -07:00
Родитель 99d448a167 0465f1b0bb
Коммит 124447763b
10 изменённых файлов: 540 добавлений и 61 удалений

Просмотреть файл

@ -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
}