Skip to content

Instantly share code, notes, and snippets.

@webstrand
Created November 22, 2023 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webstrand/d01c3ebab64f64b4f5a531f0357227d2 to your computer and use it in GitHub Desktop.
Save webstrand/d01c3ebab64f64b4f5a531f0357227d2 to your computer and use it in GitHub Desktop.
Using Proxy and extensive abuse of #private we implement multiple inheritance in typescript
function Multi<Left extends new (...args: any[]) => object, Right extends new (...args: any[]) => object, Largs extends any[], Rargs extends any[], Linst extends object, Rinst extends object>(Left: Left & (new (...args: Largs) => Linst), Right: Right & (new (...args: Rargs) => Rinst)): {
new (largs: Largs, rargs: Rargs): Linst & Rinst
} & { [P in keyof Left]: Left[P] } & { [P in keyof Right]: Right[P] } {
let proxy;
class Multi extends (Left as any) {
#bastard: Rinst;
constructor(largs: Largs, rargs: Rargs) {
super(...largs);
this.#bastard = new Right(...rargs);
return new Proxy(this, Multi.#handler);
}
static #handler: ProxyHandler<Multi> = {
getPrototypeOf: (target) => { throw "forbidden multi" },
setPrototypeOf: (target, v) => { throw "forbidden multi" },
isExtensible: (target) => Reflect.isExtensible(target) || Reflect.isExtensible(target.#bastard),
preventExtensions: (target) => (Reflect.preventExtensions(target), Reflect.preventExtensions(target.#bastard)),
getOwnPropertyDescriptor: (target, p) => Reflect.getOwnPropertyDescriptor(target, p) ?? Reflect.getOwnPropertyDescriptor(target.#bastard, p),
defineProperty: (target, p, attributes) => +Reflect.defineProperty(target, p, attributes) + +Reflect.defineProperty(target.#bastard, p, attributes) > 0,
has: (target, p) => Reflect.has(target, p) || Reflect.has(target.#bastard, p),
get: (target, p, receiver) => Reflect.has(target, p) ? Reflect.get(target, p, receiver) : Reflect.get(target.#bastard, p, receiver),
set: (target, p, value, receiver) => +Reflect.set(target, p, value, receiver) + +Reflect.set(target.#bastard, p, value, receiver) > 0,
deleteProperty: (target, p) => Reflect.deleteProperty(target, p) || Reflect.deleteProperty(target.#bastard, p),
ownKeys: (target) => Reflect.ownKeys(target).concat(Reflect.ownKeys(target.#bastard)),
};
static #right = Right;
static #proxy = proxy = new Proxy(this, {
isExtensible: (target) => Reflect.isExtensible(target) || Reflect.isExtensible(target.#right),
preventExtensions: (target) => (Reflect.preventExtensions(target), Reflect.preventExtensions(target.#right)),
getOwnPropertyDescriptor: (target, p) => Reflect.getOwnPropertyDescriptor(target, p) ?? Reflect.getOwnPropertyDescriptor(target.#right, p),
defineProperty: (target, p, attributes) => +Reflect.defineProperty(target, p, attributes) + +Reflect.defineProperty(target.#right, p, attributes) > 0,
has: (target, p) => Reflect.has(target, p) || Reflect.has(target.#right, p),
get: (target, p, receiver) => Reflect.has(target, p) ? Reflect.get(target, p, receiver) : Reflect.get(target.#right, p, receiver),
set: (target, p, value, receiver) => +Reflect.set(target, p, value, receiver) + +Reflect.set(target.#right, p, value, receiver) > 0,
deleteProperty: (target, p) => Reflect.deleteProperty(target, p) || Reflect.deleteProperty(target.#right, p),
ownKeys: (target) => Reflect.ownKeys(target).concat(Reflect.ownKeys(target.#right)),
})
};
return proxy as never;
}
class Stupid { x = 1; static x = "x" }
class Evil { y = 2; static y = "y" }
class Foo extends Multi(Stupid, Evil) {}
const foo = new Foo([], []);
console.log(foo.x, foo.y, Foo.x, Foo.y)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment