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

Type 'true' is not assignable to type 'T2 extends keyof T1 ? true : false' #22735

Closed
AlCalzone opened this issue Mar 20, 2018 · 7 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@AlCalzone
Copy link
Contributor

TypeScript Version: 2.8.0-dev.20180320

Search Terms: conditional type assignable

Code

function foo<T1, T2 extends string>(target: T1, prop: T2): T2 extends keyof T1 ? true : false {
	if (prop in target) return true;
	return false; // doesn't matter if this line is here or not.
}
// or
function foo<T1, T2 extends string>(target: T1, prop: T2): T2 extends keyof T1 ? true : false {
	return true;
}
// or
function foo<T1, T2 extends string>(target: T1, prop: T2): T2 extends keyof T1 ? true : null {
	if (prop in target) return true;
}

Expected behavior:
It works

Actual behavior:
error TS2322: Type 'true' is not assignable to type 'T2 extends keyof T1 ? true : false'.

This repro is a very reduced version of some code I'm trying to type with the new conditional syntax. Basically I want to achieve the following:

  • Case A: prop is a member of a specific union (here: the keys/property names of target) => the function returns a value of type 1
  • Case B: prop is NOT a member of that union => the function returns null

Am I misunderstanding something obvious here or is what I'm trying to do simply not possible?

@RyanCavanaugh
Copy link
Member

Likely duplicate of #22596

@ahejlsberg
Copy link
Member

In order for this to work you have to explicitly cast the return values to match the return type annotation because the type checker isn't capable of proving that it is always true:

function foo<T1, T2 extends string>(target: T1, prop: T2): T2 extends keyof T1 ? true : false {
    if (prop in target) return <T2 extends keyof T1 ? true : false>true;
    return <T2 extends keyof T1 ? true : false>false;
}

But I'm wondering why you're using a conditional type in the return type annotation (as opposed to just boolean)?

@RyanCavanaugh This is unrelated to #22596.

@AlCalzone
Copy link
Contributor Author

AlCalzone commented Mar 21, 2018

But I'm wondering why you're using a conditional type in the return type annotation (as opposed to just boolean)?

This is just a simplified repro which happened to result in the same error. I actually want to type something slightly more complicated:

  • Case A: prop is a member of a specific union (here: keyof T1) => the function gets the design:type of target[prop] using Reflection and uses that for a lookup in a Record object.
  • Case B: prop is NOT a member of that union => the function returns null

I guess the error happens because (like you said) the type checker is not sure if the condition is fulfilled. However I'd expect that returning one of the possible outcomes of the conditional type should be possible.

What about this case? If I type it using string | null as a return value, y is incorrectly inferred as string.

function ensureString<T>(bar: T): T extends string ? string : null {
    if (typeof bar === "string") return bar as string;
    // ^ ERROR: string is not assignable to T extends string ? string : null
    return;
}

var x = ensureString(""); // correctly inferred as string
var y = ensureString(1); // correctly inferred as null

@AlCalzone
Copy link
Contributor Author

Sorry for bringing this up again... but is the behavior I described in my last post intended?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 28, 2018

but is the behavior I described in my last post intended?

it is expected given the current design. but it is more of a design limitation than a desired behavior.

The type of bar is narrowed in the if statement to T & string, neither that nor string are assignable to T extends string ? string : null, since the return type really depends on T, and string does not have that par in it.

in a sense what you need the compiler to do here is 1. narrow the type of bar to T extends string, and thus the type of the return statement to T extends string ? string : never, then narrow the type of bar on the else branch to T extends string ? never : null, then unify the two return statements into one return type which is T extends string? string : null which is the same as the declared type. but the compiler is not geared to do this at the moment.

you can cast your way out of this using a conditional type using T , e.g.:

    if (typeof bar === "string") return <T extends string ? T : never> bar;

@mhegazy mhegazy added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Mar 28, 2018
@AlCalzone
Copy link
Contributor Author

Alright, thanks for the explanation!

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

5 participants