Skip to content
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

Fix failing FieldMask.Merge for well-known wrapper field types #9602

Conversation

vassilvk
Copy link

@vassilvk vassilvk commented Mar 8, 2022

Closes #9283.

@google-cla
Copy link

google-cla bot commented Mar 8, 2022

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

For more information, open the CLA check for this pull request.

@acozzette
Copy link
Member

@vassilvk Thank you for the fix! We need the CLA before we can review this, but it sounds like you're already working on sorting that out. Let me know if you need any help dealing with the CLA.

@vassilvk
Copy link
Author

vassilvk commented Mar 8, 2022

Thank you @acozzette.

We are trying to get through the process of establishing a corporate contributor CLA for my employer (and it is taking some time).
What is the best way to reach you in case we have questions?

@acozzette
Copy link
Member

You can either ask here or feel free to email me at acozzette@google.com.

@vassilvk
Copy link
Author

vassilvk commented Apr 9, 2022

@acozzette, FYI, the CLA is signed.

@jskeet
Copy link
Contributor

jskeet commented Apr 12, 2022

Do we know whether Java has the same issue? It looks like it, but I don't know if getJavaType() behaves differently for wrapper types.

@jskeet
Copy link
Contributor

jskeet commented Apr 12, 2022

Ah, I suspect that in Java it's okay because the reflection value for a wrapper field is still the wrapper, whereas in .NET it's the boxed primitive.

However, this leads to an issue in this fix - because it means that the for wrapper fields, options.ReplacePrimitiveFields is used instead of options.ReplaceMessageFields - when fundamentally it's still a message field. I'd like to see the options behave consistently.

@vassilvk
Copy link
Author

Hi @jskeet,

Please see cef9f67.

Please note that the well known wrappers are semantically primitive values in a sense that the Replace* options would have no logical bearing on the outcome of them being merged. In other words, for those fields "merge" is equivalent to "replace".

That said, the above commit moves the check into the message branch of the merge operation as per your suggestion.

@jskeet
Copy link
Contributor

jskeet commented Apr 12, 2022

I'll have a look at this tomorrow - I'm afraid I won't be able to get to it today.

@jskeet
Copy link
Contributor

jskeet commented Apr 13, 2022

Okay, I think I understand now (this whole piece of code is new to me). I think the new behavior is reasonable, but I can imagine it possibly behaving differently to the equivalent Java code. I suspect that in the Java code, the wrapper types are treated like any other message types - so a wrapper type with a value of 0 won't actually end up replacing the original value, because it will be merged field-wise, with the absent value field being ignored.

Given the way reflection for wrapper types works in C#, I suspect we don't want to replicate that behavior, but I'd like @acozzette's opinion before merging.

@vassilvk
Copy link
Author

vassilvk commented May 5, 2022

Hello @acozzette. Any update?
Thanks.

@acozzette
Copy link
Member

Sorry I missed this! I think Jon brings up an interesting point about how in Java, a zero-valued wrapper type can't override the original value. I glanced at the C++ implementation and I suspect this is true in C++ as well, and probably in just about every language, since as far as I know we didn't take any special care to handle this situation.

So unfortunately I think we probably want to imitate the Java behavior--even though it's less intuitive, I think it's best to aim for consistency across languages. Hopefully this is an easy tweak to make: I think we just want to ignore the source field in the special case where a wrapper field is present in both the source and destination but the contents of the source field are empty.

@vassilvk
Copy link
Author

vassilvk commented May 7, 2022

Thanks @acozzette.
Suggested changes are complete.

@jskeet
Copy link
Contributor

jskeet commented May 9, 2022

I'll have a look later in the week, thanks.

Copy link
Contributor

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Production code looks good to me, although it's possible that I've missed some subtlety. I think it would be good to separate out a "wrapper field with null value" test (with both ReplaceMessageFields true and false) and "wrapper field with non-null value" (where we probably don't need parameterization).

},
false);

// Make sure only the targeted fields were updated.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect this to look at Int32Field and BoolField... given that StringField is targeted, I wouldn't expect it to be asserted in this section. I suspect we effectively want two commented sections:

  • Make sure only the targeted fields were updated (Int32Field and BoolField haven't changed)
  • "Prove that ReplaceMessageFields works for wrapper types" - I'd expect the same test for both StringField and Int64Field here (see earlier comment about mixing null and non-null behavior in one test).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comment above and c4e7df1

csharp/src/Google.Protobuf/FieldMaskTree.cs Show resolved Hide resolved
}
else
{
field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString));
var sourceByteString = ((IMessage)sourceField).ToByteString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for anyone else reviewing this: the code here really hasn't changed at all other than indentation - the diff is just confusing. (It's slightly better in split mode than in unified mode, but either way it's not clear that really we're just conditionalizing the existing behavior.)

