#Issue with Type Inference in Inherited Components List

21 messages · Page 1 of 1 (latest)

opaque star
#

Hey everyone,

I'm trying to implement an inheritance pattern in TypeScript.

I have this basic type:

export type Component = {
    id: number;
    name: string;
};

With the following main type:

export interface ComponentsList {
   name: string;
   components: Record<string, Component>;
}

const listA: ComponentsList = {
   name: "list-a",
   components: { a: { id: 0, name: "A" }}
}

Now i wanted to make another type, let's call it InheritedComponentsList, that i can use to define an object who inherits the components key from the parent, something like this:

const listA: ComponentsList = {
    name: "list-a",
    components: {
        a: {
            id: 0,
            name: "A"
        }
    }
}

const listB: InheritedComponentsList<typeof listA> = {
    name: "list-b",
    parent: listA,
    components: {
        // Should give me error telling me "a" is missing from listB components
    }
}

I thought i could do something like this:

export interface InheritedComponentsList<T extends ComponentsList> {
    name: string;
    parent: T;
    components: Record<keyof T["components"], Component>;
}

But it's not working...

See TS Playground attached

neat spadeBOT
#
_zer0xx_#0

Preview:```ts
export type Component = {
id: number
name: string
}

export interface ComponentsList {
name: string
components: Record<string, Component>
}

export interface InheritedComponentsList<
T extends ComponentsList

{
name: string
parent
...```

keen warren
#

@opaque star The problem is

const listA: ComponentsList = {

By annotating listA that way, it's typed only as type ComponentsList, it isn't going to 'remember' any of the specific structure of it

#

If you remove that annotation you get the error that you expect

#

Though realistically you probably want:

const listA = {
  //...
} satisfies ComponentsList;
opaque star
#

ah...

#

I don't understand why this works that way to be honest

#

It's so weird, i'm simply stating listA is going to be ComponentsList i don't understand why it has such a huge impact on listB typechecking

#

i'll deepen on the ts documentation, thanks a lot @keen warren

#

!resolved

keen warren
#

@opaque star Because you're relying on the fact that components has an a key which isn't part of the ComponentsList type

#

Here's a simpler example:

neat spadeBOT
#

type Example = Record<string, number>

const x: Example = {
  a: 0,
}
x.a;
//^? - number | undefined
x.b;
//^? - number | undefined
keen warren
#

TS 'forgets' the specific structure of x because that's how the type is annotated.

neat spadeBOT
#

type Example = Record<string, number>

const x = {
  a: 0,
} satisfies Example;
x.a;
//^? - (property) a: number
x.b;
//^
// Property 'b' does not exist on type '{ a: number; }'.
//^? - any
keen warren
#

Whereas satisfies checks the structure of x but doesn't change its type.

opaque star
#

Yes, i finally got it

#

satisfies ensures listA accepts the formal definition of ComponentsList without "forgetting" his implementation

#

This creates a new type defined as Record<"a",Component> that finally listB can detect

#

otherwise it would still result as Record<string, Component>

#

i don't know if what i wrote made sense but i got the point