#Wrapping constructors correctly without losing types

1 messages · Page 1 of 1 (latest)

barren impBOT
#
m.a.t.t.t#0

Preview:```ts
// @lib esnext
import 'node'

class Foo {
__brand = Symbol()
}

class Bar extends Foo {
constructor(public a: string){
super()
}
}

class Baz extends Foo {
constructor(public b: boolean) {
super()
}
}

const exports = {
bar: Bar,
baz: Baz,
...```

harsh harness
#

there are two issues here

1 - inside the dynamic fn it doesn't seem to like using ...any[] spread
2 - the resulting object has lost the types that exist on the (A) static object

still scaffold
#

!:unsafe-keys

barren impBOT
#
retsam19#0
`!retsam19:unsafe-keys`:

Since TS allows objects to have extra properties not specified in the type, it doesn't assume that all the keys on the type are the only keys on the object. This means that Object.keys returns string[] not a specific type, and for(const key in obj), key is string, (not keyof typeof obj).

If you wish to assume otherwise, this utility is often helpful:

// A signature for `Object.keys` that assumes the only keys are the ones indicated by the type
const unsafeKeys = Object.keys as <T>(obj: T) => Array<keyof T>;
still scaffold
#

You have to cast the return type, so the implementation doesn't matter that much either, you can just cast whatever to make it pass.

harsh harness
#

the example is a little different to my code since exports is actually coming via:

import * as iset from './iset.js'
#

i ended up with an absolute mess that seems to work though:

type ISet = typeof iset
type IClass = new (...args: any) => Instruction
type IBuilder<T extends IClass> = (
  ...args: ConstructorParameters<T>
) => InstanceType<T>
type IBuilders = {
  [K in keyof ISet as Uncapitalize<K>]: IBuilder<ISet[K]>
}

const i = Object.fromEntries(
    Object.entries(iset).map((e: [string, IClass]) => {
      const [name, Ctor] = e
      const fn = (...args: any[]) => stack.cursor.insert(new Ctor(...args))
      const key = name.replace(/^./, x => x.toLowerCase())
      return [key, fn] as const
    }),
  ) as IBuilders
still scaffold
#

That's pretty much how you would write it yeah.

harsh harness
#

that seems to be my latest approach to typescript, as long as the external typing API looks good and plays well with the rest of the codebase, I can come back and clean up the internals later

still scaffold
#

IClass might be a bit misleading, personally I'd do:

type Ctor = new (...args: never) => unknown

(or => object)

#

Ah actually I see what you are doing, you are basically using that to cast.

harsh harness
#

sorry i should have also included some of iset.ts, everything exported from that file is a class that extends Instruction

#

im writing a compiler that converts an AST into bytecode for a VM

still scaffold
#

Yeah I'd go with something like InstructionCtor rather than IClass.

harsh harness
#

oh got it, that makes sense!