英文:
Typescript falls back to the default value of generic type for no reason
问题
抱歉,你的请求不太清楚,代码部分已被排除在翻译之外。你是否需要其他帮助?
英文:
Sorry if the title is not very obvious. I don't know what is the problem so I couldn't come up with a good question. This is a simplified version of the problem that I'm struggling with:
type Key = unknown[];
type Option<TKey extends Key> = {
key: TKey;
updateKey: (v: TKey) => TKey;
};
type Getter = <Value>(v: Value) => Value
function createConfigWithOption<TKey extends Key>(
getOptions: ((g: Getter) => Option<TKey>)
) {
}
createConfigWithOption
receives a function whose first parameter is a getter function (doesn't matter what it does) and returns an option object. The option object has a key
key and an updateKey
function. The input and output of updateKey
should be inferred from the key
.
It works correctly if I don't use the getter:
createConfigWithOption(() => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: string
return [];
},
}));
But it falls back to the default value of Key
when I use the getter:
createConfigWithOption((get) => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: unknown
return [];
},
}));
Here is a Typescript playground if you want to try it out yourself.
I've been scratching my head for several hours to understand what's going on but didn't find an answer.
答案1
得分: 2
以下是您要翻译的部分:
查看microsoft/TypeScript#47599以获取有关此问题的权威描述。
TypeScript的类型推断算法远非完美;它实际上是一组启发式规则的集合,适用于广泛的用例,但不幸的是,有时会出现无法推断出显而易见的情况。一个可能出问题的地方是,编译器需要同时推断泛型类型参数以及上下文推断回调参数类型。
以下两者之间的区别:
createConfigWithOption(() => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: string
return [];
},
}));
和
createConfigWithOption((get) => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: unknown
return [];
},
}));
区别在于,在后者中,传递给createConfigWithOption
的回调具有编译器要为您推断的回调参数get
的类型。这使整个回调函数与上下文相关。一般来说,如果编译器不知道函数的参数类型,它也无法知道函数内部发生的事情的类型。当前的算法倾向于推迟上下文敏感的推断,直到推断出泛型类型参数。但这是个问题,因为让回调函数成为黑匣子意味着编译器不知道要为E
泛型类型参数推断什么,因此它会退回到约束,之后get
将被适当推断...但为时已晚,因为E
比您想要的更宽泛。
请注意,在这个特定情况下,事实证明get
在函数体内部实际上没有被使用,因此实际上没有任何依赖于其类型。但编译器对函数体内部的检查并不多。因此,像(get) =>
这样使用会破坏推断。
这就是发生的情况。最近在这种循环上下文和泛型推断方面已经有一些改进,如microsoft/TypeScript#48538所述,但它们无法处理每种可能的情况,而您的代码是其中之一,仍然无法按您的期望工作。
解决方法要么完全将其省略,如您的第一个示例中的() =>
,要么明确注释它的类型,以便编译器不必推断它:
createConfigWithOption((get: Getter) => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: string
return [];
},
}));
英文:
See microsoft/TypeScript#47599 for an authoritative description of this issue.
TypeScript's type inference algorithm isn't perfect by any means; it's effectively a collection of heuristic rules that works well over a wide range of use cases, but there are unfortunately situations where it fails to infer something that a human developer would see as obvious. One place where things can go awry is if the compiler needs to infer generic type arguments at the same time it needs to contextually infer callback parameter types.
The difference between
createConfigWithOption(() => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: string
return [];
},
}));
and
createConfigWithOption((get) => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: unknown
return [];
},
}));
is that in the latter, the callback passed to createConfigWithOption
has a callback parameter get
whose type the compiler wants to infer for you. That makes the whole callback function context sensitive. Generally speaking, if the compiler doesn't know the parameter type of a function, it can't know the types of what's going on inside the function either. The current algorithm tends to delay context sensitive inferences until after generic type arguments are inferred. But this is a problem, since leaving the callback as a black box means that the compiler has no idea what to infer for the E
generic type parameter, so it falls back to the constraint, after which get
is inferred properly... but it's too late, since E
is wider than you want.
Note that in this particular instance, it turns out that get
is not used anywhere inside the function body, so in fact nothing actually depends on its type. But the compiler doesn't do much of an inspection into the function body to check for that sort of thing. So using (get)=>
like that messes up inference for you.
So that's what's going on. There have been recent improvements in this sort of circular contextual-and-generic inference, as described in microsoft/TypeScript#48538, but they can't address every possible situation, and your code is one of those places that still doesn't work as you want.
The workarounds here are either to leave it out entirely like ()=>
as in your first example, or to explicitly annotate it with its type so the compiler doesn't have to infer it:
createConfigWithOption((get: Getter) => ({
key: ["1"],
updateKey: (currentKey) => {
const a = currentKey[0];
// ^? const a: string
return [];
},
}));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论