From e130cd654e81913887b953ef95946605803ef417 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Tue, 26 Mar 2019 09:32:39 +0000 Subject: [PATCH 01/23] Narrow non-declared unions by discriminant Typo --- src/compiler/checker.ts | 10 +- .../discriminantsAndTypePredicates.js | 91 ++++++++++- .../discriminantsAndTypePredicates.symbols | 138 +++++++++++++++++ .../discriminantsAndTypePredicates.types | 142 ++++++++++++++++++ .../discriminantsAndTypePredicates.ts | 56 ++++++- 5 files changed, 431 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ed426f24005c0..9360079dacb84 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16175,14 +16175,16 @@ namespace ts { } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { - if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { + if (!isAccessExpression(expr)) { return false; } const name = getAccessedPropertyName(expr); if (name === undefined) { return false; } - return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); + let propType; + return isMatchingReference(reference, expr.expression) && + (isDiscriminantProperty(computedType, name) || (propType = getTypeOfPropertyOfType(computedType, name)) && isUnitType(propType)); } function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { @@ -16250,10 +16252,10 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, declaredType)) { + if (isMatchingReferenceDiscriminant(left, type)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right, declaredType)) { + if (isMatchingReferenceDiscriminant(right, type)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index 66cfe056ab711..fe02fb33e7159 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -29,7 +29,62 @@ function foo2(x: A | B): any { return x; // B } x; // never -} +} + +// Repro from #30557 + +interface TypeA { + Name: "TypeA"; + Value1: "Cool stuff!"; +} + +interface TypeB { + Name: "TypeB"; + Value2: 0; +} + +type Type = TypeA | TypeB; + +declare function isType(x: unknown): x is Type; + +function WorksProperly(data: Type) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } +} + +function DoesNotWork(data: unknown) { + if (isType(data)) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } + } +} + +function narrowToNever(data: Type): "Cool stuff!" | 0 { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; +} + +function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { + if (isType(data)) { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; + } +} + //// [discriminantsAndTypePredicates.js] // Repro from #10145 @@ -57,3 +112,37 @@ function foo2(x) { } x; // never } +function WorksProperly(data) { + if (data.Name === "TypeA") { + // TypeA + var value1 = data.Value1; + } +} +function DoesNotWork(data) { + if (isType(data)) { + if (data.Name === "TypeA") { + // TypeA + var value1 = data.Value1; + } + } +} +function narrowToNever(data) { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; +} +function narrowToNeverUnknown(data) { + if (isType(data)) { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; + } +} diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 4f4128239364f..a4943c6fe17a2 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -92,3 +92,141 @@ function foo2(x: A | B): any { x; // never >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 20, 14)) } + +// Repro from #30557 + +interface TypeA { +>TypeA : Symbol(TypeA, Decl(discriminantsAndTypePredicates.ts, 30, 1)) + + Name: "TypeA"; +>Name : Symbol(TypeA.Name, Decl(discriminantsAndTypePredicates.ts, 34, 17)) + + Value1: "Cool stuff!"; +>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) +} + +interface TypeB { +>TypeB : Symbol(TypeB, Decl(discriminantsAndTypePredicates.ts, 37, 1)) + + Name: "TypeB"; +>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + Value2: 0; +>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18)) +} + +type Type = TypeA | TypeB; +>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1)) +>TypeA : Symbol(TypeA, Decl(discriminantsAndTypePredicates.ts, 30, 1)) +>TypeB : Symbol(TypeB, Decl(discriminantsAndTypePredicates.ts, 37, 1)) + +declare function isType(x: unknown): x is Type; +>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 46, 24)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 46, 24)) +>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1)) + +function WorksProperly(data: Type) { +>WorksProperly : Symbol(WorksProperly, Decl(discriminantsAndTypePredicates.ts, 46, 47)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23)) +>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1)) + + if (data.Name === "TypeA") { +>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23)) +>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + // TypeA + const value1 = data.Value1; +>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 51, 6)) +>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23)) +>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) + } +} + +function DoesNotWork(data: unknown) { +>DoesNotWork : Symbol(DoesNotWork, Decl(discriminantsAndTypePredicates.ts, 53, 1)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) + + if (isType(data)) { +>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) + + if (data.Name === "TypeA") { +>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) +>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + // TypeA + const value1 = data.Value1; +>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 59, 10)) +>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) +>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) + } + } +} + +function narrowToNever(data: Type): "Cool stuff!" | 0 { +>narrowToNever : Symbol(narrowToNever, Decl(discriminantsAndTypePredicates.ts, 62, 1)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1)) + + if (data.Name === "TypeA") { +>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + return data.Value1; +>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) + } + if (data.Name === "TypeB") { +>data.Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + return data.Value2; +>data.Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18)) + } + return data; +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23)) +} + +function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { +>narrowToNeverUnknown : Symbol(narrowToNeverUnknown, Decl(discriminantsAndTypePredicates.ts, 72, 1)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) + + if (isType(data)) { +>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) + + if (data.Name === "TypeA") { +>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) +>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + return data.Value1; +>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) +>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) + } + if (data.Name === "TypeB") { +>data.Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) +>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17)) + + return data.Value2; +>data.Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18)) +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) +>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18)) + } + return data; +>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) + } +} + diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index 73e05ac59c421..3c5270fea883f 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -88,3 +88,145 @@ function foo2(x: A | B): any { x; // never >x : never } + +// Repro from #30557 + +interface TypeA { + Name: "TypeA"; +>Name : "TypeA" + + Value1: "Cool stuff!"; +>Value1 : "Cool stuff!" +} + +interface TypeB { + Name: "TypeB"; +>Name : "TypeB" + + Value2: 0; +>Value2 : 0 +} + +type Type = TypeA | TypeB; +>Type : Type + +declare function isType(x: unknown): x is Type; +>isType : (x: unknown) => x is Type +>x : unknown + +function WorksProperly(data: Type) { +>WorksProperly : (data: Type) => void +>data : Type + + if (data.Name === "TypeA") { +>data.Name === "TypeA" : boolean +>data.Name : "TypeA" | "TypeB" +>data : Type +>Name : "TypeA" | "TypeB" +>"TypeA" : "TypeA" + + // TypeA + const value1 = data.Value1; +>value1 : "Cool stuff!" +>data.Value1 : "Cool stuff!" +>data : TypeA +>Value1 : "Cool stuff!" + } +} + +function DoesNotWork(data: unknown) { +>DoesNotWork : (data: unknown) => void +>data : unknown + + if (isType(data)) { +>isType(data) : boolean +>isType : (x: unknown) => x is Type +>data : unknown + + if (data.Name === "TypeA") { +>data.Name === "TypeA" : boolean +>data.Name : "TypeA" | "TypeB" +>data : Type +>Name : "TypeA" | "TypeB" +>"TypeA" : "TypeA" + + // TypeA + const value1 = data.Value1; +>value1 : "Cool stuff!" +>data.Value1 : "Cool stuff!" +>data : TypeA +>Value1 : "Cool stuff!" + } + } +} + +function narrowToNever(data: Type): "Cool stuff!" | 0 { +>narrowToNever : (data: Type) => 0 | "Cool stuff!" +>data : Type + + if (data.Name === "TypeA") { +>data.Name === "TypeA" : boolean +>data.Name : "TypeA" | "TypeB" +>data : Type +>Name : "TypeA" | "TypeB" +>"TypeA" : "TypeA" + + return data.Value1; +>data.Value1 : "Cool stuff!" +>data : TypeA +>Value1 : "Cool stuff!" + } + if (data.Name === "TypeB") { +>data.Name === "TypeB" : boolean +>data.Name : "TypeB" +>data : TypeB +>Name : "TypeB" +>"TypeB" : "TypeB" + + return data.Value2; +>data.Value2 : 0 +>data : TypeB +>Value2 : 0 + } + return data; +>data : never +} + +function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { +>narrowToNeverUnknown : (data: unknown) => 0 | "Cool stuff!" +>data : unknown + + if (isType(data)) { +>isType(data) : boolean +>isType : (x: unknown) => x is Type +>data : unknown + + if (data.Name === "TypeA") { +>data.Name === "TypeA" : boolean +>data.Name : "TypeA" | "TypeB" +>data : Type +>Name : "TypeA" | "TypeB" +>"TypeA" : "TypeA" + + return data.Value1; +>data.Value1 : "Cool stuff!" +>data : TypeA +>Value1 : "Cool stuff!" + } + if (data.Name === "TypeB") { +>data.Name === "TypeB" : boolean +>data.Name : "TypeB" +>data : TypeB +>Name : "TypeB" +>"TypeB" : "TypeB" + + return data.Value2; +>data.Value2 : 0 +>data : TypeB +>Value2 : 0 + } + return data; +>data : never + } +} + diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index c21ab7ec8f49a..8826b2987d677 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -28,4 +28,58 @@ function foo2(x: A | B): any { return x; // B } x; // never -} \ No newline at end of file +} + +// Repro from #30557 + +interface TypeA { + Name: "TypeA"; + Value1: "Cool stuff!"; +} + +interface TypeB { + Name: "TypeB"; + Value2: 0; +} + +type Type = TypeA | TypeB; + +declare function isType(x: unknown): x is Type; + +function WorksProperly(data: Type) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } +} + +function DoesNotWork(data: unknown) { + if (isType(data)) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } + } +} + +function narrowToNever(data: Type): "Cool stuff!" | 0 { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; +} + +function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { + if (isType(data)) { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; + } +} From e91ee148cdcc4b95446626f227987b37931ae682 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Tue, 26 Mar 2019 19:11:13 +0000 Subject: [PATCH 02/23] Only perform fallback when computed type is not an intersection or union --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9360079dacb84..1239e16590dcb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16184,7 +16184,10 @@ namespace ts { } let propType; return isMatchingReference(reference, expr.expression) && - (isDiscriminantProperty(computedType, name) || (propType = getTypeOfPropertyOfType(computedType, name)) && isUnitType(propType)); + (isDiscriminantProperty(computedType, name) || + ((computedType.flags & TypeFlags.UnionOrIntersection) === 0) && + (propType = getTypeOfPropertyOfType(computedType, name)) + && isUnitType(propType)); } function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { From e038290c894c8623affff48ae3aaeb4ee75ac03b Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Tue, 26 Mar 2019 19:17:16 +0000 Subject: [PATCH 03/23] Do not rule out intersection case --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1239e16590dcb..30460e16ecc19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16185,7 +16185,7 @@ namespace ts { let propType; return isMatchingReference(reference, expr.expression) && (isDiscriminantProperty(computedType, name) || - ((computedType.flags & TypeFlags.UnionOrIntersection) === 0) && + ((computedType.flags & TypeFlags.Union) === 0) && (propType = getTypeOfPropertyOfType(computedType, name)) && isUnitType(propType)); } From 74c7c923f083f686bf2203c157171f7101a00827 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 27 Mar 2019 13:34:50 +0000 Subject: [PATCH 04/23] Try reuse declaredType property cache when possible --- src/compiler/checker.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 30460e16ecc19..b50b9aab49fca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16174,7 +16174,7 @@ namespace ts { return result; } - function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { + function isMatchingInFlowDiscriminant(expr: Expression, computedType: Type) { if (!isAccessExpression(expr)) { return false; } @@ -16184,10 +16184,21 @@ namespace ts { } let propType; return isMatchingReference(reference, expr.expression) && - (isDiscriminantProperty(computedType, name) || - ((computedType.flags & TypeFlags.Union) === 0) && - (propType = getTypeOfPropertyOfType(computedType, name)) - && isUnitType(propType)); + ((computedType.flags & TypeFlags.Union) ? + isDiscriminantProperty(computedType, name) : + (propType = getTypeOfPropertyOfType(computedType, name)) && + isUnitType(propType)); + } + + function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { + if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { + return false; + } + const name = getAccessedPropertyName(expr); + if (name === undefined) { + return false; + } + return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); } function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { @@ -16255,10 +16266,10 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, type)) { + if (isMatchingReferenceDiscriminant(left, declaredType) || isMatchingInFlowDiscriminant(left, type)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right, type)) { + if (isMatchingReferenceDiscriminant(left, declaredType) || isMatchingInFlowDiscriminant(left, type)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { From 61d2eaf5dc33d14265a925149ff67ee85a20853c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 27 Mar 2019 15:51:09 +0000 Subject: [PATCH 05/23] Telling left and right is hard, apparently --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b50b9aab49fca..c7942d6d8ce0e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16269,7 +16269,7 @@ namespace ts { if (isMatchingReferenceDiscriminant(left, declaredType) || isMatchingInFlowDiscriminant(left, type)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(left, declaredType) || isMatchingInFlowDiscriminant(left, type)) { + if (isMatchingReferenceDiscriminant(right, declaredType) || isMatchingInFlowDiscriminant(right, type)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { From 4b3a96a83980baef308ec2cbc8295cf7610b4869 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 27 Mar 2019 21:34:22 +0000 Subject: [PATCH 06/23] Add firstDiscriminable to hit one cache --- src/compiler/checker.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c7942d6d8ce0e..a991e7b5fac0f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15831,6 +15831,7 @@ namespace ts { function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string | undefined; let flowDepth = 0; + let firstDiscriminable: UnionType | undefined; if (flowAnalysisDisabled) { return errorType; } @@ -16174,20 +16175,14 @@ namespace ts { return result; } - function isMatchingInFlowDiscriminant(expr: Expression, computedType: Type) { - if (!isAccessExpression(expr)) { - return false; + function getTypeToDiscriminate(computedType: Type): Type { + if (firstDiscriminable) { + return firstDiscriminable; } - const name = getAccessedPropertyName(expr); - if (name === undefined) { - return false; + if (declaredType.flags & TypeFlags.Union) { + return firstDiscriminable = declaredType; } - let propType; - return isMatchingReference(reference, expr.expression) && - ((computedType.flags & TypeFlags.Union) ? - isDiscriminantProperty(computedType, name) : - (propType = getTypeOfPropertyOfType(computedType, name)) && - isUnitType(propType)); + return (computedType.flags & TypeFlags.Union) ? (firstDiscriminable = computedType) : declaredType; } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { @@ -16266,10 +16261,10 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, declaredType) || isMatchingInFlowDiscriminant(left, type)) { + if (isMatchingReferenceDiscriminant(left, getTypeToDiscriminate(type))) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right, declaredType) || isMatchingInFlowDiscriminant(right, type)) { + if (isMatchingReferenceDiscriminant(right, getTypeToDiscriminate(type))) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { From 229300149eb6dca11750c841c501427d8937e040 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Mon, 29 Apr 2019 22:43:50 +0100 Subject: [PATCH 07/23] Set and unset root type at appropriate transition --- src/compiler/checker.ts | 25 +- .../discriminantsAndTypePredicates.js | 110 +++++++++ .../discriminantsAndTypePredicates.symbols | 211 ++++++++++++++++ .../discriminantsAndTypePredicates.types | 232 ++++++++++++++++++ .../discriminantsAndTypePredicates.ts | 63 +++++ 5 files changed, 630 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fb1efeded8a0..0307512c78676 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16035,7 +16035,7 @@ namespace ts { function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string | undefined; let flowDepth = 0; - let firstDiscriminable: UnionType | undefined; + let rootDiscriminable: UnionType | undefined; if (flowAnalysisDisabled) { return errorType; } @@ -16379,14 +16379,9 @@ namespace ts { return result; } - function getTypeToDiscriminate(computedType: Type): Type { - if (firstDiscriminable) { - return firstDiscriminable; - } - if (declaredType.flags & TypeFlags.Union) { - return firstDiscriminable = declaredType; - } - return (computedType.flags & TypeFlags.Union) ? (firstDiscriminable = computedType) : declaredType; + function isFlowToRootDiscriminant(type: Type, candidate: Type, isRelated: (t1: Type, t2: Type) => boolean) { + return !(declaredType.flags & TypeFlags.Union) && !(type.flags & TypeFlags.Union) && (candidate.flags & TypeFlags.Union) !== 0 + && isRelated(candidate, type); } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { @@ -16465,10 +16460,10 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, getTypeToDiscriminate(type))) { + if (isMatchingReferenceDiscriminant(left, rootDiscriminable || declaredType)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right, getTypeToDiscriminate(type))) { + if (isMatchingReferenceDiscriminant(right, rootDiscriminable || declaredType)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { @@ -16762,6 +16757,14 @@ namespace ts { } function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { + if (isFlowToRootDiscriminant(type, candidate, isRelated)) { + if (assumeTrue) { + rootDiscriminable = candidate as UnionType; + } + else { + rootDiscriminable = undefined; + } + } if (!assumeTrue) { return filterType(type, t => !isRelated(t, candidate)); } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index fe02fb33e7159..db3d3fae6f564 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -84,6 +84,69 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { return data; } } + +type Foo = { kind: "a", a: number } | { kind: "b", b: number }; +type Bar = { kind: "c", c: number } | { kind: "d", d: number }; + +declare function isFoo(x: unknown): x is Foo; +declare function isBar(x: unknown): x is Bar; + +function blah(x: unknown) { + if (isFoo(x)) { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + else if (isBar(x)) { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + x // unknown +} + +type PrimitiveUnion = number | string +type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; +type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; + +declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; +declare function isFooComplex(x: unknown): x is FooComplex; +declare function isBarComplex(x: unknown): x is BarComplex; + +function bluergh(x: unknown) { + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + if (isFooComplex(x) && typeof x === "object") { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + if (isBarComplex(x) && typeof x === "object") { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + x // unknown +} //// [discriminantsAndTypePredicates.js] @@ -146,3 +209,50 @@ function narrowToNeverUnknown(data) { return data; } } +function blah(x) { + if (isFoo(x)) { + if (x.kind === "a") { + var a = x.a; + } + else if (x.kind === "b") { + var b = x.b; + } + } + else if (isBar(x)) { + if (x.kind === "c") { + var c = x.c; + } + else if (x.kind === "d") { + var d = x.d; + } + } + x; // unknown +} +function bluergh(x) { + if (isPrimitiveUnion(x)) { + var a = x; + } + if (isFooComplex(x) && typeof x === "object") { + if (x.kind === "a") { + var a = x.a; + } + else if (x.kind === "b") { + var b = x.b; + } + } + if (isPrimitiveUnion(x)) { + var a = x; + } + if (isBarComplex(x) && typeof x === "object") { + if (x.kind === "c") { + var c = x.c; + } + else if (x.kind === "d") { + var d = x.d; + } + } + if (isPrimitiveUnion(x)) { + var a = x; + } + x; // unknown +} diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index a4943c6fe17a2..817fd06a04437 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -230,3 +230,214 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { } } +type Foo = { kind: "a", a: number } | { kind: "b", b: number }; +>Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 84, 1)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) + +type Bar = { kind: "c", c: number } | { kind: "d", d: number }; +>Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 86, 63)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) + +declare function isFoo(x: unknown): x is Foo; +>isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 87, 63)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 89, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 89, 23)) +>Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 84, 1)) + +declare function isBar(x: unknown): x is Bar; +>isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 89, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 90, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 90, 23)) +>Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 86, 63)) + +function blah(x: unknown) { +>blah : Symbol(blah, Decl(discriminantsAndTypePredicates.ts, 90, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) + + if (isFoo(x)) { +>isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 87, 63)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) + + if (x.kind === "a") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12), Decl(discriminantsAndTypePredicates.ts, 86, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12), Decl(discriminantsAndTypePredicates.ts, 86, 39)) + + let a = x.a; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 95, 15)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) + } + else if (x.kind === "b") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) + + let b = x.b; +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 98, 15)) +>x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) + } + } + else if (isBar(x)) { +>isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 89, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) + + if (x.kind === "c") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) + + let c = x.c; +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 103, 15)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) + } + else if (x.kind === "d") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) + + let d = x.d; +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 106, 15)) +>x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) + } + } + x // unknown +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +} + +type PrimitiveUnion = number | string +>PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 110, 1)) + +type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; +>FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 112, 37)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) + +type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; +>BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 113, 79)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) + +declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 116, 34)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 116, 34)) +>PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 110, 1)) + +declare function isFooComplex(x: unknown): x is FooComplex; +>isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 116, 67)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 30)) +>FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 112, 37)) + +declare function isBarComplex(x: unknown): x is BarComplex; +>isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 117, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 118, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 118, 30)) +>BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 113, 79)) + +function bluergh(x: unknown) { +>bluergh : Symbol(bluergh, Decl(discriminantsAndTypePredicates.ts, 118, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + let a: number | string = x; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 122, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + } + if (isFooComplex(x) && typeof x === "object") { +>isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 116, 67)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + if (x.kind === "a") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19), Decl(discriminantsAndTypePredicates.ts, 113, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19), Decl(discriminantsAndTypePredicates.ts, 113, 46)) + + let a = x.a; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 126, 15)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) + } + else if (x.kind === "b") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) + + let b = x.b; +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 129, 15)) +>x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) + } + } + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + let a: number | string = x; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 133, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + } + if (isBarComplex(x) && typeof x === "object") { +>isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 117, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + if (x.kind === "c") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) + + let c = x.c; +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 137, 15)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) + } + else if (x.kind === "d") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) + + let d = x.d; +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 140, 15)) +>x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) + } + } + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + + let a: number | string = x; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 144, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) + } + x // unknown +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +} + diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index 3c5270fea883f..a2f990c252676 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -230,3 +230,235 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { } } +type Foo = { kind: "a", a: number } | { kind: "b", b: number }; +>Foo : Foo +>kind : "a" +>a : number +>kind : "b" +>b : number + +type Bar = { kind: "c", c: number } | { kind: "d", d: number }; +>Bar : Bar +>kind : "c" +>c : number +>kind : "d" +>d : number + +declare function isFoo(x: unknown): x is Foo; +>isFoo : (x: unknown) => x is Foo +>x : unknown + +declare function isBar(x: unknown): x is Bar; +>isBar : (x: unknown) => x is Bar +>x : unknown + +function blah(x: unknown) { +>blah : (x: unknown) => void +>x : unknown + + if (isFoo(x)) { +>isFoo(x) : boolean +>isFoo : (x: unknown) => x is Foo +>x : unknown + + if (x.kind === "a") { +>x.kind === "a" : boolean +>x.kind : "a" | "b" +>x : Foo +>kind : "a" | "b" +>"a" : "a" + + let a = x.a; +>a : number +>x.a : number +>x : { kind: "a"; a: number; } +>a : number + } + else if (x.kind === "b") { +>x.kind === "b" : boolean +>x.kind : "b" +>x : { kind: "b"; b: number; } +>kind : "b" +>"b" : "b" + + let b = x.b; +>b : number +>x.b : number +>x : { kind: "b"; b: number; } +>b : number + } + } + else if (isBar(x)) { +>isBar(x) : boolean +>isBar : (x: unknown) => x is Bar +>x : unknown + + if (x.kind === "c") { +>x.kind === "c" : boolean +>x.kind : "c" | "d" +>x : Bar +>kind : "c" | "d" +>"c" : "c" + + let c = x.c; +>c : number +>x.c : number +>x : { kind: "c"; c: number; } +>c : number + } + else if (x.kind === "d") { +>x.kind === "d" : boolean +>x.kind : "d" +>x : { kind: "d"; d: number; } +>kind : "d" +>"d" : "d" + + let d = x.d; +>d : number +>x.d : number +>x : { kind: "d"; d: number; } +>d : number + } + } + x // unknown +>x : unknown +} + +type PrimitiveUnion = number | string +>PrimitiveUnion : PrimitiveUnion + +type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; +>FooComplex : FooComplex +>kind : "a" +>a : number +>kind : "b" +>b : number + +type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; +>BarComplex : BarComplex +>kind : "c" +>c : number +>kind : "d" +>d : number + +declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; +>isPrimitiveUnion : (x: unknown) => x is PrimitiveUnion +>x : unknown + +declare function isFooComplex(x: unknown): x is FooComplex; +>isFooComplex : (x: unknown) => x is FooComplex +>x : unknown + +declare function isBarComplex(x: unknown): x is BarComplex; +>isBarComplex : (x: unknown) => x is BarComplex +>x : unknown + +function bluergh(x: unknown) { +>bluergh : (x: unknown) => void +>x : unknown + + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion(x) : boolean +>isPrimitiveUnion : (x: unknown) => x is PrimitiveUnion +>x : unknown + + let a: number | string = x; +>a : PrimitiveUnion +>x : PrimitiveUnion + } + if (isFooComplex(x) && typeof x === "object") { +>isFooComplex(x) && typeof x === "object" : boolean +>isFooComplex(x) : boolean +>isFooComplex : (x: unknown) => x is FooComplex +>x : unknown +>typeof x === "object" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : FooComplex +>"object" : "object" + + if (x.kind === "a") { +>x.kind === "a" : boolean +>x.kind : "a" | "b" +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } +>kind : "a" | "b" +>"a" : "a" + + let a = x.a; +>a : number +>x.a : number +>x : { kind: "a"; a: number; } +>a : number + } + else if (x.kind === "b") { +>x.kind === "b" : boolean +>x.kind : "b" +>x : { kind: "b"; b: number; } +>kind : "b" +>"b" : "b" + + let b = x.b; +>b : number +>x.b : number +>x : { kind: "b"; b: number; } +>b : number + } + } + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion(x) : boolean +>isPrimitiveUnion : (x: unknown) => x is PrimitiveUnion +>x : unknown + + let a: number | string = x; +>a : PrimitiveUnion +>x : PrimitiveUnion + } + if (isBarComplex(x) && typeof x === "object") { +>isBarComplex(x) && typeof x === "object" : boolean +>isBarComplex(x) : boolean +>isBarComplex : (x: unknown) => x is BarComplex +>x : unknown +>typeof x === "object" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : BarComplex +>"object" : "object" + + if (x.kind === "c") { +>x.kind === "c" : boolean +>x.kind : "c" | "d" +>x : { kind: "c"; c: number; } | { kind: "d"; d: number; } +>kind : "c" | "d" +>"c" : "c" + + let c = x.c; +>c : number +>x.c : number +>x : { kind: "c"; c: number; } +>c : number + } + else if (x.kind === "d") { +>x.kind === "d" : boolean +>x.kind : "d" +>x : { kind: "d"; d: number; } +>kind : "d" +>"d" : "d" + + let d = x.d; +>d : number +>x.d : number +>x : { kind: "d"; d: number; } +>d : number + } + } + if (isPrimitiveUnion(x)) { +>isPrimitiveUnion(x) : boolean +>isPrimitiveUnion : (x: unknown) => x is PrimitiveUnion +>x : unknown + + let a: number | string = x; +>a : PrimitiveUnion +>x : PrimitiveUnion + } + x // unknown +>x : unknown +} + diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index 8826b2987d677..5e930680598a4 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -83,3 +83,66 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { return data; } } + +type Foo = { kind: "a", a: number } | { kind: "b", b: number }; +type Bar = { kind: "c", c: number } | { kind: "d", d: number }; + +declare function isFoo(x: unknown): x is Foo; +declare function isBar(x: unknown): x is Bar; + +function blah(x: unknown) { + if (isFoo(x)) { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + else if (isBar(x)) { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + x // unknown +} + +type PrimitiveUnion = number | string +type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; +type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; + +declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; +declare function isFooComplex(x: unknown): x is FooComplex; +declare function isBarComplex(x: unknown): x is BarComplex; + +function bluergh(x: unknown) { + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + if (isFooComplex(x) && typeof x === "object") { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + if (isBarComplex(x) && typeof x === "object") { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + x // unknown +} From a7bcf1343d15209744e21f9164d3969fa894338f Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 10 May 2019 23:57:17 +0100 Subject: [PATCH 08/23] Do the crude thing --- src/compiler/checker.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0307512c78676..8d7747cfda328 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16379,9 +16379,8 @@ namespace ts { return result; } - function isFlowToRootDiscriminant(type: Type, candidate: Type, isRelated: (t1: Type, t2: Type) => boolean) { - return !(declaredType.flags & TypeFlags.Union) && !(type.flags & TypeFlags.Union) && (candidate.flags & TypeFlags.Union) !== 0 - && isRelated(candidate, type); + function isFlowToRootDiscriminant(type: Type, candidate: Type) { + return (type.flags & TypeFlags.Unknown | TypeFlags.Any) && (candidate.flags & TypeFlags.Union) !== 0; } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { @@ -16757,7 +16756,7 @@ namespace ts { } function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { - if (isFlowToRootDiscriminant(type, candidate, isRelated)) { + if (isFlowToRootDiscriminant(type, candidate)) { if (assumeTrue) { rootDiscriminable = candidate as UnionType; } From 9e8834cbb322c0ad864cb120b7346c6272152965 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 16 Aug 2019 14:51:29 +0100 Subject: [PATCH 09/23] Tidy up and minor fixes --- src/compiler/checker.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 666802a0a28eb..5d0d165ffe374 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17272,8 +17272,8 @@ namespace ts { return result; } - function isFlowToRootDiscriminant(type: Type, candidate: Type) { - return (type.flags & TypeFlags.Unknown | TypeFlags.Any) && (candidate.flags & TypeFlags.Union) !== 0; + function isFlowToRootDiscriminant(type: Type, candidate: Type): candidate is UnionType { + return (type.flags & (TypeFlags.Unknown | TypeFlags.Any | TypeFlags.NonPrimitive)) !== 0 && (candidate.flags & TypeFlags.Union) !== 0; } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { @@ -17653,12 +17653,7 @@ namespace ts { function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { if (isFlowToRootDiscriminant(type, candidate)) { - if (assumeTrue) { - rootDiscriminable = candidate as UnionType; - } - else { - rootDiscriminable = undefined; - } + rootDiscriminable = assumeTrue ? candidate : undefined; } if (!assumeTrue) { return filterType(type, t => !isRelated(t, candidate)); From 6d7f5e43a88810ae0a4b56123e890d9021c54186 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 29 Aug 2019 12:25:31 +0100 Subject: [PATCH 10/23] Selectively narrow using comparison with literal types --- src/compiler/checker.ts | 22 +- .../discriminantsAndTypePredicates.js | 2 + .../discriminantsAndTypePredicates.symbols | 251 +++++++++--------- .../discriminantsAndTypePredicates.types | 2 + .../discriminantsAndTypePredicates.ts | 1 + 5 files changed, 143 insertions(+), 135 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d0d165ffe374..9f09cfe355ebb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16904,7 +16904,7 @@ namespace ts { let key: string | undefined; let keySet = false; let flowDepth = 0; - let rootDiscriminable: UnionType | undefined; + let containingUnion: UnionType | undefined; if (flowAnalysisDisabled) { return errorType; } @@ -16994,6 +16994,9 @@ namespace ts { } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); + if (!isIncomplete(type) && type.flags & TypeFlags.Union) { + containingUnion = type as UnionType; + } } else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); @@ -17272,10 +17275,6 @@ namespace ts { return result; } - function isFlowToRootDiscriminant(type: Type, candidate: Type): candidate is UnionType { - return (type.flags & (TypeFlags.Unknown | TypeFlags.Any | TypeFlags.NonPrimitive)) !== 0 && (candidate.flags & TypeFlags.Union) !== 0; - } - function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { return false; @@ -17352,10 +17351,16 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, rootDiscriminable || declaredType)) { + if (isMatchingReferenceDiscriminant(left, declaredType)) { + return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); + } + if (isMatchingReferenceDiscriminant(right, declaredType)) { + return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); + } + if (containingUnion && isLiteralType(getContextFreeTypeOfExpression(right)) && isMatchingReferenceDiscriminant(left, containingUnion)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right, rootDiscriminable || declaredType)) { + if (containingUnion && isLiteralType(getContextFreeTypeOfExpression(left)) && isMatchingReferenceDiscriminant(right, containingUnion)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { @@ -17652,9 +17657,6 @@ namespace ts { } function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { - if (isFlowToRootDiscriminant(type, candidate)) { - rootDiscriminable = assumeTrue ? candidate : undefined; - } if (!assumeTrue) { return filterType(type, t => !isRelated(t, candidate)); } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index db3d3fae6f564..e3774ab3b226c 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -83,6 +83,7 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { } return data; } + throw "error"; } type Foo = { kind: "a", a: number } | { kind: "b", b: number }; @@ -208,6 +209,7 @@ function narrowToNeverUnknown(data) { } return data; } + throw "error"; } function blah(x) { if (isFoo(x)) { diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 817fd06a04437..6253c14703996 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -228,216 +228,217 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { return data; >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30)) } + throw "error"; } type Foo = { kind: "a", a: number } | { kind: "b", b: number }; ->Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 84, 1)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) - -type Bar = { kind: "c", c: number } | { kind: "d", d: number }; ->Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 86, 63)) +>Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 85, 1)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 87, 23)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 87, 50)) -declare function isFoo(x: unknown): x is Foo; ->isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 87, 63)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 89, 23)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 89, 23)) ->Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 84, 1)) +type Bar = { kind: "c", c: number } | { kind: "d", d: number }; +>Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 87, 63)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 12)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 88, 23)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 39)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 88, 50)) -declare function isBar(x: unknown): x is Bar; ->isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 89, 45)) +declare function isFoo(x: unknown): x is Foo; +>isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 88, 63)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 90, 23)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 90, 23)) ->Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 86, 63)) +>Foo : Symbol(Foo, Decl(discriminantsAndTypePredicates.ts, 85, 1)) + +declare function isBar(x: unknown): x is Bar; +>isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 90, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 91, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 91, 23)) +>Bar : Symbol(Bar, Decl(discriminantsAndTypePredicates.ts, 87, 63)) function blah(x: unknown) { ->blah : Symbol(blah, Decl(discriminantsAndTypePredicates.ts, 90, 45)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>blah : Symbol(blah, Decl(discriminantsAndTypePredicates.ts, 91, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) if (isFoo(x)) { ->isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 87, 63)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>isFoo : Symbol(isFoo, Decl(discriminantsAndTypePredicates.ts, 88, 63)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) if (x.kind === "a") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12), Decl(discriminantsAndTypePredicates.ts, 86, 39)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 12), Decl(discriminantsAndTypePredicates.ts, 86, 39)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) let a = x.a; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 95, 15)) ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 86, 23)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 96, 15)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 87, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 87, 23)) } else if (x.kind === "b") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 86, 39)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) let b = x.b; ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 98, 15)) ->x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 86, 50)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 99, 15)) +>x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 87, 50)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 87, 50)) } } else if (isBar(x)) { ->isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 89, 45)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>isBar : Symbol(isBar, Decl(discriminantsAndTypePredicates.ts, 90, 45)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) if (x.kind === "c") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 12), Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 12), Decl(discriminantsAndTypePredicates.ts, 88, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 12), Decl(discriminantsAndTypePredicates.ts, 88, 39)) let c = x.c; ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 103, 15)) ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 87, 23)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 104, 15)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 88, 23)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 88, 23)) } else if (x.kind === "d") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 87, 39)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 39)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 88, 39)) let d = x.d; ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 106, 15)) ->x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 87, 50)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 107, 15)) +>x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 88, 50)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 88, 50)) } } x // unknown ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 92, 14)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 93, 14)) } type PrimitiveUnion = number | string ->PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 110, 1)) +>PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 111, 1)) type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; ->FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 112, 37)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) - -type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; ->BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 113, 79)) +>FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 113, 37)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) + +type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; +>BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 19)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; ->isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 116, 34)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 116, 34)) ->PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 110, 1)) +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 34)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 34)) +>PrimitiveUnion : Symbol(PrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 111, 1)) declare function isFooComplex(x: unknown): x is FooComplex; ->isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 116, 67)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 30)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 117, 30)) ->FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 112, 37)) - -declare function isBarComplex(x: unknown): x is BarComplex; ->isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 117, 59)) +>isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 117, 67)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 118, 30)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 118, 30)) ->BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 113, 79)) +>FooComplex : Symbol(FooComplex, Decl(discriminantsAndTypePredicates.ts, 113, 37)) + +declare function isBarComplex(x: unknown): x is BarComplex; +>isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 118, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 119, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 119, 30)) +>BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 114, 79)) function bluergh(x: unknown) { ->bluergh : Symbol(bluergh, Decl(discriminantsAndTypePredicates.ts, 118, 59)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>bluergh : Symbol(bluergh, Decl(discriminantsAndTypePredicates.ts, 119, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) if (isPrimitiveUnion(x)) { ->isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 122, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 123, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) } if (isFooComplex(x) && typeof x === "object") { ->isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 116, 67)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 117, 67)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) if (x.kind === "a") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19), Decl(discriminantsAndTypePredicates.ts, 113, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 19), Decl(discriminantsAndTypePredicates.ts, 113, 46)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) let a = x.a; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 126, 15)) ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 113, 30)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 127, 15)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) } else if (x.kind === "b") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 113, 46)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) let b = x.b; ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 129, 15)) ->x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 113, 57)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 130, 15)) +>x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) } } if (isPrimitiveUnion(x)) { ->isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 133, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 134, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) } if (isBarComplex(x) && typeof x === "object") { ->isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 117, 59)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 118, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) if (x.kind === "c") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) let c = x.c; ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 137, 15)) ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 138, 15)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) } else if (x.kind === "d") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 46)) let d = x.d; ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 140, 15)) ->x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 114, 57)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 141, 15)) +>x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) } } if (isPrimitiveUnion(x)) { ->isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 114, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 144, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 145, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) } x // unknown ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index a2f990c252676..b56fdce49d17f 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -228,6 +228,8 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { return data; >data : never } + throw "error"; +>"error" : "error" } type Foo = { kind: "a", a: number } | { kind: "b", b: number }; diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index 5e930680598a4..bfefac206a455 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -82,6 +82,7 @@ function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { } return data; } + throw "error"; } type Foo = { kind: "a", a: number } | { kind: "b", b: number }; From 04df8829193b340e44b85da2bfefa8d308c2be42 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 29 Aug 2019 14:18:23 +0100 Subject: [PATCH 11/23] Try and fix cycles --- src/compiler/checker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f09cfe355ebb..ebc5da32444c7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16994,7 +16994,9 @@ namespace ts { } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); - if (!isIncomplete(type) && type.flags & TypeFlags.Union) { + if (isIncomplete(type)) { + containingUnion = undefined; + } else if (type.flags & TypeFlags.Union) { containingUnion = type as UnionType; } } From 7878514ca8e623fdee5802b1bb006491f3d3070c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 29 Aug 2019 14:53:34 +0100 Subject: [PATCH 12/23] Get rid of cycle fully --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ebc5da32444c7..a7a747f4e55b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17359,10 +17359,10 @@ namespace ts { if (isMatchingReferenceDiscriminant(right, declaredType)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } - if (containingUnion && isLiteralType(getContextFreeTypeOfExpression(right)) && isMatchingReferenceDiscriminant(left, containingUnion)) { + if (containingUnion && isMatchingReferenceDiscriminant(left, containingUnion)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (containingUnion && isLiteralType(getContextFreeTypeOfExpression(left)) && isMatchingReferenceDiscriminant(right, containingUnion)) { + if (containingUnion && isMatchingReferenceDiscriminant(right, containingUnion)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { From 7db6f39455665dd319c1b3488ba77cb6be6b1bde Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Thu, 29 Aug 2019 15:08:57 +0100 Subject: [PATCH 13/23] lint --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7a747f4e55b1..1586a6c0c52ee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16996,7 +16996,8 @@ namespace ts { type = getTypeAtFlowCondition(flow); if (isIncomplete(type)) { containingUnion = undefined; - } else if (type.flags & TypeFlags.Union) { + } + else if (type.flags & TypeFlags.Union) { containingUnion = type as UnionType; } } From 5278ed4a8840a7be3db410167e75cf812b7efe91 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 25 Sep 2019 14:14:26 +0100 Subject: [PATCH 14/23] Experiment --- src/compiler/checker.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 859824bef7396..46b91b5381dc5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17317,6 +17317,12 @@ namespace ts { flow = (flow).antecedent; continue; } + if (isIncomplete(type)) { + containingUnion = undefined; + } + else if (type.flags & TypeFlags.Union) { + containingUnion = type as UnionType; + } } else if (flags & FlowFlags.Call) { type = getTypeAtFlowCall(flow); @@ -17756,16 +17762,10 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isMatchingReferenceDiscriminant(left, declaredType)) { - return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); - } - if (isMatchingReferenceDiscriminant(right, declaredType)) { - return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); - } - if (containingUnion && isMatchingReferenceDiscriminant(left, containingUnion)) { + if (isMatchingReferenceDiscriminant(left, containingUnion || declaredType)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (containingUnion && isMatchingReferenceDiscriminant(right, containingUnion)) { + if (isMatchingReferenceDiscriminant(right, containingUnion || declaredType)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { From 4ec69fb901c41a92d4816094294da6c6edef979b Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 25 Sep 2019 15:19:38 +0100 Subject: [PATCH 15/23] Tidy --- src/compiler/checker.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 46b91b5381dc5..bb665defcf07b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17276,6 +17276,18 @@ namespace ts { return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); } + function captureContainingUnion(flow: FlowNode, type: FlowType) { + if (flow.flags & (FlowFlags.Assignment | FlowFlags.Condition)) { + if (isIncomplete(type)) { + containingUnion = undefined; + return; + } + if (type.flags & TypeFlags.Union) { + containingUnion = type as UnionType; + } + } + } + function getTypeAtFlowNode(flow: FlowNode): FlowType { if (flowDepth === 2000) { // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error @@ -17317,12 +17329,6 @@ namespace ts { flow = (flow).antecedent; continue; } - if (isIncomplete(type)) { - containingUnion = undefined; - } - else if (type.flags & TypeFlags.Union) { - containingUnion = type as UnionType; - } } else if (flags & FlowFlags.Call) { type = getTypeAtFlowCall(flow); @@ -17333,12 +17339,6 @@ namespace ts { } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); - if (isIncomplete(type)) { - containingUnion = undefined; - } - else if (type.flags & TypeFlags.Union) { - containingUnion = type as UnionType; - } } else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); @@ -17383,6 +17383,7 @@ namespace ts { sharedFlowTypes[sharedFlowCount] = type; sharedFlowCount++; } + captureContainingUnion(flow, type); flowDepth--; return type; } From 6f0ae39272e4ec8d6a4d1c3fd0b91a9ca81ab855 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Fri, 1 Nov 2019 14:49:26 -0700 Subject: [PATCH 16/23] More tests --- .../discriminantsAndTypePredicates.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index bfefac206a455..9447665a20a8d 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -118,10 +118,41 @@ type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; declare function isFooComplex(x: unknown): x is FooComplex; declare function isBarComplex(x: unknown): x is BarComplex; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; + +function earlyExitsAndStuff(x: unknown) { + if (!isFooComplex(x) && !isBarComplex(x)) { + if (isZZYYComplex(x)) { + if (x.kind !== "z") { + return x.yyy; + } + return x.zzz; + } + return; + } + if (!!isPrimitiveUnion(x)) { + return x; + } + if (!isZZYYComplex(x)) { + if (x.kind === "a") { + let a = x.a; + } + if (x.kind === "b") { + let b = x.b; + } + if (x.kind === "c") { + let c = x.c; + } + if (x.kind === "d") { + let d = x.d; + } + } +} function bluergh(x: unknown) { if (isPrimitiveUnion(x)) { let a: number | string = x; + return; } if (isFooComplex(x) && typeof x === "object") { if (x.kind === "a") { From ed22c7b2cc9b11c901e938f6a64df8ccba0d0aa9 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 12:00:28 +0000 Subject: [PATCH 17/23] Accept baselines and new tests --- .../discriminantsAndTypePredicates.js | 171 +++++++++ .../discriminantsAndTypePredicates.symbols | 342 ++++++++++++++++-- .../discriminantsAndTypePredicates.types | 336 +++++++++++++++++ .../discriminantsAndTypePredicates.ts | 62 ++++ 4 files changed, 883 insertions(+), 28 deletions(-) diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index e3774ab3b226c..03c3ba441dcec 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -119,10 +119,41 @@ type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; declare function isFooComplex(x: unknown): x is FooComplex; declare function isBarComplex(x: unknown): x is BarComplex; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; + +function earlyExitsAndStuff(x: unknown) { + if (!isFooComplex(x) && !isBarComplex(x)) { + if (isZZYYComplex(x)) { + if (x.kind !== "z") { + return x.yyy; + } + return x.zzz; + } + return; + } + if (!!isPrimitiveUnion(x)) { + return x; + } + if (!isZZYYComplex(x)) { + if (x.kind === "a") { + let a = x.a; + } + if (x.kind === "b") { + let b = x.b; + } + if (x.kind === "c") { + let c = x.c; + } + if (x.kind === "d") { + let d = x.d; + } + } +} function bluergh(x: unknown) { if (isPrimitiveUnion(x)) { let a: number | string = x; + return; } if (isFooComplex(x) && typeof x === "object") { if (x.kind === "a") { @@ -148,6 +179,68 @@ function bluergh(x: unknown) { } x // unknown } + +type A1 = { x: number }; +type B1 = A1 & { kind: "B"; y: number }; +type C1 = A1 & { kind: "C"; z: number }; + +function isBorC(a: A1): a is B1 | C1 { + return (a as any).kind === "B" || (a as any).kind === "C"; +} + +function isB1(a: A1): a is B1 { + return (a as any).kind === "B"; +} + +function isC1(a: A1): a is C1 { + return (a as any).kind === "C"; +} + +function fn1(a: A1) { + if (isBorC(a)) { + if (a.kind === "B") { + a.y; // OK + } + } +} + +function fn2(a: A1) { + if (!isB1(a)) { + return; + } + if (!isC1(a)) { + return; + } + if (a.kind === "B") { + a.y; // OK + } +} + +declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; +declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; + +function testComposition(x: unknown) { + if (isTypeAB(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + } + if (isTypeCD(x)) { + if (x.kind2 === 'c') { + x.c; // Ok + } + } + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + } +} //// [discriminantsAndTypePredicates.js] @@ -230,9 +323,38 @@ function blah(x) { } x; // unknown } +function earlyExitsAndStuff(x) { + if (!isFooComplex(x) && !isBarComplex(x)) { + if (isZZYYComplex(x)) { + if (x.kind !== "z") { + return x.yyy; + } + return x.zzz; + } + return; + } + if (!!isPrimitiveUnion(x)) { + return x; + } + if (!isZZYYComplex(x)) { + if (x.kind === "a") { + var a = x.a; + } + if (x.kind === "b") { + var b = x.b; + } + if (x.kind === "c") { + var c = x.c; + } + if (x.kind === "d") { + var d = x.d; + } + } +} function bluergh(x) { if (isPrimitiveUnion(x)) { var a = x; + return; } if (isFooComplex(x) && typeof x === "object") { if (x.kind === "a") { @@ -258,3 +380,52 @@ function bluergh(x) { } x; // unknown } +function isBorC(a) { + return a.kind === "B" || a.kind === "C"; +} +function isB1(a) { + return a.kind === "B"; +} +function isC1(a) { + return a.kind === "C"; +} +function fn1(a) { + if (isBorC(a)) { + if (a.kind === "B") { + a.y; // OK + } + } +} +function fn2(a) { + if (!isB1(a)) { + return; + } + if (!isC1(a)) { + return; + } + if (a.kind === "B") { + a.y; // OK + } +} +function testComposition(x) { + if (isTypeAB(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + } + if (isTypeCD(x)) { + if (x.kind2 === 'c') { + x.c; // Ok + } + } + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + } +} diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 6253c14703996..9add81b850177 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -354,91 +354,377 @@ declare function isBarComplex(x: unknown): x is BarComplex; >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 119, 30)) >BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 114, 79)) +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +>isZZYYComplex : Symbol(isZZYYComplex, Decl(discriminantsAndTypePredicates.ts, 119, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 31)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 31)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50)) +>zzz : Symbol(zzz, Decl(discriminantsAndTypePredicates.ts, 120, 61)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 78)) +>yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) + +function earlyExitsAndStuff(x: unknown) { +>earlyExitsAndStuff : Symbol(earlyExitsAndStuff, Decl(discriminantsAndTypePredicates.ts, 120, 104)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + + if (!isFooComplex(x) && !isBarComplex(x)) { +>isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 117, 67)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 118, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + + if (isZZYYComplex(x)) { +>isZZYYComplex : Symbol(isZZYYComplex, Decl(discriminantsAndTypePredicates.ts, 119, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + + if (x.kind !== "z") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 78)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 78)) + + return x.yyy; +>x.yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) + } + return x.zzz; +>x.zzz : Symbol(zzz, Decl(discriminantsAndTypePredicates.ts, 120, 61)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>zzz : Symbol(zzz, Decl(discriminantsAndTypePredicates.ts, 120, 61)) + } + return; + } + if (!!isPrimitiveUnion(x)) { +>isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + + return x; +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + } + if (!isZZYYComplex(x)) { +>isZZYYComplex : Symbol(isZZYYComplex, Decl(discriminantsAndTypePredicates.ts, 119, 59)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) + + if (x.kind === "a") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) + + let a = x.a; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 137, 15)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) + } + if (x.kind === "b") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) + + let b = x.b; +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 140, 15)) +>x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) + } + if (x.kind === "c") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) + + let c = x.c; +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 143, 15)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) + } + if (x.kind === "d") { +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46), Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) + + let d = x.d; +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 146, 15)) +>x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) + } + } +} + function bluergh(x: unknown) { ->bluergh : Symbol(bluergh, Decl(discriminantsAndTypePredicates.ts, 119, 59)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>bluergh : Symbol(bluergh, Decl(discriminantsAndTypePredicates.ts, 149, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) if (isPrimitiveUnion(x)) { >isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 123, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 153, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) + + return; } if (isFooComplex(x) && typeof x === "object") { >isFooComplex : Symbol(isFooComplex, Decl(discriminantsAndTypePredicates.ts, 117, 67)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) if (x.kind === "a") { >x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 19), Decl(discriminantsAndTypePredicates.ts, 114, 46)) let a = x.a; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 127, 15)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 158, 15)) >x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 114, 30)) } else if (x.kind === "b") { >x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 114, 46)) let b = x.b; ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 130, 15)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 161, 15)) >x.b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 114, 57)) } } if (isPrimitiveUnion(x)) { >isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 134, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 165, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) } if (isBarComplex(x) && typeof x === "object") { >isBarComplex : Symbol(isBarComplex, Decl(discriminantsAndTypePredicates.ts, 118, 59)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) if (x.kind === "c") { >x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 19), Decl(discriminantsAndTypePredicates.ts, 115, 46)) let c = x.c; ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 138, 15)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 169, 15)) >x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 115, 30)) } else if (x.kind === "d") { >x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 46)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 115, 46)) let d = x.d; ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 141, 15)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 172, 15)) >x.d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) >d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 115, 57)) } } if (isPrimitiveUnion(x)) { >isPrimitiveUnion : Symbol(isPrimitiveUnion, Decl(discriminantsAndTypePredicates.ts, 115, 79)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) let a: number | string = x; ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 145, 11)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 176, 11)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) } x // unknown ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 121, 17)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 151, 17)) +} + +type A1 = { x: number }; +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 181, 11)) + +type B1 = A1 & { kind: "B"; y: number }; +>B1 : Symbol(B1, Decl(discriminantsAndTypePredicates.ts, 181, 24)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16)) +>y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) + +type C1 = A1 & { kind: "C"; z: number }; +>C1 : Symbol(C1, Decl(discriminantsAndTypePredicates.ts, 182, 40)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 183, 16)) +>z : Symbol(z, Decl(discriminantsAndTypePredicates.ts, 183, 27)) + +function isBorC(a: A1): a is B1 | C1 { +>isBorC : Symbol(isBorC, Decl(discriminantsAndTypePredicates.ts, 183, 40)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 185, 16)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 185, 16)) +>B1 : Symbol(B1, Decl(discriminantsAndTypePredicates.ts, 181, 24)) +>C1 : Symbol(C1, Decl(discriminantsAndTypePredicates.ts, 182, 40)) + + return (a as any).kind === "B" || (a as any).kind === "C"; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 185, 16)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 185, 16)) +} + +function isB1(a: A1): a is B1 { +>isB1 : Symbol(isB1, Decl(discriminantsAndTypePredicates.ts, 187, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 189, 14)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 189, 14)) +>B1 : Symbol(B1, Decl(discriminantsAndTypePredicates.ts, 181, 24)) + + return (a as any).kind === "B"; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 189, 14)) +} + +function isC1(a: A1): a is C1 { +>isC1 : Symbol(isC1, Decl(discriminantsAndTypePredicates.ts, 191, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 193, 14)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 193, 14)) +>C1 : Symbol(C1, Decl(discriminantsAndTypePredicates.ts, 182, 40)) + + return (a as any).kind === "C"; +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 193, 14)) +} + +function fn1(a: A1) { +>fn1 : Symbol(fn1, Decl(discriminantsAndTypePredicates.ts, 195, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) + + if (isBorC(a)) { +>isBorC : Symbol(isBorC, Decl(discriminantsAndTypePredicates.ts, 183, 40)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) + + if (a.kind === "B") { +>a.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) + + a.y; // OK +>a.y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) +>y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) + } + } +} + +function fn2(a: A1) { +>fn2 : Symbol(fn2, Decl(discriminantsAndTypePredicates.ts, 203, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) +>A1 : Symbol(A1, Decl(discriminantsAndTypePredicates.ts, 179, 1)) + + if (!isB1(a)) { +>isB1 : Symbol(isB1, Decl(discriminantsAndTypePredicates.ts, 187, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) + + return; + } + if (!isC1(a)) { +>isC1 : Symbol(isC1, Decl(discriminantsAndTypePredicates.ts, 191, 1)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) + + return; + } + if (a.kind === "B") { +>a.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) + + a.y; // OK +>a.y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) +>y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) + } +} + +declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 217, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 217, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 68)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 217, 80)) + +declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 218, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 218, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 68)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 218, 80)) + +function testComposition(x: unknown) { +>testComposition : Symbol(testComposition, Decl(discriminantsAndTypePredicates.ts, 218, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) + + if (isTypeAB(x)) { +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) + + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) + + x.a; // Ok +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) + } + } + if (isTypeCD(x)) { +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) + + if (x.kind2 === 'c') { +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) + + x.c; // Ok +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) + } + } + if (isTypeAB(x)) { +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) + + if (isTypeCD(x)) { +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) + + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) + + x.a; // Ok +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) + } + if (x.kind2 === 'c') { +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) + + x.c; // Error +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) + } + } + } } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index b56fdce49d17f..b6cb9b07e57a4 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -355,6 +355,124 @@ declare function isBarComplex(x: unknown): x is BarComplex; >isBarComplex : (x: unknown) => x is BarComplex >x : unknown +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +>isZZYYComplex : (x: unknown) => x is { kind: "z"; zzz: string; } | { kind: "y"; yyy: number; } +>x : unknown +>kind : "z" +>zzz : string +>kind : "y" +>yyy : number + +function earlyExitsAndStuff(x: unknown) { +>earlyExitsAndStuff : (x: unknown) => PrimitiveUnion +>x : unknown + + if (!isFooComplex(x) && !isBarComplex(x)) { +>!isFooComplex(x) && !isBarComplex(x) : boolean +>!isFooComplex(x) : boolean +>isFooComplex(x) : boolean +>isFooComplex : (x: unknown) => x is FooComplex +>x : unknown +>!isBarComplex(x) : boolean +>isBarComplex(x) : boolean +>isBarComplex : (x: unknown) => x is BarComplex +>x : unknown + + if (isZZYYComplex(x)) { +>isZZYYComplex(x) : boolean +>isZZYYComplex : (x: unknown) => x is { kind: "z"; zzz: string; } | { kind: "y"; yyy: number; } +>x : unknown + + if (x.kind !== "z") { +>x.kind !== "z" : boolean +>x.kind : "z" | "y" +>x : { kind: "z"; zzz: string; } | { kind: "y"; yyy: number; } +>kind : "z" | "y" +>"z" : "z" + + return x.yyy; +>x.yyy : number +>x : { kind: "y"; yyy: number; } +>yyy : number + } + return x.zzz; +>x.zzz : string +>x : { kind: "z"; zzz: string; } +>zzz : string + } + return; + } + if (!!isPrimitiveUnion(x)) { +>!!isPrimitiveUnion(x) : boolean +>!isPrimitiveUnion(x) : boolean +>isPrimitiveUnion(x) : boolean +>isPrimitiveUnion : (x: unknown) => x is PrimitiveUnion +>x : string | number | { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } + + return x; +>x : PrimitiveUnion + } + if (!isZZYYComplex(x)) { +>!isZZYYComplex(x) : boolean +>isZZYYComplex(x) : boolean +>isZZYYComplex : (x: unknown) => x is { kind: "z"; zzz: string; } | { kind: "y"; yyy: number; } +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } + + if (x.kind === "a") { +>x.kind === "a" : boolean +>x.kind : "a" | "b" | "c" | "d" +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } +>kind : "a" | "b" | "c" | "d" +>"a" : "a" + + let a = x.a; +>a : number +>x.a : number +>x : { kind: "a"; a: number; } +>a : number + } + if (x.kind === "b") { +>x.kind === "b" : boolean +>x.kind : "a" | "b" | "c" | "d" +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } +>kind : "a" | "b" | "c" | "d" +>"b" : "b" + + let b = x.b; +>b : number +>x.b : number +>x : { kind: "b"; b: number; } +>b : number + } + if (x.kind === "c") { +>x.kind === "c" : boolean +>x.kind : "a" | "b" | "c" | "d" +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } +>kind : "a" | "b" | "c" | "d" +>"c" : "c" + + let c = x.c; +>c : number +>x.c : number +>x : { kind: "c"; c: number; } +>c : number + } + if (x.kind === "d") { +>x.kind === "d" : boolean +>x.kind : "a" | "b" | "c" | "d" +>x : { kind: "a"; a: number; } | { kind: "b"; b: number; } | { kind: "c"; c: number; } | { kind: "d"; d: number; } +>kind : "a" | "b" | "c" | "d" +>"d" : "d" + + let d = x.d; +>d : number +>x.d : number +>x : { kind: "d"; d: number; } +>d : number + } + } +} + function bluergh(x: unknown) { >bluergh : (x: unknown) => void >x : unknown @@ -367,6 +485,8 @@ function bluergh(x: unknown) { let a: number | string = x; >a : PrimitiveUnion >x : PrimitiveUnion + + return; } if (isFooComplex(x) && typeof x === "object") { >isFooComplex(x) && typeof x === "object" : boolean @@ -464,3 +584,219 @@ function bluergh(x: unknown) { >x : unknown } +type A1 = { x: number }; +>A1 : A1 +>x : number + +type B1 = A1 & { kind: "B"; y: number }; +>B1 : B1 +>kind : "B" +>y : number + +type C1 = A1 & { kind: "C"; z: number }; +>C1 : C1 +>kind : "C" +>z : number + +function isBorC(a: A1): a is B1 | C1 { +>isBorC : (a: A1) => a is B1 | C1 +>a : A1 + + return (a as any).kind === "B" || (a as any).kind === "C"; +>(a as any).kind === "B" || (a as any).kind === "C" : boolean +>(a as any).kind === "B" : boolean +>(a as any).kind : any +>(a as any) : any +>a as any : any +>a : A1 +>kind : any +>"B" : "B" +>(a as any).kind === "C" : boolean +>(a as any).kind : any +>(a as any) : any +>a as any : any +>a : A1 +>kind : any +>"C" : "C" +} + +function isB1(a: A1): a is B1 { +>isB1 : (a: A1) => a is B1 +>a : A1 + + return (a as any).kind === "B"; +>(a as any).kind === "B" : boolean +>(a as any).kind : any +>(a as any) : any +>a as any : any +>a : A1 +>kind : any +>"B" : "B" +} + +function isC1(a: A1): a is C1 { +>isC1 : (a: A1) => a is C1 +>a : A1 + + return (a as any).kind === "C"; +>(a as any).kind === "C" : boolean +>(a as any).kind : any +>(a as any) : any +>a as any : any +>a : A1 +>kind : any +>"C" : "C" +} + +function fn1(a: A1) { +>fn1 : (a: A1) => void +>a : A1 + + if (isBorC(a)) { +>isBorC(a) : boolean +>isBorC : (a: A1) => a is B1 | C1 +>a : A1 + + if (a.kind === "B") { +>a.kind === "B" : boolean +>a.kind : "B" | "C" +>a : B1 | C1 +>kind : "B" | "C" +>"B" : "B" + + a.y; // OK +>a.y : number +>a : B1 +>y : number + } + } +} + +function fn2(a: A1) { +>fn2 : (a: A1) => void +>a : A1 + + if (!isB1(a)) { +>!isB1(a) : boolean +>isB1(a) : boolean +>isB1 : (a: A1) => a is B1 +>a : A1 + + return; + } + if (!isC1(a)) { +>!isC1(a) : boolean +>isC1(a) : boolean +>isC1 : (a: A1) => a is C1 +>a : B1 + + return; + } + if (a.kind === "B") { +>a.kind === "B" : boolean +>a.kind : never +>a : A1 & { kind: "B"; y: number; } & { kind: "C"; z: number; } +>kind : never +>"B" : "B" + + a.y; // OK +>a.y : number +>a : A1 & { kind: "B"; y: number; } & { kind: "C"; z: number; } +>y : number + } +} + +declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : unknown +>kind1 : "a" +>a : 1 +>kind1 : "b" +>b : 2 + +declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; +>isTypeCD : (x: unknown) => x is { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>x : unknown +>kind2 : "c" +>c : 3 +>kind2 : "d" +>d : 4 + +function testComposition(x: unknown) { +>testComposition : (x: unknown) => void +>x : unknown + + if (isTypeAB(x)) { +>isTypeAB(x) : boolean +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : unknown + + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind1 : "a" | "b" +>'a' : "a" + + x.a; // Ok +>x.a : 1 +>x : { kind1: "a"; a: 1; } +>a : 1 + } + } + if (isTypeCD(x)) { +>isTypeCD(x) : boolean +>isTypeCD : (x: unknown) => x is { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>x : unknown + + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : "c" | "d" +>x : { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>kind2 : "c" | "d" +>'c' : "c" + + x.c; // Ok +>x.c : 3 +>x : { kind2: "c"; c: 3; } +>c : 3 + } + } + if (isTypeAB(x)) { +>isTypeAB(x) : boolean +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : unknown + + if (isTypeCD(x)) { +>isTypeCD(x) : boolean +>isTypeCD : (x: unknown) => x is { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } + + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind1 : "a" | "b" +>'a' : "a" + + x.a; // Ok +>x.a : 1 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) +>a : 1 + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : "c" | "d" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind2 : "c" | "d" +>'c' : "c" + + x.c; // Error +>x.c : 3 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) +>c : 3 + } + } + } +} + diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index 9447665a20a8d..0a9ed23fac8a9 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -178,3 +178,65 @@ function bluergh(x: unknown) { } x // unknown } + +type A1 = { x: number }; +type B1 = A1 & { kind: "B"; y: number }; +type C1 = A1 & { kind: "C"; z: number }; + +function isBorC(a: A1): a is B1 | C1 { + return (a as any).kind === "B" || (a as any).kind === "C"; +} + +function isB1(a: A1): a is B1 { + return (a as any).kind === "B"; +} + +function isC1(a: A1): a is C1 { + return (a as any).kind === "C"; +} + +function fn1(a: A1) { + if (isBorC(a)) { + if (a.kind === "B") { + a.y; // OK + } + } +} + +function fn2(a: A1) { + if (!isB1(a)) { + return; + } + if (!isC1(a)) { + return; + } + if (a.kind === "B") { + a.y; // OK + } +} + +declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; +declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; + +function testComposition(x: unknown) { + if (isTypeAB(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + } + if (isTypeCD(x)) { + if (x.kind2 === 'c') { + x.c; // Ok + } + } + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; // Ok + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + } +} From 2dd1f6c36e0d87b6ab2a528b474aee12efaedfa5 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 12:28:27 +0000 Subject: [PATCH 18/23] More tests --- .../discriminantsAndTypePredicates.errors.txt | 308 ++++++++++++++++++ .../discriminantsAndTypePredicates.js | 105 ++++-- .../discriminantsAndTypePredicates.symbols | 234 ++++++++----- .../discriminantsAndTypePredicates.types | 147 ++++++++- .../discriminantsAndTypePredicates.ts | 60 +++- 5 files changed, 735 insertions(+), 119 deletions(-) create mode 100644 tests/baselines/reference/discriminantsAndTypePredicates.errors.txt diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt new file mode 100644 index 0000000000000..30511c7bd8021 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt @@ -0,0 +1,308 @@ +tests/cases/compiler/discriminantsAndTypePredicates.ts(231,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(232,11): error TS2339: Property 'a' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(240,11): error TS2339: Property 'kind2' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(241,11): error TS2339: Property 'c' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(269,15): error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? + Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(270,15): error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. + Property 'c' does not exist on type '{ kind1: "a"; a: 1; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(273,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(274,11): error TS2339: Property 'a' does not exist on type 'unknown'. + + +==== tests/cases/compiler/discriminantsAndTypePredicates.ts (8 errors) ==== + // Repro from #10145 + + interface A { type: 'A' } + interface B { type: 'B' } + + function isA(x: A | B): x is A { return x.type === 'A'; } + function isB(x: A | B): x is B { return x.type === 'B'; } + + function foo1(x: A | B): any { + x; // A | B + if (isA(x)) { + return x; // A + } + x; // B + if (isB(x)) { + return x; // B + } + x; // never + } + + function foo2(x: A | B): any { + x; // A | B + if (x.type === 'A') { + return x; // A + } + x; // B + if (x.type === 'B') { + return x; // B + } + x; // never + } + + // Repro from #30557 + + interface TypeA { + Name: "TypeA"; + Value1: "Cool stuff!"; + } + + interface TypeB { + Name: "TypeB"; + Value2: 0; + } + + type Type = TypeA | TypeB; + + declare function isType(x: unknown): x is Type; + + function WorksProperly(data: Type) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } + } + + function DoesNotWork(data: unknown) { + if (isType(data)) { + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } + } + } + + function narrowToNever(data: Type): "Cool stuff!" | 0 { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; + } + + function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 { + if (isType(data)) { + if (data.Name === "TypeA") { + return data.Value1; + } + if (data.Name === "TypeB") { + return data.Value2; + } + return data; + } + throw "error"; + } + + type Foo = { kind: "a", a: number } | { kind: "b", b: number }; + type Bar = { kind: "c", c: number } | { kind: "d", d: number }; + + declare function isFoo(x: unknown): x is Foo; + declare function isBar(x: unknown): x is Bar; + + function blah(x: unknown) { + if (isFoo(x)) { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + else if (isBar(x)) { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + x // unknown + } + + type PrimitiveUnion = number | string + type FooComplex = { kind: "a", a: number } | { kind: "b", b: number } | number; + type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; + + declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; + declare function isFooComplex(x: unknown): x is FooComplex; + declare function isBarComplex(x: unknown): x is BarComplex; + declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string } | { kind: "y", yyy: number }; + + function earlyExitsAndStuff(x: unknown) { + if (!isFooComplex(x) && !isBarComplex(x)) { + if (isZZYYComplex(x)) { + if (x.kind !== "z") { + return x.yyy; + } + return x.zzz; + } + return; + } + if (!!isPrimitiveUnion(x)) { + return x; + } + if (!isZZYYComplex(x)) { + if (x.kind === "a") { + let a = x.a; + } + if (x.kind === "b") { + let b = x.b; + } + if (x.kind === "c") { + let c = x.c; + } + if (x.kind === "d") { + let d = x.d; + } + } + } + + function bluergh(x: unknown) { + if (isPrimitiveUnion(x)) { + let a: number | string = x; + return; + } + if (isFooComplex(x) && typeof x === "object") { + if (x.kind === "a") { + let a = x.a; + } + else if (x.kind === "b") { + let b = x.b; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + if (isBarComplex(x) && typeof x === "object") { + if (x.kind === "c") { + let c = x.c; + } + else if (x.kind === "d") { + let d = x.d; + } + } + if (isPrimitiveUnion(x)) { + let a: number | string = x; + } + x // unknown + } + + type A1 = { x: number }; + type B1 = A1 & { kind: "B"; y: number }; + type C1 = A1 & { kind: "C"; z: number }; + + function isBorC(a: A1): a is B1 | C1 { + return (a as any).kind === "B" || (a as any).kind === "C"; + } + + function isB1(a: A1): a is B1 { + return (a as any).kind === "B"; + } + + function isC1(a: A1): a is C1 { + return (a as any).kind === "C"; + } + + function fn1(a: A1) { + if (isBorC(a)) { + if (a.kind === "B") { + a.y; + } + } + } + + function fn2(a: A1) { + if (!isB1(a)) { + return; + } + if (!isC1(a)) { + if (a.kind === "B") { + a.y; + } + return; + } + if (a.kind === "B") { + a.y; + } + } + + declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; + declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; + + function testComposition(x: unknown) { + if (isTypeAB(x)) { + if (x.kind1 === 'a') { + x.a; + } + return; + } + if (x.kind1 === 'a') { + ~~~~~ +!!! error TS2339: Property 'kind1' does not exist on type 'unknown'. + x.a; // Error + ~ +!!! error TS2339: Property 'a' does not exist on type 'unknown'. + } + if (isTypeCD(x)) { + if (x.kind2 === 'c') { + x.c; + } + return; + } + if (x.kind2 === 'c') { + ~~~~~ +!!! error TS2339: Property 'kind2' does not exist on type 'unknown'. + x.c; // Error + ~ +!!! error TS2339: Property 'c' does not exist on type 'unknown'. + } + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + } + } + + function looper(getter: () => unknown) { + let x = getter(); + while (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + ~~~~~ +!!! error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? +!!! error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; }'. + x.c; // Error + ~ +!!! error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. +!!! error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; }'. + } + } + if (x.kind1 === 'a') { + ~~~~~ +!!! error TS2339: Property 'kind1' does not exist on type 'unknown'. + x.a; // error + ~ +!!! error TS2339: Property 'a' does not exist on type 'unknown'. + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index 03c3ba441dcec..061edaad31a1d 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -49,17 +49,17 @@ declare function isType(x: unknown): x is Type; function WorksProperly(data: Type) { if (data.Name === "TypeA") { - // TypeA - const value1 = data.Value1; + // TypeA + const value1 = data.Value1; } } function DoesNotWork(data: unknown) { if (isType(data)) { - if (data.Name === "TypeA") { - // TypeA - const value1 = data.Value1; - } + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } } } @@ -119,7 +119,7 @@ type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; declare function isFooComplex(x: unknown): x is FooComplex; declare function isBarComplex(x: unknown): x is BarComplex; -declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string } | { kind: "y", yyy: number }; function earlyExitsAndStuff(x: unknown) { if (!isFooComplex(x) && !isBarComplex(x)) { @@ -199,7 +199,7 @@ function isC1(a: A1): a is C1 { function fn1(a: A1) { if (isBorC(a)) { if (a.kind === "B") { - a.y; // OK + a.y; } } } @@ -209,10 +209,13 @@ function fn2(a: A1) { return; } if (!isC1(a)) { + if (a.kind === "B") { + a.y; + } return; } if (a.kind === "B") { - a.y; // OK + a.y; } } @@ -222,25 +225,56 @@ declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', function testComposition(x: unknown) { if (isTypeAB(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; } + return; + } + if (x.kind1 === 'a') { + x.a; // Error } if (isTypeCD(x)) { if (x.kind2 === 'c') { - x.c; // Ok + x.c; } + return; + } + if (x.kind2 === 'c') { + x.c; // Error } if (isTypeAB(x)) { if (isTypeCD(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; } if (x.kind2 === 'c') { - x.c; // Error + x.c; } } } } + +function looper(getter: () => unknown) { + let x = getter(); + while (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + if (x.kind1 === 'a') { + x.a; // error + } +} //// [discriminantsAndTypePredicates.js] @@ -392,7 +426,7 @@ function isC1(a) { function fn1(a) { if (isBorC(a)) { if (a.kind === "B") { - a.y; // OK + a.y; } } } @@ -401,31 +435,64 @@ function fn2(a) { return; } if (!isC1(a)) { + if (a.kind === "B") { + a.y; + } return; } if (a.kind === "B") { - a.y; // OK + a.y; } } function testComposition(x) { if (isTypeAB(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; } + return; + } + if (x.kind1 === 'a') { + x.a; // Error } if (isTypeCD(x)) { if (x.kind2 === 'c') { - x.c; // Ok + x.c; } + return; + } + if (x.kind2 === 'c') { + x.c; // Error } if (isTypeAB(x)) { if (isTypeCD(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + } +} +function looper(getter) { + var x = getter(); + while (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; } if (x.kind2 === 'c') { - x.c; // Error + x.c; } } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + if (x.kind1 === 'a') { + x.a; // error } } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 9add81b850177..d2d69059706ca 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -136,9 +136,9 @@ function WorksProperly(data: Type) { >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23)) >Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) - // TypeA - const value1 = data.Value1; ->value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 51, 6)) + // TypeA + const value1 = data.Value1; +>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 51, 13)) >data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23)) >Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) @@ -153,18 +153,18 @@ function DoesNotWork(data: unknown) { >isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26)) >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) - if (data.Name === "TypeA") { + if (data.Name === "TypeA") { >data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) >Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17)) - // TypeA - const value1 = data.Value1; ->value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 59, 10)) + // TypeA + const value1 = data.Value1; +>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 59, 17)) >data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) >data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21)) >Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18)) - } + } } } @@ -354,17 +354,17 @@ declare function isBarComplex(x: unknown): x is BarComplex; >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 119, 30)) >BarComplex : Symbol(BarComplex, Decl(discriminantsAndTypePredicates.ts, 114, 79)) -declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string } | { kind: "y", yyy: number }; >isZZYYComplex : Symbol(isZZYYComplex, Decl(discriminantsAndTypePredicates.ts, 119, 59)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 31)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 120, 31)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50)) >zzz : Symbol(zzz, Decl(discriminantsAndTypePredicates.ts, 120, 61)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 78)) ->yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 79)) +>yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 90)) function earlyExitsAndStuff(x: unknown) { ->earlyExitsAndStuff : Symbol(earlyExitsAndStuff, Decl(discriminantsAndTypePredicates.ts, 120, 104)) +>earlyExitsAndStuff : Symbol(earlyExitsAndStuff, Decl(discriminantsAndTypePredicates.ts, 120, 105)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) if (!isFooComplex(x) && !isBarComplex(x)) { @@ -378,14 +378,14 @@ function earlyExitsAndStuff(x: unknown) { >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) if (x.kind !== "z") { ->x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 78)) +>x.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 79)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) ->kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 78)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 120, 50), Decl(discriminantsAndTypePredicates.ts, 120, 79)) return x.yyy; ->x.yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) +>x.yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 90)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 122, 28)) ->yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 89)) +>yyy : Symbol(yyy, Decl(discriminantsAndTypePredicates.ts, 120, 90)) } return x.zzz; >x.zzz : Symbol(zzz, Decl(discriminantsAndTypePredicates.ts, 120, 61)) @@ -607,7 +607,7 @@ function fn1(a: A1) { >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) - a.y; // OK + a.y; >a.y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 197, 13)) >y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) @@ -630,6 +630,16 @@ function fn2(a: A1) { >isC1 : Symbol(isC1, Decl(discriminantsAndTypePredicates.ts, 191, 1)) >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) + if (a.kind === "B") { +>a.kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) +>kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16)) + + a.y; +>a.y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) +>y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) + } return; } if (a.kind === "B") { @@ -637,7 +647,7 @@ function fn2(a: A1) { >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) >kind : Symbol(kind, Decl(discriminantsAndTypePredicates.ts, 182, 16), Decl(discriminantsAndTypePredicates.ts, 183, 16)) - a.y; // OK + a.y; >a.y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) >a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 205, 13)) >y : Symbol(y, Decl(discriminantsAndTypePredicates.ts, 182, 27)) @@ -645,86 +655,162 @@ function fn2(a: A1) { } declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 217, 26)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 217, 26)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 68)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 217, 80)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 220, 80)) declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 218, 26)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 218, 26)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 68)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 218, 80)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 221, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 221, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 221, 80)) function testComposition(x: unknown) { ->testComposition : Symbol(testComposition, Decl(discriminantsAndTypePredicates.ts, 218, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>testComposition : Symbol(testComposition, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) if (isTypeAB(x)) { ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) - - x.a; // Ok ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) } + return; + } + if (x.kind1 === 'a') { +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) + + x.a; // Error +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) } if (isTypeCD(x)) { ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) if (x.kind2 === 'c') { ->x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) - - x.c; // Ok ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.c; +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } + return; + } + if (x.kind2 === 'c') { +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) + + x.c; // Error +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) } if (isTypeAB(x)) { ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 215, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) + + if (isTypeCD(x)) { +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) + + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) + } + if (x.kind2 === 'c') { +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.c; +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) + } + } + } +} + +function looper(getter: () => unknown) { +>looper : Symbol(looper, Decl(discriminantsAndTypePredicates.ts, 252, 1)) +>getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 254, 16)) + + let x = getter(); +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 254, 16)) + + while (isTypeAB(x)) { +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) if (isTypeCD(x)) { ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 217, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 217, 45), Decl(discriminantsAndTypePredicates.ts, 217, 68)) - - x.a; // Ok ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 217, 57)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) } if (x.kind2 === 'c') { ->x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 218, 45), Decl(discriminantsAndTypePredicates.ts, 218, 68)) - - x.c; // Error ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 25)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 218, 57)) +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.c; +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } } + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) + } + if (x.kind2 === 'c') { +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) + + x.c; // Error +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) + } + } + if (x.kind1 === 'a') { +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) + + x.a; // error +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) } } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index b6cb9b07e57a4..a096ecbfac87a 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -125,8 +125,8 @@ function WorksProperly(data: Type) { >Name : "TypeA" | "TypeB" >"TypeA" : "TypeA" - // TypeA - const value1 = data.Value1; + // TypeA + const value1 = data.Value1; >value1 : "Cool stuff!" >data.Value1 : "Cool stuff!" >data : TypeA @@ -143,20 +143,20 @@ function DoesNotWork(data: unknown) { >isType : (x: unknown) => x is Type >data : unknown - if (data.Name === "TypeA") { + if (data.Name === "TypeA") { >data.Name === "TypeA" : boolean >data.Name : "TypeA" | "TypeB" >data : Type >Name : "TypeA" | "TypeB" >"TypeA" : "TypeA" - // TypeA - const value1 = data.Value1; + // TypeA + const value1 = data.Value1; >value1 : "Cool stuff!" >data.Value1 : "Cool stuff!" >data : TypeA >Value1 : "Cool stuff!" - } + } } } @@ -355,7 +355,7 @@ declare function isBarComplex(x: unknown): x is BarComplex; >isBarComplex : (x: unknown) => x is BarComplex >x : unknown -declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string } | { kind: "y", yyy: number }; >isZZYYComplex : (x: unknown) => x is { kind: "z"; zzz: string; } | { kind: "y"; yyy: number; } >x : unknown >kind : "z" @@ -664,7 +664,7 @@ function fn1(a: A1) { >kind : "B" | "C" >"B" : "B" - a.y; // OK + a.y; >a.y : number >a : B1 >y : number @@ -690,6 +690,18 @@ function fn2(a: A1) { >isC1 : (a: A1) => a is C1 >a : B1 + if (a.kind === "B") { +>a.kind === "B" : boolean +>a.kind : "B" +>a : B1 +>kind : "B" +>"B" : "B" + + a.y; +>a.y : number +>a : B1 +>y : number + } return; } if (a.kind === "B") { @@ -699,7 +711,7 @@ function fn2(a: A1) { >kind : never >"B" : "B" - a.y; // OK + a.y; >a.y : number >a : A1 & { kind: "B"; y: number; } & { kind: "C"; z: number; } >y : number @@ -738,11 +750,24 @@ function testComposition(x: unknown) { >kind1 : "a" | "b" >'a' : "a" - x.a; // Ok + x.a; >x.a : 1 >x : { kind1: "a"; a: 1; } >a : 1 } + return; + } + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : any +>x : unknown +>kind1 : any +>'a' : "a" + + x.a; // Error +>x.a : any +>x : unknown +>a : any } if (isTypeCD(x)) { >isTypeCD(x) : boolean @@ -756,11 +781,24 @@ function testComposition(x: unknown) { >kind2 : "c" | "d" >'c' : "c" - x.c; // Ok + x.c; >x.c : 3 >x : { kind2: "c"; c: 3; } >c : 3 } + return; + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : any +>x : unknown +>kind2 : any +>'c' : "c" + + x.c; // Error +>x.c : any +>x : unknown +>c : any } if (isTypeAB(x)) { >isTypeAB(x) : boolean @@ -779,7 +817,54 @@ function testComposition(x: unknown) { >kind1 : "a" | "b" >'a' : "a" - x.a; // Ok + x.a; +>x.a : 1 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) +>a : 1 + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : "c" | "d" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind2 : "c" | "d" +>'c' : "c" + + x.c; +>x.c : 3 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) +>c : 3 + } + } + } +} + +function looper(getter: () => unknown) { +>looper : (getter: () => unknown) => void +>getter : () => unknown + + let x = getter(); +>x : unknown +>getter() : unknown +>getter : () => unknown + + while (isTypeAB(x)) { +>isTypeAB(x) : boolean +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : unknown + + if (isTypeCD(x)) { +>isTypeCD(x) : boolean +>isTypeCD : (x: unknown) => x is { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } + + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind1 : "a" | "b" +>'a' : "a" + + x.a; >x.a : 1 >x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) >a : 1 @@ -791,12 +876,48 @@ function testComposition(x: unknown) { >kind2 : "c" | "d" >'c' : "c" - x.c; // Error + x.c; >x.c : 3 >x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) >c : 3 } } + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind1 : "a" | "b" +>'a' : "a" + + x.a; +>x.a : 1 +>x : { kind1: "a"; a: 1; } +>a : 1 + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : any +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind2 : any +>'c' : "c" + + x.c; // Error +>x.c : any +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>c : any + } + } + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : any +>x : unknown +>kind1 : any +>'a' : "a" + + x.a; // error +>x.a : any +>x : unknown +>a : any } } diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index 0a9ed23fac8a9..42762f1b2a81c 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -48,17 +48,17 @@ declare function isType(x: unknown): x is Type; function WorksProperly(data: Type) { if (data.Name === "TypeA") { - // TypeA - const value1 = data.Value1; + // TypeA + const value1 = data.Value1; } } function DoesNotWork(data: unknown) { if (isType(data)) { - if (data.Name === "TypeA") { - // TypeA - const value1 = data.Value1; - } + if (data.Name === "TypeA") { + // TypeA + const value1 = data.Value1; + } } } @@ -118,7 +118,7 @@ type BarComplex = { kind: "c", c: number } | { kind: "d", d: number } | string; declare function isPrimitiveUnion(x: unknown): x is PrimitiveUnion; declare function isFooComplex(x: unknown): x is FooComplex; declare function isBarComplex(x: unknown): x is BarComplex; -declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string} | { kind: "y", yyy: number }; +declare function isZZYYComplex(x: unknown): x is { kind: "z"; zzz: string } | { kind: "y", yyy: number }; function earlyExitsAndStuff(x: unknown) { if (!isFooComplex(x) && !isBarComplex(x)) { @@ -198,7 +198,7 @@ function isC1(a: A1): a is C1 { function fn1(a: A1) { if (isBorC(a)) { if (a.kind === "B") { - a.y; // OK + a.y; } } } @@ -208,10 +208,13 @@ function fn2(a: A1) { return; } if (!isC1(a)) { + if (a.kind === "B") { + a.y; + } return; } if (a.kind === "B") { - a.y; // OK + a.y; } } @@ -221,22 +224,53 @@ declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', function testComposition(x: unknown) { if (isTypeAB(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; } + return; + } + if (x.kind1 === 'a') { + x.a; // Error } if (isTypeCD(x)) { if (x.kind2 === 'c') { - x.c; // Ok + x.c; } + return; + } + if (x.kind2 === 'c') { + x.c; // Error } if (isTypeAB(x)) { if (isTypeCD(x)) { if (x.kind1 === 'a') { - x.a; // Ok + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + } +} + +function looper(getter: () => unknown) { + let x = getter(); + while (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; } if (x.kind2 === 'c') { - x.c; // Error + x.c; } } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; // Error + } + } + if (x.kind1 === 'a') { + x.a; // error } } From b2b522e9bdd9e27d39552a35292a3f5d75320dba Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 14:16:43 +0000 Subject: [PATCH 19/23] Reset and cache --- src/compiler/checker.ts | 18 +- .../discriminantsAndTypePredicates.errors.txt | 68 ++++- .../discriminantsAndTypePredicates.js | 67 ++++- .../discriminantsAndTypePredicates.symbols | 262 ++++++++++++------ .../discriminantsAndTypePredicates.types | 119 +++++++- .../discriminantsAndTypePredicates.ts | 35 ++- 6 files changed, 462 insertions(+), 107 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 29e1a78ed1179..2de4ca44d8a50 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -858,6 +858,7 @@ namespace ts { const flowLoopTypes: Type[][] = []; const sharedFlowNodes: FlowNode[] = []; const sharedFlowTypes: FlowType[] = []; + const sharedFlowUnions: UnionType[] = []; const flowNodeReachable: (boolean | undefined)[] = []; const potentialThisCollisions: Node[] = []; const potentialNewTargetCollisions: Node[] = []; @@ -18888,18 +18889,13 @@ namespace ts { } function captureContainingUnion(flow: FlowNode, type: FlowType) { - if (flow.flags & (FlowFlags.Assignment | FlowFlags.Condition)) { - if (isIncomplete(type)) { - containingUnion = undefined; - return; - } - if (type.flags & TypeFlags.Union) { - containingUnion = type as UnionType; - } + if (flow.flags & (FlowFlags.Assignment | FlowFlags.Condition) && !isIncomplete(type) && type.flags & TypeFlags.Union) { + containingUnion = type as UnionType; } } function getTypeAtFlowNode(flow: FlowNode): FlowType { + containingUnion = undefined; if (flowDepth === 2000) { // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error // and disable further control flow analysis in the containing function or module body. @@ -18917,6 +18913,7 @@ namespace ts { for (let i = sharedFlowStart; i < sharedFlowCount; i++) { if (sharedFlowNodes[i] === flow) { flowDepth--; + containingUnion = sharedFlowUnions[i]; return sharedFlowTypes[i]; } } @@ -18988,13 +18985,16 @@ namespace ts { // simply return the non-auto declared type to reduce follow-on errors. type = convertAutoToAny(declaredType); } + captureContainingUnion(flow, type); if (flags & FlowFlags.Shared) { // Record visited node and the associated type in the cache. sharedFlowNodes[sharedFlowCount] = flow; sharedFlowTypes[sharedFlowCount] = type; + if (containingUnion) { + sharedFlowUnions[sharedFlowCount] = containingUnion; + } sharedFlowCount++; } - captureContainingUnion(flow, type); flowDepth--; return type; } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt index 30511c7bd8021..784aab798fa33 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt +++ b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt @@ -1,16 +1,22 @@ -tests/cases/compiler/discriminantsAndTypePredicates.ts(231,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(232,11): error TS2339: Property 'a' does not exist on type 'unknown'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(240,11): error TS2339: Property 'kind2' does not exist on type 'unknown'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(241,11): error TS2339: Property 'c' does not exist on type 'unknown'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(269,15): error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? +tests/cases/compiler/discriminantsAndTypePredicates.ts(238,15): error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; }'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(270,15): error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(239,15): error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Property 'c' does not exist on type '{ kind1: "a"; a: 1; }'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(273,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. -tests/cases/compiler/discriminantsAndTypePredicates.ts(274,11): error TS2339: Property 'a' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(252,15): error TS2339: Property 'a' does not exist on type '{ kind1: string; a?: number; b?: number; } | { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. + Property 'a' does not exist on type '{ kind1: "b"; b: 2; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(264,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(265,11): error TS2339: Property 'a' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(273,11): error TS2339: Property 'kind2' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(274,11): error TS2339: Property 'c' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(302,15): error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? + Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(303,15): error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. + Property 'c' does not exist on type '{ kind1: "a"; a: 1; }'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(306,11): error TS2339: Property 'kind1' does not exist on type 'unknown'. +tests/cases/compiler/discriminantsAndTypePredicates.ts(307,11): error TS2339: Property 'a' does not exist on type 'unknown'. -==== tests/cases/compiler/discriminantsAndTypePredicates.ts (8 errors) ==== +==== tests/cases/compiler/discriminantsAndTypePredicates.ts (11 errors) ==== // Repro from #10145 interface A { type: 'A' } @@ -231,10 +237,52 @@ tests/cases/compiler/discriminantsAndTypePredicates.ts(274,11): error TS2339: Pr } } + declare function isTypeObj(x: unknown): x is { kind1: string, a?: number, b?: number }; declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; - function testComposition(x: unknown) { + function testComposition1(x: unknown) { + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + ~~~~~ +!!! error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. Did you mean 'kind1'? +!!! error TS2551: Property 'kind2' does not exist on type '{ kind1: "a"; a: 1; }'. + x.c; + ~ +!!! error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. +!!! error TS2339: Property 'c' does not exist on type '{ kind1: "a"; a: 1; }'. + } + } + } + + function testComposition2(x: unknown) { + if (isTypeObj(x)) { + if (isTypeAB(x)) { + if (x.kind1 === "a") { + x.a; + } + } + if (x.kind1 === "a") { + x.a; // Error + ~ +!!! error TS2339: Property 'a' does not exist on type '{ kind1: string; a?: number; b?: number; } | { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; }'. +!!! error TS2339: Property 'a' does not exist on type '{ kind1: "b"; b: 2; }'. + } + } + } + + function testComposition3(x: unknown) { if (isTypeAB(x)) { if (x.kind1 === 'a') { x.a; diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index 061edaad31a1d..793df33788c2f 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -219,10 +219,43 @@ function fn2(a: A1) { } } +declare function isTypeObj(x: unknown): x is { kind1: string, a?: number, b?: number }; declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; -function testComposition(x: unknown) { +function testComposition1(x: unknown) { + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } +} + +function testComposition2(x: unknown) { + if (isTypeObj(x)) { + if (isTypeAB(x)) { + if (x.kind1 === "a") { + x.a; + } + } + if (x.kind1 === "a") { + x.a; // Error + } + } +} + +function testComposition3(x: unknown) { if (isTypeAB(x)) { if (x.kind1 === 'a') { x.a; @@ -444,7 +477,37 @@ function fn2(a) { a.y; } } -function testComposition(x) { +function testComposition1(x) { + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } +} +function testComposition2(x) { + if (isTypeObj(x)) { + if (isTypeAB(x)) { + if (x.kind1 === "a") { + x.a; + } + } + if (x.kind1 === "a") { + x.a; // Error + } + } +} +function testComposition3(x) { if (isTypeAB(x)) { if (x.kind1 === 'a') { x.a; diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index d2d69059706ca..0b374eba1eaca 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -654,163 +654,259 @@ function fn2(a: A1) { } } -declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 26)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 26)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 68)) ->b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 220, 80)) +declare function isTypeObj(x: unknown): x is { kind1: string, a?: number, b?: number }; +>isTypeObj : Symbol(isTypeObj, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 27)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 220, 27)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 46)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 61)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 220, 73)) -declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) +declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 221, 26)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 221, 26)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 68)) ->d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 221, 80)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>b : Symbol(b, Decl(discriminantsAndTypePredicates.ts, 221, 80)) + +declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 222, 26)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 222, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 68)) +>d : Symbol(d, Decl(discriminantsAndTypePredicates.ts, 222, 80)) + +function testComposition1(x: unknown) { +>testComposition1 : Symbol(testComposition1, Decl(discriminantsAndTypePredicates.ts, 222, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) + + if (isTypeAB(x)) { +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) + + if (isTypeCD(x)) { +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) + + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) + } + if (x.kind2 === 'c') { +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) + + x.c; +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) + } + } + if (x.kind1 === 'a') { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) + } + if (x.kind2 === 'c') { +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) + + x.c; +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 224, 26)) + } + } +} + +function testComposition2(x: unknown) { +>testComposition2 : Symbol(testComposition2, Decl(discriminantsAndTypePredicates.ts, 241, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) + + if (isTypeObj(x)) { +>isTypeObj : Symbol(isTypeObj, Decl(discriminantsAndTypePredicates.ts, 218, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) + + if (isTypeAB(x)) { +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) + + if (x.kind1 === "a") { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.a; +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) + } + } + if (x.kind1 === "a") { +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 46), Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 46), Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) + + x.a; // Error +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 61), Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 61), Decl(discriminantsAndTypePredicates.ts, 221, 57)) + } + } +} -function testComposition(x: unknown) { ->testComposition : Symbol(testComposition, Decl(discriminantsAndTypePredicates.ts, 221, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +function testComposition3(x: unknown) { +>testComposition3 : Symbol(testComposition3, Decl(discriminantsAndTypePredicates.ts, 254, 1)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) if (isTypeAB(x)) { ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) x.a; ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } return; } if (x.kind1 === 'a') { ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) x.a; // Error ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) } if (isTypeCD(x)) { ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) if (x.kind2 === 'c') { ->x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) x.c; ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) } return; } if (x.kind2 === 'c') { ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) x.c; // Error ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) } if (isTypeAB(x)) { ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) if (isTypeCD(x)) { ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) x.a; ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } if (x.kind2 === 'c') { ->x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) x.c; ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 223, 25)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 256, 26)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) } } } } function looper(getter: () => unknown) { ->looper : Symbol(looper, Decl(discriminantsAndTypePredicates.ts, 252, 1)) ->getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 254, 16)) +>looper : Symbol(looper, Decl(discriminantsAndTypePredicates.ts, 285, 1)) +>getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 287, 16)) let x = getter(); ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 254, 16)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>getter : Symbol(getter, Decl(discriminantsAndTypePredicates.ts, 287, 16)) while (isTypeAB(x)) { ->isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 218, 1)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>isTypeAB : Symbol(isTypeAB, Decl(discriminantsAndTypePredicates.ts, 220, 87)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) if (isTypeCD(x)) { ->isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 220, 88)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>isTypeCD : Symbol(isTypeCD, Decl(discriminantsAndTypePredicates.ts, 221, 88)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) x.a; ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } if (x.kind2 === 'c') { ->x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x.kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>kind2 : Symbol(kind2, Decl(discriminantsAndTypePredicates.ts, 222, 45), Decl(discriminantsAndTypePredicates.ts, 222, 68)) x.c; ->x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x.c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>c : Symbol(c, Decl(discriminantsAndTypePredicates.ts, 222, 57)) } } if (x.kind1 === 'a') { ->x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 45), Decl(discriminantsAndTypePredicates.ts, 220, 68)) +>x.kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) x.a; ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 57)) +>x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) +>a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 221, 57)) } if (x.kind2 === 'c') { ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) x.c; // Error ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) } } if (x.kind1 === 'a') { ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) x.a; // error ->x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 255, 7)) +>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 288, 7)) } } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index a096ecbfac87a..9788fc9ed5438 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -718,6 +718,13 @@ function fn2(a: A1) { } } +declare function isTypeObj(x: unknown): x is { kind1: string, a?: number, b?: number }; +>isTypeObj : (x: unknown) => x is { kind1: string; a?: number; b?: number; } +>x : unknown +>kind1 : string +>a : number +>b : number + declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; >isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } >x : unknown @@ -734,8 +741,116 @@ declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', >kind2 : "d" >d : 4 -function testComposition(x: unknown) { ->testComposition : (x: unknown) => void +function testComposition1(x: unknown) { +>testComposition1 : (x: unknown) => void +>x : unknown + + if (isTypeAB(x)) { +>isTypeAB(x) : boolean +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : unknown + + if (isTypeCD(x)) { +>isTypeCD(x) : boolean +>isTypeCD : (x: unknown) => x is { kind2: "c"; c: 3; } | { kind2: "d"; d: 4; } +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } + + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind1 : "a" | "b" +>'a' : "a" + + x.a; +>x.a : 1 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) +>a : 1 + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : "c" | "d" +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "a"; a: 1; } & { kind2: "d"; d: 4; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "d"; d: 4; }) +>kind2 : "c" | "d" +>'c' : "c" + + x.c; +>x.c : 3 +>x : ({ kind1: "a"; a: 1; } & { kind2: "c"; c: 3; }) | ({ kind1: "b"; b: 2; } & { kind2: "c"; c: 3; }) +>c : 3 + } + } + if (x.kind1 === 'a') { +>x.kind1 === 'a' : boolean +>x.kind1 : "a" | "b" +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind1 : "a" | "b" +>'a' : "a" + + x.a; +>x.a : 1 +>x : { kind1: "a"; a: 1; } +>a : 1 + } + if (x.kind2 === 'c') { +>x.kind2 === 'c' : boolean +>x.kind2 : any +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind2 : any +>'c' : "c" + + x.c; +>x.c : any +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>c : any + } + } +} + +function testComposition2(x: unknown) { +>testComposition2 : (x: unknown) => void +>x : unknown + + if (isTypeObj(x)) { +>isTypeObj(x) : boolean +>isTypeObj : (x: unknown) => x is { kind1: string; a?: number; b?: number; } +>x : unknown + + if (isTypeAB(x)) { +>isTypeAB(x) : boolean +>isTypeAB : (x: unknown) => x is { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>x : { kind1: string; a?: number; b?: number; } + + if (x.kind1 === "a") { +>x.kind1 === "a" : boolean +>x.kind1 : "a" | "b" +>x : { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind1 : "a" | "b" +>"a" : "a" + + x.a; +>x.a : 1 +>x : { kind1: "a"; a: 1; } +>a : 1 + } + } + if (x.kind1 === "a") { +>x.kind1 === "a" : boolean +>x.kind1 : string +>x : { kind1: string; a?: number; b?: number; } | { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>kind1 : string +>"a" : "a" + + x.a; // Error +>x.a : any +>x : { kind1: string; a?: number; b?: number; } | { kind1: "a"; a: 1; } | { kind1: "b"; b: 2; } +>a : any + } + } +} + +function testComposition3(x: unknown) { +>testComposition3 : (x: unknown) => void >x : unknown if (isTypeAB(x)) { diff --git a/tests/cases/compiler/discriminantsAndTypePredicates.ts b/tests/cases/compiler/discriminantsAndTypePredicates.ts index 42762f1b2a81c..762d6d174132c 100644 --- a/tests/cases/compiler/discriminantsAndTypePredicates.ts +++ b/tests/cases/compiler/discriminantsAndTypePredicates.ts @@ -218,10 +218,43 @@ function fn2(a: A1) { } } +declare function isTypeObj(x: unknown): x is { kind1: string, a?: number, b?: number }; declare function isTypeAB(x: unknown): x is { kind1: 'a', a: 1 } | { kind1: 'b', b: 2 }; declare function isTypeCD(x: unknown): x is { kind2: 'c', c: 3 } | { kind2: 'd', d: 4 }; -function testComposition(x: unknown) { +function testComposition1(x: unknown) { + if (isTypeAB(x)) { + if (isTypeCD(x)) { + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } + if (x.kind1 === 'a') { + x.a; + } + if (x.kind2 === 'c') { + x.c; + } + } +} + +function testComposition2(x: unknown) { + if (isTypeObj(x)) { + if (isTypeAB(x)) { + if (x.kind1 === "a") { + x.a; + } + } + if (x.kind1 === "a") { + x.a; // Error + } + } +} + +function testComposition3(x: unknown) { if (isTypeAB(x)) { if (x.kind1 === 'a') { x.a; From 98ad12eea31e6e0ece3062e5397e9b7965a3747c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 14:41:43 +0000 Subject: [PATCH 20/23] Remove union cache --- src/compiler/checker.ts | 8 ++------ .../reference/discriminantsAndTypePredicates.symbols | 2 -- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2de4ca44d8a50..e85e377dc8d2b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -858,7 +858,6 @@ namespace ts { const flowLoopTypes: Type[][] = []; const sharedFlowNodes: FlowNode[] = []; const sharedFlowTypes: FlowType[] = []; - const sharedFlowUnions: UnionType[] = []; const flowNodeReachable: (boolean | undefined)[] = []; const potentialThisCollisions: Node[] = []; const potentialNewTargetCollisions: Node[] = []; @@ -18913,7 +18912,7 @@ namespace ts { for (let i = sharedFlowStart; i < sharedFlowCount; i++) { if (sharedFlowNodes[i] === flow) { flowDepth--; - containingUnion = sharedFlowUnions[i]; + captureContainingUnion(flow, sharedFlowTypes[i]); return sharedFlowTypes[i]; } } @@ -18985,16 +18984,13 @@ namespace ts { // simply return the non-auto declared type to reduce follow-on errors. type = convertAutoToAny(declaredType); } - captureContainingUnion(flow, type); if (flags & FlowFlags.Shared) { // Record visited node and the associated type in the cache. sharedFlowNodes[sharedFlowCount] = flow; sharedFlowTypes[sharedFlowCount] = type; - if (containingUnion) { - sharedFlowUnions[sharedFlowCount] = containingUnion; - } sharedFlowCount++; } + captureContainingUnion(flow, type); flowDepth--; return type; } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 0b374eba1eaca..92907b0752bb5 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -761,9 +761,7 @@ function testComposition2(x: unknown) { >kind1 : Symbol(kind1, Decl(discriminantsAndTypePredicates.ts, 220, 46), Decl(discriminantsAndTypePredicates.ts, 221, 45), Decl(discriminantsAndTypePredicates.ts, 221, 68)) x.a; // Error ->x.a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 61), Decl(discriminantsAndTypePredicates.ts, 221, 57)) >x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 243, 26)) ->a : Symbol(a, Decl(discriminantsAndTypePredicates.ts, 220, 61), Decl(discriminantsAndTypePredicates.ts, 221, 57)) } } } From a3062b6510fa5ec477ab8ebb09998a6efe841a3d Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 15:08:56 +0000 Subject: [PATCH 21/23] Check other cases --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e85e377dc8d2b..adb3a890fec80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19319,7 +19319,7 @@ namespace ts { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } - if (isMatchingReferenceDiscriminant(expr, declaredType)) { + if (isMatchingReferenceDiscriminant(expr, containingUnion || declaredType)) { return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } if (containsMatchingReferenceDiscriminant(reference, expr)) { @@ -19795,7 +19795,7 @@ namespace ts { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } - if (isMatchingReferenceDiscriminant(expr, declaredType)) { + if (isMatchingReferenceDiscriminant(expr, containingUnion || declaredType)) { return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } if (containsMatchingReferenceDiscriminant(reference, expr)) { From fde48ddaf8ddb6f821ad968a7fbd8f1b12e1eaeb Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Sun, 3 Nov 2019 15:35:41 +0000 Subject: [PATCH 22/23] Update baselines --- src/compiler/checker.ts | 12 +++-- .../discriminantsAndTypePredicates.errors.txt | 21 +++++++++ .../discriminantsAndTypePredicates.js | 30 ++++++++++++ .../discriminantsAndTypePredicates.symbols | 43 +++++++++++++++++ .../discriminantsAndTypePredicates.types | 47 +++++++++++++++++++ 5 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3328df49fbd6b..ea4eb6dab772d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19004,8 +19004,8 @@ namespace ts { return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); } - function captureContainingUnion(flow: FlowNode, type: FlowType) { - if (flow.flags & (FlowFlags.Assignment | FlowFlags.Condition) && !isIncomplete(type) && type.flags & TypeFlags.Union) { + function captureContainingUnion(type: FlowType) { + if (!isIncomplete(type) && type.flags & TypeFlags.Union) { containingUnion = type as UnionType; } } @@ -19029,7 +19029,7 @@ namespace ts { for (let i = sharedFlowStart; i < sharedFlowCount; i++) { if (sharedFlowNodes[i] === flow) { flowDepth--; - captureContainingUnion(flow, sharedFlowTypes[i]); + captureContainingUnion(sharedFlowTypes[i]); return sharedFlowTypes[i]; } } @@ -19107,7 +19107,7 @@ namespace ts { sharedFlowTypes[sharedFlowCount] = type; sharedFlowCount++; } - captureContainingUnion(flow, type); + captureContainingUnion(type); flowDepth--; return type; } @@ -19311,6 +19311,7 @@ namespace ts { function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { const antecedentTypes: Type[] = []; + const containedUnions: Type[] = []; let subtypeReduction = false; let seenIncomplete = false; for (const antecedent of flow.antecedents!) { @@ -19330,6 +19331,7 @@ namespace ts { return type; } pushIfUnique(antecedentTypes, type); + pushIfUnique(containedUnions, containingUnion || type); // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. @@ -19340,6 +19342,8 @@ namespace ts { seenIncomplete = true; } } + const containingUnionType = createFlowType(getUnionOrEvolvingArrayType(containedUnions, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); + captureContainingUnion(containingUnionType); return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt index 784aab798fa33..6c06f976b62e5 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt +++ b/tests/baselines/reference/discriminantsAndTypePredicates.errors.txt @@ -353,4 +353,25 @@ tests/cases/compiler/discriminantsAndTypePredicates.ts(307,11): error TS2339: Pr !!! error TS2339: Property 'a' does not exist on type 'unknown'. } } + + interface Success { + success: true; + response: object; + } + + interface Error { + success: false; + error: object; + } + + function request(): Success | Error { + return null as any; + } + + // This does not work: + let r + r = request(); + if (r.success) { + r.response; + } \ No newline at end of file diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.js b/tests/baselines/reference/discriminantsAndTypePredicates.js index 793df33788c2f..68c7e3207589f 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.js +++ b/tests/baselines/reference/discriminantsAndTypePredicates.js @@ -308,6 +308,27 @@ function looper(getter: () => unknown) { x.a; // error } } + +interface Success { + success: true; + response: object; +} + +interface Error { + success: false; + error: object; +} + +function request(): Success | Error { + return null as any; +} + +// This does not work: +let r +r = request(); +if (r.success) { + r.response; +} //// [discriminantsAndTypePredicates.js] @@ -559,3 +580,12 @@ function looper(getter) { x.a; // error } } +function request() { + return null; +} +// This does not work: +var r; +r = request(); +if (r.success) { + r.response; +} diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.symbols b/tests/baselines/reference/discriminantsAndTypePredicates.symbols index 92907b0752bb5..8ad74bc79a7da 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.symbols +++ b/tests/baselines/reference/discriminantsAndTypePredicates.symbols @@ -908,3 +908,46 @@ function looper(getter: () => unknown) { } } +interface Success { +>Success : Symbol(Success, Decl(discriminantsAndTypePredicates.ts, 308, 1)) + + success: true; +>success : Symbol(Success.success, Decl(discriminantsAndTypePredicates.ts, 310, 19)) + + response: object; +>response : Symbol(Success.response, Decl(discriminantsAndTypePredicates.ts, 311, 18)) +} + +interface Error { +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(discriminantsAndTypePredicates.ts, 313, 1)) + + success: false; +>success : Symbol(Error.success, Decl(discriminantsAndTypePredicates.ts, 315, 17)) + + error: object; +>error : Symbol(Error.error, Decl(discriminantsAndTypePredicates.ts, 316, 19)) +} + +function request(): Success | Error { +>request : Symbol(request, Decl(discriminantsAndTypePredicates.ts, 318, 1)) +>Success : Symbol(Success, Decl(discriminantsAndTypePredicates.ts, 308, 1)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(discriminantsAndTypePredicates.ts, 313, 1)) + + return null as any; +} + +// This does not work: +let r +>r : Symbol(r, Decl(discriminantsAndTypePredicates.ts, 325, 3)) + +r = request(); +>r : Symbol(r, Decl(discriminantsAndTypePredicates.ts, 325, 3)) +>request : Symbol(request, Decl(discriminantsAndTypePredicates.ts, 318, 1)) + +if (r.success) { +>r : Symbol(r, Decl(discriminantsAndTypePredicates.ts, 325, 3)) + + r.response; +>r : Symbol(r, Decl(discriminantsAndTypePredicates.ts, 325, 3)) +} + diff --git a/tests/baselines/reference/discriminantsAndTypePredicates.types b/tests/baselines/reference/discriminantsAndTypePredicates.types index 9788fc9ed5438..72e4e500aeec1 100644 --- a/tests/baselines/reference/discriminantsAndTypePredicates.types +++ b/tests/baselines/reference/discriminantsAndTypePredicates.types @@ -1036,3 +1036,50 @@ function looper(getter: () => unknown) { } } +interface Success { + success: true; +>success : true +>true : true + + response: object; +>response : object +} + +interface Error { + success: false; +>success : false +>false : false + + error: object; +>error : object +} + +function request(): Success | Error { +>request : () => Success | Error + + return null as any; +>null as any : any +>null : null +} + +// This does not work: +let r +>r : any + +r = request(); +>r = request() : Success | Error +>r : any +>request() : Success | Error +>request : () => Success | Error + +if (r.success) { +>r.success : any +>r : any +>success : any + + r.response; +>r.response : any +>r : any +>response : any +} + From d7985575cb9f3d025208cbbcbc6f598fedf1f6a7 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 20 Nov 2019 18:02:34 +0000 Subject: [PATCH 23/23] Try and handle join points properly --- src/compiler/checker.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ea4eb6dab772d..cb6c0485d2edc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -844,6 +844,7 @@ namespace ts { const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; const flowLoopCaches: Map[] = []; + const flowLoopContainingUnionCache: (UnionType | undefined)[] = []; const flowAssignmentTypes: Type[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; @@ -19004,8 +19005,8 @@ namespace ts { return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); } - function captureContainingUnion(type: FlowType) { - if (!isIncomplete(type) && type.flags & TypeFlags.Union) { + function captureContainingUnion(type: FlowType, flags?: FlowFlags) { + if ((flags === undefined || (flags & FlowFlags.BranchLabel) === 0) && !isIncomplete(type) && type.flags & TypeFlags.Union) { containingUnion = type as UnionType; } } @@ -19107,7 +19108,7 @@ namespace ts { sharedFlowTypes[sharedFlowCount] = type; sharedFlowCount++; } - captureContainingUnion(type); + captureContainingUnion(type, flags); flowDepth--; return type; } @@ -19328,6 +19329,7 @@ namespace ts { // are the same), there is no reason to process more antecedents since the only // possible outcome is subtypes that will be removed in the final union type anyway. if (type === declaredType && declaredType === initialType) { + containingUnion = undefined; return type; } pushIfUnique(antecedentTypes, type); @@ -19342,8 +19344,13 @@ namespace ts { seenIncomplete = true; } } - const containingUnionType = createFlowType(getUnionOrEvolvingArrayType(containedUnions, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); - captureContainingUnion(containingUnionType); + containingUnion = undefined; + captureContainingUnion( + createFlowType( + getUnionOrEvolvingArrayType(containedUnions, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), + seenIncomplete + ) + ); return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } @@ -19359,6 +19366,7 @@ namespace ts { } const cached = cache.get(key); if (cached) { + containingUnion = flowLoopContainingUnionCache[id]; return cached; } // If this flow loop junction and reference are already being processed, return @@ -19379,12 +19387,14 @@ namespace ts { const antecedentTypes: Type[] = []; let subtypeReduction = false; let firstAntecedentType: FlowType | undefined; + let unionContainedAtTop: UnionType | undefined; for (const antecedent of flow.antecedents!) { let flowType; if (!firstAntecedentType) { // The first antecedent of a loop junction is always the non-looping control // flow path that leads to the top. flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); + unionContainedAtTop = containingUnion; } else { // All but the first antecedent are the looping control flow paths that lead @@ -19400,6 +19410,7 @@ namespace ts { // the resulting type and bail out. const cached = cache.get(key); if (cached) { + containingUnion = flowLoopContainingUnionCache[id]; return cached; } } @@ -19422,8 +19433,10 @@ namespace ts { // is incomplete. const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); if (isIncomplete(firstAntecedentType!)) { + containingUnion = undefined; return createFlowType(result, /*incomplete*/ true); } + containingUnion = flowLoopContainingUnionCache[id] = unionContainedAtTop; cache.set(key, result); return result; }