flutterdart

Why can Dart not infer or remember the type of my property?


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) {}

Solution

  • 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) {}