TypeScript无法通过使用'is'操作符的用户定义类型保护来总是推断正确的类型,

qij5mzcb  于 6个月前  发布在  TypeScript
关注(0)|答案(4)|浏览(94)

🔎 搜索词

"typeguard", "is operator"

🕗 版本与回归信息

在typescript@5.1.3及更高版本中进行测试

⏯ 代码沙盒链接

  • 无响应*

💻 实际行为

TypeScript无法推断出'this'的类型为'Test<Exclude<kind, kind.c>>',尽管存在类型保护。在'do'方法中,TypeScript抛出一个TSError,指出'this.value':对象可能是'undefined'。
如果我们明确将'this'转换为'Test<Exclude<kind, kind.c>>',我们可以访问'value'属性,它按预期工作。然而,不知何故,类型保护没有产生相同的效果。

🙂 预期行为

由于在'this.value.id'行周围存在类型保护,TypeScript应该将'this'处理为'Test<Exclude<kind, kind.c>>'类型。至少,这是我对类型保护的理解。

关于问题的附加信息

  • 无响应*
xmjla07d

xmjla07d1#

在这里我们无法做太多事情——即使在类型保护之后,value 的类型仍然通用,并且急切地解析一个基于类型参数的条件的类型是不安全的。这是错误的;你观察到的错误与下面的错误相同
然而,在寻找解决方法时,我发现了另一个错误

enum kind {
    a = 'a',
    b = 'b',
    c = 'c'
}

interface KindKind {
    [kind.a]: ID;
    [kind.b]: ID;
    [kind.c]: undefined;
}

type NotC = Exclude<kind, kind.c>;

type ID = {
    id: number
}

class Test<T extends kind> {
    value: KindKind[T];
    type: T

    constructor(
        type: T,
        value: KindKind[T]
    ) {
        this.type = type
        this.value = value
    }

    hasID(): this is Test<NotC> {
        return this.type !== kind.c
    }

    doSomething() {
        const self: Test<kind> = this;
        if (self.hasID()) {
            // OK, but removing type annotation -> error
            const x: number = self.value.id;
            return x;
        }
        return 0;
    }
}
u5rb5r59

u5rb5r592#

问题在于,这种缩小范围的方法没有返回类型注解(我们也可以通过注解返回类型来"修复"这个问题) - 因此它必须推断返回类型。
为了做到这一点,它开始计算 Test<kind>.doSomething 的返回类型,进入控制流逻辑,并最终根据以下内容选择缩小的类型:

// t: Test<kind>
// c: Test<NotC>
isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType

出于某种原因,当它出错时,第一个检查返回 true ,从而选择了 t - 而当它没有出错时,是第二个检查返回了 true 。实际上,对于那些在“损坏”的情况下的所有类型,所有这些检查都返回了 true
通过进一步检查,我们可以了解到它没有返回 true ,因为它对结果很有信心。实际上的结果是 1 (也称为 Ternary.Unknown )。我们永远不会知道这个,因为那被“转换”为 boolean - 但无论如何,如果双向检查都返回 Ternany.Unknown ,我们如何确定在这里应该选择哪一个?另一方面,如果整个事情都返回 neverType ,那么紧跟其后的代码可能会创建这两种类型的交集,这可能有助于解决这个问题:TS Playground

knsnq2tg

knsnq2tg3#

一个稍微小一点但更容易理解的这个bug(或其他bug)的例子:

type A<T> = {
    foo: T
} & (
    T extends number ? {
        bar: string
    } : unknown
)

const test = <T,>(args: A<T>) => {
    if(typeof args.foo === "number") {
        // return args.bar // ts(2339)
        return (args as A<typeof args.foo>).bar // compiles successfully
    }
    return args.foo
}

我正在努力弄清楚该怎么做。有什么建议吗?

8fq7wneg

8fq7wneg4#

这是一个不同的问题 - 它不使用任何用户定义的类型保护。
TypeScript 今天还无法进行这种推理。通过检查 typeof args.foo === "number",你可以确保编译器 args.foo 具有 T & number 类型,但这并不会“传播”到父类型(args 的类型)。我现在称之为设计限制。

相关问题