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

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

Bug报告

🔎 搜索词

undefined narrowed 2322

🕗 版本与回归信息

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

⏯ Playground链接

带有相关代码的Playground链接

💻 代码

type StarStats = {
    mass: number;
    surfaceTemperature: number;
    planets: number;
}
type PlanetStats = {
    mass: number;
    moons: number;
    orbitalPeriod: number;
    orbitalInclination: number;
}
interface PlanetaryBodiesMap {
    'Planet' : PlanetStats;
    'Star' : StarStats;
}
const getStatFromSet = function<
    TN extends keyof PlanetaryBodiesMap,
>(
    statSet : Partial<PlanetaryBodiesMap[TN]>,
    statName : keyof typeof statSet,
) : number {
    const potentialResult = statSet[statName];
    //Adding redundant `&& potentialResult !== undefined` to the conditional,
    //the error message changes between v4.7.4 and v4.8.0-beta
    if(typeof potentialResult !== 'undefined') {
        potentialResult;
        //Hover above to see: Partial<PlanetaryBodiesMap[TN]>[keyof PlanetaryBodiesMap[TN]]
        //Error ts(2322) below:  Type 'PlanetaryBodiesMap[TN][keyof PlanetaryBodiesMap[TN]] | undefined' is not assignable to type 'number'.
        //Where does that extra `| undefined` come from after having narrowed the type of the `const`?
        return potentialResult;
    } else {
        return 0;
    }
}

🙁 实际行为

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

🙂 预期行为

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

hgtggwj01#

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

const getStatFromSet = function <K extends keyof PlanetaryBodiesMap>(
    statSet: PlanetaryBodiesMap[K],
    statName: keyof typeof statSet,
): number {
    return statSet[statName]; // error
}

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

baubqpgj

baubqpgj2#

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

jgzswidk

jgzswidk3#

Not seeing how, given that the following still fails:

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

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#

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

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

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

cqoc49vn

cqoc49vn5#

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

interface Foo {
    a: { b: string }
}

function f<K extends keyof Foo>(f: Partial<Foo[K]>, k: keyof Foo[K]) {
    const x = f[k];
    if (typeof x !== "undefined") {
        let _: {} = x; // Should error because of possible `null`, but errors because of `undefined`
    }

    const y = f[k];
    if (y != undefined) {
        let _: {} = y;
    }

    x; // This is weird too
    y;
}
bksxznpy

bksxznpy6#

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

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

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

相关问题