Skip to content
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

Do we need a generic constraint for nullable only generics? #143

Closed
Tracked by #110
leafpetersen opened this issue Dec 17, 2018 · 9 comments
Closed
Tracked by #110

Do we need a generic constraint for nullable only generics? #143

leafpetersen opened this issue Dec 17, 2018 · 9 comments
Assignees
Labels
nnbd NNBD related issues

Comments

@leafpetersen
Copy link
Member

The following class will be ill-formed when we move to non-nullable types:

class C<T> {
  T x;  // Error, since T may be non-nullable, and x is not initialized
  List<T> l = new List(10); // Error, since T may be non-nullable and the list is not initialized
}

One solution is to simply require the user to manually put a ? on the type where required:

class C<T> {
  T? x;  
  List<T?> l = new List(10);
}

Another possibility is to allow the class to specify a constraint indicating that it may only be applied to nullable types:

class C<T?> {  // T is required to be nullable
  T x;   // Ok, required to be nullable
  List<T> l = new List(10); // Ok, required to be nullable
}

It's not clear how common this is. Also, this is something that could probably be added later if we discover we really want it (though changing classes to use it would be a breaking library change).

cc @lrhn @munificent @eernstg

@munificent
Copy link
Member

For the record, my position on this is and has been that I don't think this is needed or very useful. If we get evidence to the contrary, I'm definitely open to adding it. When I was prototyping NNBD a while back and converting over the core libraries, I don't recall running into cases where I wanted this.

@leafpetersen
Copy link
Member Author

@lrhn has a concern around ending up with classes that are equivalent for nullable and non-nullable type, but end up with incompatible types. For example:

class A<T extends Object?> {
  final T? value;
  A(this.value);
}

Now A<int> and A<int?> are two different types which have the same operations, but are not equivalent types.

This can be avoided if we are able to write a constraint saying that the type variable must be non-nullable:

class A<T extends Object> {
  final T? value;
  A(this.value);
}

This class can only be instantiated as A<int> and not A<int?>, allowing the API designer to keep users from running into the issue. But this class is now not usable in a context where you have a type variable which is not known to be non-nullable.

The alternative of having a constraint that a type variable must be nullable avoids this.

// T is required to be nullable
class A<T? extends Object?> {
  final T value;
  A(this.value);
}

Now you can never write A<int>, you must write A<int?> (or conceivably we automatically treat the former as the latter). And if you have something that you don't know is nullable, you can always add the ? on to make it nullable: A<X?> works if X is not known to be a nullable type variable.

@eernstg
Copy link
Member

eernstg commented Jan 17, 2019

@leafpetersen wrote

But this class is now not usable in a context where you have
a type variable which is not known to be non-nullable.

If that other type variable, X, can genuinely be nullable (that is, if it can be a supertype of Null, which is true if its upper bound is a supertype of Null) then we would genuinely need to adjust its value before using it with A, but that could be specified as A<X!>, cf. this comment.

Otherwise, if that other type variable X should also be non-null, its declaration should be adjusted to X extends Object, in which case the problem becomes a migration issue (but A<X!> might be used during the migration, and a lint could later reveal cases where that ! operator is used but not needed).

@leafpetersen leafpetersen added the nnbd NNBD related issues label Jan 23, 2019
@leafpetersen
Copy link
Member Author

@lrhn Where are you in your thinking on this? I still think that I'm on the side of the fence that this is not needed, but open to discussion.

@lrhn
Copy link
Member

lrhn commented Feb 12, 2019

I think I'm also hoping it won't be needed, but I want to keep the door open to being convinced otherwise.
So, maybe start out without the feature, but don't design ourselves into a corner where it cannot be added.

If we start seeing classes where the type variable is nullable-bounded (or unbounded) and all occurrences of the type variable has a ?, then we probably needs the feature.

If we have ! as a user-available operator on type variables, then I'm not as worried, then you can use a non-nullable bound and null all the type variable occurrences without getting into ambiguity where C<int?> isn't assignable to C<int>, even if they are really the same.

@leafpetersen
Copy link
Member Author

I think we're converging towards holding off on this. @lrhn is there discussion/thoughts you want to raise or just record here for posterity?

@lrhn
Copy link
Member

lrhn commented Mar 13, 2019

I've become more convinced that not having this feature is a good thing.

If you actually care whether your the type of your type variable is nullable, you should just make it non-nullable and write the ? as appropriate. If you inspect the value of a potentially nullable type variable, then you're probably doing something weird and fragile.

I'm actually close to requiring all actual bounds to be non-nullable, the only way to get non-nullable type variable is to use no bound and accept all types, because that means that you won't be able to expect anything from the object anyway, only passing it through uninspected.

A generic type parameter like F<X extends int?> can be fulfilled by int?, int, Null and bottom. Here int and Null have nothing in common, so the bounds buy you nothing. If something has type X, you can't do anything to it without first checking that it's not null, then you know that it's int and can use that. Internally in the generic code. There is no difference from declaring it as F<X extends int> and writing X? as the type. The only reason to allow that would be to allow the client code to have either F<int> or F<int?> (or F<Null>) and knowing something about the values coming out of the code. This feels speculative.

I think allowing nullable type bounds is fine from a consistency standpoint, but I expect style guides to recommend against it unless the bound is Object? anyway.

@eernstg
Copy link
Member

eernstg commented Mar 21, 2019

@leafpetersen, @lrhn, @munificent, sounds like we can conclude that we will not have a mechanism that allows a type variable declaration to require that the value must be a nullable type. "Decision" and close?

@leafpetersen
Copy link
Member Author

Decided, no, we can always add this later if we find it's important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nnbd NNBD related issues
Projects
None yet
Development

No branches or pull requests

4 participants