Reputation: 13095
I would like to enforce a (compile-time) constraint that a variable of type T2
be assignable to a variable of type T1
.
If I have a value (t2
) of type T2
then I can do the following:
const t1: T1 = t2;
Is there a better way to do this ? Preferably without having to create additional runtime entities.
I don't define either of these types directly in my application so I can not make one the superclass of other.
Upvotes: 2
Views: 178
Reputation: 327934
Are you looking for a witness that will give you a compile error if T2
is not assignable to T1
without emitting anything at runtime? Sure, you can define something like this:
type ExtendsWitness<U extends T, T> = U
and use it like this:
type T2Witness = ExtendsWitness<T2, T1>
If T2
is assignable to T1
, you will get no errors. Otherwise, you will get an error on the first type argument to ExtendsWitness
saying that T2
does not meet the constraint T1
.
Here's an example with some concrete types:
interface Super {
foo: string;
bar: number;
}
interface GoodSub {
foo: 'a';
bar: 3;
baz: boolean;
}
type GoodWitness = ExtendsWitness<GoodSub, Super> // okay
Notice that GoodSub
is not defined in terms of Super
, but GoodSub
is witnessed by the compiler to be a subtype of Super
nonetheless. Here's an example of a bad subtype:
interface BadSub {
foo: string;
baz: boolean;
}
type BadWitness = ExtendsWitness<BadSub, Super> // error
The compiler cannot witness that BadSub
is a subtype of Super
, and the error tells you exactly what the problem is: Type 'BadSub' does not satisfy the constraint 'Super'. Property 'bar' is missing in type 'BadSub'.
Hope that helps; good luck!
Upvotes: 1
Reputation: 24541
I don't think the thing you want is achievable as is. Sorry for the stupid sentence, but as long as you cannot define the relativity of the types => you cannot define relativity of the types.
However, you still have the following options:
Use any
type to simply assign the values
const t1: T1 = <any>t2;
const t3: T2 = <any>t1;`
Similar solution with a caster function:
function toT1(t: T2): T1 {
return <any>t;
}
const t1 = toT1(t2);
Advantage is that you don't need to define t1 type.
Create a type that merges two types into one
type T3 = T1 | T2;
const t1: T3 = t2;
This solves the problem with assignment but could lead to some problems with the entities that are already bound to those T1 and T2. This can be used situationally depending on the needs
Create a custom type guard:
interface T1 { a: any; }
interface T2 { b: any; }
function isT1(t: T1 | T2): t is T1 {
return true;
}
const t2: T2 = { b: 1 };
const t1: T1 = isT1(t2) ? t2 : null;
This returns true all the time so you can assign your variable to t1
without any problem. However this is still ugly.
I would go with a way number 1. It is simple and fully covers your needs. Over engineering is also bad.
Upvotes: 0