Search code examples
flutterdarttype-promotion

Null check doesn't cause type promotion in Dart


I'm upgrading a personal package that is based on the Flutter framework. I noticed here in the Flutter Text widget source code that there is a null check:

if (textSpan != null) {
  properties.add(textSpan!.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
}

However, textSpan! is still using the ! operator. Shouldn't textSpan be promoted to a non-nullable type without having to use the ! operator? However, trying to remove the operator gives the following error:

An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.

Here is a self-contained example:

class MyClass {
  String? _myString;
  
  String get myString {
    if (_myString == null) {
      return '';
    }
    
    return _myString; //   <-- error here
  }
}

I get a compile-time error:

Error: A value of type 'String?' can't be returned from function 'myString' because it has a return type of 'String'.

Or if I try to get _mySting.length I get the following error:

The property 'length' can't be unconditionally accessed because the receiver can be 'null'.

I thought doing the null check would promote _myString to a non-nullable type. Why doesn't it?

My question was solved on GitHub so I'm posting an answer below.


Solution

  • Dart engineer Erik Ernst says on GitHub:

    Type promotion is only applicable to local variables. ... Promotion of an instance variable is not sound, because it could be overridden by a getter that runs a computation and returns a different object each time it is invoked. Cf. dart-lang/language#1188 for discussions about a mechanism which is similar to type promotion but based on dynamic checks, with some links to related discussions.

    So local type promotion works:

      String myMethod(String? myString) {
        if (myString == null) {
          return '';
        }
        
        return myString;
      }
    

    But instance variables that aren't final and that aren't private don't get promoted. (Prior to Dart 3.2, non-local variables never got promoted.) For that you need to manually tell Dart that you are sure that the instance variable isn't null in this case by using the ! operator:

    class MyClass {
      String? _myString;
      
      String myMethod() {
        if (_myString == null) {
          return '';
        }
        
        return _myString!;
      }
    }