I'm relatively new to Dart and am trying to model an algebraic data type (like Swift's enums with associated values).
When I attempt to check the runtime type of a value, though, I'm seeing some behaviour that I don't really understand. Consider the following code:
abstract class Fetchable<T> {
factory Fetchable.success(T object) = FetchableSuccess;
factory Fetchable.error(Error error) = FetchableError;
}
class FetchableSuccess<T> implements Fetchable<T> {
final T object;
const FetchableSuccess(this.object);
}
class FetchableError<T> implements Fetchable<T> {
final Error error;
const FetchableError(this.error);
}
void main() {
// ------------- CASE 1 -------------
final s = Fetchable.success("hi there");
print(s.runtimeType); // FetchableSuccess<dynamic>
print(s is FetchableSuccess); // true
print(s is FetchableSuccess<String>); // false
print(s is FetchableSuccess<dynamic>); // true
if (s is FetchableSuccess) {
print(s.object); // compile error
}
if (s is FetchableSuccess<dynamic>) {
print(s.object); // compile error
}
// ------------- CASE 2 -------------
final e = Fetchable.error(StateError("uh oh"));
print(e is FetchableError); // true
if (e is FetchableError) {
print(e.error); // Works OK
}
}
As you can see, I create two cases... I just can't seem to extract the object
variable from the success
case. The is
keyword tells me that it is a FetchableSuccess<dynamic>
(which seems odd... I thought that Dart doesn't erase the type? I would have expected it to be a FetchableSuccess<String>
)
Even then, though, if I cast to FetchableSuccess<dynamic>
the compiler still tells me it doesn't know about the object
variable in the FetchableSuccess
subclass.
Contrast that, though, to FetchableError
(which doesn't explicitly use a value of type T
)... is
works correctly and the compiler is able to access the error
variable.
What am I missing? Any guidance would be much appreciated.
We have a couple of issues here: First, you need to add <T>
to the right hand sides of the two factory constructors.
If you do that the run-time type of s
will be ReflectableSucces<String>
as it should be, and you'll get true
from the following 3 lines in main
.
Then you're testing s is FetchableSuccess
which means s is FetchableSuccess<dynamic>
, and that's not a subtype of the statically known type Fetchable<String>
so you don't get promotion (so there is no object
getter in the static type, hence the error). Testing s is FetchableSucces<dynamic>
explicitly is of course subject to the same treatment.
So the remaining question is, why don't you get an error for those missing <T>
s? I'm searching issues for that now, it is probably a known bug.
Update: https://github.com/dart-lang/sdk/issues/34714 addresses the fact that inference is not done in the common front end even though it should. So we get the analysis using inference from the analyzer, and then we get the semantics at run time which does not use inference (and thus defaults to <dynamic>
), and that's the reason why we get this unfortunate clash where the actual type argument is wrong at run time, and there is no compile-time error. It should be resolved when said issue is landed.
I agree, this is really weird. The short explanation: the type is inferred as Fetable<String>
, so here are three fun ways to work around it:
var a = Fetchable.success("hi there");
if (a is FetchableSuccess<String>) {
print(a.object); // fine
}
var b = Fetchable<dynamic>.success("hi there");
if (b is FetchableSuccess) {
print(b.object); // fine
}
var c = Fetchable.success("hi there" as dynamic);
if (c is FetchableSuccess) {
print(c.object); // fine
}
I'll send email to the language folks to see if they have thoughts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With