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

Time:11-22

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]
  : '';

CodePudding user response:

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.

  • Related