Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Narrow non-directly related weak types by intersecting them with candidates #58520

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28787,9 +28787,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType,
);
// If no constituents are directly related, create intersections for any generic constituents that
// are related by constraint.
// are related by constraint or when the type is weak.
return directlyRelated.flags & TypeFlags.Never ?
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) :
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) || isWeakType(type) ? getIntersectionType([t, c]) : neverType) :
directlyRelated;
});
// If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two
Expand Down
35 changes: 34 additions & 1 deletion tests/baselines/reference/typeGuardIntersectionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,31 @@ function beastFoo(beast: Object) {
if (hasLegs(beast) && hasWings(beast)) {
beast; // Legged & Winged
}
}
}

// https://github.com/microsoft/TypeScript/issues/58518

interface A_58518 {
label?: string;
a?: string;
}

interface B_58518 {
label?: string;
b: boolean;
}

function isB_58518(thing: object): thing is B_58518 {
return "b" in thing;
}

function test_58518(thing: A_58518) {
thing.a;
if (isB_58518(thing)) {
thing.a;
}
}


//// [typeGuardIntersectionTypes.js]
function f1(obj) {
Expand Down Expand Up @@ -180,3 +204,12 @@ function beastFoo(beast) {
beast; // Legged & Winged
}
}
function isB_58518(thing) {
return "b" in thing;
}
function test_58518(thing) {
thing.a;
if (isB_58518(thing)) {
thing.a;
}
}
70 changes: 62 additions & 8 deletions tests/baselines/reference/typeGuardIntersectionTypes.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -178,37 +178,37 @@ 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))
}
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))
}
}

// All non-winged beasts with legs
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))
}
}

Expand Down Expand Up @@ -257,3 +257,57 @@ function beastFoo(beast: Object) {
>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 99, 18))
}
}

// https://github.com/microsoft/TypeScript/issues/58518

interface A_58518 {
>A_58518 : Symbol(A_58518, Decl(typeGuardIntersectionTypes.ts, 110, 1))

label?: string;
>label : Symbol(A_58518.label, Decl(typeGuardIntersectionTypes.ts, 114, 19))

a?: string;
>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17))
}

interface B_58518 {
>B_58518 : Symbol(B_58518, Decl(typeGuardIntersectionTypes.ts, 117, 1))

label?: string;
>label : Symbol(B_58518.label, Decl(typeGuardIntersectionTypes.ts, 119, 19))

b: boolean;
>b : Symbol(B_58518.b, Decl(typeGuardIntersectionTypes.ts, 120, 17))
}

function isB_58518(thing: object): thing is B_58518 {
>isB_58518 : Symbol(isB_58518, Decl(typeGuardIntersectionTypes.ts, 122, 1))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19))
>B_58518 : Symbol(B_58518, Decl(typeGuardIntersectionTypes.ts, 117, 1))

return "b" in thing;
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19))
}

function test_58518(thing: A_58518) {
>test_58518 : Symbol(test_58518, Decl(typeGuardIntersectionTypes.ts, 126, 1))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20))
>A_58518 : Symbol(A_58518, Decl(typeGuardIntersectionTypes.ts, 110, 1))

thing.a;
>thing.a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20))
>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17))

if (isB_58518(thing)) {
>isB_58518 : Symbol(isB_58518, Decl(typeGuardIntersectionTypes.ts, 122, 1))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20))

thing.a;
>thing.a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17))
>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20))
>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17))
}
}

90 changes: 80 additions & 10 deletions tests/baselines/reference/typeGuardIntersectionTypes.types
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,16 @@ function identifyBeast(beast: Beast) {
> : ^^^^^^^
>hasWings : (x: Beast) => x is Winged
> : ^ ^^ ^^^^^^^^^^^^^^^^
>beast : Legged
> : ^^^^^^
>beast : Beast & Legged
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially the same test case as the added one. Without the change, we can't access beast.wings here (even though it's permitted before narrowing).

Regardless of that, I find it useful to include the @danvk's test case here as I could include the ticket reference in a clean manner above it.

