i have this scenario:
abstract class AbstractClass<T> {
abstract getData(): T;
getBase(): Partial<T> {
return {};
}
}
interface Contract {
prop1: string;
prop2: string;
}
class Impl extends AbstractClass<Contract> {
get prop1() {
// some complex logic here
return '';
}
getBase() {
return {
prop2: 'foo'
}
}
}
how do i express the constraint correct implementation of AbstractClass has to cover all properties from Contract interface? simple solution is Impl implements Contract, but then i will have to duplicate declarations for all properties that do not have a complex logic getter. so it would be nice to be able to also use inferred type of getBase() implementation.
the goal is to have a compile-time error if there is no value provided either on Impl itself or as property of inferred return type of getBase().
is it possible in principle using Typescript?
If you make getBase abstract and specify that the return must be the difference of the properties of the current class and the interface, you will get a compile time error if the property is not in either the result of getBase or the class:
abstract class AbstractClass<T, TThis> {
abstract getBase(): { [P in Exclude<keyof T, keyof TThis>]: T[P] };
}
interface Contract {
prop1: string;
prop2: string;
}
class Impl extends AbstractClass<Contract, Impl> {
get prop1() {
// some complex logic here
return '';
}
getBase() {
return {
prop2: ""
}
}
}
class ImplWrong extends AbstractClass<Contract, ImplWrong> {
get prop1() {
// some complex logic here
return '';
}
getBase() { // error Property 'getBase' in type 'ImplWrong' is not assignable to the same property in base type
return {
prop3: ""
}
}
}
You will notice that I had to pass the class itself as a type argumentto the base class, using the this type is not a solution as the keys of this are never fully known.
Also getBase must return at the very least the difference between Impl and Contract but it could return more properties (typescript allows implementations to return a super type of the implementation method). So if you have prop1 in both the class and the return of getBase it will not be an error.
Another solution is to add constraint for Impl class, and leave getBase return type inferred:
abstract class AbstractClass<T> {
abstract getData(): T;
getBase(): Partial<T> {
return {};
}
}
interface Contract {
prop1: string;
prop2: string;
}
type ImplConstraint<T, I extends AbstractClass<T>> =
{ [n in Exclude<keyof T, keyof ReturnType<I['getBase']>>]: T[n] };
class Impl extends AbstractClass<Contract> implements ImplConstraint<Contract, Impl> {
get prop1() {
// some complex logic here
return '';
}
getBase() {
return {
prop2: 'foo'
}
}
getData(): Contract {
return {} as Contract;
}
}
class ImplWrong extends AbstractClass<Contract> implements ImplConstraint<Contract, ImplWrong> {
// Class 'ImplWrong' incorrectly implements interface
// 'ImplConstraint<Contract, Impl>'
// Property 'prop1' is missing in type 'ImplWrong'.
get prop11() {
// some complex logic here
return '';
}
getBase() {
return {
prop2: 'foo'
}
}
getData(): Contract {
return {} as Contract;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With