Reputation: 383
Given the following:
interface Component {}
interface Composite<component extends Component> {
getChildren(): component[];
}
class StackSegment implements Component {}
class Stack implements Composite<StackSegment> {
getChildren(): StackSegment[] {
return new Array<StackSegment>();
}
}
Why does the following cause a compiler error?
class Bar<component extends Component = StackSegment,
composite extends Composite<component> = Stack> {
}
The error that I get (on Stack as the default value of composite) says that StackSegment is not assignable to type 'component'.
Upvotes: 0
Views: 499
Reputation: 328302
Note: in what follows I'm using short uppercase identifiers for type parameters, as the normal convention. You can replace CN
with component
and CS
with composite
at your pleasure (but I'd recommend against using regular identifiers for generic type parameters)
I'm not sure what your use cases are, but default type parameters don't set constraints. In your code,
class Bar<CN extends Component = StackSegment,
CS extends Composite<CN> = Stack> { // error
}
The type parameter CN
is not required to be StackSegment
, and thus it's possible for Stack
not to meet the constraint of Composite<CN>
. One way to deal with it is to make the composite just Composite<CN>
instead of Stack
:
class Bar<CN extends Component = StackSegment,
CS extends Composite<CN> = Composite<CN>> { // okay
}
If you really want to see the default as Stack
(e.g., if Stack
has some extra methods that just Composite<CN>
doesn't), then you can do this:
class Bar<CN extends Component = StackSegment,
CS extends Composite<CN> = Stack & Composite<CN>> { // okay
}
since Stack & Composite<StackSegment>
is the same structural type as Stack
. But again, I don't know your use case. As of now, you get stuff like:
interface OtherComponent extends Component {
thingy: string;
}
class OtherComposite implements Composite<OtherComponent> {
getChildren(): OtherComponent[] {
throw new Error("Method not implemented.");
}
}
new Bar(); // Bar<StackSegment, Stack> as desired
new Bar<OtherComponent, OtherComposite>(); // also makes sense
// but this is a Bar<OtherComponent, Stack & Composite<OtherComponent>>
new Bar<OtherComponent>(); // wha?
// which is weird.
Do you ever intend to use just one default parameter? If not, maybe there's a better way to represent the generics. But without more use case information I wouldn't know how to advise you.
Good luck.
EDIT: An ugly syntax that works the way I think you want is to specify both types in one parameter, like this:
class Bar<CC extends [Component, Composite<CC[0]>]=[StackSegment, Stack],
CN extends CC[0] = CC[0],
CS extends CC[1] = CC[1]> {
}
In the above, CC
is a two-tuple of types with the constraint you want (the second parameter must be compatible with Composite<>
of the first parameter), and it defaults to the pair [StackSegment, Stack]
as desired. (The CN
and CS
types are just there for convenience so you don't need to use CC[0]
for the component type and CC[1]
for the composite type).
Now the behavior is something like this:
new Bar(); // CN is StackSegment and CS is Stack, as desired
new Bar<[OtherComponent, OtherComposite]>(); // also makes sense
But you can't easily break it like before:
new Bar<[OtherComponent]>(); // error
new Bar<[OtherComponent, Stack]>(); // error
Okay, good luck again!
Upvotes: 1
Reputation: 383
Changing the line of code that causes the compiler error as follows:
class Bar<component extends Component = StackSegment,
composite extends Composite<Component> = Stack> {}
eliminates the error.
Now the question is, will it work as expected when overridden.
Upvotes: 0
Reputation: 18292
The problem, I think, is that the second type parameter depends on the first. I mean, if the first parameter is of type P
then the second must be of type Composite<P>
(assuming P
implments Component
).
But as you are assigning default types, you could do this:
let b = new Bar<P>();
Here I don't pass a second type parameter, so TypeScript assumes it is Stack
. However, Stack
does not extend P
.
A default value for a type parameter not always enforces the type constraints, and so such a type cannot be declared.
Upvotes: 1