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
Add null checks to GetOption #7130
Add null checks to GetOption #7130
Conversation
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
Outdated
Show resolved
Hide resolved
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.
Using default(T) works fine for our use cases.
I also just ran into that problem and would like the idea of having |
@Falco20019 I suggest we merge this, but also add the I'm not familiar with the release pipeline here or how to push this along. I think @ObsidianMinor is king. |
@thesoftwarejedi I'm working on a TryGet PR right now. Also I don't work at Google, I'm just a friendly OSS contributor. Ask @jtattermusch. |
Perfect, thanks! Saved my weekend ;)
I figured as much. Thanks for your contributions! |
Is there any way I can help with getting this checked in? This and the |
var options = Proto.Options; | ||
if (options == null) | ||
{ | ||
return default(T); |
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.
I don't think returning default(T)
is a good idea. It seems that for e.g. extension of type int, you won't be able to differentiate between the option not being present at all (which should return null) and the option having value of 0.
Given that the GetOption API is relatively new (and looks like it has been kind of broken for the very beginning), should we just introduce TryGetOption methods instead, to keep the semantics clean?
the GetOption can either be removed (that should be fine because it was only introduced as part of the proto2 work and we've marked proto2 support as "experimental") or it can just throw an exception if the value is not present (which renders it not very useful, so we might wanna mark it as obsolete?).
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.
Returning default(T)
is a mistake. It should be the default value for the extension.
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.
Still, even if you return extension.DefaultValue
, it is unclear to me how I would distinguish between 1. the option not being present at all and 2. the option being present and being set to the default value (AFAIK options/extensions don't need to have default values).
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.
You can't, which is why #7282 was made. Should we also add a HasOption
API to go with it?
@@ -184,13 +184,21 @@ public void NoOptions() | |||
var fileDescriptor = UnittestProto3Reflection.Descriptor; | |||
var messageDescriptor = TestAllTypes.Descriptor; | |||
Assert.NotNull(fileDescriptor.CustomOptions); | |||
Assert.AreEqual(fileDescriptor.GetOption(Fileopt), null); |
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.
why are we intermixing the GetOption tests with a CustomOptions-related test (and all that in CustomOptionsTest.cs).
I think it would be better if GetOption methods was tested in a separate test case.
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.
I thought that even though the GetOption APIs weren't in a "CustomOptions" type they still worked with "custom options" so it'd be easier to test everything relating to custom options together.
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.
I'm probably fine keeping the test in the same file (CustomOptionsTest.cs), but we should at least put them in a separate test case (method) as I proposed. I think like this the test will be pretty hard to maintain and refactor
(and frankly it's not clear to me why Assert.NotNull(messageDescriptor.Fields[1].CustomOptions);
and Assert.AreEqual(messageDescriptor.Fields[1].GetOption(Fieldopt), null);
would be related.
Wouldn’t it be nicer if |
@@ -555,7 +555,13 @@ public override string ToString() | |||
/// </summary> | |||
public T GetOption<T>(Extension<FileOptions, T> extension) | |||
{ | |||
var value = Proto.Options.GetExtension(extension); | |||
var options = Proto.Options; | |||
if (options == null) |
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.
actually, looking at the whole options API in protobuf C# from a more high level perspective, we only need to check whether options == null
because e.g. the FileDescriptor doesn't provide access to the file options (the property FileDescriptor.Proto is internal, so one cannot access to FileDescriptor.Proto.Options).
This actually has several downsides:
- we need extra logic such as a GetOption methods, instead of just exposing e.g. the FileOptions msg (or other options) on a descriptor and letting the user use GetExtension methods the same way they would do it for any other message with extensions (which is the way reading custom options is done for e.g. C++, Java and python, as documented here: https://developers.google.com/protocol-buffers/docs/proto#customoptions)
- because the "options" messages are not exposed, one cannot access the standard (=the non-extension ones) options at all.
This makes me think that the whole GetOptions() and TryGetOption() API is a bad idea and we should rather focus on how to expose the descriptor.Options property safely, because then we wouldn't need to worry about the GetOptions/TryGetOptions etc. API's at all (they could be removed and everyone could just rely on standard extension APIs).
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 only way we could expose descriptor.Options safely is by deep cloning it (since all the protos are supposed to be immutable). With these APIs the only thing we need to clone is the value, if we need to clone it at all.
C++ has const references, Java has readers, and Python parses a local options value (but allows mutation).
Maybe we could do it like Python but, again, we'd still clone the whole options message once for every unique descriptor on get.
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.
Maybe the deep clone isn't really all that problematic. Most projects would only have a few options in place and it seems fair to require users to cache the results they got from e.g. messageDescriptor.Options if they wanted to process a large number of options.
It seems to me that just exposing description.Options (as a defensive copy) would lead to a more logical and straightforward API (with smaller footprint and fewer duplications, and also more similar to what other languages do) - WDYT?
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.
Could we make a internal wrapper class that exposes the IExtendableMessage API but only implements the pure functions? Then GetOptions would just return an IExtendableMessage implementation.
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.
But you actually want to be able to access e.g. the FileOptions' properties (the "standard" options, such as "java_package" etc.)
see
public string JavaPackage { |
Superseded by #7434 (GetOption API is now obsolete). |
Fixes #7127
Null continues to erode away at my soul.
Should we add a TryGetOption API in another PR to better match up with the CustomOptions API @jtattermusch?