简体   繁体   English

泛型类型的递归推断

[英]Recursive infer with generic types

The setup: 设置:

export type SchemaOne<T> =
  | Entity<T>
  | SchemaObjectOne<T>;
export interface SchemaObjectOne<T> {
  [key: string]: SchemaOne<T>;
}
export type SchemaOf<T> = T extends SchemaOne<infer R> ? R : never;

const sch: Entity<ArticleResource> = ArticleResource.getEntitySchema();
const a = { a: sch  };
const aa: SchemaOne<ArticleResource> = a;
// works!
type Z = SchemaOf<typeof a>;
// Z is ArticleResource as expected

const b = { a: { b: sch }  };
type ZZ = SchemaOf<typeof b>;
// ZZ is unknown - SADBEAR

So I have my recursive definition matching correctly (methodology stolen from https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540 ). 所以我的递归定义正确匹配(从https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540盗取的方法)。 (hence the works after aa definition). (因此,作品在aa定义之后)。 However, now I want to be able to infer the type even without making it more general. 但是,现在我希望能够推断出该类型,即使不使其更通用。 (getting bb's type). (获得bb的类型)。

So for some reason this works only one level deep. 因此由于某种原因,此功能只能深入一个级别。 Is this a limitation of typescript? 这是打字稿的限制吗? Is there some sort of recursion with infer I can use to actually find the generic type? 我可以使用某种递归来实际找到泛型吗?

I don't know how the compiler actually goes about inferring conditional types. 我不知道编译器实际上如何推断条件类型。 I'm not surprised that there would be a limit to how many type instantiations it does before it gives up with unknown (or any ). 我不惊讶它会放弃unknown (或any )之前进行多少次类型实例化。 Since infer R by itself doesn't do it for you, I'd suggest coming up with your own way of inspecting a type to determine what, if any, R should be returned for a given type. 由于infer R本身并不能为您完成此操作,因此建议您使用自己的方式检查类型以确定给定类型应返回什么R (如果有)。

First of all I'm defining these so that it compiles... your own types are likely different, but they're not in the question statement (so I get to pick them, right? ... right?) 首先,我要定义它们以便编译...您自己的类型可能有所不同,但是它们不在问题陈述中(所以我可以选择它们,对吗?……对吗?)

// Making these up

interface Entity<T> {
  e: T;
}

class ArticleResource {
  static getEntitySchema<T>(): Entity<T> {
    return null!;
  }
  ar = "ArticleResource";
}

Here's one possible implementation of SchemaOf<T> : 这是SchemaOf<T>的一种可能的实现:

type _SchemaOf<T> = T extends Entity<infer R>
  ? R
  : T extends object ? { [K in keyof T]: _SchemaOf<T[K]> }[keyof T] : never;

(I'm naming it _SchemaOf because I will use it to build my final SchemaOf ). (我将其命名为_SchemaOf因为我将使用它来构建最终的SchemaOf )。 Here, if T is just Entity<R> for some R , then return R . 在这里,如果T对于某些R只是Entity<R> ,则返回R That's should work well. 那应该很好。 Otherwise, check if T is an object . 否则,检查T是否为object If not, then just return never . 如果不是,则never返回。 (This allows us to short-circuit to never if we're checking against a primitive type). (如果我们检查原始类型,这将使​​我们never会短路)。 Otherwise, calculate the union of _SchemaOf<T[K]> for every key K of T . 否则,计算的联合_SchemaOf<T[K]>对于每一个键KT (Possible warning: This type doesn't seem to count as a circular conditional type, and does not result in a compiler error, but I'm not sure if it's exactly legal. See this GitHub issue for a discussion of circular conditional types.) (可能的警告:此类型似乎不算作循环条件类型,并且不会导致编译器错误,但是我不确定它是否完全合法。有关循环条件类型的讨论,请参见GitHub问题 。 )

So, _SchemaOf<Entity<A>> should just be A , and _SchemaOf<{a: X, b: Y, c: Z}> should be equivalent to _SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z> 因此, _SchemaOf<Entity<A>>应该只是A ,而_SchemaOf<{a: X, b: Y, c: Z}>应该等效于_SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z> _SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z> _SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z> . _SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z> That will end up recursing down through the object types and pulling out all Entity types and getting a union of them. 最终将递归遍历对象类型,并拉出所有Entity类型并获得它们的并集。

That's nearly what you want, but it does return some weird answers when you pass in something that's not a SchemaOne<T> for any T , like _SchemaOf<{a: Entity<A>, b: string}> . 这几乎是您想要的,但是当您为T传递的不是SchemaOne<T>东西时,确实会返回一些奇怪的答案,例如_SchemaOf<{a: Entity<A>, b: string}> That will give you just A , even though {a: Entity<A>, b: string} is not a SchemaOne<A> . 即使{a: Entity<A>, b: string}不是SchemaOne<A> ,这也只会给您A

So we do a sanity check like this: 因此,我们进行如下检查:

type SchemaOf<T> = T extends SchemaOne<_SchemaOf<T>> ? _SchemaOf<T> : never;

We check the result of _SchemaOf<T> to make sure that SchemaOne<_SchemaOf<T>> matches T . 我们检查_SchemaOf<T>的结果,以确保SchemaOne<_SchemaOf<T>>匹配T If so, great. 如果是这样,那太好了。 If not, we get never . 如果没有,我们never

Let's test it out: 让我们测试一下:

type Z = SchemaOf<typeof a>;
// Z is ArticleResource as expected

const b = { a: { b: sch } };
type ZZ = SchemaOf<typeof b>;
// ZZ is ArticleResource too

That works for you. 对你有用。 And then: 接着:

const c = { a: { b: sch, c: "string" } };
type _C = _SchemaOf<typeof c>; // ArticleResource
type C = SchemaOf<typeof c>; // never

This rejects c , as I think you'd want. 我认为这会拒绝c And, to see the union stuff: 并且,查看工会的东西:

const d = { a: sch, b: null! as Entity<string> };
type D = SchemaOf<typeof d>; // string | ArticleResource
const dd: SchemaOne<D> = d; // okay

Okay, hope that helps you proceed. 好的,希望对您有所帮助。 Good luck! 祝好运!

Link to code 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM