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()
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.
Expected behavior:
Whether I use the
cargument or not when callingnoSubKey()/noSuperKey(),it should always infer the type of
KeyTcorrectly for all cases.Actual behavior:
noSubKey()With
cargument, it correctly infersKeyTin the error messageWith
cargument, it incorrectly infersKeyTin the tooltipWith string literals, it correctly infers
KeyTin the error messageWith string literals, it correctly infers
KeyTin the tooltipnoSuperKey()With
cargument, it incorrectly infersKeyTin the error messageWith
cargument, it incorrectly infersKeyTin the tooltipWith string literals, it correctly infers
KeyTin the error messageWith string literals, it correctly infers
KeyTin the tooltipPlayground 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<>onFunctionT.I use
KeyTand make the parameter(c) => KeyTand it works in some cases but breaks in this case fornoSuperKey()and works (mostly) fine fornoSubKey()