{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/5032","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/5032/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/5032/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/5032/events","html_url":"https://github.com/dotnet/roslyn/issues/5032","id":104989057,"number":5032,"title":"Proposal: Nullable reference types and nullability checking","user":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Feature%20Request","name":"Feature Request","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Feature%20Specification","name":"Feature Specification","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-C%23","name":"Language-C#","color":"207de5"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-VB","name":"Language-VB","color":"207de5"}],"state":"open","locked":false,"assignee":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"milestone":null,"comments":89,"created_at":"2015-09-05T00:00:31Z","updated_at":"2015-10-26T17:07:42Z","closed_at":null,"body":"Null reference exceptions are rampant in languages like C#, where any reference type can reference a null value. Some type systems separate types into a `T` that cannot be null and an `Option<T>` (or similar) that can be null but cannot be dereferenced without an explicit null check that unpacks the non-null value.\r\n\r\nThis approach is attractive, but is difficult to add to a language where every type has a default value. For instance, a newly created array will contain all nulls. Also, such systems are notorious for problems around initialization and cyclic data structures, unless non-null types are permitted to at least temporarily contain null.\r\n\r\nOn top of these issues come challenges stemming from the fact that C# already has null-unsafe types, that allow both null values and dereferencing. How can a safer approach be added to the language without breaking changes, without leaving the language more complex than necessary and with a natural experience for hardening existing code against null errors in your own time?\r\n\r\n# Approach: Nullable reference types plus nullability warnings\r\n\r\nThe approach suggested here consists of a couple of elements:\r\n\r\n* Add a notion of safe *nullable* reference types `T?` to the language, in addition to the current ones `T` which are from now on *non-nullable*.\r\n* Detect and warn on cases where nullable types are dereferenced, or when null is values are assigned to (or left in) non-nullable variables.\r\n* Offer opt-in means to deal with breaking behavior and compat across versions and assemblies.\r\n\r\nThe feature will not provide airtight guarantees, but should help find most nullability errors in code. It can be assumed that most values are intended *not* to be null, so this scheme provides the minimal annotation overhead, leaving current code to be assumed non-nullable everywhere. \r\n\r\nNullable reference types are helpful, in that they help find code that may dereference null and help guard it with null checks. Making current reference types Non-nullable is helpful in that it helps prevent variables from inadvertently containing an null value.\r\n\r\n# Nullable and non-nullable reference types\r\n\r\nFor every existing reference type `T` is now \"non-nullable\", and there is now a corresponding nullable reference type `T?`. \r\n\r\nSyntactically speaking, nullable reference types don't add much, since nullable value types have the same syntax. However, a few syntactic corner cases are new, like `T[]?`.\r\n\r\nFrom a semantic viewpoint, `T` and `T?` are mostly equivalent: they are the same type in that they have the same domain of values. Specifically, because the system is far from watertight, non-nullable reference types can contain null. The *only* way in which they differ is in the warnings caused by their use. \r\n\r\nFor type inference purposes `T` and `T?` are considered the same type, except that nullability is propagated to the outcome. If a reference type `T` is the result of type inference, and at least one of the candidate expressions is nullable, then the result is nullable too.\r\n\r\nIf an expression is by declaration of type `T?`, it will still be considered to be of type `T` if it occurs in a context where by flow analysis we consider it known that it is not null. Thus we don't need new language features to \"unpack\" a nullable value; the existing idioms in the language should suffice.\r\n\r\n``` c#\r\nstring s;\r\nstring? ns = \"hello\";\r\n\r\ns = ns; // warning\r\nif (ns != null) { s = ns; } // ok\r\n\r\nWriteLine(ns.Length); // warning\r\nWriteLine(ns?.Length); // ok\r\n```\r\n\r\n## Warnings for nullable reference types\r\n\r\nValues of nullable reference types should not be used in a connection where a) they are not known to (probably) contain a non-null value and b) the use would require them to be non-null. Such uses will be flagged with a warning.\r\n\r\na) means that a flow analysis has determined that they are very likely to not be null. There will be specific rules for this flow analysis, similar to those for definite assignment. It is an open question which variables are tracked by this analysis. Just locals and parameters? All dotted names?\r\n\r\nb) means dereferencing (e.g. with dot or invocation) or implicitly converting to a non-nullable reference type.\r\n\r\n## Warnings for non-nullable reference types\r\n\r\nVariables of non-nullable reference types should not be assigned the literal `null` or `default(T)`; nor should nullable value types be boxed to them.  Such uses will result in a warning. \r\n\r\nAdditionally, fields with a non-nullable reference type must be protected by their constructor so that they are a) not used before they are assigned, and b) assigned before the constructor returns. Otherwise a warning is issued. (As an alternative to (a) we can consider allowing use before assignment, but in that case treating the variable as nullable.) \r\n\r\nNote that there is no warning to prevent new arrays of non-nullable reference type from keeping the null elements they are initially created with. There is no good static way to ensure this. We *could* consider a requirement that *something* must be done to such an array before it can be read from, assigned or returned; e.g. there must be at least one element assignment to it, or it must be passed as an argument to something that could potentially initialize it. That would at least catch the situation where you simply forgot to initialize it. But it is doubtful that this has much value.\r\n\r\n# Generics\r\n\r\nConstraints can be both nullable and non-nullable reference types. The default constraint for an unconstrained type parameter is `object?`.\r\n\r\nA warning is issued if a type parameter with at least one non-nullable reference constraint is instantiated with a nullable reference type.\r\n\r\nA type parameter with at least one non-nullable constraint is treated as a non-nullable type in terms of warnings given.\r\n\r\nA type parameter with no non-nullable reference constraints is treated as *both* a nullable *and* a non-nullable reference type in terms of warnings given (since it could without warning have been instantiated with either). This means that *both* sets of warnings apply.\r\n\r\n`?` is allowed to be applied to any type parameter `T`. For type parameters with the `struct` constraint it has the usual meaning. For all other type parameters it has this meaning, where `S` is the type with which `T` is instantiated:\r\n\r\n* If `S` is a non-nullable reference type then `T?` refers to `S?`\r\n* Otherwise, `S?` refers to `S`\r\n\r\nNote: This rule is not elegant - in particular it is bad that the introduction of a `struct` constraint changes the meaning of `?`. But we believe we need it to faithfully express the type of common APIs such as `FirstOrDefault()`.\r\n\r\n## Opting in and opting out\r\n\r\nSome of the nullability warnings warn on code that exists without warnings today. There should be a way of opting out of those nullability warnings for compatibility purposes.\r\n\r\nWhen opting *in*, assemblies generated should contain a module-level attribute with the purpose of signaling that nullable and non-nullable types in signatures should generate appropriate warnings in consuming code.\r\n\r\nWhen consuming code references an assembly that does *not* have such a top-level attribute, the types in that assembly should be treated as *neither* nullable *nor* non-nullable. That is, neither set of warnings should apply to those types.\r\n\r\nThis mechanism exists such that code that was not written to work with nullability warnings, e.g. code from a previous version of C#, does indeed not trigger such warnings. Only assemblies that opt in by having the compiler-produced attribute, will cause the nullability warnings to happen in consuming code accessing their signatures.\r\n\r\nWhen warnings haven't been opted in to, the compiler should give some indication that there are likely bugs one would find by opting in. For instance, it could give (as an informational message, not a warning) a count of how many nullability warnings it *would* have given.\r\n\r\nEven when a library has opted in, consuming code may be written with an earlier version of C#, and may not recognize the nullability annotations. Such code will work without warning. To facilitate smooth upgrade of the consuming code, it should probably be possible to opt out of the warnings from *a given* library that will now start to occur. Again, such per-assembly opt-out could be accompanied by an informational message reminding that nullability bugs may be going unnoticed.\r\n\r\n# Libraries and compatibility\r\n\r\nAn example: In my C# client code, I use libraries A and B:\r\n\r\n``` c#\r\n// library A\r\npublic class A\r\n{\r\n  public static string M(string s1, string s2);\r\n}\r\n\r\n// library B\r\npublic class B\r\n{\r\n  public static object N(object o1, object o2);\r\n}\r\n\r\n// client C, referencing A and B\r\nConsole.WriteLine(A.M(\"a\", null).Length);\r\nConsole.WriteLine(B.N(\"b\", null).ToString());\r\n```\r\n\r\nNow library B upgrades to C# 7, and starts using nullability annotations:\r\n\r\n``` c#\r\n// upgraded library B\r\npublic class B\r\n{\r\n  public static object? N(object o1, object o2); // o1 and o2 not supposed to be null\r\n}\r\n\r\n```\r\n\r\nIt is clear that my client code probably has a bug: apparently it was not supposed to pass null to B.N. However, the C# 6 compiler knows nothing of all this, and ignores the module-level attribute opting in to it.\r\n\r\nNow I upgrade to C# 7 and start getting warnings on my call to B.N: the second argument shouldn't be null, and I shouldn't dot into the return value without checking it for null. It may not be convenient for me to look at those potential bugs right now; I just want a painless upgrade. So I can opt out of getting nullability warnings at all, or for that specific assembly. On compile, I am informed that I may have nullability bugs, so I don't forget to turn it on later.\r\n\r\nEventually I do, I get my warnings and I fix my bugs:\r\n\r\n``` c#\r\nConsole.WriteLine(B.N(\"b\", \"\")?.ToString());\r\n```\r\n\r\nPassing the empty string instead of null, and using the null-conditional operator to test the result for null.\r\n\r\nNow the owner of library A decides to add nullability annotations:\r\n\r\n``` c#\r\n// library A\r\npublic class A\r\n{\r\n  public static string? M(string s1, string s2); // s1 and s2 shouldn't be null\r\n}\r\n```\r\n\r\nAs I compile against this new version, I get new nullability warnings in my code. Again, I may not be ready for this - I may have upgraded to the new version of the library just for bug fixes - and I may temporarily opt out for that assembly.\r\n\r\nIn my own time, I opt it in, get my warnings and fix my code. I am now completely in the new world. During the whole process I never got \"broken\" unless I asked for it with some explicit gesture (upgrade compiler or libraries), and was able to opt out if I wasn't ready. When I did opt in, the warnings told me that I used a library against its intentions, so fixing those places probably addressed a bug."},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/151211451","html_url":"https://github.com/dotnet/roslyn/issues/5032#issuecomment-151211451","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/5032","id":151211451,"user":{"login":"olmobrutall","id":720953,"avatar_url":"https://avatars.githubusercontent.com/u/720953?v=3","gravatar_id":"","url":"https://api.github.com/users/olmobrutall","html_url":"https://github.com/olmobrutall","followers_url":"https://api.github.com/users/olmobrutall/followers","following_url":"https://api.github.com/users/olmobrutall/following{/other_user}","gists_url":"https://api.github.com/users/olmobrutall/gists{/gist_id}","starred_url":"https://api.github.com/users/olmobrutall/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olmobrutall/subscriptions","organizations_url":"https://api.github.com/users/olmobrutall/orgs","repos_url":"https://api.github.com/users/olmobrutall/repos","events_url":"https://api.github.com/users/olmobrutall/events{/privacy}","received_events_url":"https://api.github.com/users/olmobrutall/received_events","type":"User","site_admin":false},"created_at":"2015-10-26T17:07:42Z","updated_at":"2015-10-26T17:07:42Z","body":"@yaakov-h, I also don't see any point in ImplicitNullabeProperties, it's a bad solution but the problem remains: \r\n\r\nIf is encouraged to create POCOs with not-Nullable reference types then a non-Nullable reference type will mean just \"I promise I'll fill this property... If I've time\". \r\n\r\nIt is a very week promise, but is probably the best solution nevertheless, a more strong promise, even based on warnings, could be annoying to use. "}}