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

Generic type param not narrowed in true branch of conditional type #42077

Open
pedro-pedrosa opened this issue Dec 22, 2020 · 4 comments
Open
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@pedro-pedrosa
Copy link

TypeScript Version: 4.1.2

Search Terms: conditional type true branch generic narrowing

Code

type Num = 0 | 1 | 2
type Chr = 'a' | 'b' | 'c'

type Unit = Num | Chr

type NumCommand = 'add' | 'invert'
type ChrCommand = 'concat' | 'toLower'

type Command<U extends Unit> =
  U extends Num ? NumCommand :
  U extends Chr ? ChrCommand :
  never

type NumCommandData<C extends NumCommand> =
  C extends 'add' ? { otherNum: Num } :
  never

type ChrCommandData<C extends ChrCommand> =
  C extends 'concat' ? { otherChr: Chr } :
  never

type CommandData<U extends Unit, C extends Command<U>> =
  U extends Num ? NumCommandData<C> :
  U extends Chr ? ChrCommandData<C> :
  never

Expected behavior: in CommandData we are narrowing the generic parameter U using a conditional type, so in the true branch of the conditional C should also be "narrowed" (not sure if that's the correct term in this case) to the specific command type that depends on U.

Actual behavior: the compiler says that in the true branches of CommandData conditionals, C does not satisfy the constraint of either NumCommandData or ChrCommandData

Playground Link: playground

Related Issues: maybe #24085 ?

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Dec 23, 2020
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.2.1 milestone Dec 23, 2020
@RyanCavanaugh
Copy link
Member

Currently narrowing doesn't apply to constraints; I'm not sure how feasible that would be.

Simpler example

type NumberIfOne<T> = T extends number ? 1 : 0;
type OneOnly<T extends 1> = T;
// Error, but should be OK (?)
type S<T, U extends NumberIfOne<T>> = T extends number ? OneOnly<U> : false;

@agentcooper
Copy link
Contributor

agentcooper commented Dec 24, 2020

It seems that this example illustrates the same point. It was unclear to me why test2 is behaving differently until I saw Ryan's reply above.

function test1(input: boolean) {
    if (input === true) {
        throw new Error("!")
    }
    needsFalse(input) // OK, expected
}

function test2<T extends boolean>(input: T) {
    if (input === true) {
        throw new Error("!")
    }
    needsFalse(input)
    /**
     * Argument of type 'T' is not assignable to parameter of type 'false'.
     *   Type 'boolean' is not assignable to type 'false'.(2345)
     */
}

declare function needsFalse(input: false): void

I need a generic there to eventually write a conditional return type based on the function parameter.

@n9
Copy link

n9 commented Feb 4, 2021

Is this a similar case?

type ValueType<T> = [T] extends [string] ? Value<string> : Value<T>;

interface Value<T> {
    set(v: T): void
}

export type Foo1<T> = T extends true ? boolean : number

function foo1<T>(vt: ValueType<Foo1<T>>, v: Foo1<T>) {
    vt.set(v); // <-- error here
}

export type Foo2<T> = T | undefined

function foo2<T>(vt: ValueType<Foo2<T>>, v: Foo2<T>) {
    vt.set(v); // <-- error here
}

export type Foo3<T> = T & { bar: boolean }

function foo3<T>(vt: ValueType<Foo3<T>>, v: Foo3<T>) {
    vt.set(v); // <-- error here
}

Playground

Or it fails for a different reason?

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Mar 4, 2021
@pedro-pedrosa
Copy link
Author

this is still giving my example an error after 4.3.0-beta, but agentcooper's example compiles now. I'm guessing it's because he's using a type guard where I'm using a conditional type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

6 participants