Open bar4488 opened 2 hours ago
Summary: The issue is that Dart's type inference fails to recognize that a generic type parameter T
cannot be null when it's initialized with a non-null value, leading to compile errors when attempting to use the value in operations that require non-nullable types.
This is working as intended. You expect a.value
to be promoted by the test a.value == null
, but this would not be sound. That is so because value
is an instance member of of a
, and it could be overridden by a getter whose value differs each time you call it. (So it wouldn't be sound to assume that a.value
is non-null just because it was non-null the previous time you called it).
We do actually support a special case: If you change the name of value
to _value
(such that it's a private member of A
) then it can be promoted. The point is that private members cannot be overridden in a different library, and this means that we can safely rely on the information which can be obtained by investigating the current library.
Another possible way ahead would be to adopt 'stable getters', dart-lang/language#1518, which would establish a guarantee that a certain getter will return the same value if you call it multiple times. However, that's a proposal rather than an actual language mechanism, so we can't do that today. (You can vote for it, though ;-)
ok thanks! seems about right.
@eernstg is there a reason we do not support it in the case of final
classes?
final class A {
final int? value;
A(this.value);
}
void main() {
int? n = 3;
int n2 = n == null ? 3 : n + 2; // as intended
if (n != null) {
int n2 = n; // as intended
}
A a = A(3);
int b = a.value == null
? 3
: a.value +
2; // error Operator '+' cannot be called on 'int?' because it is potentially null
if (a.value != null) {
int b = a.value; // error
}
}
// --- Library 'a.dart'.
final class A1 {
final int value;
A1(this.value);
}
base class A2 extends A1 {
A2(super.value);
}
// --- Library 'b.dart'.
import 'a.dart';
var _sillyCounter = 0;
base class B extends A2 {
B(): super(0);
int get value => ++_sillyCounter;
}
// --- Library 'main.dart'.
import 'a.dart';
import 'b.dart';
void main() {
A1 a = B();
print('The value is ${a.value}, then ${a.value}');
}
Just like promotion, superinterface relationships also have special privileges in the same library (in this case: a subclass of a final
class, in the same library, can be base
, and this means that we can create a subclass in a different library).
In these cases we should be able to infer that the type cannot be null, however the current implementation raises a compile error: