Craig Edwards
Craig Edwards

Reputation: 2298

Using 'is' to check runtime type of Dart generic

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.

Upvotes: 9

Views: 4094

Answers (2)

Erik Ernst
Erik Ernst

Reputation: 1054

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.

Upvotes: 4

Kevin Moore
Kevin Moore

Reputation: 6161

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.

Upvotes: 4

Related Questions