Typescript 仅在重载签名中获取错误2589

Typescript 仅在重载签名中获取错误2589,typescript,Typescript,我有一个递归泛型类型定义,当它用作重载时会出错,但当它未重载或未在另一个泛型中使用时不会出错 type JTDDataDef<S, D extends Record<string, unknown>> = | (// ref S extends { ref: string } ? JTDDataDef<D[S["ref"]], D> : // type S extend

我有一个递归泛型类型定义,当它用作重载时会出错,但当它未重载或未在另一个泛型中使用时不会出错

type JTDDataDef<S, D extends Record<string, unknown>> =
    | (// ref
        S extends { ref: string }
        ? JTDDataDef<D[S["ref"]], D>
        : // type
        S extends { type: "string" }
        ? string
        : // properties
        S extends {
            properties: Record<string, unknown>
        }
        ? { -readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D> }
        :
        never)
    | (S extends { nullable: true } ? null : never)

type JTDDataType<S> = S extends { definitions: Record<string, unknown> }
    ? JTDDataDef<S, S["definitions"]>
    : JTDDataDef<S, Record<string, never>>

interface ValidateFunction<T> {
    (data: unknown): data is T
}

function compile1<S>(schema: S): ValidateFunction<JTDDataType<S>>
function compile1<T>(schema: unknown): ValidateFunction<T> {
    return (() => true) as unknown as ValidateFunction<T>;
}

function compile2<S>(schema: S): ValidateFunction<JTDDataType<S>> {
    return (() => true) as unknown as ValidateFunction<JTDDataType<S>>;
}

function compile3<S>(schema: S): (data: unknown) => data is JTDDataType<S>
function compile3<T>(schema: unknown): (data: unknown) => data is T {
    return (() => true) as unknown as (data: unknown) => data is T;
}
但是,
compile2
compile3
不会引发任何错误

我的猜测是,由于使用
ref
进行递归,该类型可能是无限的。但是,typescript实际上似乎可以很好地处理该类型,例如,如果我定义了一个无限递归类型:

const llSchema = {
    definitions: {
        node: {
            properties: {
                val: { type: "string" },
                next: {
                    ref: "node",
                    nullable: true
                }
            }
        }
    },
    ref: "node",
    nullable: true,
} as const;

const isLinkedList = compile1(llSchema);
const list: unknown = null;

type LinkedList = { val: string; next: LinkedList; } | null;

function getNextVal(data: LinkedList): string | null {
    return data && data.next && data.next.val;
}

if (isLinkedList(list)) {
    const val = list && list.val;
    const nextVal = getNextVal(list);
}
typescript实际上可以使用它,并且可以对它进行适当的类型检查,即使对于引发错误的重载函数也是如此。我想了解它为什么会抛出错误,特别是对于重载签名,以及除了在这种情况下看起来像重锤的
/@ts expect error
之外,是否还有其他方法不抛出错误


[]

请记住,重载是函数交叉点

修复
compile1

类型jtdatadef=
|(//参考
S扩展了{ref:string}
?jtdatadef
://类型
S扩展了{type:“string”}
一串
://属性
S延伸{
属性:记录
}
?{-readonly[K in keyof S[“properties”]]-?:jtdatadef}
:
(从未)
|(S扩展了{nullable:true}?null:never)
类型jtdatatype=S扩展了{定义:记录}
? jtdatadef
:jtdatadef
接口验证函数{
(数据:未知):数据为T
}
类型O=ValidateFunction
类型重载=
&((架构:jtdatatype)=>ValidateFunction)
&((模式:T)=>ValidateFunction)
const compile1:重载=(模式:T):ValidateFunction=>{
返回(()=>true)与ValidateFunction一样未知;
}
常量llSchema={
定义:{
节点:{
特性:{
val:{type:“string”},
下一步:{
参考:“节点”,
可为空:真
}
}
}
},
参考:“节点”,
可为空:是的,
}作为常量;
const isLinkedList=compile1(llSchema);
函数compile2(架构:S):ValidateFunction{
返回(()=>true)与ValidateFunction一样未知;
}
//这里有新的错误
函数compile3(架构:S):(数据:未知)=>数据为JTDDataType
函数compile3(模式:未知):(数据:未知)=>数据为T{
将(()=>true)作为未知返回(数据:未知)=>数据为T;
}
正如您可能已经注意到的,我没有更改compile3,但是我在这里遇到了一个错误

我认为这是因为当您出现第一个深度递归错误时,TS已经停止,并且没有检查
compile2
compile3

但这只是我的假设

让我们修复
编译3

类型重载2=
&((架构:S)=>(数据:未知)=>数据为JTDDataType)
&((架构:未知)=>(数据:未知)=>数据为T)
const compile3:重载2=(架构:未知):(数据:未知)=>数据为T=>{
将(()=>true)作为未知返回(数据:未知)=>数据为T;
}
整体解决方案:

类型jtdatadef=
|(//参考
S扩展了{ref:string}
?jtdatadef
://类型
S扩展了{type:“string”}
一串
://属性
S延伸{
属性:记录
}
?{-readonly[K in keyof S[“properties”]]-?:jtdatadef}
:
(从未)
|(S扩展了{nullable:true}?null:never)
类型jtdatatype=S扩展了{定义:记录}
? jtdatadef
:jtdatadef
接口验证函数{
(数据:未知):数据为T
}
类型重载=
&((架构:jtdatatype)=>ValidateFunction)
&((模式:T)=>ValidateFunction)
const compile1:重载=(模式:T):ValidateFunction=>{
返回(()=>true)与ValidateFunction一样未知;
}
常量llSchema={
定义:{
节点:{
特性:{
val:{type:“string”},
下一步:{
参考:“节点”,
可为空:真
}
}
}
},
参考:“节点”,
可为空:是的,
}作为常量;
const isLinkedList=compile1(llSchema);
函数compile2(架构:S):ValidateFunction{
返回(()=>true)与ValidateFunction一样未知;
}
类型重载2=
&((架构:S)=>(数据:未知)=>数据为JTDDataType)
&((架构:未知)=>(数据:未知)=>数据为T)
const compile3:重载2=(架构:未知):(数据:未知)=>数据为T=>{
将(()=>true)作为未知返回(数据:未知)=>数据为T;
}

但老实说,我不认为函数类型的定义是好的

  • 只为函数定义一个重载是不好的做法。这是不安全的

  • 你用了很多双类型铸造。这不好

  • 让我们回到你的主要问题:

    为什么它会抛出错误

    TypeScript有自己的递归限制。因此,这里的
    ValidateFunction
    TS无法确定递归的深度。这根本不可能

    ,您可以找到有关

    这不是一个“对任何人的隐性铸造”;它是一个递归保护,可以防止无限计算。如果S需要将自身与S进行比较以确定可分配性,然后S需要将自身与S进行比较,然后S需要将自身与S进行比较,然后S需要将自身与S进行比较,此时检查停止,并假设答案为“是”。事实上,这种情况在实践中确实出现了不少;如果您的模型依赖于任意深度的检查,那么实际上您要求编译器有时永久冻结对S>>>>>>的检查(这反过来将检查S>>>>)


    我只使用了一个重载,因为它突出显示了错误,实际上有几个。你是正确的,compile3没有错误,typescript只是提前放弃了。有趣的是,为第一个添加了
    ts expect error
    ,仍然阻止了第二个错误的触发。但是,我仍然感到困惑,因为你没有真正问题是什么
    const llSchema = {
        definitions: {
            node: {
                properties: {
                    val: { type: "string" },
                    next: {
                        ref: "node",
                        nullable: true
                    }
                }
            }
        },
        ref: "node",
        nullable: true,
    } as const;
    
    const isLinkedList = compile1(llSchema);
    const list: unknown = null;
    
    type LinkedList = { val: string; next: LinkedList; } | null;
    
    function getNextVal(data: LinkedList): string | null {
        return data && data.next && data.next.val;
    }
    
    if (isLinkedList(list)) {
        const val = list && list.val;
        const nextVal = getNextVal(list);
    }