简体   繁体   中英

Typescript not understanding that T extends interface in a specific complex custom type

So I'm using the Paths type I got from here , full disclosure, I don't fully understand how it's built and it will probably assist me in understanding the problem, I'll have my implementation of it at the bottom. In summary if I have the interface:

interface Person {
  name: string,
  contacts: {
    email: string,
    phone: string,
  }
}

Then Paths<Person> would result in union of literal types:

'name' | 'contacts' | 'contacts.email' | 'contacts.phone'

But when trying to use it with a generic that extends some type, I'm running into weird troubles.

function foo<T extends Person> {
  let personParamGood: Paths<Person>;
  personParamGood = 'name' // great, works.
  
  let personParamBad: Paths<T>
  personParamBad = 'name' // typescript screams at me
  
}

The type error I get says

error TS2322: Type '"name"' is not assignable to type 'T extends object ? { [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, T[K] extends object ? { [K in keyof T[K]]-?: K extends string | number ? `${K}` | Join<K, never> : never; }[keyof T[K]] : ""> : never; }[keyof T] : ""'.

I don't get why, shouldn't T basically be Person ?

It does work on a simple type:

function foo<T extends Person> {
  let personParamGood: keyof Person;
  personParamGood = 'name' // great, works.
  
  let personParamGoodToo: keyof T
  personParamGoodToo = 'name' // great, works.
  
}

My implementation of Paths :

type Prev = [never, 0, 1, 2, 3, 5, 6, 7, 8, ...0[]];

type Join<K, P> = K extends string | number
  ? P extends string | number
    ? `${K}${'' extends P ? '' : '.'}${P}`
    : never
  : never;

export type Paths<T, D extends number = 10> = [D] extends [never]
  ? never
  : T extends object
  ? {
      [K in keyof T]-?: K extends string | number
        ? `${K}` | Join<K, Paths<T[K], Prev[D]>>
        : never;
    }[keyof T]
  : '';

That's because T will never resolve inside the function implementation as it can be anything. You added an extends Person constraint on it but it can still be anything that extends Person . The actual T type is only known outside the function.

Notice how this code works when you hover the person variable:

interface Person {
  name: string,
  contacts: {
    email: string,
    phone: string,
  }
}

interface ExtendedPerson extends Person {
  address: string;
}

declare function foo<T extends Person>(): Paths<T>;

const person = foo<ExtendedPerson>();

But you'll never be able to make T (and thus Paths<T> ) resolve to an actual type inside the function implementation, that's how generics work in TypeScript and any other language.

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