Home > Blockchain >  "??" ("if null") operator type resolution issue
"??" ("if null") operator type resolution issue

Time:02-02

Consider code below:

mixin M {}

abstract class I {}

class A = I with M;

class B = I with M;

void main() {
  final A? a = A();
  final B? b = B();

  final I? i1 = a ?? b; // compilation error saying "A value of type 'Object?' can't be assigned to a variable of type 'I?'"
}

Could you please help me to understand why I am having compilation error using "??" operator.

Dart 2.19.0

CodePudding user response:

The a ?? b expression needs to figure out a single type which represents the two possible resulting values, one of type A or one of type B?.

Rather than checking that both A and B? are assignable to I?, it tries to figure out the type of the result of a ?? b as a single type combining both A and B?, which means finding a supertype of both A and B.

To do that, it computes an upper bound using the language-specified "least upper bound" algorithm. (I quote the name, because it finds an upper bound, which is sometimes a least upper bound, but not always.)

The two types are:

  • A with immediate supertypes I and M, both of which have immediate supertype Object, which has supertype Object? (and all other top types).
  • B? with supertypes I?, M? and their supertype Object?.

The problem here is that while it can see that I and I? occur in those types, and it can therefore decide that I? is an upper bound, it also finds M and M? and decides that M? is an upper bound, and those two types are otherwise completely equivalent, they have the same length of the super-class chain up to Object and are not related to each other. Neither is a least upper bound. So the algorithm ignores them and look for something that's both shared and does not have another type of equal "depth from Object". And it finds Object?. Which is not assignable to I.

This is a known shortcoming (among several) of the least upper bound algorithm in Dart, but it's a hard problem to solve optimally, because it's very, very easy for a class to introduce some internal private superclass, and sometimes that type then becomes the least upper bound of two public subclasses, leaving users scratching their head.

There are requests to do better, for example by not ignoring the context type, but changing the type inference has to be done very carefully. It can break existing code which has been fine-tuned to the current behavior.

There are no great workarounds here, but there are functional ones.

You will have to rewrite the code to ensure that the static type of the two branches do not both have the unnecessary M type in them. Rather than down-casting the result to I? at the end, which requires a runtime type check, I'd up-cast the original values:

final I? i3 = a ?? (b as I?); // ignore: unnecessary_cast

That should be completely free up-casts, but may (will!) cause analyzer warnings that you'll have to ignore.

CodePudding user response:

Compiler doesn't seem to recognise that objects a and b are of classes inherited of I in that case. So the syntax does the same, but in the first case needs help with casting.

final I? i1 = (a ?? b) as I?; 

This does not give an error.

  •  Tags:  
  • dart
  • Related