# C# Language Design Notes for Apr 25, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Warn on nullable ref type argument when T has `class` constraint ``` c# static void F(T t) where T : class { t.ToString(); /* no warning */ } F(maybeNull); // warning: string? does not satisfy 'class' constraint F(string.Empty); // warning: string? does not satisfy 'class' constraint ``` This is a real danger, and we should be warning here. Note that in both cases the warning is about constraint checking: even in the second line we infer `string?` and it warns on constraint check. We should probably have the error message say that this is because of nullability. # Warn on nullable reference type argument when T has non-nullable reference type or interface type constraint ``` c# static void F(T t) where T : IDisposable { } F(maybeNull); // warning: Stream? does not satisfy 'IDisposable' constraint ``` Yes, warn for same reasons. We also want to warn (or error) on inconsistent nullability in constraints. Specifically if one constraint is known to be non-nullable and another is known to be nullable. What about ``` c# class C where T: class? where U : T, IDisposable where V : T, IDisposable? { // Fine } class D where T: class where U : T, IDisposable where V : T, IDisposable? { // Warn on V } ``` ``` c# ... where T : Node? ... where T : Node // both currently allowed, but not where T : Node? ... where T : class, INode ... where T : class?, INode? ``` ``` c# static void F(T t) where T : IFoo { } IFoo foo; F(foo); // warning, unless IFoo is covariant ``` Another kind of clash: ``` c# interface IBar : IFoo {} static void F(T t) where T : IFoo, IBar { } ``` We want to warn on this: maybe find shared base interfaces between the two constraints, and check if they have consistent nullability. Other, slightly more complex example, same conclusion: ``` c# interface IBar : IFoo {} static void F(T t) where T : IFoo, IBar where U : class{ } ``` # Allow nullable reference types and interface types as constraints `where T : Person?, IDisposable?` Yes # Allow `class?` constraint Yes How do we express in metadata? Let's put top-level nullability as an attribute on the type parameter itself, rather than on the constraints. We can noodle more on this later. # Allow `object` constraint ``` c# static void F(T t) where T : object { } F(maybeNull); // warning: constraint violated ``` ## Need to talk about relationship to value types soon ``` c# int? i = null; object o = i; o.ToString(); ``` # Allow `object?` explicitly? It's the "most general" constraint that is implied by unconstrained generics. Today we disallow `object` because of that. We don't know that this is a terribly useful restriction today, but we'll keep doing it (now with `object?`) unless we get evidence to the contrary. # Warn on dereference of T when T is unconstrained? Yes, if a type parameter `T` can be instantiated with a nullable reference type, then we should track null state and warn on unguarded dereference. The warning when occurring on unconstrained generics might suggest using the `object` constraint. # Warn on assignment to `object` of unconstrained `T`? Yes. This can be a `W` (cosmetic) warning when the `object` variable is a local. # default(T) with unconstrained T ``` c# T M() { T t = default(T); // W warning return default(T); // safety warning } ``` This is something completely safe. I don't want it to warn!: ``` c# T M() { var t = default(T); if (something) t = somethingelse; if (t != null) WriteLine(t.ToString()); } ``` This could be an argument for allowing `T?` that is not about special methods. Or it is an argument against having the W warnings at all. Let's keep this example around and revisit. But for now, let's consider `default(T)` to be potentially null, and therefore warn on its unguarded use. # Annotations We would like to deal with methods with special null semantics, such as string.IsNullOrEmpty, TryGetValue, Assert.NotNull, Object.Equals, Object.ReferenceEquals. We'll come back to this later.