Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
3 changes: 3 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27681,6 +27681,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!flowNode) {
return declaredType;
}
declaredType = isNoInferType(declaredType) ? (declaredType as SubstitutionType).baseType : declaredType;
initialType = isNoInferType(initialType) ? (initialType as SubstitutionType).baseType : initialType;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

My first instinct was that I should re-wrap the result of narrowing with NoInfer but quoting the original NoInfer PR:

Other than blocking inference, NoInfer markers have no effect on T. Indeed, T and NoInfer are considered identical types in all other contexts.

So I don't see a reason why that would be necessary. I also tested something like this (TS playground) to see if this would be needed and I don't think it is:

declare function inner<T>(arg: T): T;
declare function outer<T>(a: T, cb: (arg: NoInfer<T>) => void): void;

outer({ foo: "bar" }, (arg) => {
  arg
  // ^? (parameter) arg: NoInfer<{ foo: string; }>
  const result = inner(arg); // inferred despite `arg` being a `NoInfer` type!
  //    ^? const result: NoInfer<{ foo: string; }>
});

Copy link
Member

Choose a reason for hiding this comment

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

I agree it's fine to erase NoInfer in variable, parameter, and property references, but I think it would be simpler and more consistent to do it first thing in the getNarrowableTypeForReference function. I think that's all you'd need.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved it to getNarrowableTypeForReference, works like a charm - thanks!

flowInvocationCount++;
const sharedFlowStart = sharedFlowCount;
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode));
Expand Down Expand Up @@ -28832,6 +28834,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
// will be a subtype or the same type as the argument.
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
type = isNoInferType(type) ? (type as SubstitutionType).baseType : type;
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
if (
isExpressionOfOptionalChainRoot(expr) ||
Expand Down
63 changes: 63 additions & 0 deletions tests/baselines/reference/narrowingNoInfer1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//// [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))

);

98 changes: 98 additions & 0 deletions tests/baselines/reference/narrowingNoInfer1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//// [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
> : ^^^^^^^

);

19 changes: 19 additions & 0 deletions tests/cases/compiler/narrowingNoInfer1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @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,
);