Skip to content

Instantly share code, notes, and snippets.

@Fasteroid
Last active May 7, 2025 14:33
Show Gist options
  • Select an option

  • Save Fasteroid/2389c1cdbc43a4d046d3b3669dcf1be7 to your computer and use it in GitHub Desktop.

Select an option

Save Fasteroid/2389c1cdbc43a4d046d3b3669dcf1be7 to your computer and use it in GitHub Desktop.
Multiple inheritance! The holy grail of programming!
type Class = (abstract new (...args: any) => any)
function pickFallback(sources: any[], prop: string | number | symbol) {
for (let fallback of sources) {
if( prop in fallback ){
return fallback;
}
}
return undefined;
}
/** Creates a proxy handler which effectively simulates having multiple prototypes */
function FallbackProxyHandler<T extends object>(...sources: any[]): ProxyHandler<T> {
return {
get(target, prop, receiver) {
if( Reflect.has(target, prop) ) return Reflect.get(target, prop, receiver);
const fallback = pickFallback(sources, prop);
if( fallback ) return fallback[prop];
return undefined;
},
set(target, prop, value, receiver) {
if( Reflect.has(target, prop) ) return Reflect.set(target, prop, value, receiver);
const fallback = pickFallback(sources, prop);
if( fallback ){
fallback[prop] = value;
return true;
}
return Reflect.set(target, prop, value, receiver);
},
has(target, prop) {
// console.log( "HAS:", target, "has", prop, Reflect.has(target, prop) )
return Reflect.has(target, prop) || pickFallback(sources, prop);
},
deleteProperty(target, prop) {
throw new TypeError("Not applicable for multiple inheritance.")
},
}
}
/**
* Helper type to extract tuple of constructor parameters from an array of constructor types
* @author Claude 3.7 Sonnet
*/
type ManyConstructorParameters<T extends Class[]> = {
[K in keyof T]: ConstructorParameters<T[K]>;
};
/**
* Helper type to intersect the return values from a set of constructors
* @author Fasteroid
*/
type CombinedConstructorResult<CLASSES extends Class[], RETURN extends unknown = unknown> =
CLASSES['length'] extends 0 ? // are we done?
RETURN // if so return
:
CLASSES extends [
abstract new (...args: any) => infer FIRST,
...infer REMAINING extends any[]
]
?
CombinedConstructorResult<REMAINING, RETURN & FIRST>
:
RETURN
;
/** Gets the full constructor signature for an amalgam class */
type MultipleInheritanceClass<BASES extends Class[]> = new (...args: ManyConstructorParameters<BASES>) => CombinedConstructorResult<BASES>
/**
* Creates a class with multiple base classes.
*
* Can be extended like any other normal class.
*
* Inherited properties are prioritized in order of the passed base classes to avoid the {@link https://en.wikipedia.org/wiki/Multiple_inheritance?useskin=vector#The_diamond_problem | diamond problem}.
* @author Fasteroid, Claude 3.7 Sonnet
*/
export function amalgam<BASES extends Class[]>(...constructors: BASES): MultipleInheritanceClass<BASES> {
return class {
constructor(...args: ManyConstructorParameters<BASES>) {
console.log(args, constructors)
// Create instances of all provided constructors
const instances = constructors.map((ctor, idx) => {
// @ts-ignore
return new ctor(...args[idx]);
});
// Create a proxy that falls back through all instances in order
const self = new Proxy({}, FallbackProxyHandler(...instances));
Object.defineProperties( self, Object.getOwnPropertyDescriptors( Object.getPrototypeOf(this) ) );
Object.setPrototypeOf(this, self);
}
} as MultipleInheritanceClass<BASES>;
}
class A {
constructor(private input: string){}
a() { console.log(this.input) };
}
class B {
b() { console.log("b") };
a(): number { return 1 }
}
class C {
c() { console.log("c") };
}
class X extends amalgam(A, B, C) {
constructor(){
super(["a"],[],[]);
this.a(); // console.log("a")
}
}
@Fasteroid
Copy link
Author

I think I've peaked guys...

@Fasteroid
Copy link
Author

@Fasteroid
Copy link
Author

If you need this to work with generics, you'll have to work around ts(2562)

Here's a "nice" way to do it:

const Amalgam = 
    ( class extends amalgam(GenericThing) {} ) as 
    new <T>(...args: ManyConstructorParameters<[typeof GenericThing]>) => GenericThing<T>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment