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: oneof=union breaks wrapper types #458 #462

Merged
merged 11 commits into from
Jan 5, 2022
Merged

fix: oneof=union breaks wrapper types #458 #462

merged 11 commits into from
Jan 5, 2022

Conversation

boukeversteegh
Copy link
Collaborator

@boukeversteegh boukeversteegh commented Jan 4, 2022

Tests and fixes the problem that the unwrap functions need to consider oneof=union option. See #458 .

  • Added this case to the existing integration test that has oneof=unions enabled.
  • Fixed a few small issues here and there on the way
    • my original unwrapping code accidentally used the fieldName to read properties from the passed object, instead of the jsonName. fixed.
    • made the generated code somewhat more readable by moving screen-filling boilerplate into a method (isObject and isSet)

During implementation, I found out that protobuf.js doesn't unwrap Value objects, so it becomes incompatible.
Originally, I wanted to solve this with an extra option to ts-proto, that 'unwraps' Value objects within fromJSON, but it made things complicated and broke other things, so I decided to keep things simple for now and just work around the issue in the test itself.

@boukeversteegh boukeversteegh linked an issue Jan 4, 2022 that may be closed by this pull request
@boukeversteegh boukeversteegh marked this pull request as ready for review January 5, 2022 10:23
Copy link
Owner

@stephenh stephenh left a comment

Choose a reason for hiding this comment

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

Few minor questions but overall looks great, thanks @boukeversteegh !

@@ -78,7 +78,9 @@ export const BatchQueryRequest = {

fromJSON(object: any): BatchQueryRequest {
const message = createBaseBatchQueryRequest();
message.ids = (object.ids ?? []).map((e: any) => String(e));
if (Array.isArray(object?.ids)) {
Copy link
Owner

Choose a reason for hiding this comment

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

I like the isSet helper method, but for the arrays, @webmaster128 had just done quite a bit of work to rework all of our assignments to be one-liners, with the idea of a) minimizing code size, and b) eventually turning these into object literals, like:

return {
  a: ...,
  b: ....,
  c: ...,
}

Which in theory should (with some handwaving) help v8 optimiziations.

Can we keep the previous ?? [] approach?

Copy link
Collaborator Author

@boukeversteegh boukeversteegh Jan 5, 2022

Choose a reason for hiding this comment

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

I see, so the idea is to get rid of the createBase...() call?

It made me wonder already why the default values for each field were overwritten each time. Thanks for clarifying, now it makes more sense.

I'll change back to the ternary style.

Copy link
Owner

Choose a reason for hiding this comment

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

I see, so the idea is to get rid of the createBase...() call

Yep! @aikoven has an in-flight PR that is either doing this soon, or at least moving us closer to that I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@stephenh Ah I didn't know @aikoven was already working on this. So I actually just made a PR #463 to do what you described. It was kind of simple as I was already deep into this part of the code, and had a solution in mind for the trickier 'oneof' case.

So, @aikoven, if you also had something in the works and want to finish that instead, please close my PR. Sorry if I got in the way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for adapting. Yeah the overall direction is to move away from mutability where you never know for sure what is set when. So a lot of recent work sent into exactly one assignment per field.

This PR nicely adds to that journey

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, so the idea is to get rid of the createBase...() call?

We can easily get rid of it in fromJSON and fromPartial. For decode it will probably still be required as we are reading the serialized data into the object in unknown order and not all fields are serialized.

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, for decode I was thinking that calling createBase() to get an object literal with every field assigned (right now I don't think it does literally every field, just some primitives), and then have the decode for loop just assign over existing keys, would be most likely to trigger the supposedly / I'm-admittedly-cargo-culting-a-little-bit optimization of "create objects with all keys set and then don't add/remove keys". :-)

Copy link
Collaborator

Choose a reason for hiding this comment

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

right now I don't think it does literally every field, just some primitives

That changed in #457, see e.g. #457 (comment). Now all fields are set to default and decode can override them.

Copy link
Owner

Choose a reason for hiding this comment

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

Ah great!

);

const isSet = conditionalOutput(
'isSet',
Copy link
Owner

Choose a reason for hiding this comment

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

Nice

chunks.push(code`unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
if (ctx.options.oneof === OneofOption.UNIONS) {
chunks.push(code`unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
if (message.kind?.$case === 'nullValue') {
Copy link
Owner

Choose a reason for hiding this comment

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

Ah sure, this makes sense...

Fwiw I was looking for a struct.ts that had this code in it, but don't see it ... looks like you did change the oneof-union test case to use a struct, but maybe the new google/protobuf/struct.ts just didn't get checked in as part of the PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great catch! Wow :-) Just pushed it.

Copy link
Owner

@stephenh stephenh left a comment

Choose a reason for hiding this comment

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

Looks great, thanks @boukeversteegh !

@boukeversteegh boukeversteegh merged commit dd16992 into main Jan 5, 2022
@boukeversteegh boukeversteegh deleted the fix/458 branch January 5, 2022 16:39
@boukeversteegh
Copy link
Collaborator Author

Looks great, thanks @boukeversteegh !

Thank you for your review and coordination!

stephenh pushed a commit that referenced this pull request Jan 5, 2022
## [1.97.1](v1.97.0...v1.97.1) (2022-01-05)

### Bug Fixes

* oneof=union breaks wrapper types [#458](#458) ([#462](#462)) ([dd16992](dd16992))
@stephenh
Copy link
Owner

stephenh commented Jan 5, 2022

🎉 This PR is included in version 1.97.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@@ -81,3 +80,7 @@ if (util.Long !== Long) {
util.Long = Long as any;
configure();
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
Copy link
Collaborator

@webmaster128 webmaster128 Jan 5, 2022

Choose a reason for hiding this comment

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

Now that this is pulled out into a nicely named helper, you can even use value != null.

Do we need type narrowing? You can do something like

function isSet<X>(value: X  | undefined | null): value is X {
  return value != null;
}

Comment on lines +466 to +468
function isObject(value: any): boolean {
return typeof value === 'object' && value !== null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

Just in case type narrowing matters at some point, have a look at this implementation I wrote for a different project: https://github.com/cosmos/cosmjs/blob/v0.27.0-rc2/packages/utils/src/typechecks.ts#L1-L12

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

Successfully merging this pull request may close these issues.

google.protobuf.NullValue in oneof
3 participants