Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using 'is' to check runtime type of Dart generic

Tags:

generics

dart

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.

like image 936
Craig Edwards Avatar asked Oct 06 '18 01:10

Craig Edwards


2 Answers

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.

like image 169
Erik Ernst Avatar answered Oct 04 '22 15:10

Erik Ernst


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.

like image 29
Kevin Moore Avatar answered Oct 04 '22 14:10

Kevin Moore