Skip to content

Commit 5d3fe0a

Browse files
committed
Improve .origin preservation in getNarrowedTypeWorker
1 parent 83dc0bb commit 5d3fe0a

File tree

7 files changed

+196
-27
lines changed

7 files changed

+196
-27
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29342,27 +29342,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2934229342
// We first attempt to filter the current type, narrowing constituents as appropriate and removing
2934329343
// constituents that are unrelated to the candidate.
2934429344
const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf;
29345-
const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined;
29346-
const narrowedType = mapType(candidate, c => {
29347-
// If a discriminant property is available, use that to reduce the type.
29348-
const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName);
29349-
const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant);
29350-
// For each constituent t in the current type, if t and and c are directly related, pick the most
29351-
// specific of the two. When t and c are related in both directions, we prefer c for type predicates
29352-
// because that is the asserted type, but t for `instanceof` because generics aren't reflected in
29353-
// prototype object types.
29354-
const directlyRelated = mapType(
29355-
matching || type,
29356-
checkDerived ?
29357-
t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType :
29358-
t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType,
29359-
);
29360-
// If no constituents are directly related, create intersections for any generic constituents that
29361-
// are related by constraint.
29362-
return directlyRelated.flags & TypeFlags.Never ?
29363-
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) :
29364-
directlyRelated;
29365-
});
29345+
let matchedCandidates: Type[] = [];
29346+
let narrowedType = mapType(type, t =>
29347+
mapType(
29348+
candidate,
29349+
c => {
29350+
const directlyRelated = checkDerived ?
29351+
(isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType) :
29352+
(isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType);
29353+
if (!(directlyRelated.flags & TypeFlags.Never)) {
29354+
matchedCandidates = appendIfUnique(matchedCandidates, c);
29355+
}
29356+
return directlyRelated;
29357+
},
29358+
));
29359+
if (matchedCandidates.length !== countTypes(candidate)) {
29360+
narrowedType = getUnionType([
29361+
narrowedType,
29362+
mapType(candidate, c => {
29363+
return !containsType(matchedCandidates, c)
29364+
? mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType)
29365+
: neverType;
29366+
}),
29367+
]);
29368+
}
2936629369
// If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two
2936729370
// based on assignability, or as a last resort produce an intersection.
2936829371
return !(narrowedType.flags & TypeFlags.Never) ? narrowedType :

tests/baselines/reference/controlFlowOptionalChain.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,12 +2857,12 @@ function f30(o: Thing | undefined) {
28572857
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
28582858

28592859
o.foo;
2860-
>o.foo : NonNullable<string | number | undefined>
2861-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2860+
>o.foo : string | number
2861+
> : ^^^^^^^^^^^^^^^
28622862
>o : Thing
28632863
> : ^^^^^
2864-
>foo : NonNullable<string | number | undefined>
2865-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2864+
>foo : string | number
2865+
> : ^^^^^^^^^^^^^^^
28662866
}
28672867
}
28682868

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/compiler/narrowingUnionByUnionCandidate1.ts] ////
2+
3+
//// [narrowingUnionByUnionCandidate1.ts]
4+
// https://github.com/microsoft/TypeScript/issues/61581
5+
6+
type Result<A, E> =
7+
| {
8+
readonly _tag: "Ok";
9+
readonly value: A;
10+
}
11+
| {
12+
readonly _tag: "Fail";
13+
readonly error: E;
14+
};
15+
16+
declare const isResult: (u: unknown) => u is Result<any, any>;
17+
18+
export const fn = <A, E>(inp: Result<A, E> | string) =>
19+
isResult(inp) ? inp : "ok";
20+
21+
22+
//// [narrowingUnionByUnionCandidate1.js]
23+
"use strict";
24+
// https://github.com/microsoft/TypeScript/issues/61581
25+
Object.defineProperty(exports, "__esModule", { value: true });
26+
exports.fn = void 0;
27+
var fn = function (inp) {
28+
return isResult(inp) ? inp : "ok";
29+
};
30+
exports.fn = fn;
31+
32+
33+
//// [narrowingUnionByUnionCandidate1.d.ts]
34+
type Result<A, E> = {
35+
readonly _tag: "Ok";
36+
readonly value: A;
37+
} | {
38+
readonly _tag: "Fail";
39+
readonly error: E;
40+
};
41+
export declare const fn: <A, E>(inp: Result<A, E> | string) => Result<A, E> | "ok";
42+
export {};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/compiler/narrowingUnionByUnionCandidate1.ts] ////
2+
3+
=== narrowingUnionByUnionCandidate1.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/61581
5+
6+
type Result<A, E> =
7+
>Result : Symbol(Result, Decl(narrowingUnionByUnionCandidate1.ts, 0, 0))
8+
>A : Symbol(A, Decl(narrowingUnionByUnionCandidate1.ts, 2, 12))
9+
>E : Symbol(E, Decl(narrowingUnionByUnionCandidate1.ts, 2, 14))
10+
11+
| {
12+
readonly _tag: "Ok";
13+
>_tag : Symbol(_tag, Decl(narrowingUnionByUnionCandidate1.ts, 3, 5))
14+
15+
readonly value: A;
16+
>value : Symbol(value, Decl(narrowingUnionByUnionCandidate1.ts, 4, 26))
17+
>A : Symbol(A, Decl(narrowingUnionByUnionCandidate1.ts, 2, 12))
18+
}
19+
| {
20+
readonly _tag: "Fail";
21+
>_tag : Symbol(_tag, Decl(narrowingUnionByUnionCandidate1.ts, 7, 5))
22+
23+
readonly error: E;
24+
>error : Symbol(error, Decl(narrowingUnionByUnionCandidate1.ts, 8, 28))
25+
>E : Symbol(E, Decl(narrowingUnionByUnionCandidate1.ts, 2, 14))
26+
27+
};
28+
29+
declare const isResult: (u: unknown) => u is Result<any, any>;
30+
>isResult : Symbol(isResult, Decl(narrowingUnionByUnionCandidate1.ts, 12, 13))
31+
>u : Symbol(u, Decl(narrowingUnionByUnionCandidate1.ts, 12, 25))
32+
>u : Symbol(u, Decl(narrowingUnionByUnionCandidate1.ts, 12, 25))
33+
>Result : Symbol(Result, Decl(narrowingUnionByUnionCandidate1.ts, 0, 0))
34+
35+
export const fn = <A, E>(inp: Result<A, E> | string) =>
36+
>fn : Symbol(fn, Decl(narrowingUnionByUnionCandidate1.ts, 14, 12))
37+
>A : Symbol(A, Decl(narrowingUnionByUnionCandidate1.ts, 14, 19))
38+
>E : Symbol(E, Decl(narrowingUnionByUnionCandidate1.ts, 14, 21))
39+
>inp : Symbol(inp, Decl(narrowingUnionByUnionCandidate1.ts, 14, 25))
40+
>Result : Symbol(Result, Decl(narrowingUnionByUnionCandidate1.ts, 0, 0))
41+
>A : Symbol(A, Decl(narrowingUnionByUnionCandidate1.ts, 14, 19))
42+
>E : Symbol(E, Decl(narrowingUnionByUnionCandidate1.ts, 14, 21))
43+
44+
isResult(inp) ? inp : "ok";
45+
>isResult : Symbol(isResult, Decl(narrowingUnionByUnionCandidate1.ts, 12, 13))
46+
>inp : Symbol(inp, Decl(narrowingUnionByUnionCandidate1.ts, 14, 25))
47+
>inp : Symbol(inp, Decl(narrowingUnionByUnionCandidate1.ts, 14, 25))
48+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//// [tests/cases/compiler/narrowingUnionByUnionCandidate1.ts] ////
2+
3+
=== narrowingUnionByUnionCandidate1.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/61581
5+
6+
type Result<A, E> =
7+
>Result : Result<A, E>
8+
> : ^^^^^^^^^^^^
9+
10+
| {
11+
readonly _tag: "Ok";
12+
>_tag : "Ok"
13+
> : ^^^^
14+
15+
readonly value: A;
16+
>value : A
17+
> : ^
18+
}
19+
| {
20+
readonly _tag: "Fail";
21+
>_tag : "Fail"
22+
> : ^^^^^^
23+
24+
readonly error: E;
25+
>error : E
26+
> : ^
27+
28+
};
29+
30+
declare const isResult: (u: unknown) => u is Result<any, any>;
31+
>isResult : (u: unknown) => u is Result<any, any>
32+
> : ^ ^^ ^^^^^
33+
>u : unknown
34+
> : ^^^^^^^
35+
36+
export const fn = <A, E>(inp: Result<A, E> | string) =>
37+
>fn : <A, E>(inp: Result<A, E> | string) => Result<A, E> | "ok"
38+
> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^
39+
><A, E>(inp: Result<A, E> | string) => isResult(inp) ? inp : "ok" : <A, E>(inp: Result<A, E> | string) => Result<A, E> | "ok"
40+
> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^
41+
>inp : string | Result<A, E>
42+
> : ^^^^^^^^^^^^^^^^^^^^^
43+
44+
isResult(inp) ? inp : "ok";
45+
>isResult(inp) ? inp : "ok" : Result<A, E> | "ok"
46+
> : ^^^^^^^^^^^^^^^^^^^
47+
>isResult(inp) : boolean
48+
> : ^^^^^^^
49+
>isResult : (u: unknown) => u is Result<any, any>
50+
> : ^ ^^ ^^^^^
51+
>inp : string | Result<A, E>
52+
> : ^^^^^^^^^^^^^^^^^^^^^
53+
>inp : Result<A, E>
54+
> : ^^^^^^^^^^^^
55+
>"ok" : "ok"
56+
> : ^^^^
57+

tests/baselines/reference/narrowingUnionToUnion.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,8 @@ if (isEmpty(test)) {
557557
> : ^^^^^^^^^^^^^^^^^^^^^^^^^
558558

559559
test; // EmptyString
560-
>test : EmptyString
561-
> : ^^^^^^^^^^^
560+
>test : "" | null | undefined
561+
> : ^^^^^^^^^^^^^^^^^^^^^
562562
}
563563

564564
// Repro from #43825
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/61581
5+
6+
type Result<A, E> =
7+
| {
8+
readonly _tag: "Ok";
9+
readonly value: A;
10+
}
11+
| {
12+
readonly _tag: "Fail";
13+
readonly error: E;
14+
};
15+
16+
declare const isResult: (u: unknown) => u is Result<any, any>;
17+
18+
export const fn = <A, E>(inp: Result<A, E> | string) =>
19+
isResult(inp) ? inp : "ok";

0 commit comments

Comments
 (0)