skip to Main Content

After checking if a property of an object in Dart is of a particular subtype, Dart doesn’t seem to infer / know / remember that the property todo is of that subtype further down in the code.

For example, if I check state.todo is of type Todo_hasChildren and then later use state.todo as a Todo_hasChildren type, I get a Dart analysis error ("The argument type ‘Todo3’ can’t be assigned to the parameter type ‘Todo3_hasChildren’")

I know how to fix this (convert using the as keyword or assign the property to its own variable) but why does it not know the type? Is this temporary in the evolution of Dart or is this something that is permenant?

class MyState {
  final Todo todo;

  MyState(this.todo);
}

void doStuff(MyState state) {
  if (state.todo is Todo_hasChildren) //
    processHasChild(state.todo);
}

void processHasChild(Todo_hasChildren todo) {}

3

Answers


  1. This is a known limitation in Dart’s type inference system. Dart’s type inference is based on the code’s control flow, and it can’t track the type of a variable across different branches of the control flow.

    Here’s how you can do it:

    void doStuff(MyState state) {
      if (state.todo is Todo_hasChildren) {
        processHasChild(state.todo as Todo_hasChildren);
      }
    }
    

    or

    void doStuff(MyState state) {
      if (state.todo is Todo_hasChildren) {
        Todo_hasChildren todo = state.todo;
        processHasChild(todo);
      }
    }
    
    Login or Signup to reply.
  2. Because todo is a property of state, and Dart can only infer the type if you promote it from a local variable. This is a very similar case to the nullable type promotion. See also: Working with nullable fields.

    So you have to do this instead:

    void doStuff(MyState state) {
      final todo = state.todo;
      if (todo is Todo_hasChildren) //
        processHasChild(todo);
    }
    
    Login or Signup to reply.
  3. The reason is actually that subclasses of your MyState class may override the field with a getter that doesn’t consistently give the same value back. Like this:

    class MyState2 extends MyState {
      MyState2(super.todo);
    
      @override
      Todo get todo => Random().nextBool() ? Todo_hasChildren() : someOtherTodo();
    }
    

    Now, when calling doStuff with a MyState2 the type check might pass the first time but when getting it for the processHasChild it might return the wrong type.

    You can try out this full program to see for yourself that it occasionally crashes because of this:

    import 'dart:math';
    
    void main() {
        doStuff(MyState2(Todo()));
    }
    
    class Todo{}
    class Todo_hasChildren extends Todo{}
    
    class MyState2 extends MyState {
      MyState2(super.todo);
        @override
        Todo get todo => Random().nextBool() ? Todo_hasChildren() : Todo();
    }
    
    class MyState {
        final Todo todo;
    
        MyState(this.todo);
    }
    
    void doStuff(MyState state) {
        if (state.todo is Todo_hasChildren) {
            processHasChild(state.todo as Todo_hasChildren);
        }
    }
    
    void processHasChild(Todo_hasChildren todo) {}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search