TypeScript fails to narrow out undefined via typeof check on generic indexed access

wvt8vs2t  于 10个月前  发布在  TypeScript
关注(0)|答案(6)|浏览(145)

Bug报告

🔎 搜索词

undefined narrowed 2322

🕗 版本与回归信息

  • 在所有我尝试过的版本中,这种行为都是如此,我也查看了 FAQ 中的关于 'undefined' 的条目(包括通过页面上的查找功能)

⏯ Playground链接

带有相关代码的Playground链接

💻 代码

  1. type StarStats = {
  2. mass: number;
  3. surfaceTemperature: number;
  4. planets: number;
  5. }
  6. type PlanetStats = {
  7. mass: number;
  8. moons: number;
  9. orbitalPeriod: number;
  10. orbitalInclination: number;
  11. }
  12. interface PlanetaryBodiesMap {
  13. 'Planet' : PlanetStats;
  14. 'Star' : StarStats;
  15. }
  16. const getStatFromSet = function<
  17. TN extends keyof PlanetaryBodiesMap,
  18. >(
  19. statSet : Partial<PlanetaryBodiesMap[TN]>,
  20. statName : keyof typeof statSet,
  21. ) : number {
  22. const potentialResult = statSet[statName];
  23. //Adding redundant `&& potentialResult !== undefined` to the conditional,
  24. //the error message changes between v4.7.4 and v4.8.0-beta
  25. if(typeof potentialResult !== 'undefined') {
  26. potentialResult;
  27. //Hover above to see: Partial<PlanetaryBodiesMap[TN]>[keyof PlanetaryBodiesMap[TN]]
  28. //Error ts(2322) below: Type 'PlanetaryBodiesMap[TN][keyof PlanetaryBodiesMap[TN]] | undefined' is not assignable to type 'number'.
  29. //Where does that extra `| undefined` come from after having narrowed the type of the `const`?
  30. return potentialResult;
  31. } else {
  32. return 0;
  33. }
  34. }

🙁 实际行为

TypeScript 抱怨说,在已经检查并排除了该常量的条件下的 if 语句块中,常量值可以是 undefined

🙂 预期行为

  1. 当一个条件只能在常量类型不是 undefined 的情况下进入时,TypeScript 从常量的可能类型中排除 | undefined
  2. 当显示关于如何不能将该常量赋值给 Y 的错误消息时,类型 X 应该与悬停时显示的类型相同。在这个例子中,它们之间存在 | undefined 的差异,这很重要。在简化版本中,类型更复杂时,悬停时显示的常量的类型与错误中显示的类型在语言上非常不同,使得调试这个问题变得更加困难。
hgtggwj0

hgtggwj01#

这似乎与undefined本身关系不大;同样的问题也出现在

  1. const getStatFromSet = function <K extends keyof PlanetaryBodiesMap>(
  2. statSet: PlanetaryBodiesMap[K],
  3. statName: keyof typeof statSet,
  4. ): number {
  5. return statSet[statName]; // error
  6. }

上。
这对我来说感觉像是#32365

baubqpgj

baubqpgj2#

3-year-old #32365 看起来相关,可能与潜在原因有关;修复这个问题也有可能解决这个。然而,我认为这个问题也可能在不修复那个问题的情况下被解决,方法是专注于消除条件内的“未定义”,从其类型中消除未定义。

jgzswidk

jgzswidk3#

Not seeing how, given that the following still fails:

  1. const getStatFromSet = function <K extends keyof PlanetaryBodiesMap>(
  2. statSet: Partial<PlanetaryBodiesMap[K]>,
  3. statName: keyof typeof statSet,
  4. ): number | undefined {
  5. return statSet[statName]; // error!
  6. }

If statSet[statName] cannot be seen as assignable to number | undefined , then eliminating undefined wouldn't help it being seen as assignable to number . I really am not processing how undefined is the issue here. Can you articulate how that would work? Or maybe you have some more motivating code example handy?

gdrx4gfi

gdrx4gfi4#

