Skip to content

unable to infer non nullable type in case of generics #56954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bar4488 opened this issue Oct 24, 2024 · 4 comments
Closed

unable to infer non nullable type in case of generics #56954

bar4488 opened this issue Oct 24, 2024 · 4 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-as-intended Closed as the reported issue is expected behavior

Comments

@bar4488
Copy link

bar4488 commented Oct 24, 2024

In these cases we should be able to infer that the type cannot be null, however the current implementation raises a compile error:

class A<T> {
  final T 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<int?> 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: A value of type 'int?' can't be assigned to a variable of type 'int'.
  }
}
@dart-github-bot
Copy link
Collaborator

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.

@dart-github-bot dart-github-bot added area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Oct 24, 2024
@eernstg
Copy link
Member

eernstg commented Oct 24, 2024

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 ;-)

@eernstg eernstg added closed-as-intended Closed as the reported issue is expected behavior and removed type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. labels Oct 24, 2024
@eernstg eernstg closed this as completed Oct 24, 2024
@bar4488
Copy link
Author

bar4488 commented Oct 24, 2024

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

@eernstg
Copy link
Member

eernstg commented Oct 24, 2024

// --- 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-as-intended Closed as the reported issue is expected behavior
Projects
None yet
Development

No branches or pull requests

3 participants