Reputation: 869
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?
Upvotes: 3
Views: 403
Reputation: 51629
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;
}
}
Upvotes: 2
Reputation: 249676
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.
Upvotes: 2