-
Notifications
You must be signed in to change notification settings - Fork 227
Description
The current spec allows static extensions to declare generative redirecting constructors, but forbids their use as a super constructor in other generative constructors. As discussed here, this means the only reason to use them is to allow for a const constructor. Moreover, replacing a normal constructor with one declared in an extension becomes more breaking than it otherwise need be.
This restriction is proposed because a generative redirecting constructor can implicitly pass more specific generic arguments to the super constructor call than what is expected from the class structure. Consider these examples:
class A<T> {
T x;
A(this.x);
}
class B extends A<int> {
B() : super.named() {
x.isEven;
}
}
class C extends A<Object> {
C() : super.named() {
x = "hello";
}
}
extension on A<num> {
A.named() : this(3.5);
}Both classes B and C extend A with type arguments that are different (more specific and less specific, respectively) than those actually provided to the call to the A() constructor in the redirection in the extension.
If we assume that those type arguments are respected, and that the instances created by calls to the B() or C() constructors are also instances of A<num> then we immediately encounter issues.
First, the B() constructor creates an object whose static type is known to be a subtype of A<int>, but whose runtime type is not compatible with this. The read of x in the body of the B constructor demonstrates this unsoundness, calling isEven on a double.
Second, the C() constructor creates an object where even within the scope of the instance members, where we would otherwise be guaranteed that we have a precise type for this, we have a static type for this which is more general than the runtime type. The write to this.x here must be guarded by a covariance check. While Dart already has support for runtime checked covariance, this change would invalidate a useful invariant that is often relied upon both by compilers and programmers (that the type for this is known to be exact).
In this scenario, it seems to me that the only reasonable approach to this would be to try to specify invariance here: that is, to specify that a super invocation must have exactly the type of the super class (for some definition of "exactly the type", e.g. mutual subtype). Assuming that redirection can never introduce subsumption (something that should be validated), this is likely sufficient? [Edit] This is not true, unless we separately enforce it. See this comment for example.
It is possible that there is an alternative approach in which the redirection is essentially "inlined" into the super invocation with type arguments provided exactly as given in the class header. Combined with a requirement that the static type of the super invocation be a subtype of the superclass type, this might be sufficient to guarantee coherence.
cc @dart-lang/language-team