TypeScript 相关类型约束在返回类型推断下失效

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

TypeScript 版本: 3.5.1
搜索词:

返回类型,泛型,约束,可分配,相关类型

代码

type XStr = {x:string};
type XNum = {x:number};
type U = XStr|XNum;
type Args = { str : XStr, num : XNum };

declare function foo<
    ReturnT extends U,
    ValueT extends ReturnT["x"]
> (
    f : (args : Args) => ReturnT,
    value : ValueT
) : void;

/*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
foo<XStr, string|number>(
    (args:Args) => args.str,
    ""
);
//Inferred type, foo<XStr, string | number>
foo(
    args => args.str,
    //Expected: Error
    //Actual: OK
    "" as string|number
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
    "" as string|number
);

/////

/*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
foo<XStr, 1>(
    (args:Args) => args.str,
    1
);
//Inferred type, foo<XStr, 1>
foo(
    args => args.str,
    //Expected: Error
    //Actual: OK
    1
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
    1
);

预期行为:

我只是称之为 相关类型 ,因为它让我想起了SQL中的相关子查询。

  1. ValueT 的约束类型取决于 ReturnT 的类型。
  2. f 没有参数,或者所有参数都 显式 标注时,
    ValueT 被正确推断。
  3. f 显式标注的参数时,
    ValueT 被错误地推断。
  4. 尝试显式设置无效的类型参数将按预期出错。
  • foo<XStr, string|number> 不被允许
  • foo<XStr, 1> 不被允许
    实际行为:
  • foo<XStr, string|number> 在推断下被允许
  • foo<XStr, 1> 在推断下被允许
    Playground 链接:

Playground

相关问题:

32540 (评论)

#29133
一个不同且更复杂的示例,

14829 (评论)

[编辑]
有人能想出一个更好的名字吗?
我正在重写我的类型安全的SQL构建器库,它依赖于泛型函数的返回类型被正确推断。但似乎返回类型推断在很多意想不到的地方会出错。
匿名回调函数经常用于构建 WHEREORDER BYGROUP BYHAVINGJOIN 等子句。
由于泛型函数的返回类型推断不可靠,基本上对我来说是一个障碍=()

lxkprmvk

lxkprmvk1#

从上述代码片段的行为以及由工具提示显示的推断类型来看,感觉像是有人忘记复制粘贴一些类型推断逻辑或其他东西。
例如,Inferred type, foo<XStr, 1> 代码片段的类型被推断为 foo<XStr, 1>,但它的行为却像 foo<U, 1>

tyg4sfes

tyg4sfes2#

!@#$ YES!
我终于找到了一个解决方法!

type XStr = {x:string};
type XNum = {x:number};
type U = XStr|XNum;
type Args = { str : XStr, num : XNum };

declare function foo<
    ReturnT extends U,
    ValueT extends ReturnT["x"]
> (
    ...f : (
        (
            ReturnT extends U ?
            [((args : Args) => ReturnT), ValueT] :
            never
        )
    )
) : ValueT;

/*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
foo<XStr, string|number>(
    (args:Args) => args.str,
    ""
);
//Inferred type, foo<XStr, string>
foo(
    args => args.str,
    /*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
    "" as string|number
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
    "" as string|number
);

/////

/*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
foo<XStr, 1>(
    (args:Args) => args.str,
    1
);
//Inferred type, foo<XStr, string>
foo(
    args => args.str,
    /*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
    1
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
    1
);

//Return type is "hello" as expected
foo(
    args => args.str,
    "hello"
);

//Return type is 42 as expected
foo(
    args => args.num,
    42
);

Playground
这里的魔法是,

...f : (
        (
            ReturnT extends U ?
            [((args : Args) => ReturnT), ValueT] :
            never
        )
    )

它将 ReturnT 转换为元组(2元组,在这个例子中),然后使用剩余参数。
[编辑]
在花了周六的大部分时间思考解决方法后,我突然在随机回想起这里的评论后想到了这个主意。

32596 (评论)

我曾经提出过使用元组和剩余参数作为解决这个问题的可能方法(在该问题中有另一种更好的表达方式)。
[编辑]
等等...我刚刚意识到这个解决方法没有使用 ValueT 。我一定是太累了才会犯这种错误。
Brb. 要修好它。(或者尝试)。
我的用例需要 ReturnTValueT ,并在返回类型中对其进行操作。
[编辑]
修复了使用 ValueT 的问题。
[编辑]
算了吧。我试图将其适应到实际的用例中,但这个解决方法崩溃了。
首先,我想让我的 ReturnT 有选择成为联合类型的权利。
所以,我不能让条件类型执行分布。
如果它不分布,我们就会回到推理问题的第一步。
Playground

ldioqlga

ldioqlga3#

这更接近我的个人用例,

type XStr = {x:string};
type XNum = {x:number};
type U = XStr|XNum;
type Args = { str : XStr, num : XNum };

declare function foo<
    ReturnT extends U,
    ValueT extends ReturnT["x"]
> (
    ...f : (
        (
            //Uses `& ReturnT["x"]` to make this work
            [((args : Args) => ReturnT), ValueT & ReturnT["x"]]
        )
    )
) : ValueT;

//OK!
//Inferred type, foo<XStr | XNum, string | number>
foo(
    args => Math.random() > 0.5 ? args.str : args.num,
    "" as string|number
);

/*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
foo<XStr, string|number>(
    (args:Args) => args.str,
    ""
);
//Inferred type, foo<XStr, string|number>
foo(
    args => args.str,
    /*
Expected: Error
Actual: OK
*/
    "" as string|number
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'.
*/
    "" as string|number
);

/////

/*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
foo<XStr, 1>(
    (args:Args) => args.str,
    1
);
//Inferred type, foo<XStr, 1>
foo(
    args => args.str,
    /*
Expected: Error
Actual: OK
*/
    1
);
//Inferred type, foo<XStr, string>
foo(
    //Added explicit type annotation to function params
    (args:Args) => args.str,
    /*
Error as expected.

Type '1' does not satisfy the constraint 'string'.
*/
    1
);

//Return type is "hello" as expected
foo(
    args => args.str,
    "hello"
);

//Return type is 42 as expected
foo(
    args => args.num,
    42
);

Playground
我需要允许返回一个联合类型并相应地处理它。
我的实际用例需要使用 UnionToIntersection<> ,尽管如此。

0wi1tuuw

0wi1tuuw4#

看起来使用UnionToIntersection<>会破坏它,
NoInfer<>也没有帮助。

type UnionToIntersection<U> = (
    (
        U extends any ? (k: U) => void : never
    ) extends (
        (k: infer I) => void
    ) ? I : never
);

type XStr = {x:{propStr:string}};
type XNum = {x:{propNum:number}};
type U = XStr|XNum;
type Args = { str : XStr, num : XNum };

declare function foo<
    ReturnT extends U
> (
    ...f : (
        (
            [
                ((args : Args) => ReturnT),
                UnionToIntersection<
                    ReturnT["x"]
                >
            ]
        )
    )
) : void;

foo(
    args => Math.random() > 0.5 ? args.str : args.num,
    //OK!
    //Error: Property 'propNum' is missing
    {
        propStr : "hello",
    }
);
foo(
    args => Math.random() > 0.5 ? args.str : args.num,
    //OK!
    //Error: Property 'propStr' is missing
    {
        propNum : 42,
    }
);
foo(
    args => Math.random() > 0.5 ? args.str : args.num,
    //OK!
    //No error
    {
        propStr : "hello",
        propNum : 42,
    }
);

//Expected: foo<XStr>
//Actual  : foo<U>
foo(
    args => args.str,
    //Expected: OK
    //Actual  : Property 'propNum' is missing
    { propStr : "hello" }
);

type NoInfer<T> = [T][T extends any ? 0 : never];
declare function foo2<
    ReturnT extends U
> (
    ...f : (
        (
            [
                ((args : Args) => ReturnT),
                UnionToIntersection<
                    //NoInfer doesn't seem to work here
                    NoInfer<ReturnT>["x"]
                >
            ]
        )
    )
) : void;
//Expected: foo2<XStr>
//Actual  : foo2<U>
foo2(
    args => args.str,
    //Expected: OK
    //Actual  : Property 'propNum' is missing
    { propStr : "hello" }
);

Playground
更好地重现了UnionToIntersection<>如何破坏这个解决方法,
Playground

yxyvkwin

yxyvkwin5#

我忽略了除了OP之外的长篇大论。

为什么函数定义只是这个?

$x_{1a0b1}^{x}$

或者,似乎你需要一个非推断类型的参数使用操作符? $x_{1e0f1}^{x}$

zujrkrfu

zujrkrfu6#

我需要 arg 可能成为 ReturnT["x"] 的子类型。
在我的实际情况中,它用于返回类型(在我的 OP 中,返回类型为 void 以简化)。
目前,所有尝试使用非推断类型的参数用法的解决方法都在这个用例中失效了。
该函数表示 SQL 中的一列。返回类型是该列的类型。
ValueT 表示该列的可能值。
假设我们正在构建由库提供的查询,

SELECT
  myTable.myColumn
FROM
  myTable

假设该列的类型为 null|number
如果我们添加这个 WHERE 子句,

SELECT
  myTable.myColumn
FROM
  myTable
WHERE
  myTable.myColumn <=> 1

那么我们可以在不执行查询的情况下静态缩小查询中 myColumn 的类型为 1

相关问题