Below is some code that (currently) doesn't compile:
sealed class MyValue<ValueT> {
final ValueT value;
const MyValue(this.value);
const factory MyValue.bool(bool value) = MyBool;
const factory MyValue.num(num value) = MyNumber;
}
final class MyNumber extends MyValue<num> {
const MyNumber(super.value);
}
final class MyBool extends MyValue<bool> {
const MyBool(super.value);
}
Dart complains that "The return type 'MyBool/MyNum' of the redirected constructor isn't a subtype of 'MyValue'. Try redirecting to a different constructor."
However, I don't understand why. I feel like it should be perfectly feasible for const MyValue.bool(true)
to be a valid MyBool
constant.
This doesn't have anything to do with constants, though, as the same problem happens with non-const factories:
sealed class MyValue<ValueT> {
final ValueT value;
const MyValue(this.value);
factory MyValue.bool(bool value) => MyBool(value);
factory MyValue.num(num value) => MyNumber(value);
}
final class MyNumber extends MyValue<num> {
const MyNumber(super.value);
}
final class MyBool extends MyValue<bool> {
const MyBool(super.value);
}
Now, I did see this question which does seem related to the problem I am having, except in my situation I'm not trying to return different types depending on the input. I also wasn't satisfied by the answer, since something like
sealed class MyValue<ValueT> {
final ValueT value;
const MyValue(this.value);
factory MyValue.bool(bool value) => (MyBool(value) as MyValue<ValueT>);
factory MyValue.num(num value) => (MyNumber(value) as MyValue<ValueT>);
}
final class MyNumber extends MyValue<num> {
const MyNumber(super.value);
}
final class MyBool extends MyValue<bool> {
const MyBool(super.value);
}
not only compiles, but works completely fine at runtime. The problem is, having the explicit as
cast seems redundant, and I also still can't make const
instances...
Could someone explain to me why the type system doesn't work the way I expect here? (I'm assuming that I'm missing something here and there is a specific design choice that leads to this outcome I am seeing.) It's also worth mentioning that what I'm really trying to do is make a sort of "type-union", something where I can exhaustively switch
over the types of "my values" (in this case, num
and bool
) -- so if somebody could tell me of another way to accomplish this, that'd work just as well too. Thanks in advance!
The rules for forwarding constructors is that the constructor they forward to must return a subtype of the type the constructor is called on, for any type variables supplied to the superclass.
If you write:
abstract class Super<T> {
T get value;
factory Super.bool(bool b) = SubBool;
}
class SubBool implements Super<bool> {
final bool value;
SubBool(this.value);
}
then you have the problem that someone can write:
Super<String>.bool(true);
That cannot be allowed, because the static type of that is Super<String>
, and the value it would create is a SubBool
which implements Super<bool>
, which is-not-a Super<String>
.
Rather than trying to prevent that in some cases, and allow it in other cases, you're simply disallowed from writing that forwarding constructor.
The forwarding constructor must create a Super<T>
, and a SubBool
is-not-a Super<T>
in general.
As for the
not only compiles, but works completely fine at runtime.
that's because you haven't tried to write MyValue<String>.bool(true)
.
What you can do instead of factory constructors is static functions:
import "dart:core";
import "dart:core" as core;
abstract class Super<T> {
T get value;
static Super<core.bool> bool(core.bool b) => SubBool(b);
}
(The use of the name bool
as a method, conflicting with the type bool
, complicates everything. Otherwise it would be a simple static function.)