> : ^^^^^^^^^^^^^^

if (beast.legs === 4) {
>beast.legs === 4 : boolean
> : ^^^^^^^
>beast.legs : number
> : ^^^^^^
>beast : Legged & Winged
> : ^^^^^^^^^^^^^^^
>beast : Beast & Legged & Winged
> : ^^^^^^^^^^^^^^^^^^^^^^^
>legs : number
> : ^^^^^^
>4 : 4
Expand All @@ -269,8 +269,8 @@ function identifyBeast(beast: Beast) {
> : ^^^^^^^
>beast.legs : number
> : ^^^^^^
>beast : Legged & Winged
> : ^^^^^^^^^^^^^^^
>beast : Beast & Legged & Winged
> : ^^^^^^^^^^^^^^^^^^^^^^^
>legs : number
> : ^^^^^^
>2 : 2
Expand All @@ -294,8 +294,8 @@ function identifyBeast(beast: Beast) {
> : ^^^^^^
>beast.legs : number
> : ^^^^^^
>beast : Legged & Winged
> : ^^^^^^^^^^^^^^^
>beast : Beast & Legged & Winged
> : ^^^^^^^^^^^^^^^^^^^^^^^
>legs : number
> : ^^^^^^
}
Expand All @@ -312,8 +312,8 @@ function identifyBeast(beast: Beast) {
> : ^^^^^^
>beast.legs : number
> : ^^^^^^
>beast : Legged
> : ^^^^^^
>beast : Beast & Legged
> : ^^^^^^^^^^^^^^
>legs : number
> : ^^^^^^
}
Expand Down Expand Up @@ -402,3 +402,73 @@ function beastFoo(beast: Object) {
> : ^^^^^^^^^^^^^^^
}
}

// https://github.com/microsoft/TypeScript/issues/58518

interface A_58518 {
label?: string;
>label : string | undefined
> : ^^^^^^^^^^^^^^^^^^

a?: string;
>a : string | undefined
> : ^^^^^^^^^^^^^^^^^^
}

interface B_58518 {
label?: string;
>label : string | undefined
> : ^^^^^^^^^^^^^^^^^^

b: boolean;
>b : boolean
> : ^^^^^^^
}

function isB_58518(thing: object): thing is B_58518 {
>isB_58518 : (thing: object) => thing is B_58518
> : ^ ^^ ^^^^^
>thing : object
> : ^^^^^^

return "b" in thing;
>"b" in thing : boolean
> : ^^^^^^^
>"b" : "b"
> : ^^^
>thing : object
> : ^^^^^^
}

function test_58518(thing: A_58518) {
>test_58518 : (thing: A_58518) => void
> : ^ ^^ ^^^^^^^^^
>thing : A_58518
> : ^^^^^^^

thing.a;
>thing.a : string | undefined
> : ^^^^^^^^^^^^^^^^^^
>thing : A_58518
> : ^^^^^^^
>a : string | undefined
> : ^^^^^^^^^^^^^^^^^^

if (isB_58518(thing)) {
>isB_58518(thing) : boolean
> : ^^^^^^^
>isB_58518 : (thing: object) => thing is B_58518
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^
>thing : A_58518
> : ^^^^^^^

thing.a;
>thing.a : string | undefined
> : ^^^^^^^^^^^^^^^^^^
>thing : A_58518 & B_58518
> : ^^^^^^^^^^^^^^^^^
>a : string | undefined
> : ^^^^^^^^^^^^^^^^^^
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,27 @@ function beastFoo(beast: Object) {
if (hasLegs(beast) && hasWings(beast)) {
beast; // Legged & Winged
}
}
}

// https://github.com/microsoft/TypeScript/issues/58518

interface A_58518 {
label?: string;
a?: string;
}

interface B_58518 {
label?: string;
b: boolean;
}

function isB_58518(thing: object): thing is B_58518 {
return "b" in thing;
}

function test_58518(thing: A_58518) {
thing.a;
if (isB_58518(thing)) {
thing.a;
}
}