diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bf50e8b15cdbf..b2007333d343b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19747,15 +19747,10 @@ namespace ts { return assignableType; } } - // If the candidate type is a subtype of the target type, narrow to the candidate type. - // Otherwise, if the target type is assignable to the candidate type, keep the target type. - // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate - // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the - // two types. - return isTypeSubtypeOf(candidate, type) ? candidate : - isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); + // If the candidate type is a subtype of the target type, narrow to the candidate type, + // if the target type is a subtype of the candidate type, narrow to the target type, + // otherwise, narrow to an intersection of the two types. + return isTypeSubtypeOf(candidate, type) ? candidate : isTypeSubtypeOf(type, candidate) ? type : getIntersectionType([type, candidate]); } function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/instanceOfAssignability.types b/tests/baselines/reference/instanceOfAssignability.types index 7ce2209e9492e..85d15c944d1cc 100644 --- a/tests/baselines/reference/instanceOfAssignability.types +++ b/tests/baselines/reference/instanceOfAssignability.types @@ -70,8 +70,8 @@ function fn2(x: Base) { // 1.5: y: Base // Want: y: Derived1 let y = x; ->y : Derived1 ->x : Derived1 +>y : Base & Derived1 +>x : Base & Derived1 } } @@ -104,8 +104,8 @@ function fn4(x: Base|Derived2) { // 1.5: y: {} // Want: Derived1 let y = x; ->y : Derived1 ->x : Derived1 +>y : (Base & Derived1) | (Derived2 & Derived1) +>x : (Base & Derived1) | (Derived2 & Derived1) } } diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.symbols b/tests/baselines/reference/typeGuardIntersectionTypes.symbols index 21da29e434fec..f99148a9f0500 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.symbols +++ b/tests/baselines/reference/typeGuardIntersectionTypes.symbols @@ -176,17 +176,17 @@ function identifyBeast(beast: Beast) { >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) if (beast.legs === 4) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`pegasus - 4 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) } else if (beast.legs === 2) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`bird - 2 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) @@ -194,9 +194,9 @@ function identifyBeast(beast: Beast) { else { log(`unknown - ${beast.legs} legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } @@ -204,9 +204,9 @@ function identifyBeast(beast: Beast) { else { log(`manbearpig - ${beast.legs} legs, no wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.types b/tests/baselines/reference/typeGuardIntersectionTypes.types index a9f43b4917930..bf119d5743c6e 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.types +++ b/tests/baselines/reference/typeGuardIntersectionTypes.types @@ -166,12 +166,12 @@ function identifyBeast(beast: Beast) { if (hasWings(beast)) { >hasWings(beast) : boolean >hasWings : (x: Beast) => x is Winged ->beast : Legged +>beast : Beast & Legged if (beast.legs === 4) { >beast.legs === 4 : boolean >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number >4 : 4 @@ -183,7 +183,7 @@ function identifyBeast(beast: Beast) { else if (beast.legs === 2) { >beast.legs === 2 : boolean >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number >2 : 2 @@ -198,7 +198,7 @@ function identifyBeast(beast: Beast) { >log : (s: string) => void >`unknown - ${beast.legs} legs, wings` : string >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number } } @@ -210,7 +210,7 @@ function identifyBeast(beast: Beast) { >log : (s: string) => void >`manbearpig - ${beast.legs} legs, no wings` : string >beast.legs : number ->beast : Legged +>beast : Beast & Legged >legs : number } } diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.js b/tests/baselines/reference/typeGuardsWithInstanceOf.js index 34af7037f0bd9..1f6b37e7c01ea 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.js +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.js @@ -1,12 +1,39 @@ //// [typeGuardsWithInstanceOf.ts] interface I { global: string; } -var result: I; -var result2: I; +var result!: I; +var result2!: I; if (!(result instanceof RegExp)) { result = result2; } else if (!result.global) { -} +} + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +} +interface Validator { + validate(): null | Record; +} + +class C { + validate() { + return {} + } +} + +function foo() { + let v: Validator & Partial = null as any; + if (v instanceof C) { + v // Validator & Partial & C + } + v // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +} + //// [typeGuardsWithInstanceOf.js] var result; @@ -16,3 +43,21 @@ if (!(result instanceof RegExp)) { } else if (!result.global) { } +var C = /** @class */ (function () { + function C() { + } + C.prototype.validate = function () { + return {}; + }; + return C; +}()); +function foo() { + var v = null; + if (v instanceof C) { + v; // Validator & Partial & C + } + v; // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +} diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.symbols b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols index 90be23463b9bf..7433250dc0a4c 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.symbols +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols @@ -3,11 +3,11 @@ interface I { global: string; } >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) >global : Symbol(I.global, Decl(typeGuardsWithInstanceOf.ts, 0, 13)) -var result: I; +var result!: I; >result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) -var result2: I; +var result2!: I; >result2 : Symbol(result2, Decl(typeGuardsWithInstanceOf.ts, 2, 3)) >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) @@ -24,3 +24,63 @@ if (!(result instanceof RegExp)) { >result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) >global : Symbol(global, Decl(typeGuardsWithInstanceOf.ts, 0, 13), Decl(lib.es5.d.ts, --, --)) } + +// Repro from #31155 + +interface OnChanges { +>OnChanges : Symbol(OnChanges, Decl(typeGuardsWithInstanceOf.ts, 7, 1)) + + onChanges(changes: Record): void +>onChanges : Symbol(OnChanges.onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>changes : Symbol(changes, Decl(typeGuardsWithInstanceOf.ts, 12, 14)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +} +interface Validator { +>Validator : Symbol(Validator, Decl(typeGuardsWithInstanceOf.ts, 13, 1)) + + validate(): null | Record; +>validate : Symbol(Validator.validate, Decl(typeGuardsWithInstanceOf.ts, 14, 21)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +} + +class C { +>C : Symbol(C, Decl(typeGuardsWithInstanceOf.ts, 16, 1)) + + validate() { +>validate : Symbol(C.validate, Decl(typeGuardsWithInstanceOf.ts, 18, 9)) + + return {} + } +} + +function foo() { +>foo : Symbol(foo, Decl(typeGuardsWithInstanceOf.ts, 22, 1)) + + let v: Validator & Partial = null as any; +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>Validator : Symbol(Validator, Decl(typeGuardsWithInstanceOf.ts, 13, 1)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>OnChanges : Symbol(OnChanges, Decl(typeGuardsWithInstanceOf.ts, 7, 1)) + + if (v instanceof C) { +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>C : Symbol(C, Decl(typeGuardsWithInstanceOf.ts, 16, 1)) + + v // Validator & Partial & C +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) + } + v // Validator & Partial via subtype reduction +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) + + if (v.onChanges) { +>v.onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) + + v.onChanges({}); +>v.onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) + } +} + diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.types b/tests/baselines/reference/typeGuardsWithInstanceOf.types index fa425123812af..c7a8a1c9ac76e 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.types +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.types @@ -2,10 +2,10 @@ interface I { global: string; } >global : string -var result: I; +var result!: I; >result : I -var result2: I; +var result2!: I; >result2 : I if (!(result instanceof RegExp)) { @@ -26,3 +26,61 @@ if (!(result instanceof RegExp)) { >result : I & RegExp >global : never } + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +>onChanges : (changes: Record) => void +>changes : Record +} +interface Validator { + validate(): null | Record; +>validate : () => Record | null +>null : null +} + +class C { +>C : C + + validate() { +>validate : () => {} + + return {} +>{} : {} + } +} + +function foo() { +>foo : () => void + + let v: Validator & Partial = null as any; +>v : Validator & Partial +>null as any : any +>null : null + + if (v instanceof C) { +>v instanceof C : boolean +>v : Validator & Partial +>C : typeof C + + v // Validator & Partial & C +>v : Validator & Partial & C + } + v // Validator & Partial via subtype reduction +>v : Validator & Partial + + if (v.onChanges) { +>v.onChanges : ((changes: Record) => void) | undefined +>v : Validator & Partial +>onChanges : ((changes: Record) => void) | undefined + + v.onChanges({}); +>v.onChanges({}) : void +>v.onChanges : (changes: Record) => void +>v : Validator & Partial +>onChanges : (changes: Record) => void +>{} : {} + } +} + diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts index 2750eb96ebfbb..85370505250ee 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts @@ -1,8 +1,36 @@ -interface I { global: string; } -var result: I; -var result2: I; +// @strictNullChecks: true + +interface I { global: string; } +var result!: I; +var result2!: I; if (!(result instanceof RegExp)) { result = result2; } else if (!result.global) { -} \ No newline at end of file +} + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +} +interface Validator { + validate(): null | Record; +} + +class C { + validate() { + return {} + } +} + +function foo() { + let v: Validator & Partial = null as any; + if (v instanceof C) { + v // Validator & Partial & C + } + v // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +}