Skip to content

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

Open
@pedro-pedrosa

Description

@pedro-pedrosa

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 ?

Activity

RyanCavanaugh

RyanCavanaugh commented on Dec 23, 2020

@RyanCavanaugh
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

agentcooper commented on Dec 24, 2020

@agentcooper
Contributor

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

n9 commented on Feb 4, 2021

@n9

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?

added
RescheduledThis issue was previously scheduled to an earlier milestone
on Mar 4, 2021
pedro-pedrosa

pedro-pedrosa commented on May 27, 2021

@pedro-pedrosa
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.

removed this from the TypeScript 4.4.1 (RC) milestone on Aug 31, 2021

14 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Needs InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @agentcooper@n9@pedro-pedrosa@andrewbranch@ahejlsberg

      Issue actions

        Generic type param not narrowed in true branch of conditional type · Issue #42077 · microsoft/TypeScript