Skip to content

Commit

Permalink
Unwrap NoInfer types when narrowing (#58292)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist authored May 31, 2024
1 parent 22eaccb commit f5b2d9b
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29479,6 +29479,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
if (isNoInferType(type)) {
type = (type as SubstitutionType).baseType;
}
// When the type of a reference is or contains an instantiable type with a union type constraint, and
// when the reference is in a constraint position (where it is known we'll obtain the apparent type) or
// has a contextual type containing no top-level instantiables (meaning constraints will determine
Expand Down
100 changes: 100 additions & 0 deletions tests/baselines/reference/narrowingNoInfer1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//// [tests/cases/compiler/narrowingNoInfer1.ts] ////

=== narrowingNoInfer1.ts ===
// https://github.com/microsoft/TypeScript/issues/58266

type TaggedA = { _tag: "a" };
>TaggedA : Symbol(TaggedA, Decl(narrowingNoInfer1.ts, 0, 0))
>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16))

type TaggedB = { _tag: "b" };
>TaggedB : Symbol(TaggedB, Decl(narrowingNoInfer1.ts, 2, 29))
>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 3, 16))

type TaggedUnion = TaggedA | TaggedB;
>TaggedUnion : Symbol(TaggedUnion, Decl(narrowingNoInfer1.ts, 3, 29))
>TaggedA : Symbol(TaggedA, Decl(narrowingNoInfer1.ts, 0, 0))
>TaggedB : Symbol(TaggedB, Decl(narrowingNoInfer1.ts, 2, 29))

const m: { result: NoInfer<TaggedUnion> }[] = [];
>m : Symbol(m, Decl(narrowingNoInfer1.ts, 7, 5))
>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>TaggedUnion : Symbol(TaggedUnion, Decl(narrowingNoInfer1.ts, 3, 29))

function map<A, B>(items: readonly A[], f: (a: NoInfer<A>) => B) {
>map : Symbol(map, Decl(narrowingNoInfer1.ts, 7, 49))
>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13))
>B : Symbol(B, Decl(narrowingNoInfer1.ts, 9, 15))
>items : Symbol(items, Decl(narrowingNoInfer1.ts, 9, 19))
>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13))
>f : Symbol(f, Decl(narrowingNoInfer1.ts, 9, 39))
>a : Symbol(a, Decl(narrowingNoInfer1.ts, 9, 44))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13))
>B : Symbol(B, Decl(narrowingNoInfer1.ts, 9, 15))

return items.map(f);
>items.map : Symbol(ReadonlyArray.map, Decl(lib.es5.d.ts, --, --))
>items : Symbol(items, Decl(narrowingNoInfer1.ts, 9, 19))
>map : Symbol(ReadonlyArray.map, Decl(lib.es5.d.ts, --, --))
>f : Symbol(f, Decl(narrowingNoInfer1.ts, 9, 39))
}

const something = map(m, (_) =>
>something : Symbol(something, Decl(narrowingNoInfer1.ts, 13, 5))
>map : Symbol(map, Decl(narrowingNoInfer1.ts, 7, 49))
>m : Symbol(m, Decl(narrowingNoInfer1.ts, 7, 5))
>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26))

_.result._tag === "a" ? { ..._, result: _.result } : null,
>_.result._tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16), Decl(narrowingNoInfer1.ts, 3, 16))
>_.result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10))
>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26))
>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10))
>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16), Decl(narrowingNoInfer1.ts, 3, 16))
>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26))
>result : Symbol(result, Decl(narrowingNoInfer1.ts, 14, 33))
>_.result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10))
>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26))
>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10))

);

declare function test2<T1, T2>(a: T1, b: T2, cb: (thing: NoInfer<T1> | NoInfer<T2>) => void): void;
>test2 : Symbol(test2, Decl(narrowingNoInfer1.ts, 15, 2))
>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23))
>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26))
>a : Symbol(a, Decl(narrowingNoInfer1.ts, 17, 31))
>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23))
>b : Symbol(b, Decl(narrowingNoInfer1.ts, 17, 37))
>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26))
>cb : Symbol(cb, Decl(narrowingNoInfer1.ts, 17, 44))
>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 17, 50))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26))

