#Assignment issue with generic static method upon overriding

39 messages · Page 1 of 1 (latest)

humble parrot
#

I'm having an issue when trying to override a generic method with an implementation, I think it's because the base type is more broad so it's not fully assignable to the implementation.

Here's a playground link to the issue: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgKIDcLgEIFcDOAPBlmAEoQID2UAJshAB6Qi37IXV2EDWEAnlRhpM4TjXoAyZPjBRQAcwA0yXCB4gqAdxABtALoA+Q8gDeAKGRWGAW2BgAFBFFgAXMj6DhJMZQkq4KAV8dx9yPzpdMH4ABwghBhd9AEp3dCpgWgBucwBfc3MEABs4fHZsUogwvHwASRsYouIXcToGZiw2DgjaXgEEsNapGTlFFTUNbT0jE2AGoogbUnZqgmbSIZMLa2QY3AAjIuAEEbgwY+QEKAgziHXfLnomFi6hvq8RDZ7kaVl5EGUqnUmh0BmMDlSnxwa0GPS2lh21muYFwUBAyBAEC0yDAAAtgPgITkdvkEVY9ocLot7E4XO5PAMWj0AkEQlDwo8orF4sJnKQUmkMvRtoirAB6MW2ezIIpUBTHMnIfKk0CQWCIFAAWX4YXYIqs+CoSzC7l0hqWAEEgu4-op9DlScVSuxtat2M9OuVKm76o1CK6XPh4TsKUcTrIzhcqJgoPJaCgrjdIBDQi4av6dYHg6LkMjUejMdi8QSiYrlQUgA

I'm not sure what I can do on this case, since the point of the static method is for it to return the overriden type. Strangely, everything else seems to work if I just remove the static method. Overriding emit doesn't seem to fix it. I'd be thankful if someone could point me on the right direction, thanks.

stiff robinBOT
#

@humble parrot Here's a shortened URL of your playground link! You can remove the full link from your message.

amgelo563#0

Preview:```ts
interface EventBus<
EventRecord extends Record<
keyof EventRecord & string,
unknown[]

{
emit(
event: keyof EventRecord,
args: EventRecord[typeof event]
): void
}

class BaseEventBusImpl<
EventRecord extends Record<
keyof EventRecord & string,
unknown[]

implements EventBus<EventRecord>
{
public static crea
...```

tulip hazel
#

your issue is probably with
EventRecord extends Record<keyof EventRecord & string, unknown[]>

#

that doesn't really make any sense

humble parrot
tulip hazel
#

the problem isnt record, it's that you're referring to EventRecord in the extends clause for EventRecord

humble parrot
#

if I use only string though, I have to make MyEvents indexable with [key: string]: unknown[]

#

which breaks the point of the type safety

#

what alternative can I use?

#

I think it doesn't like it either way

stiff robinBOT
#
interface EventBus<EventRecord extends Record<string, unknown[]>> {
    emit(event: keyof EventRecord & string, args: EventRecord[typeof event]): void;
}

class BaseEventBusImpl<EventRecord extends Record<string, unknown[]>> implements EventBus<EventRecord> {
    public static create<EventRecord extends Record<string, unknown[]>>(): EventBus<EventRecord> {
        return new this();
    }

    public emit(event: keyof EventRecord & string, args: EventRecord[typeof event]): void {
        // emit logic
    }
}

interface MyEvents extends Record<string, unknown[]> {  
    someEvent: [someArg: string];
}

class MyEventBus extends BaseEventBusImpl<MyEvents> {
//    ^^^^^^^^^^
// Class static side 'typeof MyEventBus' incorrectly extends base class static side 'typeof BaseEventBusImpl'.
//   The types returned by 'create()' are incompatible between these types.
//     Type 'EventBus<MyEvents>' is not assignable to type 'EventBus<EventRecord>'.
//       Type 'MyEvents' is not assignable to type 'EventRecord'.
//         'MyEvents' is assignable to the constraint of type 'EventRecord', but 'EventRecord' could be instantiated with a different subtype of constraint 'Record<string, unknown[]>'.
    public static override create(): EventBus<MyEvents> {
        return new this();
    }
}
humble parrot
#

Record<keyof EventRecord & string, unknown[]> seemed to achieve that

tulip hazel
#

tbh it seems like you're just way overcomplicating this with the whole class hierarchy and stuff. try going back to the basics of what it actually is you're trying to implement here

stiff robinBOT
#
littlelily#0

Preview:```ts
const eventTypes = ["foo", "bar"] as const

type EventType = typeof eventTypes[number]

type EventHandlers = {
[K in EventType]: (...args: any[]) => void
}

const eventHandlers = {
foo: (x: number, y: number) => {},
bar: () => {}
} satisfies EventHandlers
...```

humble parrot
#

my base is this kind of interface (it needs to work with this):

interface MyEvents {
    someEvent: [someArg: string];
}

this is because this type of "event arguments declaration" is already included in a library I'm using, with the plus that it seems reasonable to have a type to declare a type (which will be erased on compile time), instead of a constant that will be kept on runtime

tulip hazel
humble parrot
#

it's an example I made to reproduce it, in reality it holds more than one

tulip hazel
#

So what you mean is that the base type is really something like
type MyEvents = Record<string, (arg: string) => void>

humble parrot
#

not necessarily, there could be another event that takes a number as an argument for example

#

or more than one argument

#

the point is that it should error if I use something like bus.emit("someEvent", [1]);

tulip hazel
#

Ok, so assuming your base type is
type MyEvents = Record<string, (...args: any[]) => void>
Then all you need to do is define your events as such

const events = {
    someEvent: (x: number) => {},
    otherEvent: (foo: string, bar: number) => {}
} as const satisfies MyEvents

events.someEvent(53) // works
events.someEvent("foo") // error
#

satisfies makes sure that something conforms to a certain type without overriding the type of the variable you're assigning to

humble parrot
#

it breaks the generalization of the oop approach though, with the original approach one can easily create as many buses as required with just passing the event record as a generic

tulip hazel
#

Huh?

#

You can do that here too by just making a new object that also satisfies MyEvents

humble parrot
#

what I mean is that I can make, for example MyOtherEvents and just reuse the base implementation

const myBus = new BaseEventBusImpl<MyEvents>();

const myOtherBus = new BaseEventBusImpl<MyOtherEvents>();

the MyEventBus class is supossed to be used as a shorthand for the first line, and the create method is there because there are other properties in the constructor, but the method makes it easy to just use the defaults

#

it's pretty much just that static method failing thonk

tulip hazel
#
const myOtherEvents = {
    ...
} as const satisfies typeof events
humble parrot
#

I'm considering moving it to an external function since that shouldn't break it anymore

humble parrot
#

like grabbing the name of the current method or something

#

or write the same thing but for every event

tulip hazel
#

Idk what you mean

#

What event bus logic

humble parrot
#

notifying subscribers

tulip hazel
#

So what you really want is a way to have multiple different locations in your code be able to provide their own handlers for a common set of event types, and then call all of the handlers for a specific event type when that event occurs?

humble parrot
#

yeah, I have all of that sorted out already (event buses, subscribers, error handling, etc), the problem is just that overriding method Sadge