Skip to content

Conversation

Spiritus2424
Copy link

@Spiritus2424 Spiritus2424 commented Aug 28, 2025

This PR changes the enum generation to convert protobuf enum definition following the protobuf style guideline to respect dart naming convention at the end.

It is essential to take into account that people will follow the Protobuf style guideline and naming convention and that developer will expect the dart protoc plugin to match the dart naming conventions.

#372 Also motivates this change.

I tried to keep everything as compatible as possible. To not expose breaking change, I hide the new enum generation under an option (false by default) protobuf-enum-style.

Close #372

Copy link

google-cla bot commented Aug 28, 2025

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).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Spiritus2424 and others added 14 commits August 28, 2025 02:05
…efix from enum value and UpperSnakeCase to CamelCase
Currently `deepCopy` and `clone` create an empty message, and then merge the
cloned message into the empty message.

This is slow as merge needs to validate each element added to the message.

In this PR we instead directly copy the field set, and deeply copy map, list,
and message fields.

deepCopy benchmark results:

|                  | Before  | After   | Diff   |
|------------------|---------|---------|--------|
| AOT              | 238 us  | 151 us  | -36%   |
| dart2js -O4      | 242 us  | 140 us  | -42%   |
| dart2wasm -O2    | 230 us  | 123 us  | -46%   |
| JIT              | 189 us  | 104 us  | -44%   |

This CL is tested internally in cl/794080754.

Fixes google#60.
This fixes handling of unknown enum values in repeated and map fields and in
top-level fields.

Current behavior:

- Unknown enums in map and repeated fields throw an exception.
- Unknown enums in top-level fields reset the field value to the default one.

With this PR we just "ignore" the values:

- Unknown enums in map and repeated fields are skipped.
- Unknown enums in top-level fields do not reset the field's value to the
  default.

