From 5bcd8b3afae3882feb35fa9257b720f266f770f5 Mon Sep 17 00:00:00 2001 From: Tingan Ho Date: Tue, 6 Oct 2015 19:03:01 +0800 Subject: [PATCH 01/12] Fixes instance of type exclusion in else clause --- src/compiler/checker.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 40db300d31..3dc9ee68d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6362,9 +6362,10 @@ namespace ts { function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { // Check that type is not any, assumed result is true, and we have variable symbol on the left - if (isTypeAny(type) || !assumeTrue || expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + if (isTypeAny(type) || expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { return type; } + // Check that right operand is a function type with a prototype property let rightType = checkExpression(expr.right); if (!isTypeSubtypeOf(rightType, globalFunctionType)) { @@ -6396,6 +6397,13 @@ namespace ts { } if (targetType) { + if (!assumeTrue) { + if (type.flags & TypeFlags.Union) { + return getUnionType(filter((type).types, t => !isTypeSubtypeOf(t, targetType))); + } + return type; + } + return getNarrowedType(type, targetType); } From dd54b7a36f15da93748282eab3298071943f2969 Mon Sep 17 00:00:00 2001 From: Tingan Ho Date: Tue, 6 Oct 2015 19:03:11 +0800 Subject: [PATCH 02/12] Adds tests --- .../reference/narrowTypeByInstanceof.types | 2 +- .../reference/typeGuardOfFormInstanceOf.js | 112 +++++++-- .../typeGuardOfFormInstanceOf.symbols | 190 +++++++++++---- .../reference/typeGuardOfFormInstanceOf.types | 221 +++++++++++++----- .../typeGuards/typeGuardOfFormInstanceOf.ts | 57 ++++- 5 files changed, 447 insertions(+), 135 deletions(-) diff --git a/tests/baselines/reference/narrowTypeByInstanceof.types b/tests/baselines/reference/narrowTypeByInstanceof.types index e6a3466b88..8bc13d12ca 100644 --- a/tests/baselines/reference/narrowTypeByInstanceof.types +++ b/tests/baselines/reference/narrowTypeByInstanceof.types @@ -63,7 +63,7 @@ if (elementA instanceof FileMatch && elementB instanceof FileMatch) { } else if (elementA instanceof Match && elementB instanceof Match) { >elementA instanceof Match && elementB instanceof Match : boolean >elementA instanceof Match : boolean ->elementA : FileMatch | Match +>elementA : Match | FileMatch >Match : typeof Match >elementB instanceof Match : boolean >elementB : FileMatch | Match diff --git a/tests/baselines/reference/typeGuardOfFormInstanceOf.js b/tests/baselines/reference/typeGuardOfFormInstanceOf.js index cab04365c1..1431b4b9c6 100644 --- a/tests/baselines/reference/typeGuardOfFormInstanceOf.js +++ b/tests/baselines/reference/typeGuardOfFormInstanceOf.js @@ -14,21 +14,58 @@ class C2 { class D1 extends C1 { p3: number; } +class C3 { + p4: number; +} var str: string; var num: number; var strOrNum: string | number; -var c1Orc2: C1 | C2; -str = c1Orc2 instanceof C1 && c1Orc2.p1; // C1 -num = c1Orc2 instanceof C2 && c1Orc2.p2; // C2 -str = c1Orc2 instanceof D1 && c1Orc2.p1; // D1 -num = c1Orc2 instanceof D1 && c1Orc2.p3; // D1 +var ctor1: C1 | C2; +str = ctor1 instanceof C1 && ctor1.p1; // C1 +num = ctor1 instanceof C2 && ctor1.p2; // C2 +str = ctor1 instanceof D1 && ctor1.p1; // D1 +num = ctor1 instanceof D1 && ctor1.p3; // D1 -var c2Ord1: C2 | D1; -num = c2Ord1 instanceof C2 && c2Ord1.p2; // C2 -num = c2Ord1 instanceof D1 && c2Ord1.p3; // D1 -str = c2Ord1 instanceof D1 && c2Ord1.p1; // D1 -var r2: D1 | C2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 +var ctor2: C2 | D1; +num = ctor2 instanceof C2 && ctor2.p2; // C2 +num = ctor2 instanceof D1 && ctor2.p3; // D1 +str = ctor2 instanceof D1 && ctor2.p1; // D1 +var r2: D1 | C2 = ctor2 instanceof C1 && ctor2; // C2 | D1 + +var ctor3: C1 | C2; +if (ctor3 instanceof C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} + +var ctor4: C1 | C2 | C3; +if (ctor4 instanceof C1) { + ctor4.p1; // C1 +} +else if (ctor4 instanceof C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} + +var ctor5: C1 | D1 | C2; +if (ctor5 instanceof C1) { + ctor5.p1; // C1 +} +else { + ctor5.p2; // C2 +} + +var ctor6: C1 | C2 | C3; +if (ctor6 instanceof C1 || ctor6 instanceof C2) { +} +else { + ctor6.p4; // C3 +} //// [typeGuardOfFormInstanceOf.js] // A type guard of the form x instanceof C, where C is of a subtype of the global type 'Function' @@ -58,16 +95,51 @@ var D1 = (function (_super) { } return D1; })(C1); +var C3 = (function () { + function C3() { + } + return C3; +})(); var str; var num; var strOrNum; -var c1Orc2; -str = c1Orc2 instanceof C1 && c1Orc2.p1; // C1 -num = c1Orc2 instanceof C2 && c1Orc2.p2; // C2 -str = c1Orc2 instanceof D1 && c1Orc2.p1; // D1 -num = c1Orc2 instanceof D1 && c1Orc2.p3; // D1 -var c2Ord1; -num = c2Ord1 instanceof C2 && c2Ord1.p2; // C2 -num = c2Ord1 instanceof D1 && c2Ord1.p3; // D1 -str = c2Ord1 instanceof D1 && c2Ord1.p1; // D1 -var r2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 +var ctor1; +str = ctor1 instanceof C1 && ctor1.p1; // C1 +num = ctor1 instanceof C2 && ctor1.p2; // C2 +str = ctor1 instanceof D1 && ctor1.p1; // D1 +num = ctor1 instanceof D1 && ctor1.p3; // D1 +var ctor2; +num = ctor2 instanceof C2 && ctor2.p2; // C2 +num = ctor2 instanceof D1 && ctor2.p3; // D1 +str = ctor2 instanceof D1 && ctor2.p1; // D1 +var r2 = ctor2 instanceof C1 && ctor2; // C2 | D1 +var ctor3; +if (ctor3 instanceof C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} +var ctor4; +if (ctor4 instanceof C1) { + ctor4.p1; // C1 +} +else if (ctor4 instanceof C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} +var ctor5; +if (ctor5 instanceof C1) { + ctor5.p1; // C1 +} +else { + ctor5.p2; // C2 +} +var ctor6; +if (ctor6 instanceof C1 || ctor6 instanceof C2) { +} +else { + ctor6.p4; // C3 +} diff --git a/tests/baselines/reference/typeGuardOfFormInstanceOf.symbols b/tests/baselines/reference/typeGuardOfFormInstanceOf.symbols index 7a48d87e96..0dd2844f6a 100644 --- a/tests/baselines/reference/typeGuardOfFormInstanceOf.symbols +++ b/tests/baselines/reference/typeGuardOfFormInstanceOf.symbols @@ -24,86 +24,184 @@ class D1 extends C1 { p3: number; >p3 : Symbol(p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) } +class C3 { +>C3 : Symbol(C3, Decl(typeGuardOfFormInstanceOf.ts, 14, 1)) + + p4: number; +>p4 : Symbol(p4, Decl(typeGuardOfFormInstanceOf.ts, 15, 10)) +} var str: string; ->str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 15, 3)) +>str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 18, 3)) var num: number; ->num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 16, 3)) +>num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormInstanceOf.ts, 17, 3)) +>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormInstanceOf.ts, 20, 3)) -var c1Orc2: C1 | C2; ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +var ctor1: C1 | C2; +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) >C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) -str = c1Orc2 instanceof C1 && c1Orc2.p1; // C1 ->str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 15, 3)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +str = ctor1 instanceof C1 && ctor1.p1; // C1 +>str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 18, 3)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) ->c1Orc2.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) -num = c1Orc2 instanceof C2 && c1Orc2.p2; // C2 ->num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 16, 3)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +num = ctor1 instanceof C2 && ctor1.p2; // C2 +>num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) ->c1Orc2.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) -str = c1Orc2 instanceof D1 && c1Orc2.p1; // D1 ->str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 15, 3)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +str = ctor1 instanceof D1 && ctor1.p1; // D1 +>str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 18, 3)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) ->c1Orc2.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) -num = c1Orc2 instanceof D1 && c1Orc2.p3; // D1 ->num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 16, 3)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +num = ctor1 instanceof D1 && ctor1.p3; // D1 +>num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) ->c1Orc2.p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) ->c1Orc2 : Symbol(c1Orc2, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor1.p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) +>ctor1 : Symbol(ctor1, Decl(typeGuardOfFormInstanceOf.ts, 22, 3)) >p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) -var c2Ord1: C2 | D1; ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +var ctor2: C2 | D1; +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) -num = c2Ord1 instanceof C2 && c2Ord1.p2; // C2 ->num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 16, 3)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +num = ctor2 instanceof C2 && ctor2.p2; // C2 +>num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) ->c2Ord1.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +>ctor2.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) -num = c2Ord1 instanceof D1 && c2Ord1.p3; // D1 ->num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 16, 3)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +num = ctor2 instanceof D1 && ctor2.p3; // D1 +>num : Symbol(num, Decl(typeGuardOfFormInstanceOf.ts, 19, 3)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) ->c2Ord1.p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +>ctor2.p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >p3 : Symbol(D1.p3, Decl(typeGuardOfFormInstanceOf.ts, 12, 21)) -str = c2Ord1 instanceof D1 && c2Ord1.p1; // D1 ->str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 15, 3)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +str = ctor2 instanceof D1 && ctor2.p1; // D1 +>str : Symbol(str, Decl(typeGuardOfFormInstanceOf.ts, 18, 3)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) ->c2Ord1.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +>ctor2.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) -var r2: D1 | C2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 ->r2 : Symbol(r2, Decl(typeGuardOfFormInstanceOf.ts, 29, 3)) +var r2: D1 | C2 = ctor2 instanceof C1 && ctor2; // C2 | D1 +>r2 : Symbol(r2, Decl(typeGuardOfFormInstanceOf.ts, 32, 3)) >D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) >C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) >C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) ->c2Ord1 : Symbol(c2Ord1, Decl(typeGuardOfFormInstanceOf.ts, 25, 3)) +>ctor2 : Symbol(ctor2, Decl(typeGuardOfFormInstanceOf.ts, 28, 3)) +var ctor3: C1 | C2; +>ctor3 : Symbol(ctor3, Decl(typeGuardOfFormInstanceOf.ts, 34, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) + +if (ctor3 instanceof C1) { +>ctor3 : Symbol(ctor3, Decl(typeGuardOfFormInstanceOf.ts, 34, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) + + ctor3.p1; // C1 +>ctor3.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor3 : Symbol(ctor3, Decl(typeGuardOfFormInstanceOf.ts, 34, 3)) +>p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +} +else { + ctor3.p2; // C2 +>ctor3.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +>ctor3 : Symbol(ctor3, Decl(typeGuardOfFormInstanceOf.ts, 34, 3)) +>p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +} + +var ctor4: C1 | C2 | C3; +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) +>C3 : Symbol(C3, Decl(typeGuardOfFormInstanceOf.ts, 14, 1)) + +if (ctor4 instanceof C1) { +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) + + ctor4.p1; // C1 +>ctor4.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +} +else if (ctor4 instanceof C2) { +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) + + ctor4.p2; // C2 +>ctor4.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +} +else { + ctor4.p4; // C3 +>ctor4.p4 : Symbol(C3.p4, Decl(typeGuardOfFormInstanceOf.ts, 15, 10)) +>ctor4 : Symbol(ctor4, Decl(typeGuardOfFormInstanceOf.ts, 42, 3)) +>p4 : Symbol(C3.p4, Decl(typeGuardOfFormInstanceOf.ts, 15, 10)) +} + +var ctor5: C1 | D1 | C2; +>ctor5 : Symbol(ctor5, Decl(typeGuardOfFormInstanceOf.ts, 53, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) +>D1 : Symbol(D1, Decl(typeGuardOfFormInstanceOf.ts, 11, 1)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) + +if (ctor5 instanceof C1) { +>ctor5 : Symbol(ctor5, Decl(typeGuardOfFormInstanceOf.ts, 53, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) + + ctor5.p1; // C1 +>ctor5.p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +>ctor5 : Symbol(ctor5, Decl(typeGuardOfFormInstanceOf.ts, 53, 3)) +>p1 : Symbol(C1.p1, Decl(typeGuardOfFormInstanceOf.ts, 6, 10)) +} +else { + ctor5.p2; // C2 +>ctor5.p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +>ctor5 : Symbol(ctor5, Decl(typeGuardOfFormInstanceOf.ts, 53, 3)) +>p2 : Symbol(C2.p2, Decl(typeGuardOfFormInstanceOf.ts, 9, 10)) +} + +var ctor6: C1 | C2 | C3; +>ctor6 : Symbol(ctor6, Decl(typeGuardOfFormInstanceOf.ts, 61, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) +>C3 : Symbol(C3, Decl(typeGuardOfFormInstanceOf.ts, 14, 1)) + +if (ctor6 instanceof C1 || ctor6 instanceof C2) { +>ctor6 : Symbol(ctor6, Decl(typeGuardOfFormInstanceOf.ts, 61, 3)) +>C1 : Symbol(C1, Decl(typeGuardOfFormInstanceOf.ts, 0, 0)) +>ctor6 : Symbol(ctor6, Decl(typeGuardOfFormInstanceOf.ts, 61, 3)) +>C2 : Symbol(C2, Decl(typeGuardOfFormInstanceOf.ts, 8, 1)) +} +else { + ctor6.p4; // C3 +>ctor6.p4 : Symbol(C3.p4, Decl(typeGuardOfFormInstanceOf.ts, 15, 10)) +>ctor6 : Symbol(ctor6, Decl(typeGuardOfFormInstanceOf.ts, 61, 3)) +>p4 : Symbol(C3.p4, Decl(typeGuardOfFormInstanceOf.ts, 15, 10)) +} diff --git a/tests/baselines/reference/typeGuardOfFormInstanceOf.types b/tests/baselines/reference/typeGuardOfFormInstanceOf.types index c824e90773..6b83b57dcb 100644 --- a/tests/baselines/reference/typeGuardOfFormInstanceOf.types +++ b/tests/baselines/reference/typeGuardOfFormInstanceOf.types @@ -24,6 +24,12 @@ class D1 extends C1 { p3: number; >p3 : number } +class C3 { +>C3 : C3 + + p4: number; +>p4 : number +} var str: string; >str : string @@ -33,100 +39,199 @@ var num: number; var strOrNum: string | number; >strOrNum : string | number -var c1Orc2: C1 | C2; ->c1Orc2 : C1 | C2 +var ctor1: C1 | C2; +>ctor1 : C1 | C2 >C1 : C1 >C2 : C2 -str = c1Orc2 instanceof C1 && c1Orc2.p1; // C1 ->str = c1Orc2 instanceof C1 && c1Orc2.p1 : string +str = ctor1 instanceof C1 && ctor1.p1; // C1 +>str = ctor1 instanceof C1 && ctor1.p1 : string >str : string ->c1Orc2 instanceof C1 && c1Orc2.p1 : string ->c1Orc2 instanceof C1 : boolean ->c1Orc2 : C1 | C2 +>ctor1 instanceof C1 && ctor1.p1 : string +>ctor1 instanceof C1 : boolean +>ctor1 : C1 | C2 >C1 : typeof C1 ->c1Orc2.p1 : string ->c1Orc2 : C1 +>ctor1.p1 : string +>ctor1 : C1 >p1 : string -num = c1Orc2 instanceof C2 && c1Orc2.p2; // C2 ->num = c1Orc2 instanceof C2 && c1Orc2.p2 : number +num = ctor1 instanceof C2 && ctor1.p2; // C2 +>num = ctor1 instanceof C2 && ctor1.p2 : number >num : number ->c1Orc2 instanceof C2 && c1Orc2.p2 : number ->c1Orc2 instanceof C2 : boolean ->c1Orc2 : C1 | C2 +>ctor1 instanceof C2 && ctor1.p2 : number +>ctor1 instanceof C2 : boolean +>ctor1 : C1 | C2 >C2 : typeof C2 ->c1Orc2.p2 : number ->c1Orc2 : C2 +>ctor1.p2 : number +>ctor1 : C2 >p2 : number -str = c1Orc2 instanceof D1 && c1Orc2.p1; // D1 ->str = c1Orc2 instanceof D1 && c1Orc2.p1 : string +str = ctor1 instanceof D1 && ctor1.p1; // D1 +>str = ctor1 instanceof D1 && ctor1.p1 : string >str : string ->c1Orc2 instanceof D1 && c1Orc2.p1 : string ->c1Orc2 instanceof D1 : boolean ->c1Orc2 : C1 | C2 +>ctor1 instanceof D1 && ctor1.p1 : string +>ctor1 instanceof D1 : boolean +>ctor1 : C1 | C2 >D1 : typeof D1 ->c1Orc2.p1 : string ->c1Orc2 : D1 +>ctor1.p1 : string +>ctor1 : D1 >p1 : string -num = c1Orc2 instanceof D1 && c1Orc2.p3; // D1 ->num = c1Orc2 instanceof D1 && c1Orc2.p3 : number +num = ctor1 instanceof D1 && ctor1.p3; // D1 +>num = ctor1 instanceof D1 && ctor1.p3 : number >num : number ->c1Orc2 instanceof D1 && c1Orc2.p3 : number ->c1Orc2 instanceof D1 : boolean ->c1Orc2 : C1 | C2 +>ctor1 instanceof D1 && ctor1.p3 : number +>ctor1 instanceof D1 : boolean +>ctor1 : C1 | C2 >D1 : typeof D1 ->c1Orc2.p3 : number ->c1Orc2 : D1 +>ctor1.p3 : number +>ctor1 : D1 >p3 : number -var c2Ord1: C2 | D1; ->c2Ord1 : C2 | D1 +var ctor2: C2 | D1; +>ctor2 : C2 | D1 >C2 : C2 >D1 : D1 -num = c2Ord1 instanceof C2 && c2Ord1.p2; // C2 ->num = c2Ord1 instanceof C2 && c2Ord1.p2 : number +num = ctor2 instanceof C2 && ctor2.p2; // C2 +>num = ctor2 instanceof C2 && ctor2.p2 : number >num : number ->c2Ord1 instanceof C2 && c2Ord1.p2 : number ->c2Ord1 instanceof C2 : boolean ->c2Ord1 : C2 | D1 +>ctor2 instanceof C2 && ctor2.p2 : number +>ctor2 instanceof C2 : boolean +>ctor2 : C2 | D1 >C2 : typeof C2 ->c2Ord1.p2 : number ->c2Ord1 : C2 +>ctor2.p2 : number +>ctor2 : C2 >p2 : number -num = c2Ord1 instanceof D1 && c2Ord1.p3; // D1 ->num = c2Ord1 instanceof D1 && c2Ord1.p3 : number +num = ctor2 instanceof D1 && ctor2.p3; // D1 +>num = ctor2 instanceof D1 && ctor2.p3 : number >num : number ->c2Ord1 instanceof D1 && c2Ord1.p3 : number ->c2Ord1 instanceof D1 : boolean ->c2Ord1 : C2 | D1 +>ctor2 instanceof D1 && ctor2.p3 : number +>ctor2 instanceof D1 : boolean +>ctor2 : C2 | D1 >D1 : typeof D1 ->c2Ord1.p3 : number ->c2Ord1 : D1 +>ctor2.p3 : number +>ctor2 : D1 >p3 : number -str = c2Ord1 instanceof D1 && c2Ord1.p1; // D1 ->str = c2Ord1 instanceof D1 && c2Ord1.p1 : string +str = ctor2 instanceof D1 && ctor2.p1; // D1 +>str = ctor2 instanceof D1 && ctor2.p1 : string >str : string ->c2Ord1 instanceof D1 && c2Ord1.p1 : string ->c2Ord1 instanceof D1 : boolean ->c2Ord1 : C2 | D1 +>ctor2 instanceof D1 && ctor2.p1 : string +>ctor2 instanceof D1 : boolean +>ctor2 : C2 | D1 >D1 : typeof D1 ->c2Ord1.p1 : string ->c2Ord1 : D1 +>ctor2.p1 : string +>ctor2 : D1 >p1 : string -var r2: D1 | C2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 +var r2: D1 | C2 = ctor2 instanceof C1 && ctor2; // C2 | D1 >r2 : D1 | C2 >D1 : D1 >C2 : C2 ->c2Ord1 instanceof C1 && c2Ord1 : D1 ->c2Ord1 instanceof C1 : boolean ->c2Ord1 : C2 | D1 +>ctor2 instanceof C1 && ctor2 : D1 +>ctor2 instanceof C1 : boolean +>ctor2 : C2 | D1 >C1 : typeof C1 ->c2Ord1 : D1 +>ctor2 : D1 +var ctor3: C1 | C2; +>ctor3 : C1 | C2 +>C1 : C1 +>C2 : C2 + +if (ctor3 instanceof C1) { +>ctor3 instanceof C1 : boolean +>ctor3 : C1 | C2 +>C1 : typeof C1 + + ctor3.p1; // C1 +>ctor3.p1 : string +>ctor3 : C1 +>p1 : string +} +else { + ctor3.p2; // C2 +>ctor3.p2 : number +>ctor3 : C2 +>p2 : number +} + +var ctor4: C1 | C2 | C3; +>ctor4 : C1 | C2 | C3 +>C1 : C1 +>C2 : C2 +>C3 : C3 + +if (ctor4 instanceof C1) { +>ctor4 instanceof C1 : boolean +>ctor4 : C1 | C2 | C3 +>C1 : typeof C1 + + ctor4.p1; // C1 +>ctor4.p1 : string +>ctor4 : C1 +>p1 : string +} +else if (ctor4 instanceof C2) { +>ctor4 instanceof C2 : boolean +>ctor4 : C2 | C3 +>C2 : typeof C2 + + ctor4.p2; // C2 +>ctor4.p2 : number +>ctor4 : C2 +>p2 : number +} +else { + ctor4.p4; // C3 +>ctor4.p4 : number +>ctor4 : C3 +>p4 : number +} + +var ctor5: C1 | D1 | C2; +>ctor5 : C1 | D1 | C2 +>C1 : C1 +>D1 : D1 +>C2 : C2 + +if (ctor5 instanceof C1) { +>ctor5 instanceof C1 : boolean +>ctor5 : C1 | D1 | C2 +>C1 : typeof C1 + + ctor5.p1; // C1 +>ctor5.p1 : string +>ctor5 : C1 +>p1 : string +} +else { + ctor5.p2; // C2 +>ctor5.p2 : number +>ctor5 : C2 +>p2 : number +} + +var ctor6: C1 | C2 | C3; +>ctor6 : C1 | C2 | C3 +>C1 : C1 +>C2 : C2 +>C3 : C3 + +if (ctor6 instanceof C1 || ctor6 instanceof C2) { +>ctor6 instanceof C1 || ctor6 instanceof C2 : boolean +>ctor6 instanceof C1 : boolean +>ctor6 : C1 | C2 | C3 +>C1 : typeof C1 +>ctor6 instanceof C2 : boolean +>ctor6 : C2 | C3 +>C2 : typeof C2 +} +else { + ctor6.p4; // C3 +>ctor6.p4 : number +>ctor6 : C3 +>p4 : number +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormInstanceOf.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormInstanceOf.ts index 233909f44a..31514fca74 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormInstanceOf.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormInstanceOf.ts @@ -13,18 +13,55 @@ class C2 { class D1 extends C1 { p3: number; } +class C3 { + p4: number; +} var str: string; var num: number; var strOrNum: string | number; -var c1Orc2: C1 | C2; -str = c1Orc2 instanceof C1 && c1Orc2.p1; // C1 -num = c1Orc2 instanceof C2 && c1Orc2.p2; // C2 -str = c1Orc2 instanceof D1 && c1Orc2.p1; // D1 -num = c1Orc2 instanceof D1 && c1Orc2.p3; // D1 +var ctor1: C1 | C2; +str = ctor1 instanceof C1 && ctor1.p1; // C1 +num = ctor1 instanceof C2 && ctor1.p2; // C2 +str = ctor1 instanceof D1 && ctor1.p1; // D1 +num = ctor1 instanceof D1 && ctor1.p3; // D1 + +var ctor2: C2 | D1; +num = ctor2 instanceof C2 && ctor2.p2; // C2 +num = ctor2 instanceof D1 && ctor2.p3; // D1 +str = ctor2 instanceof D1 && ctor2.p1; // D1 +var r2: D1 | C2 = ctor2 instanceof C1 && ctor2; // C2 | D1 -var c2Ord1: C2 | D1; -num = c2Ord1 instanceof C2 && c2Ord1.p2; // C2 -num = c2Ord1 instanceof D1 && c2Ord1.p3; // D1 -str = c2Ord1 instanceof D1 && c2Ord1.p1; // D1 -var r2: D1 | C2 = c2Ord1 instanceof C1 && c2Ord1; // C2 | D1 \ No newline at end of file +var ctor3: C1 | C2; +if (ctor3 instanceof C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} + +var ctor4: C1 | C2 | C3; +if (ctor4 instanceof C1) { + ctor4.p1; // C1 +} +else if (ctor4 instanceof C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} + +var ctor5: C1 | D1 | C2; +if (ctor5 instanceof C1) { + ctor5.p1; // C1 +} +else { + ctor5.p2; // C2 +} + +var ctor6: C1 | C2 | C3; +if (ctor6 instanceof C1 || ctor6 instanceof C2) { +} +else { + ctor6.p4; // C3 +} \ No newline at end of file From c5b47fb5c8ce348a4cec1cd52654995052c20cc2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 29 Oct 2015 14:53:14 -0700 Subject: [PATCH 03/12] Correct partial signature matching --- src/compiler/checker.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 99b3d97b7b..1fde2711e1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5603,18 +5603,29 @@ namespace ts { return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } + // A source signature matches a target signature if the two signatures have the same number of required, + // optional, and rest parameters. + function isMatchingSignature(source: Signature, target: Signature) { + return source.parameters.length === target.parameters.length && + source.minArgumentCount === target.minArgumentCount && + source.hasRestParameter === target.hasRestParameter; + } + + // A source signature partially matches a target signature if the target signature has no fewer required + // parameters and no more overall parameters than the source signature (where a signature with a rest + // parameter is always considered to have more overall parameters than one without). + function isPartiallyMatchingSignature(source: Signature, target: Signature) { + return source.minArgumentCount <= target.minArgumentCount && ( + source.hasRestParameter && !target.hasRestParameter || + source.hasRestParameter === target.hasRestParameter && source.parameters.length >= target.parameters.length); + } + function compareSignatures(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { if (source === target) { return Ternary.True; } - if (source.parameters.length !== target.parameters.length || - source.minArgumentCount !== target.minArgumentCount || - source.hasRestParameter !== target.hasRestParameter) { - if (!partialMatch || - source.parameters.length < target.parameters.length && !source.hasRestParameter || - source.minArgumentCount > target.minArgumentCount) { - return Ternary.False; - } + if (!(isMatchingSignature(source, target) || partialMatch && isPartiallyMatchingSignature(source, target))) { + return Ternary.False; } let result = Ternary.True; if (source.typeParameters && target.typeParameters) { From a27ed01516873d2ac0c70b32135eddf30461eba7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 29 Oct 2015 14:53:28 -0700 Subject: [PATCH 04/12] Adding test --- .../unionTypeCallSignatures4.errors.txt | 41 +++++++++++++++++ .../reference/unionTypeCallSignatures4.js | 45 +++++++++++++++++++ .../types/union/unionTypeCallSignatures4.ts | 25 +++++++++++ 3 files changed, 111 insertions(+) create mode 100644 tests/baselines/reference/unionTypeCallSignatures4.errors.txt create mode 100644 tests/baselines/reference/unionTypeCallSignatures4.js create mode 100644 tests/cases/conformance/types/union/unionTypeCallSignatures4.ts diff --git a/tests/baselines/reference/unionTypeCallSignatures4.errors.txt b/tests/baselines/reference/unionTypeCallSignatures4.errors.txt new file mode 100644 index 0000000000..585906a6b5 --- /dev/null +++ b/tests/baselines/reference/unionTypeCallSignatures4.errors.txt @@ -0,0 +1,41 @@ +tests/cases/conformance/types/union/unionTypeCallSignatures4.ts(10,1): error TS2346: Supplied parameters do not match any signature of call target. +tests/cases/conformance/types/union/unionTypeCallSignatures4.ts(20,1): error TS2346: Supplied parameters do not match any signature of call target. +tests/cases/conformance/types/union/unionTypeCallSignatures4.ts(23,1): error TS2346: Supplied parameters do not match any signature of call target. +tests/cases/conformance/types/union/unionTypeCallSignatures4.ts(25,1): error TS2346: Supplied parameters do not match any signature of call target. + + +==== tests/cases/conformance/types/union/unionTypeCallSignatures4.ts (4 errors) ==== + type F1 = (a: string, b?: string) => void; + type F2 = (a: string, b?: string, c?: string) => void; + type F3 = (a: string, ...rest: string[]) => void; + type F4 = (a: string, b?: string, ...rest: string[]) => void; + type F5 = (a: string, b: string) => void; + + var f12: F1 | F2; + f12("a"); + f12("a", "b"); + f12("a", "b", "c"); // error + ~~~~~~~~~~~~~~~~~~ +!!! error TS2346: Supplied parameters do not match any signature of call target. + + var f34: F3 | F4; + f34("a"); + f34("a", "b"); + f34("a", "b", "c"); + + var f1234: F1 | F2 | F3 | F4; + f1234("a"); + f1234("a", "b"); + f1234("a", "b", "c"); // error + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2346: Supplied parameters do not match any signature of call target. + + var f12345: F1 | F2 | F3 | F4 | F5; + f12345("a"); // error + ~~~~~~~~~~~ +!!! error TS2346: Supplied parameters do not match any signature of call target. + f12345("a", "b"); + f12345("a", "b", "c"); // error + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2346: Supplied parameters do not match any signature of call target. + \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeCallSignatures4.js b/tests/baselines/reference/unionTypeCallSignatures4.js new file mode 100644 index 0000000000..be9a45b33c --- /dev/null +++ b/tests/baselines/reference/unionTypeCallSignatures4.js @@ -0,0 +1,45 @@ +//// [unionTypeCallSignatures4.ts] +type F1 = (a: string, b?: string) => void; +type F2 = (a: string, b?: string, c?: string) => void; +type F3 = (a: string, ...rest: string[]) => void; +type F4 = (a: string, b?: string, ...rest: string[]) => void; +type F5 = (a: string, b: string) => void; + +var f12: F1 | F2; +f12("a"); +f12("a", "b"); +f12("a", "b", "c"); // error + +var f34: F3 | F4; +f34("a"); +f34("a", "b"); +f34("a", "b", "c"); + +var f1234: F1 | F2 | F3 | F4; +f1234("a"); +f1234("a", "b"); +f1234("a", "b", "c"); // error + +var f12345: F1 | F2 | F3 | F4 | F5; +f12345("a"); // error +f12345("a", "b"); +f12345("a", "b", "c"); // error + + +//// [unionTypeCallSignatures4.js] +var f12; +f12("a"); +f12("a", "b"); +f12("a", "b", "c"); // error +var f34; +f34("a"); +f34("a", "b"); +f34("a", "b", "c"); +var f1234; +f1234("a"); +f1234("a", "b"); +f1234("a", "b", "c"); // error +var f12345; +f12345("a"); // error +f12345("a", "b"); +f12345("a", "b", "c"); // error diff --git a/tests/cases/conformance/types/union/unionTypeCallSignatures4.ts b/tests/cases/conformance/types/union/unionTypeCallSignatures4.ts new file mode 100644 index 0000000000..1e27acd208 --- /dev/null +++ b/tests/cases/conformance/types/union/unionTypeCallSignatures4.ts @@ -0,0 +1,25 @@ +type F1 = (a: string, b?: string) => void; +type F2 = (a: string, b?: string, c?: string) => void; +type F3 = (a: string, ...rest: string[]) => void; +type F4 = (a: string, b?: string, ...rest: string[]) => void; +type F5 = (a: string, b: string) => void; + +var f12: F1 | F2; +f12("a"); +f12("a", "b"); +f12("a", "b", "c"); // error + +var f34: F3 | F4; +f34("a"); +f34("a", "b"); +f34("a", "b", "c"); + +var f1234: F1 | F2 | F3 | F4; +f1234("a"); +f1234("a", "b"); +f1234("a", "b", "c"); // error + +var f12345: F1 | F2 | F3 | F4 | F5; +f12345("a"); // error +f12345("a", "b"); +f12345("a", "b", "c"); // error From 31331ff6d1399acf51ec68edef0d6052a468b255 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 5 Nov 2015 16:31:30 -0800 Subject: [PATCH 05/12] Addressing CR feedback --- src/compiler/checker.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1fde2711e1..836c16b51d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5603,28 +5603,30 @@ namespace ts { return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } - // A source signature matches a target signature if the two signatures have the same number of required, - // optional, and rest parameters. - function isMatchingSignature(source: Signature, target: Signature) { - return source.parameters.length === target.parameters.length && + function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { + // A source signature matches a target signature if the two signatures have the same number of required, + // optional, and rest parameters. + if (source.parameters.length === target.parameters.length && source.minArgumentCount === target.minArgumentCount && - source.hasRestParameter === target.hasRestParameter; - } - - // A source signature partially matches a target signature if the target signature has no fewer required - // parameters and no more overall parameters than the source signature (where a signature with a rest - // parameter is always considered to have more overall parameters than one without). - function isPartiallyMatchingSignature(source: Signature, target: Signature) { - return source.minArgumentCount <= target.minArgumentCount && ( + source.hasRestParameter === target.hasRestParameter) { + return true; + } + // A source signature partially matches a target signature if the target signature has no fewer required + // parameters and no more overall parameters than the source signature (where a signature with a rest + // parameter is always considered to have more overall parameters than one without). + if (partialMatch && source.minArgumentCount <= target.minArgumentCount && ( source.hasRestParameter && !target.hasRestParameter || - source.hasRestParameter === target.hasRestParameter && source.parameters.length >= target.parameters.length); + source.hasRestParameter === target.hasRestParameter && source.parameters.length >= target.parameters.length)) { + return true; + } + return false; } function compareSignatures(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { if (source === target) { return Ternary.True; } - if (!(isMatchingSignature(source, target) || partialMatch && isPartiallyMatchingSignature(source, target))) { + if (!(isMatchingSignature(source, target, partialMatch))) { return Ternary.False; } let result = Ternary.True; From eee211a2a6b73029554880fc917b402c09ee58c1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 6 Nov 2015 08:54:22 -0800 Subject: [PATCH 06/12] Instantiate type parameter constraints with type parameter as 'this' --- src/compiler/checker.ts | 24 +++++++++++++++++------- src/compiler/types.ts | 2 ++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d836ce38d..8161ead7c7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2990,7 +2990,7 @@ namespace ts { (type).typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter | TypeFlags.ThisType); type.thisType.symbol = symbol; - type.thisType.constraint = getTypeWithThisArgument(type); + type.thisType.constraint = type; } } return links.declaredType; @@ -3533,6 +3533,21 @@ namespace ts { return type.flags & TypeFlags.UnionOrIntersection ? getPropertiesOfUnionOrIntersectionType(type) : getPropertiesOfObjectType(type); } + /** + * The apparent type of a type parameter is the base constraint instantiated with the type parameter + * as the type argument for the 'this' type. + */ + function getApparentTypeOfTypeParameter(type: TypeParameter) { + if (!type.resolvedApparentType) { + let constraintType = getConstraintOfTypeParameter(type); + while (constraintType && constraintType.flags & TypeFlags.TypeParameter) { + constraintType = getConstraintOfTypeParameter(constraintType); + } + type.resolvedApparentType = getTypeWithThisArgument(constraintType || emptyObjectType, type); + } + return type.resolvedApparentType; + } + /** * For a type parameter, return the base constraint of the type parameter. For the string, number, * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the @@ -3540,12 +3555,7 @@ namespace ts { */ function getApparentType(type: Type): Type { if (type.flags & TypeFlags.TypeParameter) { - do { - type = getConstraintOfTypeParameter(type); - } while (type && type.flags & TypeFlags.TypeParameter); - if (!type) { - type = emptyObjectType; - } + type = getApparentTypeOfTypeParameter(type); } if (type.flags & TypeFlags.StringLike) { type = globalStringType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f536a1e469..679957d1d3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1956,6 +1956,8 @@ namespace ts { target?: TypeParameter; // Instantiation target /* @internal */ mapper?: TypeMapper; // Instantiation mapper + /* @internal */ + resolvedApparentType: Type; } export const enum SignatureKind { From 870e96e525b19ec88a9c31beef80c89f2080933c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 6 Nov 2015 08:55:08 -0800 Subject: [PATCH 07/12] Adding test --- .../types/thisType/thisTypeAndConstraints.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts diff --git a/tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts b/tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts new file mode 100644 index 0000000000..f46b251c3b --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts @@ -0,0 +1,21 @@ +class A { + self() { + return this; + } +} + +function f(x: T) { + function g(x: U) { + x = x.self(); + } + x = x.self(); +} + +class B { + foo(x: T) { + x = x.self(); + } + bar(x: U) { + x = x.self(); + } +} From a234497004b5d2ad05e6832c896e17e086980cec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 6 Nov 2015 08:55:26 -0800 Subject: [PATCH 08/12] Updating existing test --- tests/cases/conformance/types/thisType/thisTypeInClasses.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cases/conformance/types/thisType/thisTypeInClasses.ts b/tests/cases/conformance/types/thisType/thisTypeInClasses.ts index 17dc13cf55..6c650341fb 100644 --- a/tests/cases/conformance/types/thisType/thisTypeInClasses.ts +++ b/tests/cases/conformance/types/thisType/thisTypeInClasses.ts @@ -1,7 +1,6 @@ class C1 { x: this; f(x: this): this { return undefined; } - constructor(x: this) { } } class C2 { From d52455f8902a5182ebdfbc6b8813c7e740c0dede Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 6 Nov 2015 08:56:09 -0800 Subject: [PATCH 09/12] Accepting new baselines --- tests/baselines/reference/fuzzy.errors.txt | 2 + .../reference/thisTypeAndConstraints.js | 50 +++++++++ .../reference/thisTypeAndConstraints.symbols | 70 ++++++++++++ .../reference/thisTypeAndConstraints.types | 78 +++++++++++++ .../reference/thisTypeInClasses.errors.txt | 57 ---------- .../baselines/reference/thisTypeInClasses.js | 3 +- .../reference/thisTypeInClasses.symbols | 105 +++++++++--------- .../reference/thisTypeInClasses.types | 3 - 8 files changed, 252 insertions(+), 116 deletions(-) create mode 100644 tests/baselines/reference/thisTypeAndConstraints.js create mode 100644 tests/baselines/reference/thisTypeAndConstraints.symbols create mode 100644 tests/baselines/reference/thisTypeAndConstraints.types delete mode 100644 tests/baselines/reference/thisTypeInClasses.errors.txt diff --git a/tests/baselines/reference/fuzzy.errors.txt b/tests/baselines/reference/fuzzy.errors.txt index 72ac4c816f..0aa8207ff9 100644 --- a/tests/baselines/reference/fuzzy.errors.txt +++ b/tests/baselines/reference/fuzzy.errors.txt @@ -4,6 +4,7 @@ tests/cases/compiler/fuzzy.ts(21,20): error TS2322: Type '{ anything: number; on Types of property 'oneI' are incompatible. Type 'this' is not assignable to type 'I'. Type 'C' is not assignable to type 'I'. + Property 'alsoWorks' is missing in type 'C'. tests/cases/compiler/fuzzy.ts(25,20): error TS2352: Neither type '{ oneI: this; }' nor type 'R' is assignable to the other. Property 'anything' is missing in type '{ oneI: this; }'. @@ -38,6 +39,7 @@ tests/cases/compiler/fuzzy.ts(25,20): error TS2352: Neither type '{ oneI: this; !!! error TS2322: Types of property 'oneI' are incompatible. !!! error TS2322: Type 'this' is not assignable to type 'I'. !!! error TS2322: Type 'C' is not assignable to type 'I'. +!!! error TS2322: Property 'alsoWorks' is missing in type 'C'. } worksToo():R { diff --git a/tests/baselines/reference/thisTypeAndConstraints.js b/tests/baselines/reference/thisTypeAndConstraints.js new file mode 100644 index 0000000000..d62ebe6917 --- /dev/null +++ b/tests/baselines/reference/thisTypeAndConstraints.js @@ -0,0 +1,50 @@ +//// [thisTypeAndConstraints.ts] +class A { + self() { + return this; + } +} + +function f(x: T) { + function g(x: U) { + x = x.self(); + } + x = x.self(); +} + +class B { + foo(x: T) { + x = x.self(); + } + bar(x: U) { + x = x.self(); + } +} + + +//// [thisTypeAndConstraints.js] +var A = (function () { + function A() { + } + A.prototype.self = function () { + return this; + }; + return A; +})(); +function f(x) { + function g(x) { + x = x.self(); + } + x = x.self(); +} +var B = (function () { + function B() { + } + B.prototype.foo = function (x) { + x = x.self(); + }; + B.prototype.bar = function (x) { + x = x.self(); + }; + return B; +})(); diff --git a/tests/baselines/reference/thisTypeAndConstraints.symbols b/tests/baselines/reference/thisTypeAndConstraints.symbols new file mode 100644 index 0000000000..5f93df22c1 --- /dev/null +++ b/tests/baselines/reference/thisTypeAndConstraints.symbols @@ -0,0 +1,70 @@ +=== tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts === +class A { +>A : Symbol(A, Decl(thisTypeAndConstraints.ts, 0, 0)) + + self() { +>self : Symbol(self, Decl(thisTypeAndConstraints.ts, 0, 9)) + + return this; +>this : Symbol(A, Decl(thisTypeAndConstraints.ts, 0, 0)) + } +} + +function f(x: T) { +>f : Symbol(f, Decl(thisTypeAndConstraints.ts, 4, 1)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 6, 11)) +>A : Symbol(A, Decl(thisTypeAndConstraints.ts, 0, 0)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 6, 24)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 6, 11)) + + function g(x: U) { +>g : Symbol(g, Decl(thisTypeAndConstraints.ts, 6, 31)) +>U : Symbol(U, Decl(thisTypeAndConstraints.ts, 7, 15)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 6, 11)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 7, 28)) +>U : Symbol(U, Decl(thisTypeAndConstraints.ts, 7, 15)) + + x = x.self(); +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 7, 28)) +>x.self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 7, 28)) +>self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) + } + x = x.self(); +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 6, 24)) +>x.self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 6, 24)) +>self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) +} + +class B { +>B : Symbol(B, Decl(thisTypeAndConstraints.ts, 11, 1)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 13, 8)) +>A : Symbol(A, Decl(thisTypeAndConstraints.ts, 0, 0)) + + foo(x: T) { +>foo : Symbol(foo, Decl(thisTypeAndConstraints.ts, 13, 22)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 14, 8)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 13, 8)) + + x = x.self(); +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 14, 8)) +>x.self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 14, 8)) +>self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) + } + bar(x: U) { +>bar : Symbol(bar, Decl(thisTypeAndConstraints.ts, 16, 5)) +>U : Symbol(U, Decl(thisTypeAndConstraints.ts, 17, 8)) +>T : Symbol(T, Decl(thisTypeAndConstraints.ts, 13, 8)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 17, 21)) +>U : Symbol(U, Decl(thisTypeAndConstraints.ts, 17, 8)) + + x = x.self(); +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 17, 21)) +>x.self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) +>x : Symbol(x, Decl(thisTypeAndConstraints.ts, 17, 21)) +>self : Symbol(A.self, Decl(thisTypeAndConstraints.ts, 0, 9)) + } +} + diff --git a/tests/baselines/reference/thisTypeAndConstraints.types b/tests/baselines/reference/thisTypeAndConstraints.types new file mode 100644 index 0000000000..af7a06e83c --- /dev/null +++ b/tests/baselines/reference/thisTypeAndConstraints.types @@ -0,0 +1,78 @@ +=== tests/cases/conformance/types/thisType/thisTypeAndConstraints.ts === +class A { +>A : A + + self() { +>self : () => this + + return this; +>this : this + } +} + +function f(x: T) { +>f : (x: T) => void +>T : T +>A : A +>x : T +>T : T + + function g(x: U) { +>g : (x: U) => void +>U : U +>T : T +>x : U +>U : U + + x = x.self(); +>x = x.self() : U +>x : U +>x.self() : U +>x.self : () => U +>x : U +>self : () => U + } + x = x.self(); +>x = x.self() : T +>x : T +>x.self() : T +>x.self : () => T +>x : T +>self : () => T +} + +class B { +>B : B +>T : T +>A : A + + foo(x: T) { +>foo : (x: T) => void +>x : T +>T : T + + x = x.self(); +>x = x.self() : T +>x : T +>x.self() : T +>x.self : () => T +>x : T +>self : () => T + } + bar(x: U) { +>bar : (x: U) => void +>U : U +>T : T +>x : U +>U : U + + x = x.self(); +>x = x.self() : U +>x : U +>x.self() : U +>x.self : () => U +>x : U +>self : () => U + } +} + diff --git a/tests/baselines/reference/thisTypeInClasses.errors.txt b/tests/baselines/reference/thisTypeInClasses.errors.txt deleted file mode 100644 index db2ae1b579..0000000000 --- a/tests/baselines/reference/thisTypeInClasses.errors.txt +++ /dev/null @@ -1,57 +0,0 @@ -tests/cases/conformance/types/thisType/thisTypeInClasses.ts(4,20): error TS2526: A 'this' type is available only in a non-static member of a class or interface. - - -==== tests/cases/conformance/types/thisType/thisTypeInClasses.ts (1 errors) ==== - class C1 { - x: this; - f(x: this): this { return undefined; } - constructor(x: this) { } - ~~~~ -!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. - } - - class C2 { - [x: string]: this; - } - - interface Foo { - x: T; - y: this; - } - - class C3 { - a: this[]; - b: [this, this]; - c: this | Date; - d: this & Date; - e: (((this))); - f: (x: this) => this; - g: new (x: this) => this; - h: Foo; - i: Foo this)>; - j: (x: any) => x is this; - } - - declare class C4 { - x: this; - f(x: this): this; - } - - class C5 { - foo() { - let f1 = (x: this): this => this; - let f2 = (x: this) => this; - let f3 = (x: this) => (y: this) => this; - let f4 = (x: this) => { - let g = (y: this) => { - return () => this; - } - return g(this); - } - } - bar() { - let x1 = undefined; - let x2 = undefined as this; - } - } - \ No newline at end of file diff --git a/tests/baselines/reference/thisTypeInClasses.js b/tests/baselines/reference/thisTypeInClasses.js index 93344fa62c..52ad452aa5 100644 --- a/tests/baselines/reference/thisTypeInClasses.js +++ b/tests/baselines/reference/thisTypeInClasses.js @@ -2,7 +2,6 @@ class C1 { x: this; f(x: this): this { return undefined; } - constructor(x: this) { } } class C2 { @@ -53,7 +52,7 @@ class C5 { //// [thisTypeInClasses.js] var C1 = (function () { - function C1(x) { + function C1() { } C1.prototype.f = function (x) { return undefined; }; return C1; diff --git a/tests/baselines/reference/thisTypeInClasses.symbols b/tests/baselines/reference/thisTypeInClasses.symbols index 04ed47b09f..0915d6e5e6 100644 --- a/tests/baselines/reference/thisTypeInClasses.symbols +++ b/tests/baselines/reference/thisTypeInClasses.symbols @@ -9,131 +9,128 @@ class C1 { >f : Symbol(f, Decl(thisTypeInClasses.ts, 1, 12)) >x : Symbol(x, Decl(thisTypeInClasses.ts, 2, 6)) >undefined : Symbol(undefined) - - constructor(x: this) { } ->x : Symbol(x, Decl(thisTypeInClasses.ts, 3, 16)) } class C2 { ->C2 : Symbol(C2, Decl(thisTypeInClasses.ts, 4, 1)) +>C2 : Symbol(C2, Decl(thisTypeInClasses.ts, 3, 1)) [x: string]: this; ->x : Symbol(x, Decl(thisTypeInClasses.ts, 7, 5)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 6, 5)) } interface Foo { ->Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 8, 1)) ->T : Symbol(T, Decl(thisTypeInClasses.ts, 10, 14)) +>Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 7, 1)) +>T : Symbol(T, Decl(thisTypeInClasses.ts, 9, 14)) x: T; ->x : Symbol(x, Decl(thisTypeInClasses.ts, 10, 18)) ->T : Symbol(T, Decl(thisTypeInClasses.ts, 10, 14)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 9, 18)) +>T : Symbol(T, Decl(thisTypeInClasses.ts, 9, 14)) y: this; ->y : Symbol(y, Decl(thisTypeInClasses.ts, 11, 9)) +>y : Symbol(y, Decl(thisTypeInClasses.ts, 10, 9)) } class C3 { ->C3 : Symbol(C3, Decl(thisTypeInClasses.ts, 13, 1)) +>C3 : Symbol(C3, Decl(thisTypeInClasses.ts, 12, 1)) a: this[]; ->a : Symbol(a, Decl(thisTypeInClasses.ts, 15, 10)) +>a : Symbol(a, Decl(thisTypeInClasses.ts, 14, 10)) b: [this, this]; ->b : Symbol(b, Decl(thisTypeInClasses.ts, 16, 14)) +>b : Symbol(b, Decl(thisTypeInClasses.ts, 15, 14)) c: this | Date; ->c : Symbol(c, Decl(thisTypeInClasses.ts, 17, 20)) +>c : Symbol(c, Decl(thisTypeInClasses.ts, 16, 20)) >Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) d: this & Date; ->d : Symbol(d, Decl(thisTypeInClasses.ts, 18, 19)) +>d : Symbol(d, Decl(thisTypeInClasses.ts, 17, 19)) >Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) e: (((this))); ->e : Symbol(e, Decl(thisTypeInClasses.ts, 19, 19)) +>e : Symbol(e, Decl(thisTypeInClasses.ts, 18, 19)) f: (x: this) => this; ->f : Symbol(f, Decl(thisTypeInClasses.ts, 20, 18)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 21, 8)) +>f : Symbol(f, Decl(thisTypeInClasses.ts, 19, 18)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 20, 8)) g: new (x: this) => this; ->g : Symbol(g, Decl(thisTypeInClasses.ts, 21, 25)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 22, 12)) +>g : Symbol(g, Decl(thisTypeInClasses.ts, 20, 25)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 21, 12)) h: Foo; ->h : Symbol(h, Decl(thisTypeInClasses.ts, 22, 29)) ->Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 8, 1)) +>h : Symbol(h, Decl(thisTypeInClasses.ts, 21, 29)) +>Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 7, 1)) i: Foo this)>; ->i : Symbol(i, Decl(thisTypeInClasses.ts, 23, 17)) ->Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 8, 1)) +>i : Symbol(i, Decl(thisTypeInClasses.ts, 22, 17)) +>Foo : Symbol(Foo, Decl(thisTypeInClasses.ts, 7, 1)) j: (x: any) => x is this; ->j : Symbol(j, Decl(thisTypeInClasses.ts, 24, 32)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 25, 8)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 25, 8)) +>j : Symbol(j, Decl(thisTypeInClasses.ts, 23, 32)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 24, 8)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 24, 8)) } declare class C4 { ->C4 : Symbol(C4, Decl(thisTypeInClasses.ts, 26, 1)) +>C4 : Symbol(C4, Decl(thisTypeInClasses.ts, 25, 1)) x: this; ->x : Symbol(x, Decl(thisTypeInClasses.ts, 28, 18)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 27, 18)) f(x: this): this; ->f : Symbol(f, Decl(thisTypeInClasses.ts, 29, 12)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 30, 6)) +>f : Symbol(f, Decl(thisTypeInClasses.ts, 28, 12)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 29, 6)) } class C5 { ->C5 : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>C5 : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) foo() { ->foo : Symbol(foo, Decl(thisTypeInClasses.ts, 33, 10)) +>foo : Symbol(foo, Decl(thisTypeInClasses.ts, 32, 10)) let f1 = (x: this): this => this; ->f1 : Symbol(f1, Decl(thisTypeInClasses.ts, 35, 11)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 35, 18)) ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>f1 : Symbol(f1, Decl(thisTypeInClasses.ts, 34, 11)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 34, 18)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) let f2 = (x: this) => this; ->f2 : Symbol(f2, Decl(thisTypeInClasses.ts, 36, 11)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 36, 18)) ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>f2 : Symbol(f2, Decl(thisTypeInClasses.ts, 35, 11)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 35, 18)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) let f3 = (x: this) => (y: this) => this; ->f3 : Symbol(f3, Decl(thisTypeInClasses.ts, 37, 11)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 37, 18)) ->y : Symbol(y, Decl(thisTypeInClasses.ts, 37, 31)) ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>f3 : Symbol(f3, Decl(thisTypeInClasses.ts, 36, 11)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 36, 18)) +>y : Symbol(y, Decl(thisTypeInClasses.ts, 36, 31)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) let f4 = (x: this) => { ->f4 : Symbol(f4, Decl(thisTypeInClasses.ts, 38, 11)) ->x : Symbol(x, Decl(thisTypeInClasses.ts, 38, 18)) +>f4 : Symbol(f4, Decl(thisTypeInClasses.ts, 37, 11)) +>x : Symbol(x, Decl(thisTypeInClasses.ts, 37, 18)) let g = (y: this) => { ->g : Symbol(g, Decl(thisTypeInClasses.ts, 39, 15)) ->y : Symbol(y, Decl(thisTypeInClasses.ts, 39, 21)) +>g : Symbol(g, Decl(thisTypeInClasses.ts, 38, 15)) +>y : Symbol(y, Decl(thisTypeInClasses.ts, 38, 21)) return () => this; ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) } return g(this); ->g : Symbol(g, Decl(thisTypeInClasses.ts, 39, 15)) ->this : Symbol(C5, Decl(thisTypeInClasses.ts, 31, 1)) +>g : Symbol(g, Decl(thisTypeInClasses.ts, 38, 15)) +>this : Symbol(C5, Decl(thisTypeInClasses.ts, 30, 1)) } } bar() { ->bar : Symbol(bar, Decl(thisTypeInClasses.ts, 44, 5)) +>bar : Symbol(bar, Decl(thisTypeInClasses.ts, 43, 5)) let x1 = undefined; ->x1 : Symbol(x1, Decl(thisTypeInClasses.ts, 46, 11)) +>x1 : Symbol(x1, Decl(thisTypeInClasses.ts, 45, 11)) >undefined : Symbol(undefined) let x2 = undefined as this; ->x2 : Symbol(x2, Decl(thisTypeInClasses.ts, 47, 11)) +>x2 : Symbol(x2, Decl(thisTypeInClasses.ts, 46, 11)) >undefined : Symbol(undefined) } } diff --git a/tests/baselines/reference/thisTypeInClasses.types b/tests/baselines/reference/thisTypeInClasses.types index ddfdfb277a..512359decd 100644 --- a/tests/baselines/reference/thisTypeInClasses.types +++ b/tests/baselines/reference/thisTypeInClasses.types @@ -9,9 +9,6 @@ class C1 { >f : (x: this) => this >x : this >undefined : undefined - - constructor(x: this) { } ->x : this } class C2 { From c0bb2eaaea0a34a276b944ace65b868a2ad216a8 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 6 Nov 2015 14:16:44 -0800 Subject: [PATCH 10/12] Add getCurrentDirectory to call to createDocumentRegistry --- src/services/shims.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/shims.ts b/src/services/shims.ts index 8482cb5097..fca72e49d3 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1032,7 +1032,7 @@ namespace ts { public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim { try { if (this.documentRegistry === undefined) { - this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); } var hostAdapter = new LanguageServiceShimHostAdapter(host); var languageService = createLanguageService(hostAdapter, this.documentRegistry); @@ -1068,7 +1068,7 @@ namespace ts { public close(): void { // Forget all the registered shims this._shims = []; - this.documentRegistry = createDocumentRegistry(); + this.documentRegistry = undefined; } public registerShim(shim: Shim): void { From 45746d11a61a0fd8ec405f85a38d2a2045f3e567 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 6 Nov 2015 16:21:43 -0800 Subject: [PATCH 11/12] make emitter singleton, replace reading file properties with local access, use one constructor function for all nodes --- src/compiler/core.ts | 19 ++- src/compiler/declarationEmitter.ts | 93 +++++++------- src/compiler/emitter.ts | 194 ++++++++++++++++++----------- src/compiler/parser.ts | 10 +- src/compiler/scanner.ts | 12 +- src/compiler/utilities.ts | 163 ++++++++++++++---------- 6 files changed, 285 insertions(+), 206 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5c882fb530..8ed8edf927 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -804,17 +804,16 @@ namespace ts { function Signature(checker: TypeChecker) { } + function Node(pos: number, end: number, kind: SyntaxKind) { + this.kind = kind; + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.parent = undefined; + } + export let objectAllocator: ObjectAllocator = { - getNodeConstructor: kind => { - function Node(pos: number, end: number) { - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.parent = undefined; - } - Node.prototype = { kind }; - return Node; - }, + getNodeConstructor: _ => Node, getSymbolConstructor: () => Symbol, getTypeConstructor: () => Type, getSignatureConstructor: () => Signature diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index ac91a39a78..081f3fd7d0 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -45,12 +45,15 @@ namespace ts { let writeLine: () => void; let increaseIndent: () => void; let decreaseIndent: () => void; - let writeTextOfNode: (sourceFile: SourceFile, node: Node) => void; + let writeTextOfNode: (text: string, node: Node) => void; let writer = createAndSetNewTextWriterWithSymbolWriter(); let enclosingDeclaration: Node; - let currentSourceFile: SourceFile; + let currentText: string; + let currentLineMap: number[]; + let currentIdentifiers: Map; + let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; let errorNameNode: DeclarationName; const emitJsDocComments = compilerOptions.removeComments ? function (declaration: Node) { } : writeJsDocComments; @@ -134,14 +137,13 @@ namespace ts { }; function hasInternalAnnotation(range: CommentRange) { - const text = currentSourceFile.text; - const comment = text.substring(range.pos, range.end); + const comment = currentText.substring(range.pos, range.end); return comment.indexOf("@internal") >= 0; } function stripInternal(node: Node) { if (node) { - const leadingCommentRanges = getLeadingCommentRanges(currentSourceFile.text, node.pos); + const leadingCommentRanges = getLeadingCommentRanges(currentText, node.pos); if (forEach(leadingCommentRanges, hasInternalAnnotation)) { return; } @@ -243,7 +245,7 @@ namespace ts { if (errorInfo.typeName) { diagnostics.push(createDiagnosticForNode(symbolAccesibilityResult.errorNode || errorInfo.errorNode, errorInfo.diagnosticMessage, - getSourceTextOfNodeFromSourceFile(currentSourceFile, errorInfo.typeName), + getTextOfNodeFromSourceText(currentText, errorInfo.typeName), symbolAccesibilityResult.errorSymbolName, symbolAccesibilityResult.errorModuleName)); } @@ -321,10 +323,10 @@ namespace ts { function writeJsDocComments(declaration: Node) { if (declaration) { - const jsDocComments = getJsDocComments(declaration, currentSourceFile); - emitNewLineBeforeLeadingComments(currentSourceFile, writer, declaration, jsDocComments); + const jsDocComments = getJsDocCommentsFromText(declaration, currentText); + emitNewLineBeforeLeadingComments(currentLineMap, writer, declaration, jsDocComments); // jsDoc comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentSourceFile, writer, jsDocComments, /*trailingSeparator*/ true, newLine, writeCommentRange); + emitComments(currentText, currentLineMap, writer, jsDocComments, /*trailingSeparator*/ true, newLine, writeCommentRange); } } @@ -343,7 +345,7 @@ namespace ts { case SyntaxKind.VoidKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.StringLiteral: - return writeTextOfNode(currentSourceFile, type); + return writeTextOfNode(currentText, type); case SyntaxKind.ExpressionWithTypeArguments: return emitExpressionWithTypeArguments(type); case SyntaxKind.TypeReference: @@ -375,14 +377,14 @@ namespace ts { function writeEntityName(entityName: EntityName | Expression) { if (entityName.kind === SyntaxKind.Identifier) { - writeTextOfNode(currentSourceFile, entityName); + writeTextOfNode(currentText, entityName); } else { const left = entityName.kind === SyntaxKind.QualifiedName ? (entityName).left : (entityName).expression; const right = entityName.kind === SyntaxKind.QualifiedName ? (entityName).right : (entityName).name; writeEntityName(left); write("."); - writeTextOfNode(currentSourceFile, right); + writeTextOfNode(currentText, right); } } @@ -417,7 +419,7 @@ namespace ts { } function emitTypePredicate(type: TypePredicateNode) { - writeTextOfNode(currentSourceFile, type.parameterName); + writeTextOfNode(currentText, type.parameterName); write(" is "); emitType(type.type); } @@ -466,9 +468,12 @@ namespace ts { } function emitSourceFile(node: SourceFile) { - currentSourceFile = node; + currentText = node.text; + currentLineMap = getLineStarts(node); + currentIdentifiers = node.identifiers; + isCurrentFileExternalModule = isExternalModule(node); enclosingDeclaration = node; - emitDetachedComments(currentSourceFile, writer, writeCommentRange, node, newLine, true /* remove comments */); + emitDetachedComments(currentText, currentLineMap, writer, writeCommentRange, node, newLine, true /* remove comments */); emitLines(node.statements); } @@ -478,13 +483,13 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!hasProperty(currentSourceFile.identifiers, baseName)) { + if (!hasProperty(currentIdentifiers, baseName)) { return baseName; } let count = 0; while (true) { const name = baseName + "_" + (++count); - if (!hasProperty(currentSourceFile.identifiers, name)) { + if (!hasProperty(currentIdentifiers, name)) { return name; } } @@ -493,7 +498,7 @@ namespace ts { function emitExportAssignment(node: ExportAssignment) { if (node.expression.kind === SyntaxKind.Identifier) { write(node.isExportEquals ? "export = " : "export default "); - writeTextOfNode(currentSourceFile, node.expression); + writeTextOfNode(currentText, node.expression); } else { // Expression @@ -537,7 +542,7 @@ namespace ts { } // Import equals declaration in internal module can become visible as part of any emit so lets make sure we add these irrespective else if (node.kind === SyntaxKind.ImportEqualsDeclaration || - (node.parent.kind === SyntaxKind.SourceFile && isExternalModule(currentSourceFile))) { + (node.parent.kind === SyntaxKind.SourceFile && isCurrentFileExternalModule)) { let isVisible: boolean; if (asynchronousSubModuleDeclarationEmitInfo && node.parent.kind !== SyntaxKind.SourceFile) { // Import declaration of another module that is visited async so lets put it in right spot @@ -593,7 +598,7 @@ namespace ts { function emitModuleElementDeclarationFlags(node: Node) { // If the node is parented in the current source file we need to emit export declare or just export - if (node.parent === currentSourceFile) { + if (node.parent.kind === SyntaxKind.SourceFile) { // If the node is exported if (node.flags & NodeFlags.Export) { write("export "); @@ -632,7 +637,7 @@ namespace ts { write("export "); } write("import "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); write(" = "); if (isInternalModuleImportEqualsDeclaration(node)) { emitTypeWithNewGetSymbolAccessibilityDiagnostic(node.moduleReference, getImportEntityNameVisibilityError); @@ -640,7 +645,7 @@ namespace ts { } else { write("require("); - writeTextOfNode(currentSourceFile, getExternalModuleImportEqualsDeclarationExpression(node)); + writeTextOfNode(currentText, getExternalModuleImportEqualsDeclarationExpression(node)); write(");"); } writer.writeLine(); @@ -678,7 +683,7 @@ namespace ts { if (node.importClause) { const currentWriterPos = writer.getTextPos(); if (node.importClause.name && resolver.isDeclarationVisible(node.importClause)) { - writeTextOfNode(currentSourceFile, node.importClause.name); + writeTextOfNode(currentText, node.importClause.name); } if (node.importClause.namedBindings && isVisibleNamedBinding(node.importClause.namedBindings)) { if (currentWriterPos !== writer.getTextPos()) { @@ -687,7 +692,7 @@ namespace ts { } if (node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { write("* as "); - writeTextOfNode(currentSourceFile, (node.importClause.namedBindings).name); + writeTextOfNode(currentText, (node.importClause.namedBindings).name); } else { write("{ "); @@ -697,17 +702,17 @@ namespace ts { } write(" from "); } - writeTextOfNode(currentSourceFile, node.moduleSpecifier); + writeTextOfNode(currentText, node.moduleSpecifier); write(";"); writer.writeLine(); } function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { if (node.propertyName) { - writeTextOfNode(currentSourceFile, node.propertyName); + writeTextOfNode(currentText, node.propertyName); write(" as "); } - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); } function emitExportSpecifier(node: ExportSpecifier) { @@ -733,7 +738,7 @@ namespace ts { } if (node.moduleSpecifier) { write(" from "); - writeTextOfNode(currentSourceFile, node.moduleSpecifier); + writeTextOfNode(currentText, node.moduleSpecifier); } write(";"); writer.writeLine(); @@ -748,11 +753,11 @@ namespace ts { else { write("module "); } - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); while (node.body.kind !== SyntaxKind.ModuleBlock) { node = node.body; write("."); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); } const prevEnclosingDeclaration = enclosingDeclaration; enclosingDeclaration = node; @@ -772,7 +777,7 @@ namespace ts { emitJsDocComments(node); emitModuleElementDeclarationFlags(node); write("type "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); emitTypeParameters(node.typeParameters); write(" = "); emitTypeWithNewGetSymbolAccessibilityDiagnostic(node.type, getTypeAliasDeclarationVisibilityError); @@ -796,7 +801,7 @@ namespace ts { write("const "); } write("enum "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); write(" {"); writeLine(); increaseIndent(); @@ -808,7 +813,7 @@ namespace ts { function emitEnumMemberDeclaration(node: EnumMember) { emitJsDocComments(node); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); const enumMemberValue = resolver.getConstantValue(node); if (enumMemberValue !== undefined) { write(" = "); @@ -827,7 +832,7 @@ namespace ts { increaseIndent(); emitJsDocComments(node); decreaseIndent(); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); // If there is constraint present and this is not a type parameter of the private method emit the constraint if (node.constraint && !isPrivateMethodTypeParameter(node)) { write(" extends "); @@ -958,7 +963,7 @@ namespace ts { } write("class "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); const prevEnclosingDeclaration = enclosingDeclaration; enclosingDeclaration = node; emitTypeParameters(node.typeParameters); @@ -982,7 +987,7 @@ namespace ts { emitJsDocComments(node); emitModuleElementDeclarationFlags(node); write("interface "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); const prevEnclosingDeclaration = enclosingDeclaration; enclosingDeclaration = node; emitTypeParameters(node.typeParameters); @@ -1020,7 +1025,7 @@ namespace ts { // If this node is a computed name, it can only be a symbol, because we've already skipped // it if it's not a well known symbol. In that case, the text of the name will be exactly // what we want, namely the name expression enclosed in brackets. - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); // If optional property emit ? if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) && hasQuestionToken(node)) { write("?"); @@ -1107,7 +1112,7 @@ namespace ts { emitBindingPattern(bindingElement.name); } else { - writeTextOfNode(currentSourceFile, bindingElement.name); + writeTextOfNode(currentText, bindingElement.name); writeTypeOfDeclaration(bindingElement, /*type*/ undefined, getBindingElementTypeVisibilityError); } } @@ -1157,7 +1162,7 @@ namespace ts { emitJsDocComments(accessors.getAccessor); emitJsDocComments(accessors.setAccessor); emitClassMemberDeclarationFlags(node); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); if (!(node.flags & NodeFlags.Private)) { accessorWithTypeAnnotation = node; let type = getTypeAnnotationFromAccessor(node); @@ -1247,13 +1252,13 @@ namespace ts { } if (node.kind === SyntaxKind.FunctionDeclaration) { write("function "); - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); } else if (node.kind === SyntaxKind.Constructor) { write("constructor"); } else { - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); if (hasQuestionToken(node)) { write("?"); } @@ -1393,7 +1398,7 @@ namespace ts { emitBindingPattern(node.name); } else { - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); } if (resolver.isOptionalParameter(node)) { write("?"); @@ -1519,7 +1524,7 @@ namespace ts { // Example: // original: function foo({y: [a,b,c]}) {} // emit : declare function foo({y: [a, b, c]}: { y: [any, any, any] }) void; - writeTextOfNode(currentSourceFile, bindingElement.propertyName); + writeTextOfNode(currentText, bindingElement.propertyName); write(": "); } if (bindingElement.name) { @@ -1542,7 +1547,7 @@ namespace ts { if (bindingElement.dotDotDotToken) { write("..."); } - writeTextOfNode(currentSourceFile, bindingElement.name); + writeTextOfNode(currentText, bindingElement.name); } } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 464bd51a91..cb4cd3b380 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -333,6 +333,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi const jsxDesugaring = host.getCompilerOptions().jsx !== JsxEmit.Preserve; const shouldEmitJsx = (s: SourceFile) => (s.languageVariant === LanguageVariant.JSX && !jsxDesugaring); + const emitJavaScript = createFileEmitter(); + if (targetSourceFile === undefined) { forEach(host.getSourceFiles(), sourceFile => { if (shouldEmitToOwnFile(sourceFile, compilerOptions)) { @@ -470,11 +472,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function emitJavaScript(jsFilePath: string, root?: SourceFile) { - const writer = createTextWriter(newLine); + function createFileEmitter(): (jsFilePath: string, root?: SourceFile) => void { + const writer: EmitTextWriter = createTextWriter(newLine); const { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer; let currentSourceFile: SourceFile; + let currentText: string; + let currentLineMap: number[]; + let currentFileIdentifiers: Map; + let renamedDependencies: Map; + let isEs6Module: boolean; + let isCurrentFileExternalModule: boolean; + // name of an exporter function if file is a System external module // System.register([...], function () {...}) // exporting in System modules looks like: @@ -483,17 +492,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // var x;... exporter("x", x = 1) let exportFunctionForFile: string; - const generatedNameSet: Map = {}; - const nodeToGeneratedName: string[] = []; + let generatedNameSet: Map; + let nodeToGeneratedName: string[]; let computedPropertyNamesToGeneratedNames: string[]; let convertedLoopState: ConvertedLoopState; - let extendsEmitted = false; - let decorateEmitted = false; - let paramEmitted = false; - let awaiterEmitted = false; - let tempFlags = 0; + let extendsEmitted: boolean; + let decorateEmitted: boolean; + let paramEmitted: boolean; + let awaiterEmitted: boolean; + let tempFlags: TempFlags; let tempVariables: Identifier[]; let tempParameters: Identifier[]; let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; @@ -547,35 +556,74 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi [ModuleKind.CommonJS]: emitCommonJSModule, }; - if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) { - initializeEmitterWithSourceMaps(); - } + return doEmit; - if (root) { - // Do not call emit directly. It does not set the currentSourceFile. - emitSourceFile(root); - } - else { - forEach(host.getSourceFiles(), sourceFile => { - if (!isExternalModuleOrDeclarationFile(sourceFile)) { - emitSourceFile(sourceFile); - } - }); - } + function doEmit(jsFilePath: string, root?: SourceFile) { + // reset the state + writer.reset(); + currentSourceFile = undefined; + currentText = undefined; + currentLineMap = undefined; + exportFunctionForFile = undefined; + generatedNameSet = {}; + nodeToGeneratedName = []; + computedPropertyNamesToGeneratedNames = undefined; + convertedLoopState = undefined; - writeLine(); - writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM); - return; + extendsEmitted = false; + decorateEmitted = false; + paramEmitted = false; + awaiterEmitted = false; + tempFlags = 0; + tempVariables = undefined; + tempParameters = undefined; + externalImports = undefined; + exportSpecifiers = undefined; + exportEquals = undefined; + hasExportStars = undefined; + detachedCommentsInfo = undefined; + sourceMapData = undefined; + isEs6Module = false; + renamedDependencies = undefined; + isCurrentFileExternalModule = false; + + if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) { + initializeEmitterWithSourceMaps(jsFilePath, root); + } + + if (root) { + // Do not call emit directly. It does not set the currentSourceFile. + emitSourceFile(root); + } + else { + forEach(host.getSourceFiles(), sourceFile => { + if (!isExternalModuleOrDeclarationFile(sourceFile)) { + emitSourceFile(sourceFile); + } + }); + } + + writeLine(); + writeEmittedFiles(writer.getText(), jsFilePath, /*writeByteOrderMark*/ compilerOptions.emitBOM); + } function emitSourceFile(sourceFile: SourceFile): void { currentSourceFile = sourceFile; + + currentText = sourceFile.text; + currentLineMap = getLineStarts(sourceFile); exportFunctionForFile = undefined; + isEs6Module = sourceFile.symbol && sourceFile.symbol.exports && !!sourceFile.symbol.exports["___esModule"]; + renamedDependencies = sourceFile.renamedDependencies; + currentFileIdentifiers = sourceFile.identifiers; + isCurrentFileExternalModule = isExternalModule(sourceFile); + emit(sourceFile); } function isUniqueName(name: string): boolean { return !resolver.hasGlobalName(name) && - !hasProperty(currentSourceFile.identifiers, name) && + !hasProperty(currentFileIdentifiers, name) && !hasProperty(generatedNameSet, name); } @@ -667,7 +715,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return nodeToGeneratedName[id] || (nodeToGeneratedName[id] = unescapeIdentifier(generateNameForNode(node))); } - function initializeEmitterWithSourceMaps() { + function initializeEmitterWithSourceMaps(jsFilePath: string, root?: SourceFile) { let sourceMapDir: string; // The directory in which sourcemap will be // Current source map file and its index in the sources list @@ -771,7 +819,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function recordSourceMapSpan(pos: number) { - const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); + const sourceLinePos = computeLineAndCharacterOfPosition(currentLineMap, pos); // Convert the location to be one-based. sourceLinePos.line++; @@ -810,7 +858,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function recordEmitNodeStartSpan(node: Node) { // Get the token pos after skipping to the token (ignoring the leading trivia) - recordSourceMapSpan(skipTrivia(currentSourceFile.text, node.pos)); + recordSourceMapSpan(skipTrivia(currentText, node.pos)); } function recordEmitNodeEndSpan(node: Node) { @@ -818,7 +866,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function writeTextWithSpanRecord(tokenKind: SyntaxKind, startPos: number, emitFn?: () => void) { - const tokenStartPos = ts.skipTrivia(currentSourceFile.text, startPos); + const tokenStartPos = ts.skipTrivia(currentText, startPos); recordSourceMapSpan(tokenStartPos); const tokenEndPos = emitTokenText(tokenKind, tokenStartPos, emitFn); recordSourceMapSpan(tokenEndPos); @@ -912,9 +960,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi sourceMapNameIndices.pop(); }; - function writeCommentRangeWithMap(curentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) { + function writeCommentRangeWithMap(currentText: string, currentLineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) { recordSourceMapSpan(comment.pos); - writeCommentRange(currentSourceFile, writer, comment, newLine); + writeCommentRange(currentText, currentLineMap, writer, comment, newLine); recordSourceMapSpan(comment.end); } @@ -950,7 +998,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function writeJavaScriptAndSourceMapFile(emitOutput: string, writeByteOrderMark: boolean) { + function writeJavaScriptAndSourceMapFile(emitOutput: string, jsFilePath: string, writeByteOrderMark: boolean) { encodeLastRecordedSourceMapSpan(); const sourceMapText = serializeSourceMapContents( @@ -977,7 +1025,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } // Write sourcemap url to the js file and write the js file - writeJavaScriptFile(emitOutput + sourceMapUrl, writeByteOrderMark); + writeJavaScriptFile(emitOutput + sourceMapUrl, jsFilePath, writeByteOrderMark); } // Initialize source map data @@ -1059,7 +1107,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi writeComment = writeCommentRangeWithMap; } - function writeJavaScriptFile(emitOutput: string, writeByteOrderMark: boolean) { + function writeJavaScriptFile(emitOutput: string, jsFilePath: string, writeByteOrderMark: boolean) { writeFile(host, diagnostics, jsFilePath, emitOutput, writeByteOrderMark); } @@ -1269,7 +1317,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. if (node.parent) { - return getSourceTextOfNodeFromSourceFile(currentSourceFile, node); + return getTextOfNodeFromSourceText(currentText, node); } // If we can't reach the original source text, use the canonical form if it's a number, @@ -1300,7 +1348,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Find original source text, since we need to emit the raw strings of the tagged template. // The raw strings contain the (escaped) strings of what the user wrote. // Examples: `\n` is converted to "\\n", a template string with a newline to "\n". - let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node); + let text = getTextOfNodeFromSourceText(currentText, node); // text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"), // thus we need to remove those characters. @@ -1772,7 +1820,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write((node).text); } else { - writeTextOfNode(currentSourceFile, node); + writeTextOfNode(currentText, node); } write("\""); @@ -1876,7 +1924,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Identifier references named import write(getGeneratedNameForNode(declaration.parent.parent.parent)); const name = (declaration).propertyName || (declaration).name; - const identifier = getSourceTextOfNodeFromSourceFile(currentSourceFile, name); + const identifier = getTextOfNodeFromSourceText(currentText, name); if (languageVersion === ScriptTarget.ES3 && identifier === "default") { write(`["default"]`); } @@ -1902,7 +1950,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(node.text); } else { - writeTextOfNode(currentSourceFile, node); + writeTextOfNode(currentText, node); } } @@ -1943,7 +1991,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(node.text); } else { - writeTextOfNode(currentSourceFile, node); + writeTextOfNode(currentText, node); } } @@ -2403,7 +2451,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { // The name property of a short-hand property assignment is considered an expression position, so here // we manually emit the identifier to avoid rewriting. - writeTextOfNode(currentSourceFile, node.name); + writeTextOfNode(currentText, node.name); // If emitting pre-ES6 code, or if the name requires rewriting when resolved as an expression identifier, // we emit a normal property assignment. For example: // module m { @@ -2480,11 +2528,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // 1 .toString is a valid property access, emit a space after the literal // Also emit a space if expression is a integer const enum value - it will appear in generated code as numeric literal - let shouldEmitSpace: boolean; + let shouldEmitSpace = false; if (!indentedBeforeDot) { if (node.expression.kind === SyntaxKind.NumericLiteral) { // check if numeric literal was originally written with a dot - const text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node.expression); + const text = getTextOfNodeFromSourceText(currentText, node.expression); shouldEmitSpace = text.indexOf(tokenToString(SyntaxKind.DotToken)) < 0; } else { @@ -3815,18 +3863,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function nodeStartPositionsAreOnSameLine(node1: Node, node2: Node) { - return getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node1.pos)) === - getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node2.pos)); + return getLineOfLocalPositionFromLineMap(currentLineMap, skipTrivia(currentText, node1.pos)) === + getLineOfLocalPositionFromLineMap(currentLineMap, skipTrivia(currentText, node2.pos)); } function nodeEndPositionsAreOnSameLine(node1: Node, node2: Node) { - return getLineOfLocalPosition(currentSourceFile, node1.end) === - getLineOfLocalPosition(currentSourceFile, node2.end); + return getLineOfLocalPositionFromLineMap(currentLineMap, node1.end) === + getLineOfLocalPositionFromLineMap(currentLineMap, node2.end); } function nodeEndIsOnSameLineAsNodeStart(node1: Node, node2: Node) { - return getLineOfLocalPosition(currentSourceFile, node1.end) === - getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node2.pos)); + return getLineOfLocalPositionFromLineMap(currentLineMap, node1.end) === + getLineOfLocalPositionFromLineMap(currentLineMap, skipTrivia(currentText, node2.pos)); } function emitCaseOrDefaultClause(node: CaseOrDefaultClause) { @@ -3948,7 +3996,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi Debug.assert(!!(node.flags & NodeFlags.Default) || node.kind === SyntaxKind.ExportAssignment); // only allow export default at a source file level if (modulekind === ModuleKind.CommonJS || modulekind === ModuleKind.AMD || modulekind === ModuleKind.UMD) { - if (!currentSourceFile.symbol.exports["___esModule"]) { + if (!isEs6Module) { if (languageVersion === ScriptTarget.ES5) { // default value of configurable, enumerable, writable are `false`. write("Object.defineProperty(exports, \"__esModule\", { value: true });"); @@ -6304,8 +6352,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi * Here we check if alternative name was provided for a given moduleName and return it if possible. */ function tryRenameExternalModule(moduleName: LiteralExpression): string { - if (currentSourceFile.renamedDependencies && hasProperty(currentSourceFile.renamedDependencies, moduleName.text)) { - return `"${currentSourceFile.renamedDependencies[moduleName.text]}"`; + if (renamedDependencies && hasProperty(renamedDependencies, moduleName.text)) { + return `"${renamedDependencies[moduleName.text]}"`; } return undefined; } @@ -6467,7 +6515,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // - current file is not external module // - import declaration is top level and target is value imported by entity name if (resolver.isReferencedAliasDeclaration(node) || - (!isExternalModule(currentSourceFile) && resolver.isTopLevelValueImportEqualsWithEntityName(node))) { + (!isCurrentFileExternalModule && resolver.isTopLevelValueImportEqualsWithEntityName(node))) { emitLeadingComments(node); emitStart(node); @@ -6708,7 +6756,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration): string { const namespaceDeclaration = getNamespaceDeclarationNode(node); if (namespaceDeclaration && !isDefaultImport(node)) { - return getSourceTextOfNodeFromSourceFile(currentSourceFile, namespaceDeclaration.name); + return getTextOfNodeFromSourceText(currentText, namespaceDeclaration.name); } if (node.kind === SyntaxKind.ImportDeclaration && (node).importClause) { return getGeneratedNameForNode(node); @@ -7064,7 +7112,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function isCurrentFileSystemExternalModule() { - return modulekind === ModuleKind.System && isExternalModule(currentSourceFile); + return modulekind === ModuleKind.System && isCurrentFileExternalModule; } function emitSystemModuleBody(node: SourceFile, dependencyGroups: DependencyGroup[], startIndex: number): void { @@ -7927,7 +7975,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function getLeadingCommentsWithoutDetachedComments() { // get the leading comments from detachedPos - const leadingComments = getLeadingCommentRanges(currentSourceFile.text, + const leadingComments = getLeadingCommentRanges(currentText, lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos); if (detachedCommentsInfo.length - 1) { detachedCommentsInfo.pop(); @@ -7947,10 +7995,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function isTripleSlashComment(comment: CommentRange) { // Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text // so that we don't end up computing comment string and doing match for all // comments - if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.slash && + if (currentText.charCodeAt(comment.pos + 1) === CharacterCodes.slash && comment.pos + 2 < comment.end && - currentSourceFile.text.charCodeAt(comment.pos + 2) === CharacterCodes.slash) { - const textSubStr = currentSourceFile.text.substring(comment.pos, comment.end); + currentText.charCodeAt(comment.pos + 2) === CharacterCodes.slash) { + const textSubStr = currentText.substring(comment.pos, comment.end); return textSubStr.match(fullTripleSlashReferencePathRegEx) || textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ? true : false; @@ -7968,7 +8016,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } else { // get the leading comments from the node - return getLeadingCommentRangesOfNode(node, currentSourceFile); + return getLeadingCommentRangesOfNodeFromText(node, currentText); } } } @@ -7978,7 +8026,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Emit the trailing comments only if the parent's pos doesn't match because parent should take care of emitting these comments if (node.parent) { if (node.parent.kind === SyntaxKind.SourceFile || node.end !== node.parent.end) { - return getTrailingCommentRanges(currentSourceFile.text, node.end); + return getTrailingCommentRanges(currentText, node.end); } } } @@ -8017,10 +8065,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - emitNewLineBeforeLeadingComments(currentSourceFile, writer, node, leadingComments); + emitNewLineBeforeLeadingComments(currentLineMap, writer, node, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentSourceFile, writer, leadingComments, /*trailingSeparator:*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator:*/ true, newLine, writeComment); } function emitTrailingComments(node: Node) { @@ -8032,7 +8080,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi const trailingComments = getTrailingCommentsToEmit(node); // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentSourceFile, writer, trailingComments, /*trailingSeparator*/ false, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ false, newLine, writeComment); } /** @@ -8045,10 +8093,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return; } - const trailingComments = getTrailingCommentRanges(currentSourceFile.text, pos); + const trailingComments = getTrailingCommentRanges(currentText, pos); // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentSourceFile, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment); } function emitLeadingCommentsOfPositionWorker(pos: number) { @@ -8063,17 +8111,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } else { // get the leading comments from the node - leadingComments = getLeadingCommentRanges(currentSourceFile.text, pos); + leadingComments = getLeadingCommentRanges(currentText, pos); } - emitNewLineBeforeLeadingComments(currentSourceFile, writer, { pos: pos, end: pos }, leadingComments); + emitNewLineBeforeLeadingComments(currentLineMap, writer, { pos: pos, end: pos }, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentSourceFile, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); } function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile, writer, writeComment, node, newLine, compilerOptions.removeComments); + const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, node, newLine, compilerOptions.removeComments); if (currentDetachedCommentInfo) { if (detachedCommentsInfo) { @@ -8086,7 +8134,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function emitShebang() { - const shebang = getShebang(currentSourceFile.text); + const shebang = getShebang(currentText); if (shebang) { write(shebang); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ab06b12318..5ec56496ff 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2,15 +2,15 @@ /// namespace ts { - const nodeConstructors = new Array Node>(SyntaxKind.Count); + const nodeConstructors = new Array Node>(SyntaxKind.Count); /* @internal */ export let parseTime = 0; - export function getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number) => Node { + export function getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number, kind?: SyntaxKind) => Node { return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)); } export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { - return new (getNodeConstructor(kind))(pos, end); + return new (getNodeConstructor(kind))(pos, end, kind); } function visitNode(cbNode: (node: Node) => T, node: Node): T { @@ -671,7 +671,7 @@ namespace ts { return sourceFile; } - function setContextFlag(val: Boolean, flag: ParserContextFlags) { + function setContextFlag(val: boolean, flag: ParserContextFlags) { if (val) { contextFlags |= flag; } @@ -996,7 +996,7 @@ namespace ts { if (!(pos >= 0)) { pos = scanner.getStartPos(); } - return new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(pos, pos); + return new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(pos, pos, kind); } function finishNode(node: T, end?: number): T { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 4e11e0d6b9..9703ef8517 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -580,7 +580,7 @@ namespace ts { function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] { let result: CommentRange[]; let collecting = trailing || pos === 0; - while (true) { + while (pos < text.length) { const ch = text.charCodeAt(pos); switch (ch) { case CharacterCodes.carriageReturn: @@ -650,6 +650,8 @@ namespace ts { } return result; } + + return result; } export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] { @@ -741,7 +743,7 @@ namespace ts { } } - function scanNumber(): number { + function scanNumber(): string { const start = pos; while (isDigit(text.charCodeAt(pos))) pos++; if (text.charCodeAt(pos) === CharacterCodes.dot) { @@ -761,7 +763,7 @@ namespace ts { error(Diagnostics.Digit_expected); } } - return +(text.substring(start, end)); + return "" + +(text.substring(start, end)); } function scanOctalDigits(): number { @@ -1229,7 +1231,7 @@ namespace ts { return pos++, token = SyntaxKind.MinusToken; case CharacterCodes.dot: if (isDigit(text.charCodeAt(pos + 1))) { - tokenValue = "" + scanNumber(); + tokenValue = scanNumber(); return token = SyntaxKind.NumericLiteral; } if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { @@ -1343,7 +1345,7 @@ namespace ts { case CharacterCodes._7: case CharacterCodes._8: case CharacterCodes._9: - tokenValue = "" + scanNumber(); + tokenValue = scanNumber(); return token = SyntaxKind.NumericLiteral; case CharacterCodes.colon: return pos++, token = SyntaxKind.ColonToken; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index aecf8ab76b..38d4fd9c7d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -421,18 +421,26 @@ namespace ts { return getLeadingCommentRanges(sourceFileOfNode.text, node.pos); } + export function getLeadingCommentRangesOfNodeFromText(node: Node, text: string) { + return getLeadingCommentRanges(text, node.pos); + } + export function getJsDocComments(node: Node, sourceFileOfNode: SourceFile) { + return getJsDocCommentsFromText(node, sourceFileOfNode.text); + } + + export function getJsDocCommentsFromText(node: Node, text: string) { const commentRanges = (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter) ? - concatenate(getTrailingCommentRanges(sourceFileOfNode.text, node.pos), - getLeadingCommentRanges(sourceFileOfNode.text, node.pos)) : - getLeadingCommentRangesOfNode(node, sourceFileOfNode); + concatenate(getTrailingCommentRanges(text, node.pos), + getLeadingCommentRanges(text, node.pos)) : + getLeadingCommentRangesOfNodeFromText(node, text); return filter(commentRanges, isJsDocComment); function isJsDocComment(comment: CommentRange) { // True if the comment starts with '/**' but not if it is '/**/' - return sourceFileOfNode.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && - sourceFileOfNode.text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk && - sourceFileOfNode.text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash; + return text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && + text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk && + text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash; } } @@ -1383,8 +1391,8 @@ namespace ts { export function getFileReferenceFromReferencePath(comment: string, commentRange: CommentRange): ReferencePathMatchResult { const simpleReferenceRegEx = /^\/\/\/\s*/gim; - if (simpleReferenceRegEx.exec(comment)) { - if (isNoDefaultLibRegEx.exec(comment)) { + if (simpleReferenceRegEx.test(comment)) { + if (isNoDefaultLibRegEx.test(comment)) { return { isNoDefaultLib: true }; @@ -1689,7 +1697,7 @@ namespace ts { export interface EmitTextWriter { write(s: string): void; - writeTextOfNode(sourceFile: SourceFile, node: Node): void; + writeTextOfNode(text: string, node: Node): void; writeLine(): void; increaseIndent(): void; decreaseIndent(): void; @@ -1700,6 +1708,7 @@ namespace ts { getLine(): number; getColumn(): number; getIndent(): number; + reset(): void; } const indentStrings: string[] = ["", " "]; @@ -1715,11 +1724,11 @@ namespace ts { } export function createTextWriter(newLine: String): EmitTextWriter { - let output = ""; - let indent = 0; - let lineStart = true; - let lineCount = 0; - let linePos = 0; + let output: string; + let indent: number; + let lineStart: boolean; + let lineCount: number; + let linePos: number; function write(s: string) { if (s && s.length) { @@ -1731,6 +1740,14 @@ namespace ts { } } + function reset(): void { + output = ""; + indent = 0; + lineStart = true; + lineCount = 0; + linePos = 0; + } + function rawWrite(s: string) { if (s !== undefined) { if (lineStart) { @@ -1760,10 +1777,12 @@ namespace ts { } } - function writeTextOfNode(sourceFile: SourceFile, node: Node) { - write(getSourceTextOfNodeFromSourceFile(sourceFile, node)); + function writeTextOfNode(text: string, node: Node) { + write(getTextOfNodeFromSourceText(text, node)); } + reset(); + return { write, rawWrite, @@ -1777,6 +1796,7 @@ namespace ts { getLine: () => lineCount + 1, getColumn: () => lineStart ? indent * getIndentSize() + 1 : output.length - linePos + 1, getText: () => output, + reset }; } @@ -1809,6 +1829,10 @@ namespace ts { return getLineAndCharacterOfPosition(currentSourceFile, pos).line; } + export function getLineOfLocalPositionFromLineMap(lineMap: number[], pos: number) { + return computeLineAndCharacterOfPosition(lineMap, pos).line; + } + export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration { return forEach(node.members, member => { if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { @@ -1883,23 +1907,23 @@ namespace ts { }; } - export function emitNewLineBeforeLeadingComments(currentSourceFile: SourceFile, writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) { + export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) { // If the leading comments start on different line than the start of node, write new line if (leadingComments && leadingComments.length && node.pos !== leadingComments[0].pos && - getLineOfLocalPosition(currentSourceFile, node.pos) !== getLineOfLocalPosition(currentSourceFile, leadingComments[0].pos)) { + getLineOfLocalPositionFromLineMap(lineMap, node.pos) !== getLineOfLocalPositionFromLineMap(lineMap, leadingComments[0].pos)) { writer.writeLine(); } } - export function emitComments(currentSourceFile: SourceFile, writer: EmitTextWriter, comments: CommentRange[], trailingSeparator: boolean, newLine: string, - writeComment: (currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) => void) { + export function emitComments(text: string, lineMap: number[], writer: EmitTextWriter, comments: CommentRange[], trailingSeparator: boolean, newLine: string, + writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) => void) { let emitLeadingSpace = !trailingSeparator; forEach(comments, comment => { if (emitLeadingSpace) { writer.write(" "); emitLeadingSpace = false; } - writeComment(currentSourceFile, writer, comment, newLine); + writeComment(text, lineMap, writer, comment, newLine); if (comment.hasTrailingNewLine) { writer.writeLine(); } @@ -1917,8 +1941,8 @@ namespace ts { * Detached comment is a comment at the top of file or function body that is separated from * the next statement by space. */ - export function emitDetachedComments(currentSourceFile: SourceFile, writer: EmitTextWriter, - writeComment: (currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) => void, + export function emitDetachedComments(text: string, lineMap: number[], writer: EmitTextWriter, + writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) => void, node: TextRange, newLine: string, removeComments: boolean) { let leadingComments: CommentRange[]; let currentDetachedCommentInfo: {nodePos: number, detachedCommentEndPos: number}; @@ -1929,12 +1953,12 @@ namespace ts { // // var x = 10; if (node.pos === 0) { - leadingComments = filter(getLeadingCommentRanges(currentSourceFile.text, node.pos), isPinnedComment); + leadingComments = filter(getLeadingCommentRanges(text, node.pos), isPinnedComment); } } else { // removeComments is false, just get detached as normal and bypass the process to filter comment - leadingComments = getLeadingCommentRanges(currentSourceFile.text, node.pos); + leadingComments = getLeadingCommentRanges(text, node.pos); } if (leadingComments) { @@ -1943,8 +1967,8 @@ namespace ts { for (const comment of leadingComments) { if (lastComment) { - const lastCommentLine = getLineOfLocalPosition(currentSourceFile, lastComment.end); - const commentLine = getLineOfLocalPosition(currentSourceFile, comment.pos); + const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, lastComment.end); + const commentLine = getLineOfLocalPositionFromLineMap(lineMap, comment.pos); if (commentLine >= lastCommentLine + 2) { // There was a blank line between the last comment and this comment. This @@ -1962,12 +1986,12 @@ namespace ts { // All comments look like they could have been part of the copyright header. Make // sure there is at least one blank line between it and the node. If not, it's not // a copyright header. - const lastCommentLine = getLineOfLocalPosition(currentSourceFile, lastOrUndefined(detachedComments).end); - const nodeLine = getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node.pos)); + const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, lastOrUndefined(detachedComments).end); + const nodeLine = getLineOfLocalPositionFromLineMap(lineMap, skipTrivia(text, node.pos)); if (nodeLine >= lastCommentLine + 2) { // Valid detachedComments - emitNewLineBeforeLeadingComments(currentSourceFile, writer, node, leadingComments); - emitComments(currentSourceFile, writer, detachedComments, /*trailingSeparator*/ true, newLine, writeComment); + emitNewLineBeforeLeadingComments(lineMap, writer, node, leadingComments); + emitComments(text, lineMap, writer, detachedComments, /*trailingSeparator*/ true, newLine, writeComment); currentDetachedCommentInfo = { nodePos: node.pos, detachedCommentEndPos: lastOrUndefined(detachedComments).end }; } } @@ -1976,25 +2000,26 @@ namespace ts { return currentDetachedCommentInfo; function isPinnedComment(comment: CommentRange) { - return currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && - currentSourceFile.text.charCodeAt(comment.pos + 2) === CharacterCodes.exclamation; + return text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && + text.charCodeAt(comment.pos + 2) === CharacterCodes.exclamation; } + } - export function writeCommentRange(currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) { - if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) { - const firstCommentLineAndCharacter = getLineAndCharacterOfPosition(currentSourceFile, comment.pos); - const lineCount = getLineStarts(currentSourceFile).length; + export function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) { + if (text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) { + const firstCommentLineAndCharacter = computeLineAndCharacterOfPosition(lineMap, comment.pos); + const lineCount = lineMap.length; let firstCommentLineIndent: number; for (let pos = comment.pos, currentLine = firstCommentLineAndCharacter.line; pos < comment.end; currentLine++) { const nextLineStart = (currentLine + 1) === lineCount - ? currentSourceFile.text.length + 1 - : getStartPositionOfLine(currentLine + 1, currentSourceFile); + ? text.length + 1 + : lineMap[currentLine + 1]; if (pos !== comment.pos) { // If we are not emitting first line, we need to write the spaces to adjust the alignment if (firstCommentLineIndent === undefined) { - firstCommentLineIndent = calculateIndent(getStartPositionOfLine(firstCommentLineAndCharacter.line, currentSourceFile), comment.pos); + firstCommentLineIndent = calculateIndent(text, lineMap[firstCommentLineAndCharacter.line], comment.pos); } // These are number of spaces writer is going to write at current indent @@ -2014,7 +2039,7 @@ namespace ts { // More right indented comment */ --4 = 8 - 4 + 11 // class c { } // } - const spacesToEmit = currentWriterIndentSpacing - firstCommentLineIndent + calculateIndent(pos, nextLineStart); + const spacesToEmit = currentWriterIndentSpacing - firstCommentLineIndent + calculateIndent(text, pos, nextLineStart); if (spacesToEmit > 0) { let numberOfSingleSpacesToEmit = spacesToEmit % getIndentSize(); const indentSizeSpaceString = getIndentString((spacesToEmit - numberOfSingleSpacesToEmit) / getIndentSize()); @@ -2035,47 +2060,47 @@ namespace ts { } // Write the comment line text - writeTrimmedCurrentLine(pos, nextLineStart); + writeTrimmedCurrentLine(text, comment, writer, newLine, pos, nextLineStart); pos = nextLineStart; } } else { // Single line comment of style //.... - writer.write(currentSourceFile.text.substring(comment.pos, comment.end)); + writer.write(text.substring(comment.pos, comment.end)); } + } - function writeTrimmedCurrentLine(pos: number, nextLineStart: number) { - const end = Math.min(comment.end, nextLineStart - 1); - const currentLineText = currentSourceFile.text.substring(pos, end).replace(/^\s+|\s+$/g, ""); - if (currentLineText) { - // trimmed forward and ending spaces text - writer.write(currentLineText); - if (end !== comment.end) { - writer.writeLine(); - } + function writeTrimmedCurrentLine(text: string, comment: CommentRange, writer: EmitTextWriter, newLine: string, pos: number, nextLineStart: number) { + const end = Math.min(comment.end, nextLineStart - 1); + const currentLineText = text.substring(pos, end).replace(/^\s+|\s+$/g, ""); + if (currentLineText) { + // trimmed forward and ending spaces text + writer.write(currentLineText); + if (end !== comment.end) { + writer.writeLine(); + } + } + else { + // Empty string - make sure we write empty line + writer.writeLiteral(newLine); + } + } + + function calculateIndent(text: string, pos: number, end: number) { + let currentLineIndent = 0; + for (; pos < end && isWhiteSpace(text.charCodeAt(pos)); pos++) { + if (text.charCodeAt(pos) === CharacterCodes.tab) { + // Tabs = TabSize = indent size and go to next tabStop + currentLineIndent += getIndentSize() - (currentLineIndent % getIndentSize()); } else { - // Empty string - make sure we write empty line - writer.writeLiteral(newLine); + // Single space + currentLineIndent++; } } - function calculateIndent(pos: number, end: number) { - let currentLineIndent = 0; - for (; pos < end && isWhiteSpace(currentSourceFile.text.charCodeAt(pos)); pos++) { - if (currentSourceFile.text.charCodeAt(pos) === CharacterCodes.tab) { - // Tabs = TabSize = indent size and go to next tabStop - currentLineIndent += getIndentSize() - (currentLineIndent % getIndentSize()); - } - else { - // Single space - currentLineIndent++; - } - } - - return currentLineIndent; - } + return currentLineIndent; } export function modifierToFlag(token: SyntaxKind): NodeFlags { From 3f1596bba7e61df6a48a6a445d89245cdef45786 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 7 Nov 2015 15:28:21 -0800 Subject: [PATCH 12/12] remove nodeConstructors array, replace it with single local --- src/compiler/core.ts | 8 +++++--- src/compiler/parser.ts | 31 ++++++++++++++++++++++--------- src/services/services.ts | 28 +++++++++++++++------------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8ed8edf927..2ce3bbfaf3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -785,7 +785,8 @@ namespace ts { }; export interface ObjectAllocator { - getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number) => Node; + getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; + getSourceFileConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => SourceFile; getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol; getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; @@ -804,7 +805,7 @@ namespace ts { function Signature(checker: TypeChecker) { } - function Node(pos: number, end: number, kind: SyntaxKind) { + function Node(kind: SyntaxKind, pos: number, end: number) { this.kind = kind; this.pos = pos; this.end = end; @@ -813,7 +814,8 @@ namespace ts { } export let objectAllocator: ObjectAllocator = { - getNodeConstructor: _ => Node, + getNodeConstructor: () => Node, + getSourceFileConstructor: () => Node, getSymbolConstructor: () => Symbol, getTypeConstructor: () => Type, getSignatureConstructor: () => Signature diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5ec56496ff..ec2aeb25af 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2,15 +2,18 @@ /// namespace ts { - const nodeConstructors = new Array Node>(SyntaxKind.Count); /* @internal */ export let parseTime = 0; - export function getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number, kind?: SyntaxKind) => Node { - return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)); - } + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { - return new (getNodeConstructor(kind))(pos, end, kind); + if (kind === SyntaxKind.SourceFile) { + return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, pos, end); + } + else { + return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, pos, end); + } } function visitNode(cbNode: (node: Node) => T, node: Node): T { @@ -437,6 +440,10 @@ namespace ts { const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); const disallowInAndDecoratorContext = ParserContextFlags.DisallowIn | ParserContextFlags.Decorator; + // capture constructors in 'initializeState' to avoid null checks + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let sourceFile: SourceFile; let parseDiagnostics: Diagnostic[]; let syntaxCursor: IncrementalParser.SyntaxCursor; @@ -537,6 +544,9 @@ namespace ts { } function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor) { + NodeConstructor = objectAllocator.getNodeConstructor(); + SourceFileConstructor = objectAllocator.getSourceFileConstructor(); + sourceText = _sourceText; syntaxCursor = _syntaxCursor; @@ -657,10 +667,11 @@ namespace ts { } function createSourceFile(fileName: string, languageVersion: ScriptTarget): SourceFile { - const sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0); + // code from createNode is inlined here so createNode won't have to deal with special case of creating source files + // this is quite rare comparing to other nodes and createNode should be as fast as possible + const sourceFile = new SourceFileConstructor(SyntaxKind.SourceFile, /*pos*/ 0, /* end */ sourceText.length); + nodeCount++; - sourceFile.pos = 0; - sourceFile.end = sourceText.length; sourceFile.text = sourceText; sourceFile.bindDiagnostics = []; sourceFile.languageVersion = languageVersion; @@ -991,12 +1002,14 @@ namespace ts { } } + // note: this function creates only node function createNode(kind: SyntaxKind, pos?: number): Node { nodeCount++; if (!(pos >= 0)) { pos = scanner.getStartPos(); } - return new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(pos, pos, kind); + + return new NodeConstructor(kind, pos, pos); } function finishNode(node: T, end?: number): T { diff --git a/src/services/services.ts b/src/services/services.ts index 1672e4ad4f..113a52459e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -174,7 +174,7 @@ namespace ts { let jsDocCompletionEntries: CompletionEntry[]; function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { - let node = new (getNodeConstructor(kind))(pos, end); + let node = new NodeObject(kind, pos, end); node.flags = flags; node.parent = parent; return node; @@ -188,6 +188,14 @@ namespace ts { public parent: Node; private _children: Node[]; + constructor(kind: SyntaxKind, pos: number, end: number) { + this.kind = kind; + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.parent = undefined; + } + public getSourceFile(): SourceFile { return getSourceFileOfNode(this); } @@ -805,6 +813,10 @@ namespace ts { public imports: LiteralExpression[]; private namedDeclarations: Map; + constructor(kind: SyntaxKind, pos: number, end: number) { + super(kind, pos, end) + } + public update(newText: string, textChangeRange: TextChangeRange): SourceFile { return updateSourceFile(this, newText, textChangeRange); } @@ -7970,18 +7982,8 @@ namespace ts { function initializeServices() { objectAllocator = { - getNodeConstructor: kind => { - function Node(pos: number, end: number) { - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.parent = undefined; - } - let proto = kind === SyntaxKind.SourceFile ? new SourceFileObject() : new NodeObject(); - proto.kind = kind; - Node.prototype = proto; - return Node; - }, + getNodeConstructor: () => NodeObject, + getSourceFileConstructor: () => SourceFileObject, getSymbolConstructor: () => SymbolObject, getTypeConstructor: () => TypeObject, getSignatureConstructor: () => SignatureObject,