Skip to content

Instantly share code, notes, and snippets.

@trusktr
Created May 19, 2019 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save trusktr/dbdb09f9fe07e9ced59c8be87ef21894 to your computer and use it in GitHub Desktop.
Save trusktr/dbdb09f9fe07e9ced59c8be87ef21894 to your computer and use it in GitHub Desktop.
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type ImplementationKeys = 'static' | 'private' | 'protected'
type FunctionToConstructor<T, TReturn> = T extends (...a: infer A) => void ? new (...a: A) => TReturn : never
// Note, void also works the same in place of unknown
type ReplaceCtorReturn<T, TReturn> = T extends new (...a: infer A) => unknown ? new (...a: A) => TReturn : never
type ConstructorOrDefault<T> = T extends {constructor: infer TCtor} ? TCtor : (() => void)
// `Id` is an identity type, but it is also used as a trick to expand the
// type given to it so that tooltips show the basic type rather then all the
// conditional/aliases used.
type Id<T> = {} & {[P in keyof T]: T[P]}
type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T
// Although the SuperType type definiton already checks that T extends from
// Constructor, the additional check in the generic paramters is useful so
// that we don't get an error about "never" which is hard to track down. The
// generic paramter will cause a more helpful and understandable error.
// TODO ensure that TInstance is InstanceType of TBase
// prettier-ignore
type SuperType<TInstance, TSuper extends Constructor<any>> = TSuper extends Constructor<infer I, infer A>
? {constructor: (...a: A) => I} & Id<InstanceType<TSuper>>
// {constructor: (...a: A) => I} & Id<Pick<InstanceType<TSuper>, keyof InstanceType<TSuper>>>
: never
// type SuperType<
// TInstance extends InstanceType<TSuper>,
// TSuper extends Constructor<any>
// > = TSuper extends Constructor<infer I, infer A>
// ? TInstance extends InstanceType<TSuper>
// ? {constructor: (...a: A) => I} & Id<InstanceType<TSuper>>
// : never
// : never
type SuperHelper = <T, TBase extends Constructor<any>>(self: T) => SuperType<T, TBase>
type PrivateHelper = <T>(self: T) => T extends {__: {private: infer TPrivate}} ? TPrivate : never
type PublicHelper = <T>(self: T) => Omit<T, ImplementationKeys> // TODO validate instance is public?
type ProtectedHelper = <T>(self: T) => T extends {__: {protected: infer TProtected}} ? TProtected : never
// type ProtectedHelper = <T>(self: T) => T extends {protected: infer TProtected} ? TProtected : never
type Statics<T> = T extends {static: infer TStatic} ? TStatic : {}
// type InheritedProtected<T> = T extends {protected: infer TProtected} ? TProtected : {}
type StaticsAndProtected<T> = Id<
Statics<T>
// & {
// __: {protected: InheritedProtected<T>}
// }
>
// type ExtractInheritedProtected<T> = T extends {__: infer TProtected} ? TProtected : {}
// type PickImplementationKeys<T> = Omit<T, ImplementationKeys>
// type PickImplementationKeys<T> = Id<Pick<T, ImplementationKeys>>
// type _PrivateImplementation<T> = Pick<T, Extract<keyof T, ImplementationKeys>>
type PickImplementationKeys<T> = Pick<T, Extract<keyof T, ImplementationKeys>>
// this moves the implementation keys off the constructor return type and
// onto a fake __ property, so that we can reference the __ type within the
// implementatin code, but so that the outside (public) doesn't see the fake
// __ property.
type LowClassThis<T> = Id<Omit<T, ImplementationKeys> & {__: PickImplementationKeys<T>}>
type OmitImplementationKeys<T> = Omit<T, ImplementationKeys>
function Class(
name: string
): {
extends<TBase extends Constructor, T>(
base: TBase,
members: (
helpers: {
Super: SuperHelper
Public: PublicHelper
Protected: ProtectedHelper
Private: PrivateHelper
}
) => T &
Partial<InstanceType<TBase>> &
ThisType<LowClassThis<T & InstanceType<TBase> /* & ExtractInheritedProtected<TBase>*/>>
): T extends {constructor: infer TCtor}
? FunctionToConstructor<ConstructorOrDefault<T>, Id<InstanceType<TBase> & OmitImplementationKeys<T>>> &
Id<StaticsAndProtected<T> & Pick<TBase, keyof TBase>>
: ReplaceCtorReturn<TBase, Id<InstanceType<TBase> /*& Omit<T, 'constructor'>*/>> & // missing the T type here?
Id<StaticsAndProtected<T> & Pick<TBase, keyof TBase>>
}
function Class<T>(
name: string,
members: (
helpers: {Public: PublicHelper; Protected: ProtectedHelper; Private: PrivateHelper; Super: never} // TODO Super is actually Object
) => T & ThisType<LowClassThis<T>>
): FunctionToConstructor<ConstructorOrDefault<T>, Id<OmitImplementationKeys<T>>> & Id<StaticsAndProtected<T>>
function Class<T>(
name: string,
members: T & ThisType<LowClassThis<T>>
): FunctionToConstructor<ConstructorOrDefault<T>, Id<OmitImplementationKeys<T>>> & Id<StaticsAndProtected<T>>
function Class(): any {
return null as any;
}
const Animal = Class('Animal', {
sound: '',
constructor(sound: string) {
this.sound = sound
},
makeSound() {
console.log(this.sound)
},
})
const a = new Animal('')
a.makeSound()
const Dog = Class('Dog').extends(Animal, ({Super}) => ({
constructor(size: 'small' | 'big') {
if (size === 'small') Super(this).constructor('woof')
if (size === 'big') Super(this).constructor('WOOF')
},
// makeSound(d: number) {
// console.log(this.sound, d)
// },
makeSound() {
Super(this).makeSound()
console.log(this.sound)
},
bark() {
this.makeSound()
},
other() {
this.bark()
},
}))
type Dog = InstanceType<typeof Dog>
const smallDog: Dog = new Dog('small')
smallDog.bark() // "woof"
const bigDog = new Dog('big')
bigDog.bark() // "WOOF"
bigDog.bark()
bigDog.makeSound()
const Foo = Class('Foo', ({Public, Protected, Private}) => ({
constructor(s: string) {
console.log(s)
},
static: {
staticProp: 123,
staticMethod() {
console.log(Foo.staticProp) // 123
},
},
publicProp: 'blah',
publicMethod(a: string) {
// console.log(this.constructor.staticProp) // TODO
console.log(a)
this
},
protected: {
protectedProp: 456,
protectedMethod(a: string) {
console.log(a)
console.log(Protected(this).protectedProp) // TODO
},
},
private: {
privateProp: 789,
privateMethod() {
this.publicProp
this.publicMethod('') // Should this be allowed? Or do we just the below line to work ?
Public(this).publicMethod('')
},
},
test() {
this.publicMethod('')
console.log(this.publicProp) // 'blah'
let p = Protected(this)
console.log(p)
Protected(this).protectedMethod('')
console.log(Protected(this).protectedProp) // 456
Private(this).privateMethod()
console.log(Private(this).privateProp) // 789
return Private(this).privateProp
},
}))
let foo = new Foo('')
console.log(foo.publicProp)
// console.log(foo.protectedProp)
// console.log(foo.privateProp)
Foo.staticMethod()
Foo.staticProp
const Bar = Class('Bar').extends(Foo, ({Public, Protected, Private, Super}) => ({
constructor(sound: string) {
sound
},
static: {
derivedStatic: 10,
},
private: {
derivedPrivate: 10,
},
protected: {
derivedProtected: 10,
},
derivedPublicMethod() {
Protected(this).protectedMethod('')
Protected(this).protectedProp
Protected(this).derivedProtected
Private(this).derivedPrivate
this.test()
Public(this).test()
},
test() {
// should not work.
const b = {}
Super(b).test()
return Super(this).test()
},
}))
var bar = new Bar('')
bar.derivedPublicMethod()
bar.test()
Bar.derivedStatic
Bar.staticMethod()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment