打字稿中的通用约束违反不相容性

发布于 2025-01-18 03:09:40 字数 2800 浏览 4 评论 0原文

我正在尝试将标记的联合建模为数据。

首先,我创建一个 Tagged 实用程序类型,以表示与标记字段相交的任何内容:

type Tagged<T, With extends PropertyKey> = T & { _tag: With };

然后,我创建给定类型的表示形式。

class TypeRep<T = any> {
  T!: T;
  constructor(readonly x: (value: T) => void) {}
}

我们的字段表示扩展了 TypeRep,如下所示:

class FieldRep<
  Key extends PropertyKey = PropertyKey,
  Value extends TypeRep = TypeRep,
> extends TypeRep<Record<Key, Value["T"]>> {}

记录:

class RecordRep<FieldEncoders extends FieldRep[]>
  extends TypeRep<UnionToIntersection<FieldEncoders[number]["T"]>>
{}

最后,我们的标记联合类型表示:

class TaggedUnionRep<
  Tag extends PropertyKey = PropertyKey,
  FieldReps extends FieldRep[] = FieldRep[],
> extends TypeRep<Tagged<RecordRep<FieldReps>["T"], Tag>> {}

这看起来一切都很好......除了我们不能分配狭窄的 TaggedUnionRep 实例到加宽类型:/

declare const a: TaggedUnionRep<"A", []>;
const x: TaggedUnionRep<PropertyKey, FieldRep[]> = a;

当然,我们在 x 下得到以下逆变错误:

Type 'TaggedUnionRep<"A", []>' is not assignable to type 'TaggedUnionRep<PropertyKey, FieldRep<PropertyKey, TypeRep<any>>[]>'.
  Types of property 'x' are incompatible.
    Type '(value: { _tag: "A"; }) => void' is not assignable to type '(value: Tagged<Record<PropertyKey, any>, PropertyKey>) => void'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'Tagged<Record<PropertyKey, any>, PropertyKey>' is not assignable to type '{ _tag: "A"; }'.
          Types of property '_tag' are incompatible.
            Type 'PropertyKey' is not assignable to type '"A"'.
              Type 'string' is not assignable to type '"A"'.(2322)

我将非常感谢有关最佳约束方法的任何提示FieldReps(理想情况下不要通过将 Tag 扩大到 any)。

<一href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAKghgcwRAJgHhgGigdQJbAAWUEAHsBAHYoDOUACgE4D2kjoA0hCAHxQC8sKADIoAbygB9YIgBcuAsQC+AbgBQa0JCgBVSnmaUYzAJKUKjGhADGwA5 Qx9BAChglyVWlDiUQUAPxQzqTyMACUAnw+fvKUEABuEIwrZBTUdMHyeJQAZklQAEoR-FG+AYVQsQlJ6mrWADZ wNHQw4BAFEGAYAt6+fGJqULAAhKHqQ9aGNMCMAK62zIzOjBBwKIb1fiFB8XD1sxChxXzxzHgoEWJKatd1jc1Q AGJ4EPUoHV2DUFx+qZ50TFYSU43B6gLYIJAmC+ADU9gd3GkvK1IB8eij2p1oXw-ulYG0PmgOpNGOgftg4fsIA BTABEMFpAF0eP1bg0mnRiYt3p00M9XigAKKUSYoJJ0XFeflvD7U5lfSUtAm8vT2YxmCxWWz2PkvN7C0Xi6mUW YAWWARklGXSGcyeGorhp2Q94EhUKrDISvq7Ef8GCwISAfmCA8Cg9xoUNpTywBKPHjo7LGT1E505djfXiMYTXc h0FzSYTU7GeDamdhXSzxLcxeyVlBJpRpt5Qog8x7KITaQBBWnYOU8dSN5vbXPu-Se3ngsPkp56mMDnpwFRAA" rel="nofollow noreferrer">这是上面示例的复制。

谢谢你!

I'm trying to model tagged unions as data.

First I create a Tagged utility type, to represent anything intersected with the tag field:

type Tagged<T, With extends PropertyKey> = T & { _tag: With };

Then I create a representation of a given type.

class TypeRep<T = any> {
  T!: T;
  constructor(readonly x: (value: T) => void) {}
}

Our representation of fields extends the TypeRep like so:

class FieldRep<
  Key extends PropertyKey = PropertyKey,
  Value extends TypeRep = TypeRep,
> extends TypeRep<Record<Key, Value["T"]>> {}

Records:

class RecordRep<FieldEncoders extends FieldRep[]>
  extends TypeRep<UnionToIntersection<FieldEncoders[number]["T"]>>
{}

And finally, our tagged union type representation:

class TaggedUnionRep<
  Tag extends PropertyKey = PropertyKey,
  FieldReps extends FieldRep[] = FieldRep[],
> extends TypeRep<Tagged<RecordRep<FieldReps>["T"], Tag>> {}

This is all well and good it seems... except that we cannot assign a narrow TaggedUnionRep instance to the widened type :/

declare const a: TaggedUnionRep<"A", []>;
const x: TaggedUnionRep<PropertyKey, FieldRep[]> = a;

Surely enough, we get the following contravariance error beneath x:

Type 'TaggedUnionRep<"A", []>' is not assignable to type 'TaggedUnionRep<PropertyKey, FieldRep<PropertyKey, TypeRep<any>>[]>'.
  Types of property 'x' are incompatible.
    Type '(value: { _tag: "A"; }) => void' is not assignable to type '(value: Tagged<Record<PropertyKey, any>, PropertyKey>) => void'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'Tagged<Record<PropertyKey, any>, PropertyKey>' is not assignable to type '{ _tag: "A"; }'.
          Types of property '_tag' are incompatible.
            Type 'PropertyKey' is not assignable to type '"A"'.
              Type 'string' is not assignable to type '"A"'.(2322)

I'd be greatly appreciative of any tips on best approach to constraining FieldReps (ideally not through widening Tag to any).

Here is a reproduction of the example above.

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

空城仅有旧梦在 2025-01-25 03:09:40

问题是他们的属性x不匹配。

在您的示例中,ax是一个函数,该函数采用{_tag:string}之类的对象,但是xx是一个函数,该函数采用类似对象{_tag:propertyKey}。如:

// these are legal
x.x({ _tag: 'a' })
a.x({ _tag: 'a' })
x.x({ _tag: 5 })
// this is not
a.x({ _tag: 5 })

如果要分配a <代码> x ,则x的类型将允许您传递>的数字或符号_tag,在运行时可能无法胜任。

似乎没有类型安全的方法可以允许x成为任何taggedunionrep而无需更改类型的定义。 unionTo Intertersection&lt; propertyKey&gt;(即从不)出于类似的原因而发生错误,未知>未知在那里不合法。

如果您真的知道自己在做什么,并且可以完全确定这是安全的,则可以将strictfunctionTypes设置为false在tsconfig.json中以禁用此诊断。

The problem is that their properties x don't match.

In your example, a.x is a function that takes an object like { _tag: string }, but x.x is a function that takes an object like { _tag: PropertyKey }. As in:

// these are legal
x.x({ _tag: 'a' })
a.x({ _tag: 'a' })
x.x({ _tag: 5 })
// this is not
a.x({ _tag: 5 })

If you were to assign a to x, the type of x would allow you to pass a number or symbol for _tag, which may not be well-behaved at runtime.

It appears there is no type-safe way to allow x to be any TaggedUnionRep without changing the definitions of your types. UnionToIntersection<PropertyKey> (i.e. never) errors for a similar reason, and unknown isn't legal there.

If you really know what you're doing and can be completely sure this is safe, you can set strictFunctionTypes to false in your tsconfig.json to disable this diagnostic.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文