检查打字稿中的数组类型

时间:2019-12-27 11:31:37

标签: typescript abstract-syntax-tree

Typescript显然可以很好地与AST一起使用。如果我检查x.type == "Abc",则打字稿的下一行知道x的类型为Abc。请注意,我使用它来以JSDOC格式对带有类型注释的JS文件进行类型检查。但是我想纯打字稿文件也一样

但是,我在测试一系列元素时遇到问题。

第一个示例有效,因为我遍历了每个元素,并且仅在检查类型时才推送它。因此,打字稿正确地将类型Property[]推导为函数的返回类型

/**
 * @param {ObjectExpression} objectAst
 */
function getPropertiesList(objectAst) {
    let propertiesList = []
    for (let p of objectAst.value.properties) {
        if (p.type == "Property")
            propertiesList.push(p)
        else
            throw new Error("Properties field has elements that aren't of type `Property`")
    }
    return propertiesList
}

但是,这个示例在功能上是相同的(但是在我看来更清晰,并且不会创建新的数组)不起作用。推断的类型为(SpreadElement|Property|ObjectMethod|ObjectProperty|SpreadProperty)[]。因此它不会考虑支票。

/**
 * @param {ObjectExpression} objectAst
 */
function getPropertiesList(objectAst) {
    let propertiesList = objectAst.value.properties
    if (!propertiesList.every(p => p.type == "Property"))
        throw new Error("Properties field has elements that aren't of type `Property`")
    return propertiesList
}

任何人都可以对打字稿如何处理一种情况与另一种情况有所了解吗?

Typescript可以使用检查使特定类型更具体(如第一个示例所示),但是显然不能对数组执行这些检查。

这可以视为打字稿编译器中的错误吗(因为这两段代码显然应该返回相同的类型)?

编辑:为了提供上下文和可测试性,我从recast导入了类型,如下所示:

/**
 * @typedef { import('recast').types.namedTypes.ObjectExpression} ObjectExpression 
 * @typedef { import('recast').types.namedTypes.Property} Property 
*/

1 个答案:

答案 0 :(得分:3)

问题在于,编译器不了解array.every()可以用作array类型的type guard。此外,也不能将回调函数p => p.type == "Property"推断为p类型的类型保护。编译器非常擅长分析内联代码以缩小类型,但当控制流传递给函数时,它几乎gives up (see microsoft/TypeScript#9998)

如果您希望TypeScript理解调用boolean返回函数充当类型保护,则需要手动将这些函数注释为user-defined type guard。可以将foo(x: T): boolean之类的函数更改为foo(x: T): x is U,其中“ x is U"类型谓词。如果foo(val)返回true,则编译器会将val缩小为U,否则不会。

对于回调,这需要将p => p.type == "Property"更改为(p): p is Property => type == "Property"。对于array.every(),该方法是Array<T>接口内的declared in the standard library。幸运的是,您可以使用merge in extra method overloads to interfaces(请注意,如果您的代码在模块中,则可能必须专门使用global augmentation来添加到Array<T>之类的全局接口中)。看起来像这样:

interface Array<T> {
    every<U extends T>(cb: (x: T) => x is U): this is Array<U>;
}

现在,编译器将看到,如果回调是类型保护功能,则every()本身将充当类型保护。您的代码将按预期工作:

function getPropertiesList(objectAst: ObjectAST): Property[] {
    let propertiesList = objectAst.value.properties
    if (!propertiesList.every((p): p is Property => p.type == "Property"))
        throw new Error("Properties field has elements that aren't of type `Property`")
    return propertiesList
}

但是,对于every()的单次使用而言,这可能会产生太多工作。实际上,您可能应该只使用type assertion并继续前进。类型断言适用于您比编译器更了解类型的情况。这是一个合理的使用时间:

function getPropertiesListAssert(objectAst: ObjectAST): Property[] {
    let propertiesList = objectAst.value.properties
    if (!propertiesList.every(p => p.type == "Property"))
        throw new Error("Properties field has elements that aren't of type `Property`")
    return propertiesList as Property[]; // assert
}

好的,希望能有所帮助;祝你好运!

Playground Link to code