Created
May 31, 2020 19:50
-
-
Save Taytay/7d79eb02c28f62d704e93a03e95e7b2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here is the link to the playground for the following code: | |
// https://www.typescriptlang.org/play?#code/MYGwhgzhAEAiD2BzaBvAUNT0wDsCWAtmCABKQBi8ArgE4AyApojALzQAuNVDA3BlgAcqAIxB5g0ACZIAcmAIMAXNAic8OZGwBEWvlmhDR46OrzsAFALA15EZQkQAFa7YCUqfvszB4OCPBAGADoQJHMAcgBJbAJsKSQAGmwcSWhoxHh2DgALBggGAxcCO2hw6ABqQptioOlEADViblc9LABfNA7QSBgAYTAs9H1cQmIyCEpaRmZoNk5uVsxDMQlgAbkFZVUadU1oHUWDERWTfAsrapL+9mdL9yGvb19-QJCwqJi4tfYk3FT0zI5PIFC62ZRlSqgmrfRogZqHDpdcBQOBIW62Dz6ZbGOqw7jKHBUAjCBg0WbQAAMhxGRFIFGo9CYrA4XAYnTQ3RR13RxUxgmOxhhTSU0EJxNJ5KpnmSozpEwZ02Z8zZHTQDDF0AA0upUg9MA5tSlyeEDTrwglpddDak2OErWaLaqfH4sgBrHUAWTAAnJeugAG1rUFTSkALr2RLSwM6oL2sPKa6OvjsACeAgK1q9PrmaYY8AAZtB3Sks3w0Kn09AACpUASBCAAHk15OtAD4W9AGAAPdjqyQwa3QAD8AelmqS0siLtwwAYVdzTc93sDodbE-0U9UM7nC-MFbzheLkizrhX7e7vZSMGl+hQpzMynMYGU6nzEsc7hY7dwKegbWH0CONABIMAAbqSaChsBopgaSZYAPTwdAzqqFUthZr6aCIV40YpMGSDWuGqJOEUEAWth+i4ZIsYDIRCYDDyZFYUhbQIUh+6AaRGE5umBZocUpZoMx0AkKSBRmCYMAAFZUKhsm7ECnGXFmwkcTWdZ5E2LY6u2bDNhefYDjqAH+sJ+jjmZWCbuw27zumi4lsumqruRSEbtOOCznZDANoxWZnsJUEgeBNBluWubQL0yIQN5WkGVeWo6eSACiPY2MA7ANup9ZJIGvw4Cm+Upqu-oAIyhsmEWMbF+k9oZiUpLp0CpZwYAZVltY5QG47JIVvUlQATBVQkcQAqvgvhVvAU69jQ+QZXgvgNqNTXmKNnZ1QlP4AeYrrKKNn7tqB8B4KkwWku48X9tA5i7S+OBvmSkSHdAx2ne4I7ROdNDhZW5CeVl5LZXkTXjYtOBTTNpLzew4MNtKVYbZe13AzAI67QwKbKFW-oUqGSQCNj-pDS9OPldBOCwT9rZ8ChWSIAw7AAIL4LS1nbso-0SGw+ZUJ5sO+NAcWbddbYY1jWoE6RyjVQumqtq4CbRTV7Z+nTMq0pq0Xkke-mupjw3SurNLEOznkFGwlMAO4a8QWs9OYLTStKJsgGbs5BKY5ykU70o0IztA4LbbsebOfCqthVvwDQrpkdATMyLA0DZJA0AZPAqTqL2IBiPkfgMAAhByzxZN85IM8zrOm6HDDmEGcaSEk1uRQxpGO772FVrkLLUKI4lxwySm2CrySpFFPQj6E8Cumn8AZ9A+Y0PAsTsN3AD61DsBAp0MGv0B8avBS8-z4NJMIVBZIfv5tewVDECA185-ANv5tHUi4AzS+yap2S7DAkQyAAMqRFgMlfehZD4Lz5gtXwZ8GBrFkgUfcMBkHQH9i8cCHB4C9SCMJRwS8CyKDQMfGBQcK4s1lO7BgA1hbIyMo1cWygepQhKLLey8tFaRWVnLVWRsS7B3tiiNgutlz62KocY2VcQ5bnNuSZurtBEQEdtSKRVDPZnEsD7Ms+hO6-xgBAbI1AQCpBJMkTsNAl40CIfodWGoCREhJGSNgLDDjSl0XgGAHizGkksflTOWR-bgWIDAK2KcsgAAMWHhOwP7ZOYllA-jcW5asEVwgtXSplKi+FEDWiSA4PJaJSL426kVIqg0oIAB9mppRvg2LJDckiJhbjcIpuUeo-jKa2YmoYyheJwICHoeBEA4DAH3LBHBUmUxCuEXBOikLeVKOk2pWSQyN2IgUkilxil5V6p07pvSYD9KyIM4ZozAjjI4uEKZpIZlJKwAs8IP4DmigGVAIZIyxnsGwZc65NBbk2P4QIAhhZvrkhcc7OZ1Y9EqEMVQYx0BTG4HMZY6xWB-a3xoEHAArOHIS2FAHwCSIYm20hoBWyPngLsOQPFDhGhFQBYBHrjygDVJG9U2wpRqe1VGbSNnQCqU0hwjF+XNMYiVcqlVKwMsemwnytU6ENUkE1JZ3LOp5F5cREVgrCmXC1a3LZXShpsWgAAdQYDnJI5LkJItJavDxZ8L5koYOEWJwg2ozy+WS3IsSrVbhoL2SQRCSECzIYzChbMa4DSxQ2JmbKEpizEcoJmUtLjKGlaSWVMaFZpsZaSZlMUFxM14QCl0AjtbCKXAIf0YjsAwG2qnQiEj+GuyoXIhgNsFHRWUXcyIWQZKoVdP0q2KCwn8U8TAby5Al4EGBgADizUENIWRpB5BwOEAJDBiB4AAF7iXYOEOtooHESnUHE-2SRYJB1XtQRA2QgSxOsAUXwD8OBRwMPAN5YzQLChgHxJmdyABCjqh3UvHd4ix0dUWYE7qkoVpERXmG5PBgAZMRRirhnlHNrdvU5nzvmpKQ5cf5WAKKPEwPg+A6Z-W-nCEKOEzrJLQAIB47eGhTgTMrCaHVtgyjnw3QARyoHgf2mcr0Ef1Tx2ZWAW013UWYTRdxXGQpZqkfwjG7V2DuQ8hwmHXk4Y+ecz1lzrgIYcNAVD1wMNSeg8k7TSBdPHLebhwz+HONmYswMYjNmvAUao6mUo3wNgMa8cxt5bHT2XJ0wix1-tBPCYYKJjjBQ7Sees2ggOmLg5UNxcJXovhQDyV8NjbuKrL47GADPRAYBMGyTiFbMAv5PUp0wT4Cx8CshT2LMgYN4MVDvIGLQPI+8L7b0kE+iB3ceuwK9Xgc56CAigQUpA1Bnrwk-miX8bAT8rYKR-HatjTXvTpiDuoUb43FJTZwIuruAxsAZTvjnX8XijxgGQnPEADqsgQDAJIF9WHU7fbfLWp1ZKjGpGawUMQ+tqJ4qQtEVQs2QDxDXW6EDr36uNewUQfWoGVC5poEEIAA | |
class Dog { | |
animalHasFourLegs = true; | |
public dogName: string = ""; | |
public init(params: DogParams) { | |
console.log('I am a dog, and I got these params: ' + params.dogValue); | |
} | |
} | |
class Cat { | |
animalHasFourLegs = true; | |
public catName: string = ""; | |
public init(params: CatParams) { | |
console.log('I am a cat, and I got these params: ' + params.catValue); | |
} | |
} | |
class DogParams { | |
public dogValue: number = 0; | |
animalHasFourLegs = true | |
} | |
class CatParams { | |
public catValue: number = 0; | |
animalHasFourLegs = true | |
} | |
enum Kind { | |
DogKind = 'DogKind', | |
CatKind = 'CatKind', | |
} | |
const kindMap = { | |
[Kind.DogKind]: Dog, | |
[Kind.CatKind]: Cat, | |
}; | |
type KindMap = typeof kindMap; | |
type Tuples<K = Kind> = K extends Kind ? [ | |
K, | |
InstanceType<KindMap[K]>, | |
InstanceType<(typeof kindMap)[K]> extends | |
{ init: (a: infer P) => any } ? P : never | |
] : never; | |
// const paramsMap = { | |
// [Kind.DogKind]: DogParams, | |
// [Kind.CatKind]: CatParams, | |
// }; | |
// type ParamsMap = typeof paramsMap; | |
// Here it is just using the ParamsMap | |
// type Tuples<K = Kind> = K extends Kind ? [ | |
// K, | |
// InstanceType<KindMap[K]>, | |
// InstanceType<ParamsMap[K]> | |
// ] : never; | |
type ClassType<K extends Kind> = Extract<Tuples, [K, any, any]>[1]; | |
type ParamsType<K extends Kind> = Extract<Tuples, [K, any, any]>[2]; | |
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never | |
type Fnc<T = Tuples> = UnionToIntersection< | |
T extends Tuples ? (key: T[0], p: T[2]) => T[1] : never | |
>; | |
const getAnimalInstance: Fnc = function <K extends Kind>(key: K, params: ParamsType<K>): ClassType<K> { | |
const animalKlass = kindMap[key]; | |
const animalInstance = new animalKlass(); | |
animalInstance.init(params); | |
return animalInstance; | |
} | |
// works, AND has good intellisense! | |
const cat = getAnimalInstance(Kind.CatKind, new CatParams()); | |
// The trouble is, our ParamsType<K> and ClassType<K> look good from the _outside_ of the function, but they actually allow for dangerous | |
// things INSIDE of the function, because types types resolve to any. | |
// Proof: | |
function getAnimalInstance2<K extends Kind>(key: K, params: ParamsType<K>): ClassType<K> { | |
const animalKlass = kindMap[key]; | |
const animalInstance = new animalKlass(); | |
animalInstance.init(params); | |
// This should be an error: | |
const num : number = params; | |
// This is an error, and it reveals what `params` are here: any | |
// Type 'Extract<[Kind.DogKind, Dog, DogParams], [K, any, any]>[2] | Extract<[Kind.CatKind, Cat, CatParams], [K, any, any]>[2]' is not assignable to type 'never'. | |
// Type 'Extract<[Kind.DogKind, Dog, DogParams], [K, any, any]>[2]' is not assignable to type 'never'. | |
// Type 'any' is not assignable to type 'never'. | |
const proof : never = params; | |
// This should be an error: | |
return 5; | |
} | |
// So, how do we fix this? | |
type SaferClassType<K extends Kind> = Extract<Tuples, [K, Dog | Cat, DogParams | CatParams]>[1]; | |
type SaferParamsType<K extends Kind> = Extract<Tuples, [K, Dog | Cat, DogParams | CatParams]>[2]; | |
// Well, we can do this, but we're back to where we started: | |
function getAnimalInstance25<A extends Kind>(key: A, params: SaferParamsType<A>): SaferClassType<A> { | |
const animalKlass = kindMap[key as any as Kind]; | |
const animalInstance = new animalKlass(); | |
// It just knows that params is TypeFromTuple8<A>. It doesn't realize it's a number in here, even though there are only two possible values of A | |
// But now this is an error: | |
// Type 'DogParams | (CatParams & DogParams)' is not assignable to type 'CatParams'. | |
// Property 'catValue' is missing in type 'DogParams' but required in type 'CatParams'. | |
animalInstance.init(params); | |
// And so is this: | |
// Type 'Dog' is not assignable to type 'Cat | (Dog & Cat)'. | |
// Type 'Dog' is not assignable to type 'Dog & Cat'. | |
// Property 'catName' is missing in type 'Dog' but required in type 'Cat'. | |
return animalInstance; | |
} | |
// Conclusion: The Extract trick gave us a way to have correct looking function signatures outside of the function, while resolving the types to `any` and allowing anything to happen inside of the function. That actually is kinda cool, but sadly not as safe as we would have liked. | |
// I still don't know a way to make this safer. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment