Reputation: 1814
I apologize for the confusing wording in the title, I'm having a hard time coming up with a one line summary for the question. Here is an example to explain what I'm trying to do:
interface Fish {
type: 'fish';
}
interface Dog {
type: 'dog';
}
type Animal = Fish | Dog;
interface MatingPair<T> {
animal: T;
otherAnimal: T;
}
class AnimalCollection<T> {
private pairs: T[] = [];
addPair<V extends T>(subject: V) {
this.pairs.push(subject);
}
}
let td = new AnimalCollection<MatingPair<Animal>>();
// Correctly generates error, dog is not a fish
td.addPair<MatingPair<Fish>>({
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
});
// Incorrectly lets dogs and fish be a mating pair
td.addPair({
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
});
I would like to be able to declare that addPair
should receive a version of MatingPair
that not just contains two "animals", but that the two animals are the same type.
Right now this code properly verifies that the call to td.addPair
on the last line is receiving two animals of the same type (fish, in this case), but only because I'm explicitly setting T
to be Fish
. Is there a way to define this such that I can say both that MatingPair.animal
& MatingPair.otherAnimal
both have values that are types within the union Animal, but also that the type is the same for both of them?
Upvotes: 1
Views: 284
Reputation: 22352
You can reach your goal by restructuring the definition of the Animal collection class a bit like so:
interface Fish {
type: 'fish';
}
interface Dog {
type: 'dog';
}
type Animal = Fish | Dog;
interface MatingPair<T> {
animal: T;
otherAnimal: T;
}
class AnimalCollection<T>
{
private pairs: MatingPair<T>[] = [];
addPair<V extends T>(subject: MatingPair<V>)
{
this.pairs.push(subject);
}
}
let td = new AnimalCollection<Animal>();
// Correctly generates error, dog is not a fish
td.addPair<Fish>({
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
});
// Correctly generates error, dog is not a fish
td.addPair({
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
});
Its not 100% what you ask for - but will solve the problem.
A little bit of explanation. Basically in your sample you are trying to do this:
let a: MatingPair<Animal> = {
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
}
And this is perfectly legal because both animal
and otherAnimal
are of type Animal
, just different animals.
Whereas my subtle change makes code to look more like this:
let a: MatingPair<Dog> = {
animal: { type: 'dog' },
otherAnimal: { type: 'fish' }
}
And this is wrong as fish is not a dog.
In the real sample idea is the same, its just instead of Dog
I have put there requirement that V
is descendant of Animal
. So either Dog
or Fish
but not both as they are incompatible.
Upvotes: 3