Skip to content

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

Closed

Description

@AlCalzone

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?

Activity

  1. RyanCavanaugh commented on Mar 20, 2018

    @RyanCavanaugh
    Member

    Likely duplicate of #22596

  2. ahejlsberg commented on Mar 21, 2018

    @ahejlsberg
    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)?

    Ryan Cavanaugh (@RyanCavanaugh) This is unrelated to #22596.

  3. AlCalzone commented on Mar 21, 2018

    @AlCalzone
    ContributorAuthor

    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
  4. AlCalzone commented on Mar 28, 2018

    @AlCalzone
    ContributorAuthor

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

  5. mhegazy commented on Mar 28, 2018

    @mhegazy
    Contributor

    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;
  6. AlCalzone commented on Mar 29, 2018

    @AlCalzone
    ContributorAuthor

    Alright, thanks for the explanation!

  7. typescript-bot commented on Apr 12, 2018

    @typescript-bot
    Contributor

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

  8. locked and limited conversation to collaborators on Jul 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions