Having identified the issue, let's focus on a minimal example:
// interfaces:
interface ClassParameter{
x:number
}
interface ClassParameterNeeder{
y:number
}
type ClassParameterConstructor = new () => ClassParameter
type ClassParameterNeederConstructor =
new (cpc: ClassParameterConstructor) => ClassParameterNeeder
// implementations:
class MyClassParameter implements ClassParameter{
x = 12
extraField = 27
}
class MyClassParameterNeeder implements ClassParameterNeeder{
y = 19
otherExtraField = 29
constructor(mcpc: new() => MyClassParameter) {}
}
let doesntWork:ClassParameterNeederConstructor = MyClassParameterNeeder
Regarding TypeScript's complaint about the final line:
let doesntWork: ClassParameterNeederConstructor
Type 'typeof MyClassParameterNeeder' is not assignable to type 'ClassParameterNeederConstructor'.
Types of parameters 'mcpc' and 'cpc' are incompatible.
Type 'ClassParameterConstructor' is not assignable to type 'new () => MyClassParameter'.
Property 'extraField' is missing in type 'ClassParameter' but required in type 'MyClassParameter'.(2322)
input.ts(22, 5): 'extraField' is declared here.
This situation led me to question the reversed dependency, which actually makes sense. In the definition of type ClassParameterNeederConstructor
, an outer Constructor complying with that type must accept an inner ClassParameterConstructor
capable of constructing any kind of ClassParameter
, not just the specialized constructor for MyClassParameter
. But then, how do I resolve this? It seems impossible to narrow the interface in the implementation, and the implementation cannot accept any kind of ClassParameterConstructor
without specific requirements for MyClassParameter
constructors...
Any assistance would be greatly appreciated!
EDIT:
Attempting to apply @jcalz's solution to my specific issue: Playground
Previously, I tried using generics but placed the <> after the new (line 26):
RigidBody: new<S extends ShapeInterface> (shapeClass: new () => S) => RigidBodyInterface<S>
Unfortunately, that didn't work. I have now moved it up into the constructor of PhysicsInterface (now on line 23) and it functions correctly.
However, I must now list all the abstract classes used by the engine in the generics parameter list, which is a bit cumbersome (especially since TypeScript doesn't display generics parameters as tooltips), but it seems necessary.
Another attempt was made to create a type-inferring factory, but it ultimately failed (the last line throws an error), and it's also quite confusing. I would also need a mechanism to inject engine-specific implementations for the physics engine class into the physics engine class factory, so I'll stick with method A.
Currently, the implementation allows a physics engine user to access the p2-specific members of the classes and they are visible in TypeScript code completion. Is there a way to keep that private while still allowing the engine itself to access all p2 fields? Something akin to using friend
in C++...
I have a functional solution now, but any input on making the factory work or about the concept in general is appreciated.
Thank you!