Skip to content

Change in inferred type in 3.6 for function call using generic union instantiated at string literal #33227

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

Closed
lukehoban opened this issue Sep 3, 2019 · 7 comments
Assignees
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@lukehoban
Copy link
Member

TypeScript Version: 3.6.2

Search Terms: regression, string literal, union, 3.6, inference, generic.

Code

type NetworkProtocol = "TCP";
type Input<T> = Promise<T> | T;
function ifUndefined<T>(input:Input<T> | undefined, value: Input<T>): Promise<T> {
    throw new Error();   
}
const y = ifUndefined(new Promise<NetworkProtocol>(() => {}), "TCP") 
const z: Promise<NetworkProtocol> = y;

Expected behavior:
No error (3.5 and earlier).

Actual behavior:
A type error (3.6.2):

Type 'Promise<string>' is not assignable to type 'Promise<NetworkProtocol>'.
  Type 'string' is not assignable to type 'NetworkProtocol'.

Playground Link: Playground. The TypeScript Playground doesn't appear to yet use 3.6.2 though so this doesn't repro in the playground.

Note that this example does work as expected - and looks morally "the same":

type NetworkProtocol = "TCP";
function ifUndefined<T>(input:Promise<T> | undefined, value: T): Promise<T> {
    throw new Error();   
}
const y = ifUndefined(new Promise<NetworkProtocol>(() => {}), "TCP") 
const z: Promise<NetworkProtocol> = y;
@schlundus
Copy link

schlundus commented Sep 4, 2019

Yes, i can confirm there seems to be an issue in 3.6.2, simpler example here:

image

It shows the wrong type inference

Playground (3.5.1) ... looks good

image

@fatcerberus
Copy link

I think this probably has to do with #33131 (comment) and is therefore a bug.

The example above was a generic function with its argument typed as string | T. The call site passes in a string, it matches the string part of the union. The compiler then doesn't bother to infer T. T therefore defaults to unknown (its constraint), bar's type is instantiated to string | unknown which collapses to simply unknown by subtype reduction.

@sandersn
Copy link
Member

sandersn commented Sep 4, 2019

Yeah, I think it's to do with the changes to naked type parameter inference. @weswigham mind taking a look to confirm before I close this as a duplicate?

@sandersn sandersn added the Bug A bug in TypeScript label Sep 4, 2019
@sandersn sandersn added this to the TypeScript 3.6.3 milestone Sep 4, 2019
@ahejlsberg
Copy link
Member

(Hi Luke! 👋)

This is an effect of #32460, and I would argue one that is intended. In the call to ifUndefined we make two inferences for T, in the first argument from Promise<NetworkProtocol> to T | Promise<T> | undefined, and in the second argument from string to T | Promise<T>. Previously we'd give lower priority to the second inference because we infer to a naked type parameter in a union, and thus we'd end up inferring NetworkProtocol for T. Now we give equal priority to the inferences (which really is the correct behavior) and we therefore end up with string.

The string literal "TCP" is widened to string in the second argument because there is nothing to indicate that it shouldn't be. It works as intended if you add a const assertion:

const y = ifUndefined(new Promise<NetworkProtocol>(() => {}), "TCP" as const);

@ahejlsberg ahejlsberg added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Bug A bug in TypeScript labels Sep 5, 2019
@ahejlsberg ahejlsberg removed this from the TypeScript 3.6.3 milestone Sep 5, 2019
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

1 similar comment
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

7 participants