test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => {
>test2 : Symbol(test2, Decl(narrowingNoInfer1.ts, 15, 2))
>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7))
>const : Symbol(const)
>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 31))
>const : Symbol(const)
>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55))

if (thing.type === "a") {
>thing.type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7), Decl(narrowingNoInfer1.ts, 19, 31))
>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55))
>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7), Decl(narrowingNoInfer1.ts, 19, 31))

thing;
>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55))

} else {
thing;
>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55))
}
});

159 changes: 159 additions & 0 deletions tests/baselines/reference/narrowingNoInfer1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//// [tests/cases/compiler/narrowingNoInfer1.ts] ////

=== narrowingNoInfer1.ts ===
// https://github.com/microsoft/TypeScript/issues/58266

type TaggedA = { _tag: "a" };
>TaggedA : TaggedA
> : ^^^^^^^
>_tag : "a"
> : ^^^

type TaggedB = { _tag: "b" };
>TaggedB : TaggedB
> : ^^^^^^^
>_tag : "b"
> : ^^^

type TaggedUnion = TaggedA | TaggedB;
>TaggedUnion : TaggedUnion
> : ^^^^^^^^^^^

const m: { result: NoInfer<TaggedUnion> }[] = [];
>m : { result: NoInfer<TaggedUnion>; }[]
> : ^^^^^^^^^^ ^^^^^
>result : NoInfer<TaggedUnion>
> : ^^^^^^^^^^^^^^^^^^^^
>[] : never[]
> : ^^^^^^^

function map<A, B>(items: readonly A[], f: (a: NoInfer<A>) => B) {
>map : <A, B>(items: readonly A[], f: (a: NoInfer<A>) => B) => B[]
> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^
>items : readonly A[]
> : ^^^^^^^^^^^^
>f : (a: NoInfer<A>) => B
> : ^ ^^ ^^^^^
>a : NoInfer<A>
> : ^^^^^^^^^^

return items.map(f);
>items.map(f) : B[]
> : ^^^
>items.map : <U>(callbackfn: (value: A, index: number, array: readonly A[]) => U, thisArg?: any) => U[]
> : ^^^^ ^^^ ^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^
>items : readonly A[]
> : ^^^^^^^^^^^^
>map : <U>(callbackfn: (value: A, index: number, array: readonly A[]) => U, thisArg?: any) => U[]
> : ^^^^ ^^^ ^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^
>f : (a: NoInfer<A>) => B
> : ^ ^^ ^^^^^
}

const something = map(m, (_) =>
>something : ({ result: TaggedA; } | null)[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>map(m, (_) => _.result._tag === "a" ? { ..._, result: _.result } : null,) : ({ result: TaggedA; } | null)[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>map : <A, B>(items: readonly A[], f: (a: NoInfer<A>) => B) => B[]
> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^
>m : { result: NoInfer<TaggedUnion>; }[]
> : ^^^^^^^^^^ ^^^^^
>(_) => _.result._tag === "a" ? { ..._, result: _.result } : null : (_: NoInfer<{ result: NoInfer<TaggedUnion>; }>) => { result: TaggedA; } | null
> : ^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>_ : NoInfer<{ result: NoInfer<TaggedUnion>; }>
> : ^^^^^^^^^^^^^^^^^^ ^^^^

_.result._tag === "a" ? { ..._, result: _.result } : null,
>_.result._tag === "a" ? { ..._, result: _.result } : null : { result: TaggedA; } | null
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
>_.result._tag === "a" : boolean
> : ^^^^^^^
>_.result._tag : "a" | "b"
> : ^^^^^^^^^
>_.result : TaggedUnion
> : ^^^^^^^^^^^
>_ : { result: NoInfer<TaggedUnion>; }
> : ^^^^^^^^^^ ^^^
>result : TaggedUnion
> : ^^^^^^^^^^^
>_tag : "a" | "b"
> : ^^^^^^^^^
>"a" : "a"
> : ^^^
>{ ..._, result: _.result } : { result: TaggedA; }
> : ^^^^^^^^^^^^^^^^^^^^
>_ : { result: NoInfer<TaggedUnion>; }
> : ^^^^^^^^^^ ^^^
>result : TaggedA
> : ^^^^^^^
>_.result : TaggedA
> : ^^^^^^^
>_ : { result: NoInfer<TaggedUnion>; }
> : ^^^^^^^^^^ ^^^
>result : TaggedA
> : ^^^^^^^

);

declare function test2<T1, T2>(a: T1, b: T2, cb: (thing: NoInfer<T1> | NoInfer<T2>) => void): void;
>test2 : <T1, T2>(a: T1, b: T2, cb: (thing: NoInfer<T1> | NoInfer<T2>) => void) => void
> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^
>a : T1
> : ^^
>b : T2
> : ^^
>cb : (thing: NoInfer<T1> | NoInfer<T2>) => void
> : ^ ^^ ^^^^^
>thing : NoInfer<T1> | NoInfer<T2>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^

test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => {
>test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => { if (thing.type === "a") { thing; } else { thing; }}) : void
> : ^^^^
>test2 : <T1, T2>(a: T1, b: T2, cb: (thing: NoInfer<T1> | NoInfer<T2>) => void) => void
> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^
>{ type: 'a' as const } : { type: "a"; }
> : ^^^^^^^^^^^^^^
>type : "a"
> : ^^^
>'a' as const : "a"
> : ^^^
>'a' : "a"
> : ^^^
>{ type: 'b' as const } : { type: "b"; }
> : ^^^^^^^^^^^^^^
>type : "b"
> : ^^^
>'b' as const : "b"
> : ^^^
>'b' : "b"
> : ^^^
>(thing) => { if (thing.type === "a") { thing; } else { thing; }} : (thing: NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }>) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>thing : NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

if (thing.type === "a") {
>thing.type === "a" : boolean
> : ^^^^^^^
>thing.type : "a" | "b"
> : ^^^^^^^^^
>thing : NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>type : "a" | "b"
> : ^^^^^^^^^
>"a" : "a"
> : ^^^

thing;
>thing : NoInfer<{ type: "a"; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^

} else {
thing;
>thing : NoInfer<{ type: "b"; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^
}
});

8 changes: 4 additions & 4 deletions tests/baselines/reference/noInfer.types
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,12 @@ class OkClass<T> {
> : ^

return this._value; // ok
>this._value : NoInfer<T>
> : ^^^^^^^^^^
>this._value : T
> : ^
>this : this
> : ^^^^
>_value : NoInfer<T>
> : ^^^^^^^^^^
>_value : T
> : ^
}
}
class OkClass2<T> {
Expand Down
29 changes: 29 additions & 0 deletions tests/cases/compiler/narrowingNoInfer1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @strict: true
// @noEmit: true

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

type TaggedA = { _tag: "a" };
type TaggedB = { _tag: "b" };

type TaggedUnion = TaggedA | TaggedB;

const m: { result: NoInfer<TaggedUnion> }[] = [];

function map<A, B>(items: readonly A[], f: (a: NoInfer<A>) => B) {
return items.map(f);
}

const something = map(m, (_) =>
_.result._tag === "a" ? { ..._, result: _.result } : null,
);

declare function test2<T1, T2>(a: T1, b: T2, cb: (thing: NoInfer<T1> | NoInfer<T2>) => void): void;

test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => {
if (thing.type === "a") {
thing;
} else {
thing;
}
});

0 comments on commit f5b2d9b

Please sign in to comment.