Regardless of the variance of parameter on the left side, the constraints placed on Ta
and Tb
in the following declaration fail the typecheck:
class A<+TCov, -TCon, [±]Ta as TCov, [±]Tb super TCon> {
public function __construct(private Ta $ta, private Tb $tb) {}
// [various methods making use of Ta and Tb]
}
It's worth noting that the empty class declaration doesn't raise errors, but once the constrained parameters are used (in otherwise valid positions given their own variances), the typechecker raises one of the following:
Illegal use of covariant type parameter (Typing[4120])...
as
constraints are contravariantIllegal use of contravariant type parameter (Typing[4121])...
super
constraints are covariant
with reference to the parameter on the right side of the constraint.
I can more understand why generic methods pose problems. The violating positions are fairly obvious, and using the arguments in positions matching the variance of their constraints are impossible:
class A<+TCov, -TCon> {
public function cov_violate<T as TCov>(T $v): void {
// T can be cast to TCov and violate type if the original type is a subtype of T
}
public function con_violate<T super TCon>(): T {
// vice versa for the contravariant parameter
}
public function cov_impossible<T as TCov>(): T {
// how will we produce a T-typed value?
}
public function con_impossible<T super TCov>(T $v): void {
// what will we do with a T-typed value?
}
}
But what is the problem with class-wide parameters? For all six errant relationships ({+|-| }T as +TCov
and {+|-| }T super -TCon
) I can't think up a scenario where these wouldn't be type-safe. In my mind, their variances seem to either restrict their casting direction or their positions sufficiently to let declaring these relationships be safe.