Fixes google#849.
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…d code (google#1040)

In `binary_decode_packed` benchmarks we used "sink" variables to write the
decoded values, and printed these sinks at the end to make sure they're not
dropped.

Do the same in the rest of the benchmarks to make sure no code is eliminated by
a sufficiently smart compiler or VM.

Just as a sanity check I compared the numbers before and after this change,
when compiled to Wasm with `-O2`. The numbers do not change in a consistent
(reproducible) and significant way. It's still good to be safe and use the
benchmark results to avoid dropping code.
In a declaration like

    @js('JSON')
    extension type _JSON._(JSObject _) implements JSObject {
      @js('JSON.stringify')
      external static JSString _stringify(JSObject value);
    }

When I call `_JSON._stringify(...)`, dart2js calls `JSON.stringify`, but dart2wasm calls `JSON.JSON.stringify`, which is wrong.

Reading [1] it looks like the annotation `JSON.stringify` should just be `stringify`. If I do that the code works with both dart2wasm and dart2js.

(I've reported dart2wasm's handling of the annotations above and we'll work on a fix for it on dart2wasm.)

This change is not tested on the CI because when compiling with dart2wasm we use the JS decoder library used by the VM, which doesn't use JS interop. I tested this manually with the patch:

    diff --git a/protobuf/lib/src/protobuf/json/json.dart b/protobuf/lib/src/protobuf/json/json.dart
    index 05d1ac1..70b6a7c 100644
    --- a/protobuf/lib/src/protobuf/json/json.dart
    +++ b/protobuf/lib/src/protobuf/json/json.dart
    @@ -13,7 +13,7 @@ import '../utils.dart';
     // Use json_vm.dart with VM and dart2wasm, json_web.dart with dart2js.
     // json_web.dart uses JS interop for parsing, and JS interop is too slow on
     // Wasm. VM's patch performs better in Wasm.
    -export 'json_vm.dart' if (dart.library.html) 'json_web.dart';
    +export 'json_vm.dart' if (dart.library.js_interop) 'json_web.dart';

     Map<String, dynamic> writeToJsonMap(FieldSet fs) {
       dynamic convertToMap(dynamic fieldValue, int fieldType) {

[1]: https://dart.dev/interop/js-interop/usage#js
Keep the symbols to get useful perf outputs.
Copy link
Author

@Spiritus2424 Spiritus2424 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 comments to help the reviewer understand what the changes are and why this does not bring any breaking change.

@osa1

Comment on lines +67 to +81
// Generate Dart name for the enum value
String dartName;
final isProtobufEnumStyle =
parent.fileGen?.options.protobufEnumStyle ?? false;
if (isProtobufEnumStyle) {
// Strip enum prefix from protobuf-style enum values
final strippedName = stripEnumPrefix(descriptor.name, value.name);
dartName = avoidInitialUnderscore(strippedName);
} else {
// Use original style
dartName = avoidInitialUnderscore(value.name);
}

dartNames[value.name] = disambiguateName(
avoidInitialUnderscore(value.name),
dartName,
Copy link
Author

Choose a reason for hiding this comment

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

If the ProtobufEnumStyle is not defined, I keep the orignal way of doing to not incorporate breaking change

Comment on lines +117 to +129
class ProtobufEnumStyleParser implements SingleOptionParser {
bool protobufEnumStyleEnabled = false;

@override
void parse(String name, String? value, OnError onError) {
if (value != null) {
onError('Invalid protobuf-enum-style option. No value expected.');
return;
}
protobufEnumStyleEnabled = true;
}
}

Copy link
Author

Choose a reason for hiding this comment

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

New argument to enable protobuf enum style parsing. By default, the value is false to not incorporate breaking change!

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate protobuf enum style

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate protobuf enum style

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate protobuf enum style

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate protobuf enum style

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate that we keep the old behavior if the argument is not set

Copy link
Author

Choose a reason for hiding this comment

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

Adding a new golden test to validate that we keep the old behavior if the argument is not set

@Spiritus2424
Copy link
Author

@osa1 @sigurdm can you review this Pull request or give me at least feedback, my organization need this PR.

@osa1
Copy link
Member

osa1 commented Sep 9, 2025

Could you please remind me what Dart naming conventions the current generated code violates? Do you mean the constant_identifier_names lint? Or something else?

Why is this a blocker for your organization? We already generate ignore_for_file: constant_identifier_names in .pbenum.dart files. Is the generated code still causing issues?

@Spiritus2424
Copy link
Author

Could you please remind me what Dart naming conventions the current generated code violates? Do you mean the constant_identifier_names lint? Or something else?

Why is this a blocker for your organization? We already generate ignore_for_file: constant_identifier_names in .pbenum.dart files. Is the generated code still causing issues?

The problem is in multi-language environments where Dart interacts with other languages like C# over HTTP.

🎯 The Core Problem

Currently, the Dart plugin generates enum values that strictly follow Protobuf’s naming (UPPER_SNAKE_CASE with prefixes), which does not match Dart naming conventions.

For example, given the following .proto definition:

enum Status {
  STATUS_UNKNOWN = 0;
  STATUS_IN_PROGRESS = 1;
  STATUS_COMPLETED = 2;
}

Dart (current behavior)

enum Status {
  STATUS_UNKNOWN,
  STATUS_IN_PROGRESS,
  STATUS_COMPLETED
}

This is not idiomatic Dart — it violates common style guidelines (e.g., constant_identifier_names) and results in code that's harder to read and maintain.

Meanwhile, other language generators do transform enums to follow their target language’s conventions.

C# (expected, idiomatic behavior)

public enum Status {
  Unknown = 0,
  InProgress = 1,
  Completed = 2
}

The C# plugin:

  • Strips the shared prefix (STATUS_)
  • Converts names to PascalCase, matching C# conventions

This difference leads to inconsistencies across languages.

💥 Why This Is Blocking

This inconsistency becomes a real blocker in multi-language projects, especially when:

  • Dart and C# services communicate over HTTP/JSON
  • Enums are serialized as strings
  • C# expects "STATUS_COMPLETED" but C# sends "Completed"

This causes deserialization mismatches or forces developers to write workaround code to translate between the two enum formats — adding unnecessary complexity and potential for bugs.

This PR provides a safe, opt-in solution via a new flag: --protobuf-enum-style.

When enabled:

  • The Dart generator removes the shared enum prefix
  • Converts enum values to Dart-style naming (lowerCamelCase or PascalCase)

Dart (with --protobuf-enum-style enabled)

enum Status {
  unknown,
  inProgress,
  completed
}

This output:

  • Respects Dart conventions
  • Passes linters cleanly
  • Improves code readability
  • Matches what developers expect when working with generated Dart code

🙏 Why This Should Be Accepted

This is a small but meaningful change that:

  • Brings the Dart plugin in line with other official Protobuf plugins (e.g., C#, Java)
  • Improves cross-language compatibility
  • Solves real-world pain points for teams working across Dart and C#
  • Makes generated Dart code feel more idiomatic
  • Maintains full backward compatibility

Thanks for your time and consideration — I'm happy to make adjustments or discuss any concerns you may have.

@osa1
Copy link
Member

osa1 commented Sep 9, 2025

@Spiritus2424 is the code also written by an LLM?

@Spiritus2424
Copy link
Author

Not 100%, but yes, this code was assisted by an LLM. I am currently using Cursor which use GPT4/Claude behind the scenes.

Does this cause an issue?

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.

Dart enums generated by protoc don't follow dart naming conventions
2 participants