Reputation: 61
I am not being able to wrap my head around this issue in a generic way with Typescript, I would appreciate any help!
Something like this would be the goal (semi pseudocode)
interface MockFactory<F extends Factory> extends F {
deploy: (...args: Parameters<F.prototype.deploy>) => MockContract<F.prototype.deploy.returnValue>
}
In order to illustrate the issue even better I created a Playground where you can see the errors
Upvotes: 3
Views: 327
Reputation: 61
Just solved by using ReturnType and Parameters, as well needed to transform the interface into a type:
interface Contract {}
interface Factory {
deploy: (...args: any[]) => Contract;
}
class CustomContract implements Contract {}
class CustomFactory implements Factory {
deploy(x: number, y: number): CustomContract {
return {};
}
}
type MockContract<C extends Contract> = Contract & C & {
mockProperty: number;
}
type MockFactory<F extends Factory> = F & {
// deploy should have the parameters of the function deploy inside F (in this case CustomFactory)
// deploy should return a MockContract of the return type of the function deploy inside F (MockContract<CustomContract> in a generic way)
deploy: (...args: Parameters<F['deploy']>) => MockContract<ReturnType<F['deploy']>>
}
const example: MockFactory<CustomFactory> = {} as any;
example.deploy(1, 2);
Upvotes: 3
Reputation: 8718
You could go with (abstract) classes, but interfaces are possible too. In that case, making the factory itself generic seems like the wrong move. Make the contract type generic instead. After a bit of playing around, I was able to puzzle this together:
interface Contract { }
interface Factory<A extends any[], C extends Contract> {
deploy: (...args: A) => C;
}
// Some helper types
type FactoryArgs<F extends Factory<any, any>> = F extends Factory<infer A, any> ? A : never;
type FactoryContractType<F extends Factory<any, any>> = F extends Factory<any, infer C> ? C : never;
interface FactoryForClass<C extends new (...args: any) => Contract> {
//deploy: C extends new (...args: infer A) => infer T ? Factory<A, T>['deploy'] : never;
deploy: Factory<ConstructorParameters<C>, InstanceType<C>>['deploy'];
}
class CustomContract implements Contract {
constructor(a: number, b: number) { }
}
class CustomFactory implements FactoryForClass<typeof CustomContract> {
deploy(x: number, y: number): CustomContract {
return new CustomContract(x, y);
}
}
type MockContract<C extends Contract> = Contract & C & {
mockProperty: number;
}
type MockFactory<F extends Factory<any, any>> = F & {
deploy: (...args: FactoryArgs<F>) => MockContract<FactoryContractType<F>>;
}
const mockFactory: MockFactory<CustomFactory> = {
deploy(a: number, b: number) {
const customContract = new CustomContract(a, b);
const result: CustomContract & MockContract<CustomContract> = customContract as any;
result.mockProperty = 123;
return result;
}
};
Upvotes: 2