-
Notifications
You must be signed in to change notification settings - Fork 76
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
field: fix missing optional support #95
Conversation
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Reviewable! @dkunitsk @akonradi \cc @glerchundi |
Good job @sarthak40 LGTM |
Hm, I attempted integrating this with Currently the generated code is just rendering all I'll mark this as a draft for now, till I can make the required changes. Since this is a generation framework, I don't have to worry about updating any templates thank fully (yet).. |
Hey @sarthak40. Yes, thank you for pointing this out. The Implementing Proto3 Presence doc gives great advice for what API changes are needed. Of particular significance is Google's suggestion (per my reading) that, because Synthetic Oneofs are still oneofs, helpers like This means in PGV we'd have to switch to these new I'm weighing this suggestion with the more obvious and backward-compatible approach of just making the existing |
@dkunitsk are you certain that we need to expose synthetic oneofs? According to the doc:
|
Given the above, I think the
Since field presence in WDYT? |
Re the quote: I believe that's more in the same vein as the default codegen not outputting I think I've had some understanding here, and it starts with #85 being the wrong thing to do. PGS cannot support Optional, only the end user of PGS (the plugin author) can do that. As such, the right approach is to allow the user to set this on their end once they are ready to differentiate synthetic and real oneofs. This could easily be done with very minor modification to Generator (a new Line 29 in 8bc36ed
Now that the onus of Optional support is on the plugin author, the question above IMO is more clear: if the user has to indicate that they support Optional, they also indicate that they know about synthetic oneofs and have updated their codegen appropriately. This means the PGS AST can stay "correct" (i.e. actually reflecting the descriptor). We can/should still provide additional helpers though, as suggested in the doc. This treatment also inherits the positives of the synthetic oneof approach,
|
@dkunitsk thanks for looking into this! I really like the idea for the users to be able to define (through an I'll work on the updated helpers later today as well and we can get this moving. |
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
\cc @dkunitsk @glerchundi @akonradi Round 2 👍 |
@dkunitsk polite ping, have you had a chance to review this yet? |
I now have a version of PGV rendering with these changes locally! @dkunitsk @akonradi if one of you could get this reviewed we can start on resolving bufbuild/protoc-gen-validate#431 👍 |
I'm not familiar enough with how PGS abstracts over proto2&3 to have opinions on the API (mostly regarding |
Thanks for these changes. Will review in the morning. |
@dkunitsk did you get a chance to review? |
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 apologies for being late in reviewing this. This seems to be very nearly there. Please take a look at my feedback and let me know what you think. Many of these comments I'm happy to discuss further.
field.go
Outdated
@@ -56,11 +69,31 @@ func (f *field) SourceCodeInfo() SourceCodeInfo { return f.info } | |||
func (f *field) Descriptor() *descriptor.FieldDescriptorProto { return f.desc } | |||
func (f *field) Message() Message { return f.msg } | |||
func (f *field) InOneOf() bool { return f.oneof != nil } | |||
func (f *field) InRealOneOf() bool { return f.InOneOf() && !f.desc.GetProto3Optional() } |
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.
super nit: move this method below. This top section has the "really obvious" method implementations, and the methods below are the more interesting ones. This one's more interesting IMO.
init_option.go
Outdated
@@ -47,3 +48,12 @@ func FileSystem(fs afero.Fs) InitOption { return func(g *Generator) { g.persiste | |||
func BiDirectional() InitOption { | |||
return func(g *Generator) { g.workflow = &onceWorkflow{workflow: &standardWorkflow{BiDi: true}} } | |||
} | |||
|
|||
// SupportProto3Optional adds support for proto3 field presence. | |||
// See: https://github.com/protocolbuffers/protobuf/blob/master/docs/implementing_proto3_presence.md#signaling-that-your-code-generator-supports-proto3-optional |
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.
point to a commit or version instead of master, as that can change
proto.go
Outdated
// Proto3 syntax permits the use of "optional" field presence. Non optional fields default to the zero | ||
// value of that particular type if not defined. | ||
// Most of the field types in the generated go structs are value types. | ||
// See: https://github.com/protocolbuffers/protobuf/blob/master/docs/field_presence.md#presence-in-proto3-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.
Same nit about master -> version url
data, err = proto.Marshal(&plugin_go.CodeGeneratorResponse{}) | ||
if err != nil { | ||
var supportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) | ||
if data, err = proto.Marshal(&plugin_go.CodeGeneratorResponse{ |
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.
IIUC this again makes PGS by default claim to support CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL
. My suggestion was to create a new InitOption
type (in init_option.go
), which will set the generator's persister's supportedFeatures variable. This gives full control to the user.
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.
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've changed the behaviour in init_option.go
, though i've left it as-is in protoc-gen-debug
as the latter is a dev tool.
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.
Makes sense to me - but perhaps leave a comment to that effect.
field.go
Outdated
return true | ||
} | ||
|
||
func (f *field) Optional() bool { |
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.
To be extra clear and follow the guidelines more precisely, perhaps we should call this HasOptionalKeyword()
Would be awesome to get this merged to help resolve bufbuild/protoc-gen-validate#431 |
@dkunitsk thanks for the review! I haven't been able to find the time to work on this, but i'll see if I can muster up something this week. |
Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
@dkunitsk ICYMI, this is ready for review 👍 |
Guys, may I ask status of this MR? We are building a new transport stack in our company and want to combine both validation and presence tracking support in our proto definitions. And this seems to block validation support and all our concepts as well. cc: @xmlking, @glerchundi, @dkunitsk |
@dkunitsk if you could please take another look at this, i'd appreciate it. |
@@ -4,8 +4,7 @@ go_import_path: github.com/lyft/protoc-gen-star | |||
|
|||
env: | |||
matrix: | |||
- PROTOC_VER="3.5.1" |
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 please keep the other ones and just add on 3.16.0
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 may need to use https://mickey.dev/posts/go-build-tags-testing/ (for example) to mark which tests apply to the older protoc versions versus newer. But I think I really don't want to lose test coverage for the old versions, esp since it should be relatively easy to keep them. 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.
Hmm, this is turning out to be tricker than I anticipated.
I need to first edit the makefiles to ensure they don't compile the proto3 optional bits, then ensure I add the build tags for the golang tests themselves..
I am testing by isolating the testing harness into testdata/proto3_presence
for now, but let me know if you prefer a different name.
field_test.go
Outdated
f.addType(&mapT{repT: &repT{scalarT: &scalarT{}}}) | ||
assert.False(t, f.HasPresence()) | ||
|
||
f.msg = dummyMsg() |
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.
IIUC this shouldn't be necessary according to
Line 192 in 8bc36ed
m.addField(f) |
field_test.go
Outdated
assert.True(t, f.HasPresence()) | ||
} | ||
|
||
func TestField_Optional(t *testing.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.
nit: TestField_HasOptionalKeyword
field_type.go
Outdated
@@ -84,7 +83,7 @@ func (s *scalarT) Element() FieldTypeElem { return nil } | |||
func (s *scalarT) Key() FieldTypeElem { return nil } | |||
|
|||
func (s *scalarT) IsOptional() bool { | |||
return !s.fld.Syntax().SupportsRequiredPrefix() || s.ProtoLabel() == Optional | |||
return s.fld.HasOptionalKeyword() |
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 would make all proto3 fields without presence suddenly become IsOptional() == false
which is very backwards incompatible.
Proto3 optional label is not the same as proto2. The proto2 label means "this field is not required" whereas the proto3 label means "this field is still not required, but it has explicit presence semantics".
This IsOptional
in particular is referring to the field not being required, which is not affected by the explicit presence semantics. So IIUC this method does not need to change.
lang/go/type_name_test.go
Outdated
@@ -88,19 +88,40 @@ func TestType(t *testing.T) { | |||
{"Proto3.string", "string"}, | |||
{"Proto3.bytes", "[]byte"}, | |||
{"Proto3.enum", "Proto3_Enum"}, | |||
{"Proto3.ext_enum", "ptype.Syntax"}, | |||
{"Proto3.ext_enum", "typepb.Syntax"}, |
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.
hmm why did this work before...
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 not actually sure on this one, a bit perplexed myself
// value of that particular type. Most of the field types in the generated go | ||
// structs are value types. | ||
// See: https://developers.google.com/protocol-buffers/docs/proto3 | ||
// Proto3 syntax permits the use of "optional" field presence. Non optional fields default to the zero |
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 think this explanation slightly confuses optionality as the opposite of requiredness versus optionality as having explicit presence. Even with explicit presence it is true that Proto3 syntax only allows for optional fields
.
I would recommend rewording to something like
Proto3 syntax only allows for optional fields, and it defaults to the zero value of that particular type (unless experimental support for explicit presence tracking for singular proto3 fields is used, see <link>).
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.
(unless experimental support for explicit presence..
According to protobuf v3.15.0 field presence support in proto3 is no longer experimental.
data, err = proto.Marshal(&plugin_go.CodeGeneratorResponse{}) | ||
if err != nil { | ||
var supportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) | ||
if data, err = proto.Marshal(&plugin_go.CodeGeneratorResponse{ |
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.
Makes sense to me - but perhaps leave a comment to that effect.
@@ -4,8 +4,7 @@ go_import_path: github.com/lyft/protoc-gen-star | |||
|
|||
env: | |||
matrix: | |||
- PROTOC_VER="3.5.1" |
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 may need to use https://mickey.dev/posts/go-build-tags-testing/ (for example) to mark which tests apply to the older protoc versions versus newer. But I think I really don't want to lose test coverage for the old versions, esp since it should be relatively easy to keep them. WDYT.
@sarthak40 huge thank you for the diff. I left some last comments. I am confident that after one more iteration it will be fully there. I know most of the delay has been on my end, but I can promise that once you address the above, I will re-review and merge right away. |
@dkunitsk thanks for the review! Let me take a crack at your comments over the weekend and get the ball rolling! |
- revert travis coverage for older protoc versions - revert backwards incompatible change for isOptional - nit/style fixes Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
This change splits the testing harness for proto3 field presence, so we can retain functional test coverage for older protoc versions that do not support proto3 field presence. IE >v3.15.0 Signed-off-by: Sarthak Gupta <signed@sarthak.sh>
@dkunitsk addressed most of your comments, ready for round 4 👍 |
This is really close, great job @sarthak40!! Ultra friendly ping ICYMI @dkunitsk, and hoping that it's not gonna take too much of your time 🤞 |
@dkunitsk friendly ping please |
@sarthak40 brilliant work. Thank you for pushing this through. It is available here: https://github.com/lyft/protoc-gen-star/releases/tag/v0.6.0. One ask: do you think you can make a README change to tell folks how to use this and just to advertise the existence of support for field presence in PGS? |
Fantastic! Likewise, thanks a lot for the reviews, we got it there in the end 😎. I will be looking at PGV code over the weekend to get that moving now, but can also submit a README update for PGS at the same time. |
Fixes #94 & #75.
Adds support for
optional
field presence in theproto3
syntax.