-
Notifications
You must be signed in to change notification settings - Fork 15.7k
Add option to enable csharp nullable reference types #13218
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
Conversation
Thank you for all your work on this, but we're still discussing whether or not we want to include nullable reference types in code generation at all. Please see grpc/grpc#33507 where there is considerable discussion. (In general we try to avoid adding language-specific command line options - when to use NRTs is one of the key design discussions here. It feels like a new option might be the only way to go as different consumers may want to generate code for the same proto differently, but it's not certain yet.) |
No problem, I understand. By the way, I used (I don't think I need to push anything else to that PR anyway until a decision has been taken, the implementation and the tests already shows whats this solution implies and its benefits) |
The code generation needs further changing to fully support handline NRT. |
I don't think it does. As the MS documentations says:
This means that, given that the reference types are currently considered non-nullable, enabling NRT is about adding annotations so that a user knows that he might assign a null and trigger NREs. What you describe with "allowing message fields to be set to null, Equals, MergeFrom, and removing redundant null checks elsewhere" is not about NRT but rather about allowing fields to be set to null. NRT annotations can be added on top, but again, it is different. This PR is about adding NRT annotations so that users can consume this API while avoiding NREs, without any change to the generated code, unlike allowing fields to be set to null. Personally, I am not against also allowing reference fields to be set to null, but it is another topic (and more debatable). |
@ogxd Fields within a message that themselves are messages can be assigned null. message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
PHONE_TYPE_UNSPECIFIED = 0;
PHONE_TYPE_MOBILE = 1;
PHONE_TYPE_HOME = 2;
PHONE_TYPE_WORK = 3;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
PhoneNumber phone = 4; // this is a message and can be assigned null
} with code: Person person = new Person()
{
Id = 1234,
Name = "Fred",
Email = "fred@example.com",
Phone = null
}; compiles today without your changes. Setting But with your changes:
There is more involved than just adding |
We don't want NRT warnings within the generated code
I've just updated the PR to use |
One special case that I think might have been missed (only gave it a quick glance) is |
bool serializable; | ||
// Whether the generated files are annotated with "#nullable enable" to display warnings on potential null assignments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment still only talks about #nullable enable
, which is not true anymore.
I think there is a lot more work to be done. Adding As an experiment I took the code generated from unittest_well_known_types.proto and added |
@@ -79,6 +79,8 @@ bool Generator::Generate(const FileDescriptor* file, | |||
cli_options.internal_access = true; | |||
} else if (options[i].first == "serializable") { | |||
cli_options.serializable = true; | |||
} else if (options[i].first == "enable_nullable_reference_types") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be for a shorter option name. Microsoft just uses "nullable" (<Nullable>
and #nullable
) so maybe we should? Or "nullable_reference_types"? Whatever is chosen it would be good if both the protocol buffers compiler and gRPC plugin use the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the requests that we've had for generating int?
-backed properties for optional fields, I think just "nullable" could lead to confusion.
How about enable_nrt
? I think NRT is a reasonably commonly-used abbrevation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it is too long and can be confusing, enable_nrt
seems more appropriate
As the docs says about NRT, its purpose is to "minimize the likelihood that your code causes the runtime to throw System.NullReferenceException". I think the point here is that the autogenerated code is not "your" code (you don't edit it, you don't maintain it, it is simply an external API generated by an external tool). This is why I think there shouldn't be warnings from NRT within the generated code itself, and one of the reasons why ![]() |
Just my opinion (I'm not part of the protocol buffers team) but getting the generated code to be "clean" will probably help in not missing any corner cases, such as those arising with well-known types such as |
What are the warnings? Nullable warnings shouldn't come from generated code. I'm guessing the problem is Google.Protobuf itself isn't annotated. |
A lot of warnings about setting null in reflection methods and classes such as Also warnings in constructors when fields contain null values, e.g. Also warnings in I know that we could use |
It sounds like these nullable warnings are related to the generated code. In that case, generated code should be updated to fix the warnings. For example, allow things to be null where necessary. Or add the bang operator to suppress errors when there is no other choice. I suggest putting a project with a copy of the generated code (copied from the temporary obj directory) in a GH repo. Then other people can see the errors you're talking about. Right now, we have no idea. That would allow us focus to be on what generated code with nullable annotations should look like. Then once that is decided, update protoc generation to output it.
protobuf/csharp/src/Google.Protobuf.Test.TestProtos/UnittestWellKnownTypes.pb.cs Lines 2250 to 2252 in a80daa2
Messages implement public bool Equals(OneofWellKnownTypes? other)
{
// ...
} |
Just to chime in, I agree that the generated code should build warning-free. It sounds like some more work is needed - but thanks for what you've already done, and I look forward to looking more closely when it's fully "done". |
Thanks for the feedback! I just became dad on the 4th of August so my days were a little busy since but if the nullable topic hasn't addressed in parallel since I'll update the PR accordingly to your remarks when I have some time 😄 |
I'm very excited about getting this into the tooling so if there is anything I can do to help or speed things along, please let me know :) |
--csharp_out=csharp/src/Google.Protobuf.Test.TestProtos \ | ||
--csharp_opt=file_extension=.pb.cs \ | ||
--csharp_opt=enable_nullable_reference_types \ | ||
--descriptor_set_out=csharp/src/Google.Protobuf.Test/testprotos.pb \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overwriting of testprotos.pb here causes the tests in DescriptorDeclarationTest.cs to fail
TestNrtNestedMessage message_field = 1; | ||
string string_field = 2; | ||
int32 int32_field = 3; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code generated for unittest_nrt_proto3.proto causes RefStructCompatibilityTest.cs tests to fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This version of the files causes tests in DescriptorDeclarationTest.cs to fail
I've started looking at this in more detail to see if I can complete the work. |
We triage inactive PRs and issues in order to make it easier to find active work. If this PR should remain active, please add a comment. This PR is labeled |
We triage inactive PRs and issues in order to make it easier to find active work. If this PR should remain active or becomes active again, please reopen it. This PR was closed and archived because there has been no new activity in the 14 days since the |
Related to:
Context
Nullable reference types introduce a nullability context. Currently, the C# code generated via protobuf does not allow null for reference types and will intentionally throw at runtime when a null is assigned to such field.
Because generated files are annotated with the
// <auto-generated>
attribute on top, nullable reference types won't work, even if NRT is explicitly enabled at the project level for instance.The consequence of this is that there is no way to benefit from NRT static code analysis to know in advance where fields could be assigned with a null (and thus it would throw at runtime)
Solution
A first solution would be to allow removing the
// <auto-generated>
attribute, however, it has the unwanted side effects of enabling code analysis on the generated file itself, which we want to avoid.Instead, it turns out that marking generated files with
#nullable enable
works.The following PR is about this: Adding a CLI option to add
#nullable enable
on top of generated files.The unit tests show that the code generated with that option now carries the nullability information so that we have a warning for any consuming code that may assign nulls (CS8601).
Compatibility
Files annotated with
#nullable enable
can still compile fine under versions of dotnet prior to 6.0, so it is a backward-compatible change.Also, fields are still annotated as non-nullable, so I believe this feature does not break compatibility.
Todo
I haven't made any changes to the documentation yet as I would like feedback first.
CLA in progress.