Skip to content

feat(dart): use proto presence info to generate null-safe code#2726

Merged
devoncarew merged 5 commits intogoogleapis:mainfrom
devoncarew:dart_null_safety
Oct 31, 2025
Merged

feat(dart): use proto presence info to generate null-safe code#2726
devoncarew merged 5 commits intogoogleapis:mainfrom
devoncarew:dart_null_safety

Conversation

@devoncarew
Copy link
Copy Markdown
Contributor

Use proto presence info to generate null-safe code:

  • annotateField updated to use information from proto optional annotations
  • toJson() implementations write values if they're nullable and have a non-null value, or if they're not nullable and have a non-default value for that type
  • fromJson parses a value for parse a value for nullable fields if one is given; it will parse any value for non-null field or fall back to using the default value for that type
  • properly dereference null values when referring to request fields for use in url paths
  • enums definitions now get a default value (EnumClass.$default)
  • removed the older annotate.requiredFields and tests (based on usage in service calls)
  • closes use the proto 'optional' keyword and proto3 field presence rules for field nullability google-cloud-dart#24

Tests are updated for buildQueryLines() to include optional fields. Tests were added for the createFromJsonLine() and createToJsonLine() methods; previously we got our testing here from being on the same repo as the dart code, and running the dart unit tests on the generator's putput.

In-lined from the PR:

  • proto 3 fields default to implicit presence
  • the 'optional' keyword changes a field to explicit presence
  • types like lists (repeated) and maps are always implicit presence

Explicit presence means that you can know whether the user set a value or
not. Implicit presence means you can always retrieve a value; if one had
not been set, you'll see the default value for that type.

We translate explicit presence (a optional annotation) to using a nullable
type for that field. We translate implicit presence (always returning some
value) to a non-null type.

Some short-hand:
- optional == explicit == nullable
- implicit == non-nullable
- lists and maps == implicit == non-nullable
- singular message == explicit == nullable

Sample output can be seen at googleapis/google-cloud-dart#58.

@devoncarew devoncarew requested review from a team and brianquinlan October 30, 2025 19:20
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @devoncarew, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive null-safety support into the generated Dart code by leveraging protobuf field presence information. The changes ensure that the generated Dart classes correctly reflect the nullability semantics of their corresponding proto definitions, leading to more robust and type-safe code. This involves significant updates to how fields are annotated, how JSON serialization and deserialization are handled, and how URL paths are constructed, ultimately improving the reliability and maintainability of the generated Dart API clients.

Highlights

  • Null-Safety Integration: The code generation now leverages protobuf presence information to produce null-safe Dart code, distinguishing between explicit (nullable) and implicit (non-nullable) presence.
  • Field Annotation Logic: The annotateField function has been significantly updated to determine field nullability and calculate appropriate default values based on proto field properties (e.g., repeated, map, optional, oneof).
  • JSON Serialization/Deserialization: toJson() implementations now conditionally write values: if a field is nullable and has a non-null value, or if it's non-nullable and has a non-default value. fromJson() parsing correctly handles nullable fields and falls back to default values for non-nullable ones.
  • URL Path Handling: Dereferencing of request fields in URL paths has been improved to correctly handle potentially null values, using Dart's null-aware operators (!.).
  • Enum Enhancements: Generated enum definitions now include a static $default field (set to the first enum value) and an isNotDefault getter for checking against this default.
  • Removed Legacy Logic: The older annotate.requiredFields mechanism and its associated calculateRequiredFields function have been removed, streamlining the annotation process.
  • Expanded Test Coverage: New unit tests have been added for createFromJsonLine() and createToJsonLine() methods, and existing buildQueryLines() tests were updated to reflect the new null-safety and default value handling.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request makes significant strides in generating null-safe Dart code by leveraging protobuf field presence information. The approach to handling nullability in toJson and fromJson is largely well-executed, and the addition of new tests is a great improvement. However, I've identified a couple of critical issues that could lead to incorrect or non-compilable generated code. Specifically, there's a flaw in how the nullability of singular message fields is determined, and there are incorrect null-assertions on repeated fields when generating query parameters. I've also included a suggestion to enhance code maintainability.

@codecov
Copy link
Copy Markdown

codecov bot commented Oct 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.92%. Comparing base (01675b7) to head (1064995).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2726      +/-   ##
==========================================
+ Coverage   86.72%   86.92%   +0.19%     
==========================================
  Files         118      118              
  Lines        9824     9896      +72     
==========================================
+ Hits         8520     8602      +82     
+ Misses        920      915       -5     
+ Partials      384      379       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

brianquinlan
brianquinlan previously approved these changes Oct 31, 2025
Copy link
Copy Markdown
Contributor

@brianquinlan brianquinlan left a comment

Choose a reason for hiding this comment

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

There are a few unconvered lines. Could you see if it is possible to add tests for them?

@devoncarew
Copy link
Copy Markdown
Contributor Author

There are a few unconvered lines. Could you see if it is possible to add tests for them?

For sure (I haven't gotten into the habit of looking at the coverage output).

Copy link
Copy Markdown
Contributor Author

@devoncarew devoncarew left a comment

Choose a reason for hiding this comment

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

I added a few more cases for existing tests - this PR now increases overall test coverage by 0.19% 🚀

@brianquinlan
Copy link
Copy Markdown
Contributor

I added a few more cases for existing tests - this PR now increases overall test coverage by 0.19% 🚀

Awesome, merge at will.

@devoncarew devoncarew merged commit e36fb81 into googleapis:main Oct 31, 2025
6 checks passed
devoncarew added a commit to googleapis/google-cloud-dart that referenced this pull request Oct 31, 2025
Update the packages here using the null-safety generator change:

- a follow up to googleapis/librarian#2726
- update sidekick version reference
- re-generate the code
- update the hand-written code (part files, examples, and tests)

Previously as a draft at
#58; done as
separate commits to make review a little easier.

@brianquinlan, this does not include sidekick / dart package version
changes. I assume we'll want to bump everything (to 0.2.0?). Not sure if
we're bumping on changes or on publish (or don't have a specific process
yet).
ldetmer added a commit that referenced this pull request Nov 6, 2025
PR created by the Librarian CLI to initialize a release. Merging this PR
will auto trigger a release.

Librarian Version: not available
Language Image:
us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/librarian-release-container:latest
<details><summary>librarian: 1.0.0</summary>

##
[1.0.0](v0.5.0...v1.0.0)
(2025-11-06)

### Features

* Remove `..._gax` dependency (#2713)
([01675b7](01675b72))

* add context to Librarian PRs (#2698)
([0523329](0523329a))

* add a package comment linter (#2712)
([1bd3e32](1bd3e32e))

* generate pom.xml files (#2682)
([50b95f2](50b95f20))

* add test-container test logic (#2656)
([514cf7e](514cf7e5))

* improve rust-publish logging (#2671)
([585ed50](585ed50b))

* write a timing log at the end of update-image (#2771)
([5fc9e3b](5fc9e3b6))

* migrate Java container from sdk-platform-java (#2670)
([69ac47f](69ac47fd))

* switch to original head after running update-image (#2696)
([7a3e404](7a3e404a))

* format bulk commit from other sources in release notes (#2665)
([7c52da2](7c52da2b))

* enable multi-version API generation (#2699)
([86c5250](86c52507))

* release stage to update pom.xml files (#2772)
([be56755](be567550))

* use proto presence info to generate null-safe code (#2726)
([e36fb81](e36fb81a))

* Base messages in google_cloud_protobuf (#2660)
([e607ea6](e607ea63))

### Bug Fixes

* retry Github 503 requests up to 3 times (#2650)
([09468fa](09468faf))

* Changed docs to ConfigurationException (#2697)
([0950c1e](0950c1e3))

* fix the reference to the old &#34;librariangen&#34; folder (#2677)
([09dc53f](09dc53fe))

* run godoclint via golangci-lint (#2751)
([264a6a0](264a6a0e))

* pass context as the first argument (#2769)
([298a3bd](298a3bd8))

* Move HTTP client creation to `_gax` (#2672)
([4968d63](4968d63d))

* deduplicate bulk commits (#2758)
([4dfae9a](4dfae9ae))

* Support additional api paths for an existing library (#2666)
([50046f5](50046f55))

* remove stray TODO (#2748)
([5072f0e](5072f0e0))

* omit status field when empty (#2654)
([572ae4f](572ae4f3))

* use T.Context in tests (#2768)
([7e7cd2d](7e7cd2dd))

* wrap error to provide more context for commitAndPush (#2767)
([a2a41a4](a2a41a4a))

* use t.Fatal in tests for proper failure handling (#2759)
([cdabb28](cdabb287))

* enforce removal before copying library files (#2765)
([d5ac6c8](d5ac6c87))

* change log level to debug (#2798)
([f042d0b](f042d0bd))

* change path to `doc.go` in docgen test (#2700)
([fd6bae4](fd6bae40))

### Documentation

* use consistent library id in flags and examples (#2770)
([87a1005](87a10056))

</details>
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.

use the proto 'optional' keyword and proto3 field presence rules for field nullability

2 participants