我在这个错误报告中试图强调的核心预期行为是:

  1. const x = //any process at all by which x gets a value and type, even if that's a buggy process
  2. if(typeof x !== 'undefined') {
  3. //**Regardless of how x was defined,**
  4. //whether using the assignment example from the OP here or any other,
  5. //any attempt to use it here should NOT result in an error that "x could be `undefined`".
  6. }

如果修复 #32365 可以让 statSet[statName] 在给定的示例上下文中被分配给类型 number | undefined ,那么这似乎是一个进步,但对我来说,这并不一定能消除核心意外行为(这也可能源于其他原因),并且也不清楚确保核心预期行为是否一定需要修复 #32365 。(可能修复我的问题需要同时修复两者,但这并不意味着这个应该作为 #32365 的重复关闭。)

cqoc49vn

cqoc49vn5#

是的,我认为在这里typeof缩小有些奇怪。稍微简化一下,从#32365中提取:
Playground

  1. interface Foo {
  2. a: { b: string }
  3. }
  4. function f<K extends keyof Foo>(f: Partial<Foo[K]>, k: keyof Foo[K]) {
  5. const x = f[k];
  6. if (typeof x !== "undefined") {
  7. let _: {} = x; // Should error because of possible `null`, but errors because of `undefined`
  8. }
  9. const y = f[k];
  10. if (y != undefined) {
  11. let _: {} = y;
  12. }
  13. x; // This is weird too
  14. y;
  15. }
展开查看全部
bksxznpy

bksxznpy6#

以下是我怀疑的另一个相同问题的例子,尽管如果其他人认为这不是重复的话,可以将其拆分为单独的问题:

  1. //In the motivating example, these types are way more complex than simple constant strings.
  2. interface AnimalSounds { Cat: 'meow'; Dog: 'woof'; Duck: 'quack';}
  3. type AnimalSound = AnimalSounds[keyof AnimalSounds]; //"meow" | "woof" | "quack"
  4. export const callerFn = function<A extends keyof AnimalSounds> (
  5. animalTypeName: A,
  6. sonicEnvironment: Partial<AnimalSounds> //cannot be restricted to require A here
  7. ) {
  8. const sound = sonicEnvironment[animalTypeName];
  9. if (typeof sound === 'undefined') {
  10. throw new Error('Could not find sound in environment.');
  11. }
  12. //At/after this line, 'sound' should be narrowed to EXCLUDE the 'undefined' type possibility.
  13. //i.e. sound should be Exclude<Partial<AnimalSounds>[A], undefined>, but the exclusion isn't working.
  14. //You can move the error and DRY up casting by using a narrowing const, but it's still an error:
  15. const soundNarrowed: Exclude<Partial<AnimalSounds>[A], undefined> = sound; //Type 'undefined' not assignable
  16. //Error in first parameter of next line:
  17. //Argument of type 'Partial<AnimalSounds>[A]' is not assignable to parameter of type 'AnimalSounds[A]'.
  18. //Type '"meow" | undefined' is not assignable to type '"meow"'.
  19. //Type 'undefined' is not assignable to type '"meow"'.ts(2345)
  20. calledFn<AnimalSounds[A]>(sound, toUpperCaseTyped(sound));
  21. //In the line below but NOT above, the 'undefined' possibility is correctly narrowed out:
  22. calledFnNoGenericOneParam(sound);
  23. }
  24. const calledFn = function<S extends AnimalSound>(sound: S, loudSound: Uppercase<S>) {/*...*/};
  25. //Dropping the generic doesn't work in context,
  26. //because multiple types in the function are derived from the generic type parameter.
  27. const calledFnNoGenericOneParam = function(sound: AnimalSound) {/*...*/};
  28. //Bonus issue(#44268): the cast in the return statement of the next line should be automatic & unnecessary:
  29. const toUpperCaseTyped = function<S extends string>(strIn: S) {return strIn.toUpperCase() as Uppercase<S>;};

在这个例子中,行为在4.2.3和4.3.5之间发生了变化,因此 calledFnNoGenericOneParam 的参数正确地被约束为排除 undefined ;不幸的是,这个修复并没有解决对 calledFn 调用的缩小问题,但它的PR可能会提供有用的信息。

展开查看全部

相关问题