简体   繁体   中英

Using "extends" on inferred mapped types in Typescript Generics

Here's my code and the related playground :

type SomeType<T> = {
  [K in keyof T]?: K extends 'u'
    ? string
    : T[K] extends object
      ? SomeType<T[K]>
      : object
}

function someType<T>(t: SomeType<T>) {
  return t;
}

type BLA = SomeType<{
  u: 1,
  b: {
    u: 1,
  },
}>;

const blaBLA: BLA = {
  u: 1, // <-- correct error msg: Type 'number' is not assignable to type 'string'.
  b: {
    u: 1, // <-- correct error msg: Type 'number' is not assignable to type 'string'.
  },
};

const bla = someType({
  u: 1, // <-- correct error msg: Type 'number' is not assignable to type 'string'.
  b: {
    u: 1, // <-- expected: error message from above
          // the problem here is that the value for b was resolved to 'object' instead of 'SomeType<T[K]>'
          // how can I fix this?
  },
});

I am either looking for a fix to the problem which I describe in the code above or an explaination why it doesn't work the way I think it works.

As this is purely for learning (understanding) TS, I would be okay with either outcome.

If you have a function like

function someType<T>(t: SomeType<T>) {
  return t;
}

and call it with someType(val) , then the compiler is going to try to infer the generic type parameter T given that val is of the type SomeType<T> . You're asking the compiler to essentially invert the type function SomeType<T> .

But that's not really possible for the compiler to do, especially because since SomeType<T> might be the same type as SomeType<U> even when T and U are different:

type Hmm = SomeType<{ u: 1, b: "", c: { d: "" } }>;
/* type Hmm = {
    u?: string;
    b?: object;
    c?: {d?: object}
} */

type Ugh = SomeType<{ u: true, b: 123, c: { d: false } }>;
/* type Ugh = {
    u?: string;
    b?: object;
    c?: {d?: object}
} */

Inverting type functions isn't always possible, or even when it is possible, it's not always feasible for the compiler to do it.

Luckily, this isn't really the behavior you want.


Rather, it looks like you would like SomeType<T> to act as a recursive constraint of T itself. What I mean is that when you call someType(val) , you want T to be inferred as the type of val , and then you want the compiler to check that T is also assignable to SomeType<T> .

And so let's just write that directly as a generic constraint :

function someType<T extends SomeType<T>>(t: T) {
  return t;
}

That compiles with no error, so we can proceed to test it:

const bla = someType({
  u: 1, // error!
  b: {
    u: 1, // error!
  },
});

Looks good. You get exactly the errors you expect to get.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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