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: handle nullability for optional fields #2011

Merged

Conversation

martin-traverse
Copy link
Contributor

@martin-traverse martin-traverse commented Jul 24, 2024

BEGIN_COMMIT_OVERRIDE
fix: handle nullability for optional fields
END_COMMIT_OVERRIDE

@martin-traverse
Copy link
Contributor Author

This PR is to address #1779. I believe it is a more complete solution than my old PR, #1780.

The expected behaviour IMO is that fields marked as optional (or fields in a one-of) should have nullable type hints and a null default, while fields marked as required (proto2) or with no annotation (proto3) should be marked not-nullable and have a zero default. In proto3, although technically everything is optional, the protobuf system should provide a default value making it not-null in practice if the optional flag is not set, which is consistent with other implementations.

After getting more into the code, the real culprit here is the Field rule. A rule of "proto3_optional" or "optional" can get set in the parser, but these both get converted to "optional" and then ultimately discarded, before reaching the generator. To avoid losing this information in proto3, I added a proto3Optional member to the Field class which gets set before the rule is discarded. I also made the parser store the proto syntax as an option on the Root object during the parse. With these two bits of information together, there is enough info to determine what is optional in the static generator.

The new functionality is behind a --force-optional flag, which sets nullability based on the optional keyword. The default is false for now, but it could be changed later. I added a couple of tests to show this is working as expected. I chose that name to match the existing flags but happy to change to whatever you think will be most clear / consistent.

Lastly, I have a question about JSON descriptors. Are these always created by running the parser and storing the result? If so they should load back in with the syntax option and work as expected. If there is another way of creating these objects that doesn't go through the parser, the syntax option will be missing and the static generator would fall back on proto2 syntax.

Please do let me know your thoughts on this when you get time. I'm still very keen to get this fix into my project, if there are things you want tweaking / adding I'm happy to do it.

@martin-traverse
Copy link
Contributor Author

Thank you to whoever approved the CI workflows. There were a few lint errors, I have fixed and re-pushed, could you approve the workflows to run again?

cli/targets/static.js Outdated Show resolved Hide resolved
cli/targets/static.js Outdated Show resolved Hide resolved
cli/targets/static.js Outdated Show resolved Hide resolved
cli/targets/static.js Outdated Show resolved Hide resolved
@martin-traverse
Copy link
Contributor Author

Thanks for all the feedback. I believe I have addressed all the comments, changes are pushed. I believe all the tests / linting should be up to date now if you want to re-run CI.

After thinking about the "new behaviour" / "old behaviour" comment, I changed the guard flag to --null-semantics. I think this is more descriptive. With this flag, the nullability of fields in messages / interfaces reflects the semantics described in the proto file. This allows bringing in various fixes under the one flag. In particular, I have folded in the change from PR #2013 (issue #2012) regarding nullability when message types are composed. It gets away from having multiple flags, keeps backwards compatibility and keeps open the option to make semantic nulls the default in future if people think that is the right thing to do. If you're happy to take this as one change for Semantic Nulls, I can close issue #2012 and PR #2013 as these would no longer be needed.

Lastly on the coming changes for edition syntax. These methods will for sure need updating. Happy to help at that point if that is useful. Also I left an issue on the protobuf GitHub page requesting the optional keyword be maintained as syntactic sugar for [features.field_presence = EXPLICIT]. After spending some time working with this, I believe nullability is a core part of the semantic type definition, not a technical annotation for how a type should be handled. Time will tell, whether anyone on the protobuf team agrees! Here is the link: protocolbuffers/protobuf#17624

Copy link
Contributor

@danielbankhead danielbankhead left a comment

Choose a reason for hiding this comment

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

LGTM, I'd like a second opinion from @dcodeIO, if possible. I'm still familiarizing myself with the library.

cli/targets/static.js Outdated Show resolved Hide resolved
Co-authored-by: Daniel Bankhead <dan@danielbankhead.com>
@martin-traverse
Copy link
Contributor Author

Thanks @danielbankhead - I committed the update you suggested.

@martin-traverse
Copy link
Contributor Author

martin-traverse commented Jul 26, 2024

One more update. I've let implicit optional fields (proto3), repeated fields and maps be omitted from the properties. However, if specified, these fields must have a non-null value. I think this is more consistent with protobuf semantics, that assigns default values to these fields if they are missing. This only affects calls to create() / new, implicit fields are non-null once they are constructed.

Copy link

@sofisl sofisl left a comment

Choose a reason for hiding this comment

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

LGTM, with a nit about curly brackets. Also @dcodeIO, would love a second pair of eyes on this as well. TY!

@martin-traverse
Copy link
Contributor Author

Thanks @sofisl, I have updated all the code touched by this change to use braces for every if/else statement

@martin-traverse
Copy link
Contributor Author

Hello - I have closed PR #2013 as that change is now fully covered by this PR. Also I have marked all the conversations resolved since those changes are all pushed.

I believe we just need a final review now? @dcodeIO - Please let me know if there is anything you would like added / removed / changed, very happy to do the work and get this closed off!

@danielbankhead danielbankhead merged commit 59569c1 into protobufjs:master Aug 13, 2024
6 checks passed
@martin-traverse
Copy link
Contributor Author

Thank you @danielbankhead for merging this! Was very happy yesterday when I saw it had gone in.

Do you have any idea yet what the schedule is for the next release?

@martin-traverse martin-traverse deleted the fix/typescript_optional_support branch August 15, 2024 09:12
@sofisl sofisl changed the title Fix / Handle nullability for optional fields fix: handle nullability for optional fields Aug 16, 2024
@danielbankhead
Copy link
Contributor

@martin-traverse we're working on cutting a release on Monday:

@martin-traverse
Copy link
Contributor Author

Hi @danielbankhead - it looks like this release didn't get to NPM yet, is there another step that needs to happen?

I saw the Github release went out on Friday, but NPM is still showing v7.3.2 as the latest version.

@danielbankhead
Copy link
Contributor

Hey @martin-traverse, we just published:

@martin-traverse
Copy link
Contributor Author

Thank you so much! I have updated our mainline with the new package.

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

Successfully merging this pull request may close these issues.

3 participants