C# Design Notes for Jul 7, 2015 #5031

Closed
MadsTorgersen opened this Issue Sep 4, 2015 · 3 comments

Comments

@MadsTorgersen
Contributor

MadsTorgersen commented Sep 4, 2015

C# Design Notes for Jul 7 2015

Quotes of the day:

"I don't think I've had a Quote of the Day for years "
"What you just described is awful, so we can't go there!"

Agenda

With Eric Lippert from Coverity as an honored guest, we looked further at the nullability feature.

  1. Adding new warnings
  2. Generics and nullability

New warnings

The very point of this feature is to give new warnings about existing bugs. What's a reasonable experience here? Ideally folks will always be happy to be told more about the quality of the code. Of course there needs to be super straightforward ways of silencing warnings for people who just need to continue to build, especially when they treat warnings as errors (since those would be breaking). We've previously been burned by providing new warnings that then broke people.

There's a problem when those warnings are false positives, which though hopefully rare, is going to happen: I checked for null in a way the compiler doesn't recognize. Can I build an analyzer to turn warnings off if it knows better? Anti-warnings? That's a more general analyzer design question.

Coverity for instance have very advanced analysis to weed out false positives, and avoid the analysis becoming noisy.

Generics

There's a proposal to treat generics in the following way: Both nullable and nonnullable types are allowed as type arguments. Nullability flows with the type. Type inference propagates nullability - if any input type is nullable the inferred type will be, too.

Constraints can be nullable or nonnullable. Any nonnullable constraints mean that only non-nullable reference types can satisfy them (without warning). Unconstrained generics is reinterpreted to mean constrained by object?, in order to continue to allow all safe types as type arguments. If a type parameter T is constrained to be nonnullable, T? can be used without warning.

Thus:

class C<T> where T : Animal? 
{
  public T x;
  public T? y; // warning
}
class D<T> where T : Animal 
{
  public T x;
  public T? y;
}

C<Giraffe>
C<Giraffe?>

D<Squirrel>
D<Squirrel?> // warning

Inside of generics, type parameters are always expected to possibly be nonnullable. Therefore assigning null or nullable values to them always yields a warning (except probably when they are from the same possibly nullable type parameter!).

This is nice and consistent. Unfortunately it isn't quite expressive enough. There are cases such as FirstOrDefault where we'd really want to return "the nullable version of T if it isn't already nullable". Assume an operator "^" (that shouldn't actually be the syntax) to mean take the nullable of any nonnullable reference type, leave it alone otherwise:

public static T^ FirstOrDefault<T>(this IEnumerable<T> source);

var a = new string[] { "a", "b", "c" };
var b = new string[] {};

var couldBeNull = condition ? a.FirstOrDefault() : b.FirstOrDefault(); // string?

We would need to decide what the syntax for ^ actually is if we want to have that expressiveness.

T^ can also serve the purpose of helping null-check code inside of a generic method or type. A List<T> type for instance could declare its storage array to be T^[] instead of T[] so that it warns you where you use it that the array elements could be null.

T? cannot exist when T is not constrained to either struct or class, because it means different things in the two cases, and we can't code gen across them.

Cast doesn't do null check, just suppresses warning.

@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Sep 9, 2015

I've read the part about generics several times and didn't understand it.

Are reference types to be assumed non-nullable from now on? Isn't that a breaking change?

I've read the part about generics several times and didn't understand it.

Are reference types to be assumed non-nullable from now on? Isn't that a breaking change?

@qwertie

This comment has been minimized.

Show comment
Hide comment
@qwertie

qwertie Apr 15, 2016

Hmm, perhaps they're assuming an "assembly-wide not-null opt-in" that had been discussed earlier.

Cast doesn't do null check, just suppresses warning

this sounds like (given a reference type T) T x = default(T); (((T!) x) == null) == true without a warning.

EDIT: never mind, see #5032 and #5033 for improved proposal.

qwertie commented Apr 15, 2016

Hmm, perhaps they're assuming an "assembly-wide not-null opt-in" that had been discussed earlier.

Cast doesn't do null check, just suppresses warning

this sounds like (given a reference type T) T x = default(T); (((T!) x) == null) == true without a warning.

EDIT: never mind, see #5032 and #5033 for improved proposal.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Apr 25, 2016

Member

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2015-07-07%20C%23%20Design%20Meeting.md but discussion can continue here.

Member

gafter commented Apr 25, 2016

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2015-07-07%20C%23%20Design%20Meeting.md but discussion can continue here.

@gafter gafter closed this Apr 25, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment