47

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.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • This is basically another version of https://stackoverflow.com/q/56764592/ (I'm sure there are other existing questions too). – jamesdlin Nov 27 '20 at 12:06
  • @jamesdlin, that's interesting. I didn't realize this was more generalized than just null safety. – Suragch Nov 27 '20 at 13:38
  • I mentioned it in [the linked question](https://stackoverflow.com/a/56764992/), but it's probably worth pointing out here too: this is covered by https://dart.dev/tools/non-promotion-reasons – jamesdlin Dec 16 '21 at 22:48

3 Answers3

45

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 don't promote. 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!;
  }
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
31

The Error:

Let's say, this is your code and you're doing a null check on the instance variable and still seeing an error:

class Foo {
  int? x;

  double toDouble() {
    if (x != null) return x.toDouble(); // <-- Error
    return -1;
  }
}

The method 'toDouble' can't be unconditionally invoked because the receiver can be 'null'.

The error you see in code like this is because Getters are not promoted to their non-nullable counterparts. Let's talk about the reason why.


Reason of the Error:

Let's say, there's a class Bar which extends Foo and overrides x field and implemented like this:

class Bar extends Foo {
  @override
  int? get x => (++_count).isOdd ? 1 : null;
  int _count = 0;
}

Now, if you do

Bar().toDouble();

You would have run into a runtime null error, which is why getters type promotion is prohibited.


Solutions:

We need to cast away nullability from int?. There are generally 3 ways to do this.

  • Use local variable (Recommended)

    double toDouble() {
      final x = this.x; // <-- Use a local variable
      if (x != null) return x.toDouble(); 
      return -1;
    }
    
  • Use ?. with ??

    double toDouble() {
      return x?.toDouble() ?? -1; // Provide a default value
    }
    
  • Use null-assertion operator (!)

    You should only use this solution when you're 100% sure that the variable (x) will never be null.

    double toDouble() {
      return x!.toDouble(); // Null assertion operator
    }
    
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 1
    The local variable solution looks like the best option, especially when you need to use += or *= operators – user3473445 Apr 19 '21 at 20:44
  • Details on documentation: https://dart.dev/guides/language/language-tour#other-operators – Xao May 23 '21 at 11:22
  • Is the solution to use a local variable your personal recommendation, or has the dart team said anything about it? – Hannes Hultergård Aug 04 '21 at 08:53
  • @HannesHultergård It's coming from a post written by one of the members of Dart team. – CopsOnRoad Aug 04 '21 at 16:34
  • 1
    Please correct me if I'm wrong, but unless I'm missing something fundamental here, I feel that this example doesn't really illustrate the reasoning behind preventing getter promotion, since `i` obviously never changes after the null check. Wouldn't a better example for `i` in `Bar` be `int? get i => Random().nextBool() ? 0 : null;` like the one shown [here](https://youtu.be/HdKwuHQvArY?t=1671)? This way, you can really see how the compiler could never guarantee that `i` isn't null, since its value can change to null when it's accessed after the null check. – Omar Sharaki Apr 28 '22 at 20:56
  • @OmarSharaki You're right. I've updated the code. – CopsOnRoad Nov 15 '22 at 18:42
-1

style: Theme.of(context).textTheme.headline5!.copyWith(

style: Theme.of(context).textTheme.headline5!.copyWith(
                        color: Colors.white

Try making the call conditional using ? or a null safety checker - !

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-ask). – Community Sep 15 '21 at 22:41
  • The question is about why type promotion doesn't occur. This does not answer that. – jamesdlin May 26 '23 at 20:25