Copy link
Contributor

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge this, as I believe the production code is correct - but the test really is quite hard to understand at the moment, from the perspective of someone who has come into this "cold" as it were.

@jskeet
Copy link
Contributor

jskeet commented May 16, 2022

Ah, the CLA check is still failing - I'll need to check that manually.

@jskeet
Copy link
Contributor

jskeet commented May 16, 2022

Hmm - if you could email me (jonskeet@google.com) with details about the CLA signing (e.g. whether it's been done in a corporate way rather than for your GitHub account) that would be appreciated.

@vassilvk
Copy link
Author

vassilvk commented May 16, 2022

I believe the production code is correct - but the test really is quite hard to understand at the moment, from the perspective of someone who has come into this "cold" as it were.

Hi @jskeet - I understand.
Please see commit 9baab1f with split tests.

I'll take a look at the CLA.

@vassilvk
Copy link
Author

I see what the problem is with the CLA.
I believe it trips on your @pobox.com email identity:

image

@jskeet
Copy link
Contributor

jskeet commented May 16, 2022

Ah, right - thanks. I'll manually override that when I've reviewed the new tests.

@jskeet
Copy link
Contributor

jskeet commented May 16, 2022

Okay, the CLA manual override process has changed over time.

I suspect the simplest approach now is for you to:

  • Soft-reset to 92cdf87 (the parent of the first commit)
  • Commit all the changes in a single commit (we don't need all the history)
  • Force push

That should end up with a single commit just by you, which should avoid all the CLA problems. Sorry for the bureaucracy :(

@vassilvk vassilvk force-pushed the 9283-fieldmask-merge-for-wellknown-wrappers-2 branch from 9baab1f to 391e4ed Compare May 16, 2022 13:37
@google-cla google-cla bot added cla: no and removed cla: yes labels May 16, 2022
@vassilvk vassilvk force-pushed the 9283-fieldmask-merge-for-wellknown-wrappers-2 branch from 391e4ed to 1aed573 Compare May 16, 2022 13:41
@google-cla google-cla bot added cla: yes and removed cla: no labels May 16, 2022
@vassilvk
Copy link
Author

Hi @jskeet, no problem.
Done.

@jskeet jskeet merged commit 9d7a449 into protocolbuffers:main May 16, 2022
@jskeet
Copy link
Contributor

jskeet commented May 16, 2022

Thanks for your patience :)

@vassilvk
Copy link
Author

vassilvk commented May 16, 2022

Thanks for your patience :)

Thank you for yours too :)

@vassilvk vassilvk deleted the 9283-fieldmask-merge-for-wellknown-wrappers-2 branch May 16, 2022 14:44
@olivierl
Copy link

Hi, do you know when this fix will be released? Thx

@jskeet
Copy link
Contributor

jskeet commented Jun 22, 2022

@olivierl: I don't think it made it into 3.21.x, so I'm afraid you'll need to wait for 3.22.x. I don't have a timeline for that.

@fowles
Copy link
Member

fowles commented Jun 22, 2022

Not committing to anything, but I anticipate 22.x will happen in 2 or so months.

@RogerioWagner
Copy link

Hi, the issue of FieldMask.Merge with Wrappers has been fixed, or is there any workaround to create path's for wrappers? I just experienced same behavior reported here: #9283 (comment)

@jskeet
Copy link
Contributor

jskeet commented Jun 20, 2024

@RogerioWagner: I've just tried the exact repro in #9283, and it's fine using the latest NuGet package. So either you're not actually experiencing the same behavior (i.e. your situation is different in a way we can't know) or you're using an old version where this was still broken. I suggest you file a new issue with a minimal repro.

@RogerioWagner
Copy link

RogerioWagner commented Jun 20, 2024

@jskeet, you were right, it's a different behavior after I bumped the version to 3.26.0, now instead of an exception, it failed silently with a warning at MaskFilter ({ "@warning": "Invalid FieldMask", "paths": [ "description", "usage", "templateId" ] }), where description is an optional string, usage is an enum and templateId is an optional google.protobuf.Int32Value.

@jskeet
Copy link
Contributor

jskeet commented Jun 20, 2024

@RogerioWagner: Rather than half-describing the problem in a merged PR, please file a new issue with a minimal but complete repro.

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

Successfully merging this pull request may close these issues.

FieldMask.Merge not worked with WellKnownTypes wrappers
7 participants