Skip to content

Return type inference breaks in function parameter; tooltip also inconsistent #32540

Closed
@AnyhowStep

Description

@AnyhowStep

TypeScript Version: 3.5.1

Search Terms:

conditional type, type arg, inference, return type, argument, parameter, generic, callback function

Code

This is literally the minimum repro I could make and it's still too big.

export interface CompileError<_ErrorMessageT extends any[]> {
    /**
     * There should never be a value of this type
     */
    readonly __compileError : never;
}
/**
 * Each `string` element represents a column name.
 *
 * A "key" is a set of columns that uniquely identifies
 * a row in a table.
 */
export type Key = readonly string[];

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A extends Key ?
    (
        B extends Key ?
        (
            A[number] extends B[number] ?
            A :
            never
        ) :
        never
    ) :
    never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
    A extends Key ?
    (
        B extends Key ?
        (
            B[number] extends A[number] ?
            A :
            never
        ) :
        never
    ) :
    never
);

export type FindSubKey<
    ArrT extends readonly Key[],
    KeyT extends Key
> = (
    ExtractSubKey<
        ArrT[number],
        KeyT
    >
);
export type FindSuperKey<
    ArrT extends readonly Key[],
    KeyT extends Key
> = (
    ExtractSuperKey<
        ArrT[number],
        KeyT
    >
);

declare function noSubKey<KeyT extends Key> (
  arg : (
    (c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT &
    (
        FindSuperKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        unknown :
        CompileError<[
            KeyT,
            "is a sub key of",
            FindSuperKey<
                (("x"|"y")[])[],
                KeyT
            >
        ]>
    )
  )
) : void

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSubKey(c => [c.z])
//OK!
//Expected: CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Error!
//Tooltip : noSubKey<readonly string[]>
noSubKey(c => [c.x])
//OK!
//Expected: CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Error!
//Tooltip : noSubKey<readonly string[]>
noSubKey(c => [c.x, c.y])

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSubKey(() => ["z" as "z"]);
//OK!
//Expected: CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Tooltip : noSubKey<"x"[]>
noSubKey(() => ["x" as "x"]);
//OK!
//Expected: CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Tooltip : noSubKey<("x" | "y")[]>
noSubKey(() => ["x" as "x", "y" as "y"]);

declare function noSuperKey<KeyT extends Key> (
  arg : (
    ((c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT) &
    (
        FindSubKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        unknown :
        CompileError<[
            KeyT,
            "is a super key of",
            FindSubKey<
                (("x"|"y")[])[],
                KeyT
            >
        ]>
    )
  )
) : void

//Error!
//Expected: Infer KeyT as "z"[]
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.z])
//Error!
//Expected: Infer KeyT as "x"[]
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.x])
//Error!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.x, c.y])

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSuperKey<"z"[]>
noSuperKey(() => ["z" as "z"]);
//OK!
//Expected: Infer KeyT as "x"[]
//Actual  : Infer KeyT as "x"[]
//Tooltip : noSuperKey<"x"[]>
noSuperKey(() => ["x" as "x"]);
//OK!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<("x" | "y")[]>
noSuperKey(() => ["x" as "x", "y" as "y"]);

/**
 * Seems weird that using the `c` argument results in inference problems.
 * But using string literals without the `c` argument is okay.
 */

Expected behavior:

Whether I use the c argument or not when calling noSubKey()/noSuperKey(),
it should always infer the type of KeyT correctly for all cases.

Actual behavior:

noSubKey()

  • With c argument, it correctly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

noSuperKey()

  • With c argument, it incorrectly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

Playground Link:

Playground

Related Issues:

Off the top of my head, I've made similar reports before and other similar repro cases,

#29133

#23689 (comment)

From my personal experience, it feels like the moment you are using the ReturnType<> of a function in a conditional type (directly or indirectly) inside a parameter, you end up having weird issues with inference.

For the longest time, I've been trying to find a solid workaround but nothing seems to quite stick.

Every workaround I can come up with will work in some situation but not in others.


You'll notice that, in this repro, I never even use ReturnType<> on FunctionT.

I use KeyT and make the parameter (c) => KeyT and it works in some cases but breaks in this case for noSuperKey() and works (mostly) fine for noSubKey()

Metadata

Metadata

Assignees

Labels

Needs More InfoThe issue still hasn't been fully clarified

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions