From 4878307956b0cf20c063269e422724af0b69c164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Thu, 8 May 2025 10:41:22 +0100 Subject: [PATCH 1/9] Improve enum decoding -- alternative This is an alternative to #980 that doesn't make a big difference in terms of performance of AOT compiled benchmarks, but makes a big difference when compiling to Wasm, comapred to #980. When decoding an enum value, we call a callback in the enum field's `FieldInfo`. The callback then indexes a map mapping enum numbers to Dart values. When these conditions hold: - The known enum numbers are all positive. (so that we can use a list and index it with the number) - The known enum numbers are more than 70% of the large known enum number. (so that the list won't have a lot of `null` entries, wasting heap space) We now generate a list instead of a map to map enum numbers to Dart values. Note: similar to the map, the list is runtime allocated. No new code generated per message or enum type. Wasm benchmarks: - Before: `protobuf_PackedEnumDecoding(RunTimeRaw): 48200.0 us` - PR #980: `protobuf_PackedEnumDecoding(RunTimeRaw): 42120.0 us` - Diff: -12.6% - After: `protobuf_PackedEnumDecoding(RunTimeRaw): 35733.3 us` - Diff against PR #980: -15% - Diff against master: -25% AOT benchmarks: - Before: `protobuf_PackedEnumDecoding(RunTimeRaw): 49180.0 us` - PR #980: `protobuf_PackedEnumDecoding(RunTimeRaw): 45726.82 us` - Diff: -7% - This PR: `protobuf_PackedEnumDecoding(RunTimeRaw): 42929.7 us` - Diff against PR #980: -6% - Diff agianst master: -12% --- protobuf/lib/src/protobuf/protobuf_enum.dart | 13 ++- protoc_plugin/lib/protoc.dart | 1 + protoc_plugin/lib/src/enum_generator.dart | 35 ++++++-- .../lib/src/generated/dart_options.pb.dart | 6 +- .../lib/src/generated/descriptor.pb.dart | 76 +++++++++--------- .../lib/src/generated/descriptor.pbenum.dart | 79 +++++++++++++------ .../lib/src/generated/plugin.pb.dart | 10 ++- .../lib/src/generated/plugin.pbenum.dart | 11 ++- protoc_plugin/pubspec.yaml | 2 +- .../test/goldens/deprecations.pbenum | 7 +- .../test/goldens/doc_comments.pbenum | 7 +- protoc_plugin/test/goldens/enum | 4 +- .../test/goldens/messageGeneratorEnums | 4 +- .../test/goldens/topLevelEnum.pbenum | 4 +- 14 files changed, 162 insertions(+), 97 deletions(-) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index b96c119a4..970d20fc3 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -37,9 +37,16 @@ class ProtobufEnum { /// Creates a new constant [ProtobufEnum] using [value] and [name]. const ProtobufEnum(this.value, this.name); - /// Creates a Map for all of the [ProtobufEnum]s in [byIndex], mapping each - /// [ProtobufEnum]'s [value] to the [ProtobufEnum]. - static Map initByValue(List byIndex) { + static List initByValueList(List byIndex) { + if (byIndex.isEmpty) return []; + final byValue = List.filled(byIndex.last.value + 1, null); + for (final enumValue in byIndex) { + byValue[enumValue.value] = enumValue; + } + return byValue; + } + + static Map initByValueMap(List byIndex) { final byValue = {}; for (final v in byIndex) { byValue[v.value] = v; diff --git a/protoc_plugin/lib/protoc.dart b/protoc_plugin/lib/protoc.dart index e6d175bd7..2ce0dd0af 100644 --- a/protoc_plugin/lib/protoc.dart +++ b/protoc_plugin/lib/protoc.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:protobuf/protobuf.dart'; import 'const_generator.dart' show writeJsonConst; diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index a1cd8b5f7..c66c2f930 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -102,6 +102,9 @@ class EnumGenerator extends ProtobufContainer { static const int _enumValueTag = 2; void generate(IndentingWriter out) { + assert(_canonicalValues + .isSortedBy((EnumValueDescriptorProto a) => a.number)); + final commentBlock = fileGen?.commentBlock(fieldPath); if (commentBlock != null) { out.println(commentBlock); @@ -165,7 +168,6 @@ class EnumGenerator extends ProtobufContainer { } } out.println(); - out.println('static const $coreImportPrefix.List<$classname> values =' ' <$classname> ['); for (final val in _canonicalValues) { @@ -175,11 +177,32 @@ class EnumGenerator extends ProtobufContainer { out.println('];'); out.println(); - out.println( - 'static final $coreImportPrefix.Map<$coreImportPrefix.int, $classname> _byValue =' - ' $protobufImportPrefix.ProtobufEnum.initByValue(values);'); - out.println('static $classname? valueOf($coreImportPrefix.int value) =>' - ' _byValue[value];'); + var useList = _canonicalValues.isEmpty; + if (_canonicalValues.isNotEmpty) { + if (_canonicalValues.every((val) => !val.number.isNegative)) { + if (_canonicalValues.length / (_canonicalValues.last.number + 1) >= + 0.7) { + useList = true; + } + } + } + + if (useList) { + out.println( + 'static final $coreImportPrefix.List<$classname?> _byValue =' + ' $protobufImportPrefix.ProtobufEnum.initByValueList(values);'); + + out.println('static $classname? valueOf($coreImportPrefix.int value) =>' + ' value < 0 || value >= _byValue.length ? null : _byValue[value];'); + } else { + out.println( + 'static final $coreImportPrefix.Map<$coreImportPrefix.int, $classname> _byValue =' + ' $protobufImportPrefix.ProtobufEnum.initByValueMap(values);'); + + out.println('static $classname? valueOf($coreImportPrefix.int value) =>' + ' _byValue[value];'); + } + out.println(); out.println('const $classname._(super.v, super.n);'); diff --git a/protoc_plugin/lib/src/generated/dart_options.pb.dart b/protoc_plugin/lib/src/generated/dart_options.pb.dart index f045820bd..340247fca 100644 --- a/protoc_plugin/lib/src/generated/dart_options.pb.dart +++ b/protoc_plugin/lib/src/generated/dart_options.pb.dart @@ -2,7 +2,7 @@ // Generated code. Do not modify. // source: dart_options.proto // -// @dart = 2.12 +// @dart = 3.3 // ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes @@ -13,6 +13,8 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + /// A mixin that can be used in the 'with' clause of the generated Dart class /// for a proto message. class DartMixin extends $pb.GeneratedMessage { @@ -171,7 +173,7 @@ class Imports extends $pb.GeneratedMessage { /// so the generated code may contain errors. Therefore, running dartanalyzer /// on the generated file is a good idea. @$pb.TagNumber(1) - $core.List get mixins => $_getList(0); + $pb.PbList get mixins => $_getList(0); } class Dart_options { diff --git a/protoc_plugin/lib/src/generated/descriptor.pb.dart b/protoc_plugin/lib/src/generated/descriptor.pb.dart index d367d33e5..a4dfd4cb1 100644 --- a/protoc_plugin/lib/src/generated/descriptor.pb.dart +++ b/protoc_plugin/lib/src/generated/descriptor.pb.dart @@ -2,7 +2,7 @@ // Generated code. Do not modify. // source: descriptor.proto // -// @dart = 2.12 +// @dart = 3.3 // ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes @@ -16,6 +16,8 @@ import 'package:protobuf/protobuf.dart' as $pb; import 'descriptor.pbenum.dart'; +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + export 'descriptor.pbenum.dart'; /// The protocol compiler can output a FileDescriptorSet containing the .proto @@ -71,7 +73,7 @@ class FileDescriptorSet extends $pb.GeneratedMessage { static FileDescriptorSet? _defaultInstance; @$pb.TagNumber(1) - $core.List get file => $_getList(0); + $pb.PbList get file => $_getList(0); } /// Describes a complete .proto file. @@ -216,20 +218,20 @@ class FileDescriptorProto extends $pb.GeneratedMessage { /// Names of files imported by this file. @$pb.TagNumber(3) - $core.List<$core.String> get dependency => $_getList(2); + $pb.PbList<$core.String> get dependency => $_getList(2); /// All top-level definitions in this file. @$pb.TagNumber(4) - $core.List get messageType => $_getList(3); + $pb.PbList get messageType => $_getList(3); @$pb.TagNumber(5) - $core.List get enumType => $_getList(4); + $pb.PbList get enumType => $_getList(4); @$pb.TagNumber(6) - $core.List get service => $_getList(5); + $pb.PbList get service => $_getList(5); @$pb.TagNumber(7) - $core.List get extension => $_getList(6); + $pb.PbList get extension => $_getList(6); @$pb.TagNumber(8) FileOptions get options => $_getN(7); @@ -265,12 +267,12 @@ class FileDescriptorProto extends $pb.GeneratedMessage { /// Indexes of the public imported files in the dependency list above. @$pb.TagNumber(10) - $core.List<$core.int> get publicDependency => $_getList(9); + $pb.PbList<$core.int> get publicDependency => $_getList(9); /// Indexes of the weak imported files in the dependency list. /// For Google-internal migration only. Do not use. @$pb.TagNumber(11) - $core.List<$core.int> get weakDependency => $_getList(10); + $pb.PbList<$core.int> get weakDependency => $_getList(10); /// The syntax of the proto file. /// The supported values are "proto2" and "proto3". @@ -597,19 +599,19 @@ class DescriptorProto extends $pb.GeneratedMessage { void clearName() => $_clearField(1); @$pb.TagNumber(2) - $core.List get field => $_getList(1); + $pb.PbList get field => $_getList(1); @$pb.TagNumber(3) - $core.List get nestedType => $_getList(2); + $pb.PbList get nestedType => $_getList(2); @$pb.TagNumber(4) - $core.List get enumType => $_getList(3); + $pb.PbList get enumType => $_getList(3); @$pb.TagNumber(5) - $core.List get extensionRange => $_getList(4); + $pb.PbList get extensionRange => $_getList(4); @$pb.TagNumber(6) - $core.List get extension => $_getList(5); + $pb.PbList get extension => $_getList(5); @$pb.TagNumber(7) MessageOptions get options => $_getN(6); @@ -626,15 +628,15 @@ class DescriptorProto extends $pb.GeneratedMessage { MessageOptions ensureOptions() => $_ensure(6); @$pb.TagNumber(8) - $core.List get oneofDecl => $_getList(7); + $pb.PbList get oneofDecl => $_getList(7); @$pb.TagNumber(9) - $core.List get reservedRange => $_getList(8); + $pb.PbList get reservedRange => $_getList(8); /// Reserved field names, which may not be used by fields in the same message. /// A given name may only be reserved once. @$pb.TagNumber(10) - $core.List<$core.String> get reservedName => $_getList(9); + $pb.PbList<$core.String> get reservedName => $_getList(9); } class ExtensionRangeOptions extends $pb.GeneratedMessage { @@ -692,7 +694,7 @@ class ExtensionRangeOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(0); + $pb.PbList get uninterpretedOption => $_getList(0); } /// Describes a field within a message. @@ -1241,7 +1243,7 @@ class EnumDescriptorProto extends $pb.GeneratedMessage { void clearName() => $_clearField(1); @$pb.TagNumber(2) - $core.List get value => $_getList(1); + $pb.PbList get value => $_getList(1); @$pb.TagNumber(3) EnumOptions get options => $_getN(2); @@ -1261,13 +1263,13 @@ class EnumDescriptorProto extends $pb.GeneratedMessage { /// by enum values in the same enum declaration. Reserved ranges may not /// overlap. @$pb.TagNumber(4) - $core.List get reservedRange => + $pb.PbList get reservedRange => $_getList(3); /// Reserved enum value names, which may not be reused. A given name may only /// be reserved once. @$pb.TagNumber(5) - $core.List<$core.String> get reservedName => $_getList(4); + $pb.PbList<$core.String> get reservedName => $_getList(4); } /// Describes a value within an enum. @@ -1448,7 +1450,7 @@ class ServiceDescriptorProto extends $pb.GeneratedMessage { void clearName() => $_clearField(1); @$pb.TagNumber(2) - $core.List get method => $_getList(1); + $pb.PbList get method => $_getList(1); @$pb.TagNumber(3) ServiceOptions get options => $_getN(2); @@ -2087,7 +2089,7 @@ class FileOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. /// See the documentation for the "Options" section above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(20); + $pb.PbList get uninterpretedOption => $_getList(20); } class MessageOptions extends $pb.GeneratedMessage { @@ -2257,7 +2259,7 @@ class MessageOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(4); + $pb.PbList get uninterpretedOption => $_getList(4); } class FieldOptions extends $pb.GeneratedMessage { @@ -2476,7 +2478,7 @@ class FieldOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(6); + $pb.PbList get uninterpretedOption => $_getList(6); } class OneofOptions extends $pb.GeneratedMessage { @@ -2532,7 +2534,7 @@ class OneofOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(0); + $pb.PbList get uninterpretedOption => $_getList(0); } class EnumOptions extends $pb.GeneratedMessage { @@ -2627,7 +2629,7 @@ class EnumOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(2); + $pb.PbList get uninterpretedOption => $_getList(2); } class EnumValueOptions extends $pb.GeneratedMessage { @@ -2704,7 +2706,7 @@ class EnumValueOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(1); + $pb.PbList get uninterpretedOption => $_getList(1); } class ServiceOptions extends $pb.GeneratedMessage { @@ -2781,7 +2783,7 @@ class ServiceOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(1); + $pb.PbList get uninterpretedOption => $_getList(1); } class MethodOptions extends $pb.GeneratedMessage { @@ -2879,7 +2881,7 @@ class MethodOptions extends $pb.GeneratedMessage { /// The parser stores options it doesn't recognize here. See above. @$pb.TagNumber(999) - $core.List get uninterpretedOption => $_getList(2); + $pb.PbList get uninterpretedOption => $_getList(2); } /// The name of the uninterpreted option. Each string represents a segment in @@ -3061,7 +3063,7 @@ class UninterpretedOption extends $pb.GeneratedMessage { static UninterpretedOption? _defaultInstance; @$pb.TagNumber(2) - $core.List get name => $_getList(0); + $pb.PbList get name => $_getList(0); /// The value of the uninterpreted option, in whatever type the tokenizer /// identified it as during parsing. Exactly one of these should be set. @@ -3233,7 +3235,7 @@ class SourceCodeInfo_Location extends $pb.GeneratedMessage { /// this path refers to the whole field declaration (from the beginning /// of the label to the terminating semicolon). @$pb.TagNumber(1) - $core.List<$core.int> get path => $_getList(0); + $pb.PbList<$core.int> get path => $_getList(0); /// Always has exactly three or four elements: start line, start column, /// end line (optional, otherwise assumed same as start line), end column. @@ -3241,7 +3243,7 @@ class SourceCodeInfo_Location extends $pb.GeneratedMessage { /// and column numbers are zero-based -- typically you will want to add /// 1 to each before displaying to a user. @$pb.TagNumber(2) - $core.List<$core.int> get span => $_getList(1); + $pb.PbList<$core.int> get span => $_getList(1); /// If this SourceCodeInfo represents a complete declaration, these are any /// comments appearing before and after the declaration which appear to be @@ -3315,7 +3317,7 @@ class SourceCodeInfo_Location extends $pb.GeneratedMessage { void clearTrailingComments() => $_clearField(4); @$pb.TagNumber(6) - $core.List<$core.String> get leadingDetachedComments => $_getList(4); + $pb.PbList<$core.String> get leadingDetachedComments => $_getList(4); } /// Encapsulates information about the original source file from which a @@ -3415,7 +3417,7 @@ class SourceCodeInfo extends $pb.GeneratedMessage { /// ignore those that it doesn't understand, as more types of locations could /// be recorded in the future. @$pb.TagNumber(1) - $core.List get location => $_getList(0); + $pb.PbList get location => $_getList(0); } class GeneratedCodeInfo_Annotation extends $pb.GeneratedMessage { @@ -3489,7 +3491,7 @@ class GeneratedCodeInfo_Annotation extends $pb.GeneratedMessage { /// Identifies the element in the original source .proto file. This field /// is formatted the same as SourceCodeInfo.Location.path. @$pb.TagNumber(1) - $core.List<$core.int> get path => $_getList(0); + $pb.PbList<$core.int> get path => $_getList(0); /// Identifies the filesystem path to the original source .proto. @$pb.TagNumber(2) @@ -3591,7 +3593,7 @@ class GeneratedCodeInfo extends $pb.GeneratedMessage { /// An Annotation connects some span of text in generated code to an element /// of its generating .proto file. @$pb.TagNumber(1) - $core.List get annotation => $_getList(0); + $pb.PbList get annotation => $_getList(0); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart index 8b92c04a8..0c205c756 100644 --- a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart +++ b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart @@ -2,7 +2,7 @@ // Generated code. Do not modify. // source: descriptor.proto // -// @dart = 2.12 +// @dart = 3.3 // ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes @@ -14,14 +14,22 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; class FieldDescriptorProto_Type extends $pb.ProtobufEnum { + /// 0 is reserved for errors. + /// Order is weird for historical reasons. static const FieldDescriptorProto_Type TYPE_DOUBLE = FieldDescriptorProto_Type._(1, _omitEnumNames ? '' : 'TYPE_DOUBLE'); static const FieldDescriptorProto_Type TYPE_FLOAT = FieldDescriptorProto_Type._(2, _omitEnumNames ? '' : 'TYPE_FLOAT'); + + /// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + /// negative values are likely. static const FieldDescriptorProto_Type TYPE_INT64 = FieldDescriptorProto_Type._(3, _omitEnumNames ? '' : 'TYPE_INT64'); static const FieldDescriptorProto_Type TYPE_UINT64 = FieldDescriptorProto_Type._(4, _omitEnumNames ? '' : 'TYPE_UINT64'); + + /// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + /// negative values are likely. static const FieldDescriptorProto_Type TYPE_INT32 = FieldDescriptorProto_Type._(5, _omitEnumNames ? '' : 'TYPE_INT32'); static const FieldDescriptorProto_Type TYPE_FIXED64 = @@ -32,10 +40,17 @@ class FieldDescriptorProto_Type extends $pb.ProtobufEnum { FieldDescriptorProto_Type._(8, _omitEnumNames ? '' : 'TYPE_BOOL'); static const FieldDescriptorProto_Type TYPE_STRING = FieldDescriptorProto_Type._(9, _omitEnumNames ? '' : 'TYPE_STRING'); + + /// Tag-delimited aggregate. + /// Group type is deprecated and not supported in proto3. However, Proto3 + /// implementations should still be able to parse the group wire format and + /// treat group fields as unknown fields. static const FieldDescriptorProto_Type TYPE_GROUP = FieldDescriptorProto_Type._(10, _omitEnumNames ? '' : 'TYPE_GROUP'); static const FieldDescriptorProto_Type TYPE_MESSAGE = FieldDescriptorProto_Type._(11, _omitEnumNames ? '' : 'TYPE_MESSAGE'); + + /// New in version 2. static const FieldDescriptorProto_Type TYPE_BYTES = FieldDescriptorProto_Type._(12, _omitEnumNames ? '' : 'TYPE_BYTES'); static const FieldDescriptorProto_Type TYPE_UINT32 = @@ -73,14 +88,16 @@ class FieldDescriptorProto_Type extends $pb.ProtobufEnum { TYPE_SINT64, ]; - static final $core.Map<$core.int, FieldDescriptorProto_Type> _byValue = - $pb.ProtobufEnum.initByValue(values); - static FieldDescriptorProto_Type? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static FieldDescriptorProto_Type? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const FieldDescriptorProto_Type._($core.int v, $core.String n) : super(v, n); + const FieldDescriptorProto_Type._(super.v, super.n); } class FieldDescriptorProto_Label extends $pb.ProtobufEnum { + /// 0 is reserved for errors static const FieldDescriptorProto_Label LABEL_OPTIONAL = FieldDescriptorProto_Label._(1, _omitEnumNames ? '' : 'LABEL_OPTIONAL'); static const FieldDescriptorProto_Label LABEL_REQUIRED = @@ -95,18 +112,20 @@ class FieldDescriptorProto_Label extends $pb.ProtobufEnum { LABEL_REPEATED, ]; - static final $core.Map<$core.int, FieldDescriptorProto_Label> _byValue = - $pb.ProtobufEnum.initByValue(values); + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); static FieldDescriptorProto_Label? valueOf($core.int value) => - _byValue[value]; + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const FieldDescriptorProto_Label._($core.int v, $core.String n) : super(v, n); + const FieldDescriptorProto_Label._(super.v, super.n); } /// Generated classes can be optimized for speed or code size. class FileOptions_OptimizeMode extends $pb.ProtobufEnum { static const FileOptions_OptimizeMode SPEED = FileOptions_OptimizeMode._(1, _omitEnumNames ? '' : 'SPEED'); + + /// etc. static const FileOptions_OptimizeMode CODE_SIZE = FileOptions_OptimizeMode._(2, _omitEnumNames ? '' : 'CODE_SIZE'); static const FileOptions_OptimizeMode LITE_RUNTIME = @@ -119,14 +138,16 @@ class FileOptions_OptimizeMode extends $pb.ProtobufEnum { LITE_RUNTIME, ]; - static final $core.Map<$core.int, FileOptions_OptimizeMode> _byValue = - $pb.ProtobufEnum.initByValue(values); - static FileOptions_OptimizeMode? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static FileOptions_OptimizeMode? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const FileOptions_OptimizeMode._($core.int v, $core.String n) : super(v, n); + const FileOptions_OptimizeMode._(super.v, super.n); } class FieldOptions_CType extends $pb.ProtobufEnum { + /// Default mode. static const FieldOptions_CType STRING = FieldOptions_CType._(0, _omitEnumNames ? '' : 'STRING'); static const FieldOptions_CType CORD = @@ -140,18 +161,24 @@ class FieldOptions_CType extends $pb.ProtobufEnum { STRING_PIECE, ]; - static final $core.Map<$core.int, FieldOptions_CType> _byValue = - $pb.ProtobufEnum.initByValue(values); - static FieldOptions_CType? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static FieldOptions_CType? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const FieldOptions_CType._($core.int v, $core.String n) : super(v, n); + const FieldOptions_CType._(super.v, super.n); } class FieldOptions_JSType extends $pb.ProtobufEnum { + /// Use the default type. static const FieldOptions_JSType JS_NORMAL = FieldOptions_JSType._(0, _omitEnumNames ? '' : 'JS_NORMAL'); + + /// Use JavaScript strings. static const FieldOptions_JSType JS_STRING = FieldOptions_JSType._(1, _omitEnumNames ? '' : 'JS_STRING'); + + /// Use JavaScript numbers. static const FieldOptions_JSType JS_NUMBER = FieldOptions_JSType._(2, _omitEnumNames ? '' : 'JS_NUMBER'); @@ -161,11 +188,12 @@ class FieldOptions_JSType extends $pb.ProtobufEnum { JS_NUMBER, ]; - static final $core.Map<$core.int, FieldOptions_JSType> _byValue = - $pb.ProtobufEnum.initByValue(values); - static FieldOptions_JSType? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static FieldOptions_JSType? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const FieldOptions_JSType._($core.int v, $core.String n) : super(v, n); + const FieldOptions_JSType._(super.v, super.n); } /// Is this method side-effect-free (or safe in HTTP parlance), or idempotent, @@ -188,13 +216,12 @@ class MethodOptions_IdempotencyLevel extends $pb.ProtobufEnum { IDEMPOTENT, ]; - static final $core.Map<$core.int, MethodOptions_IdempotencyLevel> _byValue = - $pb.ProtobufEnum.initByValue(values); + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); static MethodOptions_IdempotencyLevel? valueOf($core.int value) => - _byValue[value]; + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const MethodOptions_IdempotencyLevel._($core.int v, $core.String n) - : super(v, n); + const MethodOptions_IdempotencyLevel._(super.v, super.n); } const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/protoc_plugin/lib/src/generated/plugin.pb.dart b/protoc_plugin/lib/src/generated/plugin.pb.dart index bad241898..e50ab709e 100644 --- a/protoc_plugin/lib/src/generated/plugin.pb.dart +++ b/protoc_plugin/lib/src/generated/plugin.pb.dart @@ -2,7 +2,7 @@ // Generated code. Do not modify. // source: plugin.proto // -// @dart = 2.12 +// @dart = 3.3 // ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes @@ -16,6 +16,8 @@ import 'package:protobuf/protobuf.dart' as $pb; import 'descriptor.pb.dart' as $0; +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + export 'plugin.pbenum.dart'; /// The version number of protocol compiler. @@ -204,7 +206,7 @@ class CodeGeneratorRequest extends $pb.GeneratedMessage { /// code generator should generate code only for these files. Each file's /// descriptor will be included in proto_file, below. @$pb.TagNumber(1) - $core.List<$core.String> get fileToGenerate => $_getList(0); + $pb.PbList<$core.String> get fileToGenerate => $_getList(0); /// The generator parameter passed on the command-line. @$pb.TagNumber(2) @@ -249,7 +251,7 @@ class CodeGeneratorRequest extends $pb.GeneratedMessage { /// Type names of fields and extensions in the FileDescriptorProto are always /// fully qualified. @$pb.TagNumber(15) - $core.List<$0.FileDescriptorProto> get protoFile => $_getList(3); + $pb.PbList<$0.FileDescriptorProto> get protoFile => $_getList(3); } /// Represents a single generated file. @@ -525,7 +527,7 @@ class CodeGeneratorResponse extends $pb.GeneratedMessage { void clearSupportedFeatures() => $_clearField(2); @$pb.TagNumber(15) - $core.List get file => $_getList(2); + $pb.PbList get file => $_getList(2); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/protoc_plugin/lib/src/generated/plugin.pbenum.dart b/protoc_plugin/lib/src/generated/plugin.pbenum.dart index f3082012f..23a359df2 100644 --- a/protoc_plugin/lib/src/generated/plugin.pbenum.dart +++ b/protoc_plugin/lib/src/generated/plugin.pbenum.dart @@ -2,7 +2,7 @@ // Generated code. Do not modify. // source: plugin.proto // -// @dart = 2.12 +// @dart = 3.3 // ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes @@ -27,13 +27,12 @@ class CodeGeneratorResponse_Feature extends $pb.ProtobufEnum { FEATURE_PROTO3_OPTIONAL, ]; - static final $core.Map<$core.int, CodeGeneratorResponse_Feature> _byValue = - $pb.ProtobufEnum.initByValue(values); + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); static CodeGeneratorResponse_Feature? valueOf($core.int value) => - _byValue[value]; + value < 0 || value >= _byValue.length ? null : _byValue[value]; - const CodeGeneratorResponse_Feature._($core.int v, $core.String n) - : super(v, n); + const CodeGeneratorResponse_Feature._(super.v, super.n); } const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml index 95c33d3b1..122eef003 100644 --- a/protoc_plugin/pubspec.yaml +++ b/protoc_plugin/pubspec.yaml @@ -7,12 +7,12 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: + collection: ^1.15.0 fixnum: ^1.0.0 path: ^1.8.0 protobuf: ^4.0.0 dev_dependencies: - collection: ^1.15.0 dart_flutter_team_lints: ^1.0.0 matcher: ^0.12.10 test: ^1.16.0 diff --git a/protoc_plugin/test/goldens/deprecations.pbenum b/protoc_plugin/test/goldens/deprecations.pbenum index 30bc5d782..59d98c7de 100644 --- a/protoc_plugin/test/goldens/deprecations.pbenum +++ b/protoc_plugin/test/goldens/deprecations.pbenum @@ -24,9 +24,10 @@ class A extends $pb.ProtobufEnum { A2, ]; - static final $core.Map<$core.int, A> _byValue = - $pb.ProtobufEnum.initByValue(values); - static A? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static A? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; const A._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/doc_comments.pbenum b/protoc_plugin/test/goldens/doc_comments.pbenum index 7cde50e13..3a7e8189c 100644 --- a/protoc_plugin/test/goldens/doc_comments.pbenum +++ b/protoc_plugin/test/goldens/doc_comments.pbenum @@ -26,9 +26,10 @@ class A extends $pb.ProtobufEnum { A2, ]; - static final $core.Map<$core.int, A> _byValue = - $pb.ProtobufEnum.initByValue(values); - static A? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = + $pb.ProtobufEnum.initByValueList(values); + static A? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; const A._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/enum b/protoc_plugin/test/goldens/enum index 812247ddf..2cc22d612 100644 --- a/protoc_plugin/test/goldens/enum +++ b/protoc_plugin/test/goldens/enum @@ -11,8 +11,8 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.Map<$core.int, PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values); - static PhoneType? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneType._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/messageGeneratorEnums b/protoc_plugin/test/goldens/messageGeneratorEnums index 3469c7bab..ad35cc4cd 100644 --- a/protoc_plugin/test/goldens/messageGeneratorEnums +++ b/protoc_plugin/test/goldens/messageGeneratorEnums @@ -11,8 +11,8 @@ class PhoneNumber_PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.Map<$core.int, PhoneNumber_PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values); - static PhoneNumber_PhoneType? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static PhoneNumber_PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneNumber_PhoneType._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/topLevelEnum.pbenum b/protoc_plugin/test/goldens/topLevelEnum.pbenum index 30d81a24e..6ebd26f44 100644 --- a/protoc_plugin/test/goldens/topLevelEnum.pbenum +++ b/protoc_plugin/test/goldens/topLevelEnum.pbenum @@ -26,8 +26,8 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.Map<$core.int, PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values); - static PhoneType? valueOf($core.int value) => _byValue[value]; + static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneType._(super.v, super.n); } From 11cd8fd41923ae32bcffd7c01dfbed9fcb2b6583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 9 May 2025 15:42:50 +0100 Subject: [PATCH 2/9] Add sparse enum decoding benchmarks With upcoming change we'll improve decoding performance of enums, but there will be a difference between "sparse" and "dense" enum decoding performance even though they'll both be faster. To be able to measure the difference add a "sparse" enum type and a benchmark for decoding it. "Sparse" means the enum has large gaps between known enum values, or negative enum values. When decoding this kind of enums, the mapping from the wire `varint` to the Dart value for the enum needs to be done by binary search, map lookup, or similar. For "dense" enums, we can have a list of enum values and index the list directly with the `varint` value, after a range check. These changes will be done in the follow-up PR(s). --- benchmarks/bin/binary_decode_packed.dart | 20 ++++++++++++++++++++ benchmarks/protos/packed_fields.proto | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/benchmarks/bin/binary_decode_packed.dart b/benchmarks/bin/binary_decode_packed.dart index f640cca43..a09c8bc1a 100644 --- a/benchmarks/bin/binary_decode_packed.dart +++ b/benchmarks/bin/binary_decode_packed.dart @@ -181,6 +181,25 @@ class PackedEnumDecodingBenchmark extends BenchmarkBase { } } +class PackedSparseEnumDecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedSparseEnumDecodingBenchmark() : super('PackedSparseEnumDecoding') { + final rand = Random(123); + final message = PackedFields(); + final numEnums = SparseEnum.values.length; + for (var i = 0; i < 1000000; i += 1) { + message.sparseEnum.add(SparseEnum.values[rand.nextInt(numEnums)]); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + void main() { PackedInt32DecodingBenchmark().report(); PackedInt64DecodingBenchmark().report(); @@ -190,6 +209,7 @@ void main() { PackedSint64DecodingBenchmark().report(); PackedBoolDecodingBenchmark().report(); PackedEnumDecodingBenchmark().report(); + PackedSparseEnumDecodingBenchmark().report(); if (int.parse('1') == 0) print(sink); } diff --git a/benchmarks/protos/packed_fields.proto b/benchmarks/protos/packed_fields.proto index 09d32d3ba..18f763944 100644 --- a/benchmarks/protos/packed_fields.proto +++ b/benchmarks/protos/packed_fields.proto @@ -10,6 +10,7 @@ message PackedFields { repeated bool packedBool = 7 [packed = true]; repeated Enum1 packedEnum1 = 8 [packed = true]; repeated Enum2 packedEnum2 = 9 [packed = true]; + repeated SparseEnum sparseEnum = 10 [packed = true]; } enum Enum1 { @@ -27,3 +28,23 @@ enum Enum2 { ENUM_2_4 = 4; ENUM_2_5 = 5; } + +// An enum with large gaps between the known values, and with negative values. +// +// This will be slower to decode as the varint to enum value mapping needs to be +// done with binary search, or map lookup etc. +enum SparseEnum { + ENUM_ZERO = 0; + ENUM_MIN_INT = -2147483648; + ENUM_1 = -1000000000; + ENUM_2 = -100000000; + ENUM_3 = -10000000; + ENUM_4 = -1000000; + ENUM_5 = -100000; + ENUM_6 = 100000; + ENUM_7 = 1000000; + ENUM_8 = 10000000; + ENUM_9 = 100000000; + ENUM_10 = 1000000000; + ENUM_MAX_INT = 2147483647; +} From 65ee8cc0afc686717bd1719b1f8f342bd53f2361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 9 May 2025 16:22:31 +0100 Subject: [PATCH 3/9] Use binary search on sparse lists --- protobuf/lib/src/protobuf/protobuf_enum.dart | 21 ++++++++++++++++++++ protoc_plugin/lib/src/enum_generator.dart | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 970d20fc3..3357b72b2 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -54,6 +54,27 @@ class ProtobufEnum { return byValue; } + static List initSparseList(List byIndex) => + byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value)); + + static T? binarySearch( + List sortedList, int value) { + var min = 0; + var max = sortedList.length; + while (min < max) { + final mid = min + ((max - min) >> 1); + final element = sortedList[mid]; + final comp = element.value.compareTo(value); + if (comp == 0) return element; + if (comp < 0) { + min = mid + 1; + } else { + max = mid; + } + } + return null; + } + /// Returns this enum's [name] or the [value] if names are not represented. @override String toString() => name == '' ? value.toString() : name; diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index c66c2f930..9d4ffc46c 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -196,11 +196,11 @@ class EnumGenerator extends ProtobufContainer { ' value < 0 || value >= _byValue.length ? null : _byValue[value];'); } else { out.println( - 'static final $coreImportPrefix.Map<$coreImportPrefix.int, $classname> _byValue =' - ' $protobufImportPrefix.ProtobufEnum.initByValueMap(values);'); + 'static final $coreImportPrefix.List<$classname> _byValue =' + ' $protobufImportPrefix.ProtobufEnum.initSparseList(values);'); out.println('static $classname? valueOf($coreImportPrefix.int value) =>' - ' _byValue[value];'); + ' $protobufImportPrefix.ProtobufEnum.binarySearch(_byValue, value);'); } out.println(); From 468128eb90edcaeaafeec99ed1142e3dc2a6043e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 10:08:46 +0100 Subject: [PATCH 4/9] Fix formatting --- protobuf/lib/src/protobuf/protobuf_enum.dart | 2 +- protoc_plugin/lib/src/enum_generator.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 3357b72b2..1dd140c6f 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -55,7 +55,7 @@ class ProtobufEnum { } static List initSparseList(List byIndex) => - byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value)); + byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value)); static T? binarySearch( List sortedList, int value) { diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index 9d4ffc46c..3085f7fff 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -195,8 +195,7 @@ class EnumGenerator extends ProtobufContainer { out.println('static $classname? valueOf($coreImportPrefix.int value) =>' ' value < 0 || value >= _byValue.length ? null : _byValue[value];'); } else { - out.println( - 'static final $coreImportPrefix.List<$classname> _byValue =' + out.println('static final $coreImportPrefix.List<$classname> _byValue =' ' $protobufImportPrefix.ProtobufEnum.initSparseList(values);'); out.println('static $classname? valueOf($coreImportPrefix.int value) =>' From cc91f0a60b23f4007e6bf28b1381e46731e62ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 10:18:16 +0100 Subject: [PATCH 5/9] Hide ProtobufEnum generated code interface --- protobuf/lib/src/protobuf/protobuf_enum.dart | 18 +++++++----------- protoc_plugin/lib/src/enum_generator.dart | 6 +++--- .../lib/src/generated/descriptor.pbenum.dart | 12 ++++++------ .../lib/src/generated/plugin.pbenum.dart | 2 +- protoc_plugin/test/goldens/deprecations.pbenum | 2 +- protoc_plugin/test/goldens/doc_comments.pbenum | 2 +- protoc_plugin/test/goldens/enum | 2 +- .../test/goldens/messageGeneratorEnums | 2 +- protoc_plugin/test/goldens/topLevelEnum.pbenum | 2 +- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 1dd140c6f..3e1072b2c 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// ignore_for_file: non_constant_identifier_names + part of '../../protobuf.dart'; /// A base class for all proto enum types. @@ -37,7 +39,8 @@ class ProtobufEnum { /// Creates a new constant [ProtobufEnum] using [value] and [name]. const ProtobufEnum(this.value, this.name); - static List initByValueList(List byIndex) { + /// @nodoc + static List $_initDenseList(List byIndex) { if (byIndex.isEmpty) return []; final byValue = List.filled(byIndex.last.value + 1, null); for (final enumValue in byIndex) { @@ -46,18 +49,11 @@ class ProtobufEnum { return byValue; } - static Map initByValueMap(List byIndex) { - final byValue = {}; - for (final v in byIndex) { - byValue[v.value] = v; - } - return byValue; - } - - static List initSparseList(List byIndex) => + /// @nodoc + static List $_initSparseList(List byIndex) => byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value)); - static T? binarySearch( + static T? $_binarySearch( List sortedList, int value) { var min = 0; var max = sortedList.length; diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index 3085f7fff..d32dcada2 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -190,16 +190,16 @@ class EnumGenerator extends ProtobufContainer { if (useList) { out.println( 'static final $coreImportPrefix.List<$classname?> _byValue =' - ' $protobufImportPrefix.ProtobufEnum.initByValueList(values);'); + ' $protobufImportPrefix.ProtobufEnum.\$_initDenseList(values);'); out.println('static $classname? valueOf($coreImportPrefix.int value) =>' ' value < 0 || value >= _byValue.length ? null : _byValue[value];'); } else { out.println('static final $coreImportPrefix.List<$classname> _byValue =' - ' $protobufImportPrefix.ProtobufEnum.initSparseList(values);'); + ' $protobufImportPrefix.ProtobufEnum.\$_initSparseList(values);'); out.println('static $classname? valueOf($coreImportPrefix.int value) =>' - ' $protobufImportPrefix.ProtobufEnum.binarySearch(_byValue, value);'); + ' $protobufImportPrefix.ProtobufEnum.\$_binarySearch(_byValue, value);'); } out.println(); diff --git a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart index 0c205c756..e9000f815 100644 --- a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart +++ b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart @@ -89,7 +89,7 @@ class FieldDescriptorProto_Type extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static FieldDescriptorProto_Type? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; @@ -113,7 +113,7 @@ class FieldDescriptorProto_Label extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static FieldDescriptorProto_Label? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; @@ -139,7 +139,7 @@ class FileOptions_OptimizeMode extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static FileOptions_OptimizeMode? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; @@ -162,7 +162,7 @@ class FieldOptions_CType extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static FieldOptions_CType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; @@ -189,7 +189,7 @@ class FieldOptions_JSType extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static FieldOptions_JSType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; @@ -217,7 +217,7 @@ class MethodOptions_IdempotencyLevel extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static MethodOptions_IdempotencyLevel? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/protoc_plugin/lib/src/generated/plugin.pbenum.dart b/protoc_plugin/lib/src/generated/plugin.pbenum.dart index 23a359df2..233053938 100644 --- a/protoc_plugin/lib/src/generated/plugin.pbenum.dart +++ b/protoc_plugin/lib/src/generated/plugin.pbenum.dart @@ -28,7 +28,7 @@ class CodeGeneratorResponse_Feature extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static CodeGeneratorResponse_Feature? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/protoc_plugin/test/goldens/deprecations.pbenum b/protoc_plugin/test/goldens/deprecations.pbenum index 59d98c7de..3aa51f4b9 100644 --- a/protoc_plugin/test/goldens/deprecations.pbenum +++ b/protoc_plugin/test/goldens/deprecations.pbenum @@ -25,7 +25,7 @@ class A extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static A? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/protoc_plugin/test/goldens/doc_comments.pbenum b/protoc_plugin/test/goldens/doc_comments.pbenum index 3a7e8189c..26e935f0e 100644 --- a/protoc_plugin/test/goldens/doc_comments.pbenum +++ b/protoc_plugin/test/goldens/doc_comments.pbenum @@ -27,7 +27,7 @@ class A extends $pb.ProtobufEnum { ]; static final $core.List _byValue = - $pb.ProtobufEnum.initByValueList(values); + $pb.ProtobufEnum.$_initDenseList(values); static A? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/protoc_plugin/test/goldens/enum b/protoc_plugin/test/goldens/enum index 2cc22d612..aebc0cb5c 100644 --- a/protoc_plugin/test/goldens/enum +++ b/protoc_plugin/test/goldens/enum @@ -11,7 +11,7 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneType._(super.v, super.n); diff --git a/protoc_plugin/test/goldens/messageGeneratorEnums b/protoc_plugin/test/goldens/messageGeneratorEnums index ad35cc4cd..4185223cf 100644 --- a/protoc_plugin/test/goldens/messageGeneratorEnums +++ b/protoc_plugin/test/goldens/messageGeneratorEnums @@ -11,7 +11,7 @@ class PhoneNumber_PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); static PhoneNumber_PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneNumber_PhoneType._(super.v, super.n); diff --git a/protoc_plugin/test/goldens/topLevelEnum.pbenum b/protoc_plugin/test/goldens/topLevelEnum.pbenum index 6ebd26f44..5b9443613 100644 --- a/protoc_plugin/test/goldens/topLevelEnum.pbenum +++ b/protoc_plugin/test/goldens/topLevelEnum.pbenum @@ -26,7 +26,7 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.initByValueList(values); + static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; const PhoneType._(super.v, super.n); From d4043e9d4c0f0b27cebf7893fea655479772c3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 10:20:05 +0100 Subject: [PATCH 6/9] Documentation formatting --- protobuf/lib/src/protobuf/protobuf_enum.dart | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 3e1072b2c..841d0e6a2 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -8,17 +8,19 @@ part of '../../protobuf.dart'; /// A base class for all proto enum types. /// -/// All proto `enum` classes inherit from [ProtobufEnum]. For example, given -/// the following enum defined in a proto file: +/// All proto `enum` classes inherit from [ProtobufEnum]. For example, given the +/// following enum defined in a proto file: /// -/// message MyMessage { -/// enum Color { -/// RED = 0; -/// GREEN = 1; -/// BLUE = 2; -/// }; -/// // ... -/// } +/// ``` +/// message MyMessage { +/// enum Color { +/// RED = 0; +/// GREEN = 1; +/// BLUE = 2; +/// }; +/// // ... +/// } +/// ``` /// /// the generated Dart file will include a `MyMessage_Color` class that extends /// `ProtobufEnum`. It will also include a `const MyMessage_Color` for each of From e1dfbaf4ccb016bdf9b04caba9f045a9a9d33aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 10:33:08 +0100 Subject: [PATCH 7/9] Hide $_binarySearch too --- protobuf/lib/src/protobuf/protobuf_enum.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 841d0e6a2..140febd7e 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -55,6 +55,7 @@ class ProtobufEnum { static List $_initSparseList(List byIndex) => byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value)); + /// @nodoc static T? $_binarySearch( List sortedList, int value) { var min = 0; From 5f7f4b63b8a78ab676d2188f1456080c50386215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 11:44:42 +0100 Subject: [PATCH 8/9] Use the lists directly --- protobuf/lib/src/protobuf/builder_info.dart | 93 ++++++++++++++----- protobuf/lib/src/protobuf/extension.dart | 12 ++- protobuf/lib/src/protobuf/field_info.dart | 30 +++++- protobuf/lib/src/protobuf/protobuf_enum.dart | 4 +- protoc_plugin/lib/src/enum_generator.dart | 15 ++- .../lib/src/extension_generator.dart | 4 + .../lib/src/generated/descriptor.pb.dart | 24 +++-- .../lib/src/generated/descriptor.pbenum.dart | 42 ++++++--- .../lib/src/generated/plugin.pbenum.dart | 7 +- protoc_plugin/lib/src/protobuf_field.dart | 6 ++ .../test/goldens/deprecations.pbenum | 7 +- .../test/goldens/doc_comments.pbenum | 7 +- protoc_plugin/test/goldens/enum | 5 +- protoc_plugin/test/goldens/messageGenerator | 2 +- .../test/goldens/messageGenerator.meta | 64 ++++++------- .../test/goldens/messageGeneratorEnums | 5 +- .../test/goldens/topLevelEnum.pbenum | 5 +- 17 files changed, 236 insertions(+), 96 deletions(-) diff --git a/protobuf/lib/src/protobuf/builder_info.dart b/protobuf/lib/src/protobuf/builder_info.dart index 1ae344133..3bd0c8f36 100644 --- a/protobuf/lib/src/protobuf/builder_info.dart +++ b/protobuf/lib/src/protobuf/builder_info.dart @@ -62,7 +62,9 @@ class BuilderInfo { dynamic defaultOrMaker, CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, + bool? sparseEnum, List? enumValues, + List? enumValuesByTag, {String? protoName}) { final index = byIndex.length; final fieldInfo = (tagNumber == 0) @@ -71,7 +73,9 @@ class BuilderInfo { defaultOrMaker: defaultOrMaker, subBuilder: subBuilder, valueOf: valueOf, + sparseEnum: sparseEnum, enumValues: enumValues, + enumValuesByTag: enumValuesByTag, protoName: protoName); _addField(fieldInfo); } @@ -99,6 +103,8 @@ class BuilderInfo { CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, List? enumValues, + List? enumValuesByTag, + bool? sparseEnum, {ProtobufEnum? defaultEnumValue, String? protoName}) { final index = byIndex.length; @@ -106,6 +112,8 @@ class BuilderInfo { name, tagNumber, index, fieldType, check, subBuilder, valueOf: valueOf, enumValues: enumValues, + enumValuesByTag: enumValuesByTag, + sparseEnum: sparseEnum, defaultEnumValue: defaultEnumValue, protoName: protoName)); } @@ -127,42 +135,48 @@ class BuilderInfo { {dynamic defaultOrMaker, CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, + bool? sparseEnum, List? enumValues, + List? enumValuesByTag, String? protoName}) { add(tagNumber, name, fieldType, defaultOrMaker, subBuilder, valueOf, - enumValues, + sparseEnum, enumValues, enumValuesByTag, protoName: protoName); } /// Adds PbFieldType.OS String with no default value to reduce generated /// code size. void aOS(int tagNumber, String name, {String? protoName}) { - add(tagNumber, name, PbFieldType.OS, null, null, null, null, + add( + tagNumber, name, PbFieldType.OS, null, null, null, null, null, null, protoName: protoName); } /// Adds PbFieldType.PS String with no default value. void pPS(int tagNumber, String name, {String? protoName}) { addRepeated(tagNumber, name, PbFieldType.PS, - getCheckFunction(PbFieldType.PS), null, null, null, + getCheckFunction(PbFieldType.PS), null, null, null, null, null, protoName: protoName); } /// Adds PbFieldType.QS String with no default value. void aQS(int tagNumber, String name, {String? protoName}) { - add(tagNumber, name, PbFieldType.QS, null, null, null, null, + add( + tagNumber, name, PbFieldType.QS, null, null, null, null, null, null, protoName: protoName); } /// Adds Int64 field with Int64.ZERO default. void aInt64(int tagNumber, String name, {String? protoName}) { add(tagNumber, name, PbFieldType.O6, Int64.ZERO, null, null, null, + null, null, protoName: protoName); } /// Adds a boolean with no default value. void aOB(int tagNumber, String name, {String? protoName}) { - add(tagNumber, name, PbFieldType.OB, null, null, null, null, + add( + tagNumber, name, PbFieldType.OB, null, null, null, null, null, null, protoName: protoName); } @@ -170,10 +184,12 @@ class BuilderInfo { void e(int tagNumber, String name, int fieldType, {dynamic defaultOrMaker, ValueOfFunc? valueOf, + bool? sparseEnum, List? enumValues, + List? enumValuesByTag, String? protoName}) { - add( - tagNumber, name, fieldType, defaultOrMaker, null, valueOf, enumValues, + add(tagNumber, name, fieldType, defaultOrMaker, null, valueOf, + sparseEnum, enumValues, enumValuesByTag, protoName: protoName); } @@ -181,7 +197,7 @@ class BuilderInfo { void p(int tagNumber, String name, int fieldType, {String? protoName}) { assert(!_isGroupOrMessage(fieldType) && !_isEnum(fieldType)); addRepeated(tagNumber, name, fieldType, getCheckFunction(fieldType), - null, null, null, + null, null, null, null, null, protoName: protoName); } @@ -189,12 +205,14 @@ class BuilderInfo { void pc(int tagNumber, String name, int fieldType, {CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, + bool? sparseEnum, List? enumValues, + List? enumValuesByTag, ProtobufEnum? defaultEnumValue, String? protoName}) { assert(_isGroupOrMessage(fieldType) || _isEnum(fieldType)); addRepeated(tagNumber, name, fieldType, _checkNotNull, subBuilder, - valueOf, enumValues, + valueOf, enumValues, enumValuesByTag, sparseEnum, defaultEnumValue: defaultEnumValue, protoName: protoName); } @@ -208,6 +226,8 @@ class BuilderInfo { subBuilder, null, null, + null, + null, protoName: protoName); } @@ -221,6 +241,8 @@ class BuilderInfo { subBuilder, null, null, + null, + null, protoName: protoName); } @@ -238,16 +260,27 @@ class BuilderInfo { required int valueFieldType, CreateBuilderFunc? valueCreator, ValueOfFunc? valueOf, + bool? sparseEnum, List? enumValues, + List? enumValuesByTag, ProtobufEnum? defaultEnumValue, PackageName packageName = const PackageName(''), String? protoName, dynamic valueDefaultOrMaker}) { - final mapEntryBuilderInfo = BuilderInfo(entryClassName, - package: packageName) - ..add(PbMap._keyFieldNumber, 'key', keyFieldType, null, null, null, null) - ..add(PbMap._valueFieldNumber, 'value', valueFieldType, - valueDefaultOrMaker, valueCreator, valueOf, enumValues); + final mapEntryBuilderInfo = + BuilderInfo(entryClassName, package: packageName) + ..add(PbMap._keyFieldNumber, 'key', keyFieldType, null, null, null, + null, null, null) + ..add( + PbMap._valueFieldNumber, + 'value', + valueFieldType, + valueDefaultOrMaker, + valueCreator, + valueOf, + sparseEnum, + enumValues, + enumValuesByTag); addMapField(tagNumber, name, keyFieldType, valueFieldType, mapEntryBuilderInfo, valueCreator, @@ -323,13 +356,31 @@ class BuilderInfo { ProtobufEnum? _decodeEnum( int tagNumber, ExtensionRegistry? registry, int rawValue) { - final f = valueOfFunc(tagNumber); - if (f != null) { - return f(rawValue); + final fi = fieldInfo[tagNumber]; + if (fi == null) { + return _decodeEnumExtension(tagNumber, registry, rawValue); + } + + final valuesByTag = fi.enumValuesByTag; + if (valuesByTag == null) { + return _decodeEnumExtension(tagNumber, registry, rawValue); + } + + if (fi.sparseEnum!) { + return ProtobufEnum.$_binarySearch(valuesByTag, rawValue); + } else { + if (rawValue >= 0 && rawValue < valuesByTag.length) { + return valuesByTag[rawValue]; + } else { + return null; + } } - return registry - ?.getExtension(qualifiedMessageName, tagNumber) - ?.valueOf - ?.call(rawValue); } + + ProtobufEnum? _decodeEnumExtension( + int tagNumber, ExtensionRegistry? registry, int rawValue) => + registry + ?.getExtension(qualifiedMessageName, tagNumber) + ?.valueOf + ?.call(rawValue); } diff --git a/protobuf/lib/src/protobuf/extension.dart b/protobuf/lib/src/protobuf/extension.dart index 1144443fc..9736d1723 100644 --- a/protobuf/lib/src/protobuf/extension.dart +++ b/protobuf/lib/src/protobuf/extension.dart @@ -13,12 +13,16 @@ class Extension extends FieldInfo { CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, List? enumValues, + List? enumValuesByTag, + bool? sparseEnum, String? protoName}) : super(name, tagNumber, null, fieldType, defaultOrMaker: defaultOrMaker, subBuilder: subBuilder, valueOf: valueOf, enumValues: enumValues, + enumValuesByTag: enumValuesByTag, + sparseEnum: sparseEnum, protoName: protoName); Extension.repeated(this.extendee, String name, int tagNumber, int fieldType, @@ -26,9 +30,15 @@ class Extension extends FieldInfo { CreateBuilderFunc? subBuilder, ValueOfFunc? valueOf, List? enumValues, + List? enumValuesByTag, + bool? sparseEnum, String? protoName}) : super.repeated(name, tagNumber, null, fieldType, check, subBuilder, - valueOf: valueOf, enumValues: enumValues, protoName: protoName); + valueOf: valueOf, + enumValues: enumValues, + enumValuesByTag: enumValuesByTag, + sparseEnum: sparseEnum, + protoName: protoName); @override int get hashCode => extendee.hashCode * 31 + tagNumber; diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart index 1777e0909..a11c5beae 100644 --- a/protobuf/lib/src/protobuf/field_info.dart +++ b/protobuf/lib/src/protobuf/field_info.dart @@ -78,11 +78,14 @@ class FieldInfo { /// Only available in fields with message type. final CreateBuilderFunc? subBuilder; - /// List of all enum values. + /// List of all enum values, in the order they appear in the proto definition. + /// (not sorted by enum name or value) /// /// Only available in enum fields. final List? enumValues; + final List? enumValuesByTag; + /// Default enum value. /// /// Only available in enum fields. @@ -93,6 +96,14 @@ class FieldInfo { /// Only available in enum fields. final ValueOfFunc? valueOf; + /// Tells the binary decoder how to search in the enum list by tag. + /// + /// When the enum is "sparse", the decoder binarys earches. + /// + /// Otherwise the decoder indexes the list (after a range check) with the enum + /// value on the wire. + final bool? sparseEnum; + /// Function to verify items when adding to a repeated field. /// /// Only available in repeated fields. @@ -103,6 +114,8 @@ class FieldInfo { this.subBuilder, this.valueOf, this.enumValues, + this.enumValuesByTag, + this.sparseEnum, this.defaultEnumValue, String? protoName}) : makeDefault = findMakeDefault(type, defaultOrMaker), @@ -112,7 +125,7 @@ class FieldInfo { assert(!_isGroupOrMessage(type) || subBuilder != null || _isMapField(type)), - assert(!_isEnum(type) || valueOf != null); + assert(!_isEnum(type) || (valueOf != null && sparseEnum != null)); // Represents a field that has been removed by a program transformation. FieldInfo.dummy(this.index) @@ -122,18 +135,25 @@ class FieldInfo { type = 0, makeDefault = null, valueOf = null, + sparseEnum = null, check = null, enumValues = null, + enumValuesByTag = null, defaultEnumValue = null, subBuilder = null; FieldInfo.repeated(this.name, this.tagNumber, this.index, this.type, CheckFunc this.check, this.subBuilder, - {this.valueOf, this.enumValues, this.defaultEnumValue, String? protoName}) + {this.valueOf, + this.sparseEnum, + this.enumValues, + this.enumValuesByTag, + this.defaultEnumValue, + String? protoName}) : makeDefault = (() => PbList(check: check)), _protoName = protoName, assert(_isRepeated(type)), - assert(!_isEnum(type) || valueOf != null); + assert(!_isEnum(type) || (valueOf != null && sparseEnum != null)); static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) { if (defaultOrMaker == null) return PbFieldType._defaultForType(type); @@ -277,7 +297,7 @@ class MapFieldInfo extends FieldInfo?> { defaultOrMaker: () => PbMap(keyFieldType, valueFieldType), defaultEnumValue: defaultEnumValue, protoName: protoName) { - assert(!_isEnum(type) || valueOf != null); + assert(!_isEnum(type) || (valueOf != null && sparseEnum != null)); } FieldInfo get valueFieldInfo => diff --git a/protobuf/lib/src/protobuf/protobuf_enum.dart b/protobuf/lib/src/protobuf/protobuf_enum.dart index 140febd7e..4168be27a 100644 --- a/protobuf/lib/src/protobuf/protobuf_enum.dart +++ b/protobuf/lib/src/protobuf/protobuf_enum.dart @@ -57,13 +57,13 @@ class ProtobufEnum { /// @nodoc static T? $_binarySearch( - List sortedList, int value) { + List sortedList, int value) { var min = 0; var max = sortedList.length; while (min < max) { final mid = min + ((max - min) >> 1); final element = sortedList[mid]; - final comp = element.value.compareTo(value); + final comp = element!.value.compareTo(value); if (comp == 0) return element; if (comp < 0) { min = mid + 1; diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index d32dcada2..8fb72eb3b 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -189,17 +189,24 @@ class EnumGenerator extends ProtobufContainer { if (useList) { out.println( - 'static final $coreImportPrefix.List<$classname?> _byValue =' + 'static final $coreImportPrefix.List<$classname?> enumValuesByTag =' ' $protobufImportPrefix.ProtobufEnum.\$_initDenseList(values);'); + out.println( + 'static final $coreImportPrefix.bool \$_sparseEnum = false;'); + out.println('static $classname? valueOf($coreImportPrefix.int value) =>' - ' value < 0 || value >= _byValue.length ? null : _byValue[value];'); + ' value < 0 || value >= enumValuesByTag.length ? null : enumValuesByTag[value];'); } else { - out.println('static final $coreImportPrefix.List<$classname> _byValue =' + out.println( + 'static final $coreImportPrefix.List<$classname> enumValuesByTag =' ' $protobufImportPrefix.ProtobufEnum.\$_initSparseList(values);'); + out.println( + 'static final $coreImportPrefix.bool \$_sparseEnum = true;'); + out.println('static $classname? valueOf($coreImportPrefix.int value) =>' - ' $protobufImportPrefix.ProtobufEnum.\$_binarySearch(_byValue, value);'); + ' $protobufImportPrefix.ProtobufEnum.\$_binarySearch(enumValuesByTag, value);'); } out.println(); diff --git a/protoc_plugin/lib/src/extension_generator.dart b/protoc_plugin/lib/src/extension_generator.dart index f9eef30f5..bf7a5568c 100644 --- a/protoc_plugin/lib/src/extension_generator.dart +++ b/protoc_plugin/lib/src/extension_generator.dart @@ -121,6 +121,8 @@ class ExtensionGenerator { } else if (type.isEnum) { named['valueOf'] = '$dartType.valueOf'; named['enumValues'] = '$dartType.values'; + named['enumValuesByTag'] = '$dartType.enumValuesByTag'; + named['sparseEnum'] = '$dartType.\$_sparseEnum'; } } else { invocation = '$protobufImportPrefix.Extension<$dartType>'; @@ -131,6 +133,8 @@ class ExtensionGenerator { final dartEnum = type.getDartType(fileGen!); named['valueOf'] = '$dartEnum.valueOf'; named['enumValues'] = '$dartEnum.values'; + named['enumValuesByTag'] = '$dartEnum.enumValuesByTag'; + named['sparseEnum'] = '$dartEnum.\$_sparseEnum'; } } final fieldDefinition = 'static final '; diff --git a/protoc_plugin/lib/src/generated/descriptor.pb.dart b/protoc_plugin/lib/src/generated/descriptor.pb.dart index a4dfd4cb1..32b5a5966 100644 --- a/protoc_plugin/lib/src/generated/descriptor.pb.dart +++ b/protoc_plugin/lib/src/generated/descriptor.pb.dart @@ -768,12 +768,16 @@ class FieldDescriptorProto extends $pb.GeneratedMessage { 4, _omitFieldNames ? '' : 'label', $pb.PbFieldType.OE, defaultOrMaker: FieldDescriptorProto_Label.LABEL_OPTIONAL, valueOf: FieldDescriptorProto_Label.valueOf, - enumValues: FieldDescriptorProto_Label.values) + enumValues: FieldDescriptorProto_Label.values, + enumValuesByTag: FieldDescriptorProto_Label.enumValuesByTag, + sparseEnum: FieldDescriptorProto_Label.$_sparseEnum) ..e( 5, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: FieldDescriptorProto_Type.TYPE_DOUBLE, valueOf: FieldDescriptorProto_Type.valueOf, - enumValues: FieldDescriptorProto_Type.values) + enumValues: FieldDescriptorProto_Type.values, + enumValuesByTag: FieldDescriptorProto_Type.enumValuesByTag, + sparseEnum: FieldDescriptorProto_Type.$_sparseEnum) ..aOS(6, _omitFieldNames ? '' : 'typeName') ..aOS(7, _omitFieldNames ? '' : 'defaultValue') ..aOM(8, _omitFieldNames ? '' : 'options', @@ -1734,7 +1738,9 @@ class FileOptions extends $pb.GeneratedMessage { 9, _omitFieldNames ? '' : 'optimizeFor', $pb.PbFieldType.OE, defaultOrMaker: FileOptions_OptimizeMode.SPEED, valueOf: FileOptions_OptimizeMode.valueOf, - enumValues: FileOptions_OptimizeMode.values) + enumValues: FileOptions_OptimizeMode.values, + enumValuesByTag: FileOptions_OptimizeMode.enumValuesByTag, + sparseEnum: FileOptions_OptimizeMode.$_sparseEnum) ..aOB(10, _omitFieldNames ? '' : 'javaMultipleFiles') ..aOS(11, _omitFieldNames ? '' : 'goPackage') ..aOB(16, _omitFieldNames ? '' : 'ccGenericServices') @@ -2313,7 +2319,9 @@ class FieldOptions extends $pb.GeneratedMessage { 1, _omitFieldNames ? '' : 'ctype', $pb.PbFieldType.OE, defaultOrMaker: FieldOptions_CType.STRING, valueOf: FieldOptions_CType.valueOf, - enumValues: FieldOptions_CType.values) + enumValues: FieldOptions_CType.values, + enumValuesByTag: FieldOptions_CType.enumValuesByTag, + sparseEnum: FieldOptions_CType.$_sparseEnum) ..aOB(2, _omitFieldNames ? '' : 'packed') ..aOB(3, _omitFieldNames ? '' : 'deprecated') ..aOB(5, _omitFieldNames ? '' : 'lazy') @@ -2321,7 +2329,9 @@ class FieldOptions extends $pb.GeneratedMessage { 6, _omitFieldNames ? '' : 'jstype', $pb.PbFieldType.OE, defaultOrMaker: FieldOptions_JSType.JS_NORMAL, valueOf: FieldOptions_JSType.valueOf, - enumValues: FieldOptions_JSType.values) + enumValues: FieldOptions_JSType.values, + enumValuesByTag: FieldOptions_JSType.enumValuesByTag, + sparseEnum: FieldOptions_JSType.$_sparseEnum) ..aOB(10, _omitFieldNames ? '' : 'weak') ..pc( 999, _omitFieldNames ? '' : 'uninterpretedOption', $pb.PbFieldType.PM, @@ -2822,7 +2832,9 @@ class MethodOptions extends $pb.GeneratedMessage { 34, _omitFieldNames ? '' : 'idempotencyLevel', $pb.PbFieldType.OE, defaultOrMaker: MethodOptions_IdempotencyLevel.IDEMPOTENCY_UNKNOWN, valueOf: MethodOptions_IdempotencyLevel.valueOf, - enumValues: MethodOptions_IdempotencyLevel.values) + enumValues: MethodOptions_IdempotencyLevel.values, + enumValuesByTag: MethodOptions_IdempotencyLevel.enumValuesByTag, + sparseEnum: MethodOptions_IdempotencyLevel.$_sparseEnum) ..pc( 999, _omitFieldNames ? '' : 'uninterpretedOption', $pb.PbFieldType.PM, subBuilder: UninterpretedOption.create) diff --git a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart index e9000f815..bc63a97d1 100644 --- a/protoc_plugin/lib/src/generated/descriptor.pbenum.dart +++ b/protoc_plugin/lib/src/generated/descriptor.pbenum.dart @@ -88,10 +88,13 @@ class FieldDescriptorProto_Type extends $pb.ProtobufEnum { TYPE_SINT64, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static FieldDescriptorProto_Type? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const FieldDescriptorProto_Type._(super.v, super.n); } @@ -112,10 +115,13 @@ class FieldDescriptorProto_Label extends $pb.ProtobufEnum { LABEL_REPEATED, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static FieldDescriptorProto_Label? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const FieldDescriptorProto_Label._(super.v, super.n); } @@ -138,10 +144,13 @@ class FileOptions_OptimizeMode extends $pb.ProtobufEnum { LITE_RUNTIME, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static FileOptions_OptimizeMode? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const FileOptions_OptimizeMode._(super.v, super.n); } @@ -161,10 +170,13 @@ class FieldOptions_CType extends $pb.ProtobufEnum { STRING_PIECE, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static FieldOptions_CType? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const FieldOptions_CType._(super.v, super.n); } @@ -188,10 +200,13 @@ class FieldOptions_JSType extends $pb.ProtobufEnum { JS_NUMBER, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static FieldOptions_JSType? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const FieldOptions_JSType._(super.v, super.n); } @@ -216,10 +231,13 @@ class MethodOptions_IdempotencyLevel extends $pb.ProtobufEnum { IDEMPOTENT, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static MethodOptions_IdempotencyLevel? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const MethodOptions_IdempotencyLevel._(super.v, super.n); } diff --git a/protoc_plugin/lib/src/generated/plugin.pbenum.dart b/protoc_plugin/lib/src/generated/plugin.pbenum.dart index 233053938..b145b4537 100644 --- a/protoc_plugin/lib/src/generated/plugin.pbenum.dart +++ b/protoc_plugin/lib/src/generated/plugin.pbenum.dart @@ -27,10 +27,13 @@ class CodeGeneratorResponse_Feature extends $pb.ProtobufEnum { FEATURE_PROTO3_OPTIONAL, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static CodeGeneratorResponse_Feature? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const CodeGeneratorResponse_Feature._(super.v, super.n); } diff --git a/protoc_plugin/lib/src/protobuf_field.dart b/protoc_plugin/lib/src/protobuf_field.dart index 7906cbec8..6d14bd058 100644 --- a/protoc_plugin/lib/src/protobuf_field.dart +++ b/protoc_plugin/lib/src/protobuf_field.dart @@ -240,6 +240,8 @@ class ProtobufField { if (value.baseType.isEnum) { named['valueOf'] = '$valueType.valueOf'; named['enumValues'] = '$valueType.values'; + named['enumValuesByTag'] = '$valueType.enumValuesByTag'; + named['sparseEnum'] = '$valueType.\$_sparseEnum'; named['valueDefaultOrMaker'] = value.generateDefaultFunction(); named['defaultEnumValue'] = value.generateDefaultFunction(); } @@ -263,6 +265,8 @@ class ProtobufField { } else if (baseType.isEnum) { named['valueOf'] = '$type.valueOf'; named['enumValues'] = '$type.values'; + named['enumValuesByTag'] = '$type.enumValuesByTag'; + named['sparseEnum'] = '$type.\$_sparseEnum'; named['defaultEnumValue'] = generateDefaultFunction(); } } @@ -275,6 +279,8 @@ class ProtobufField { named['defaultOrMaker'] = makeDefault; named['valueOf'] = '$type.valueOf'; named['enumValues'] = '$type.values'; + named['enumValuesByTag'] = '$type.enumValuesByTag'; + named['sparseEnum'] = '$type.\$_sparseEnum'; invocation = 'e<$type>'; } else if (makeDefault == null) { switch (type) { diff --git a/protoc_plugin/test/goldens/deprecations.pbenum b/protoc_plugin/test/goldens/deprecations.pbenum index 3aa51f4b9..ece62ac49 100644 --- a/protoc_plugin/test/goldens/deprecations.pbenum +++ b/protoc_plugin/test/goldens/deprecations.pbenum @@ -24,10 +24,13 @@ class A extends $pb.ProtobufEnum { A2, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static A? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const A._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/doc_comments.pbenum b/protoc_plugin/test/goldens/doc_comments.pbenum index 26e935f0e..8ea773f1f 100644 --- a/protoc_plugin/test/goldens/doc_comments.pbenum +++ b/protoc_plugin/test/goldens/doc_comments.pbenum @@ -26,10 +26,13 @@ class A extends $pb.ProtobufEnum { A2, ]; - static final $core.List _byValue = + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; static A? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; + value < 0 || value >= enumValuesByTag.length + ? null + : enumValuesByTag[value]; const A._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/enum b/protoc_plugin/test/goldens/enum index aebc0cb5c..9a659088f 100644 --- a/protoc_plugin/test/goldens/enum +++ b/protoc_plugin/test/goldens/enum @@ -11,8 +11,9 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); - static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; + static PhoneType? valueOf($core.int value) => value < 0 || value >= enumValuesByTag.length ? null : enumValuesByTag[value]; const PhoneType._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/messageGenerator b/protoc_plugin/test/goldens/messageGenerator index 5eab02036..7e3370428 100644 --- a/protoc_plugin/test/goldens/messageGenerator +++ b/protoc_plugin/test/goldens/messageGenerator @@ -6,7 +6,7 @@ class PhoneNumber extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PhoneNumber', createEmptyInstance: create) ..aQS(1, _omitFieldNames ? '' : 'number') - ..e(2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: PhoneNumber_PhoneType.MOBILE, valueOf: PhoneNumber_PhoneType.valueOf, enumValues: PhoneNumber_PhoneType.values) + ..e(2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: PhoneNumber_PhoneType.MOBILE, valueOf: PhoneNumber_PhoneType.valueOf, enumValues: PhoneNumber_PhoneType.values, enumValuesByTag: PhoneNumber_PhoneType.enumValuesByTag, sparseEnum: PhoneNumber_PhoneType.$_sparseEnum) ..a<$core.String>(3, _omitFieldNames ? '' : 'name', $pb.PbFieldType.OS, defaultOrMaker: '\$') ..aOS(4, _omitFieldNames ? '' : 'deprecatedField') ; diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta index c398f6031..f8e38f2ac 100644 --- a/protoc_plugin/test/goldens/messageGenerator.meta +++ b/protoc_plugin/test/goldens/messageGenerator.meta @@ -18,8 +18,8 @@ annotation: { path: 2 path: 1 sourceFile: - begin: 2003 - end: 2009 + begin: 2107 + end: 2113 } annotation: { path: 4 @@ -27,8 +27,8 @@ annotation: { path: 2 path: 1 sourceFile: - begin: 2051 - end: 2057 + begin: 2155 + end: 2161 } annotation: { path: 4 @@ -36,8 +36,8 @@ annotation: { path: 2 path: 1 sourceFile: - begin: 2130 - end: 2139 + begin: 2234 + end: 2243 } annotation: { path: 4 @@ -45,8 +45,8 @@ annotation: { path: 2 path: 1 sourceFile: - begin: 2182 - end: 2193 + begin: 2286 + end: 2297 } annotation: { path: 4 @@ -54,8 +54,8 @@ annotation: { path: 2 path: 0 sourceFile: - begin: 2265 - end: 2269 + begin: 2369 + end: 2373 } annotation: { path: 4 @@ -63,8 +63,8 @@ annotation: { path: 2 path: 0 sourceFile: - begin: 2310 - end: 2314 + begin: 2414 + end: 2418 } annotation: { path: 4 @@ -72,8 +72,8 @@ annotation: { path: 2 path: 0 sourceFile: - begin: 2395 - end: 2402 + begin: 2499 + end: 2506 } annotation: { path: 4 @@ -81,8 +81,8 @@ annotation: { path: 2 path: 0 sourceFile: - begin: 2445 - end: 2454 + begin: 2549 + end: 2558 } annotation: { path: 4 @@ -90,8 +90,8 @@ annotation: { path: 2 path: 2 sourceFile: - begin: 2517 - end: 2521 + begin: 2621 + end: 2625 } annotation: { path: 4 @@ -99,8 +99,8 @@ annotation: { path: 2 path: 2 sourceFile: - begin: 2568 - end: 2572 + begin: 2672 + end: 2676 } annotation: { path: 4 @@ -108,8 +108,8 @@ annotation: { path: 2 path: 2 sourceFile: - begin: 2645 - end: 2652 + begin: 2749 + end: 2756 } annotation: { path: 4 @@ -117,8 +117,8 @@ annotation: { path: 2 path: 2 sourceFile: - begin: 2695 - end: 2704 + begin: 2799 + end: 2808 } annotation: { path: 4 @@ -126,8 +126,8 @@ annotation: { path: 2 path: 3 sourceFile: - begin: 2816 - end: 2831 + begin: 2920 + end: 2935 } annotation: { path: 4 @@ -135,8 +135,8 @@ annotation: { path: 2 path: 3 sourceFile: - begin: 2922 - end: 2937 + begin: 3026 + end: 3041 } annotation: { path: 4 @@ -144,8 +144,8 @@ annotation: { path: 2 path: 3 sourceFile: - begin: 3059 - end: 3077 + begin: 3163 + end: 3181 } annotation: { path: 4 @@ -153,6 +153,6 @@ annotation: { path: 2 path: 3 sourceFile: - begin: 3169 - end: 3189 + begin: 3273 + end: 3293 } diff --git a/protoc_plugin/test/goldens/messageGeneratorEnums b/protoc_plugin/test/goldens/messageGeneratorEnums index 4185223cf..b6dbeffde 100644 --- a/protoc_plugin/test/goldens/messageGeneratorEnums +++ b/protoc_plugin/test/goldens/messageGeneratorEnums @@ -11,8 +11,9 @@ class PhoneNumber_PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); - static PhoneNumber_PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; + static PhoneNumber_PhoneType? valueOf($core.int value) => value < 0 || value >= enumValuesByTag.length ? null : enumValuesByTag[value]; const PhoneNumber_PhoneType._(super.v, super.n); } diff --git a/protoc_plugin/test/goldens/topLevelEnum.pbenum b/protoc_plugin/test/goldens/topLevelEnum.pbenum index 5b9443613..8deb35d59 100644 --- a/protoc_plugin/test/goldens/topLevelEnum.pbenum +++ b/protoc_plugin/test/goldens/topLevelEnum.pbenum @@ -26,8 +26,9 @@ class PhoneType extends $pb.ProtobufEnum { WORK, ]; - static final $core.List _byValue = $pb.ProtobufEnum.$_initDenseList(values); - static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; + static final $core.List enumValuesByTag = $pb.ProtobufEnum.$_initDenseList(values); + static final $core.bool $_sparseEnum = false; + static PhoneType? valueOf($core.int value) => value < 0 || value >= enumValuesByTag.length ? null : enumValuesByTag[value]; const PhoneType._(super.v, super.n); } From 5fb11e4ab6506404cc5264d715641a54a222fc24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Mon, 12 May 2025 11:53:17 +0100 Subject: [PATCH 9/9] Improve it a little bit --- protobuf/lib/src/protobuf/builder_info.dart | 2 +- protobuf/lib/src/protobuf/field_info.dart | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/protobuf/lib/src/protobuf/builder_info.dart b/protobuf/lib/src/protobuf/builder_info.dart index 3bd0c8f36..375fe0d64 100644 --- a/protobuf/lib/src/protobuf/builder_info.dart +++ b/protobuf/lib/src/protobuf/builder_info.dart @@ -366,7 +366,7 @@ class BuilderInfo { return _decodeEnumExtension(tagNumber, registry, rawValue); } - if (fi.sparseEnum!) { + if (fi.sparseEnum) { return ProtobufEnum.$_binarySearch(valuesByTag, rawValue); } else { if (rawValue >= 0 && rawValue < valuesByTag.length) { diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart index a11c5beae..db4034d42 100644 --- a/protobuf/lib/src/protobuf/field_info.dart +++ b/protobuf/lib/src/protobuf/field_info.dart @@ -102,7 +102,7 @@ class FieldInfo { /// /// Otherwise the decoder indexes the list (after a range check) with the enum /// value on the wire. - final bool? sparseEnum; + final bool sparseEnum; /// Function to verify items when adding to a repeated field. /// @@ -115,17 +115,18 @@ class FieldInfo { this.valueOf, this.enumValues, this.enumValuesByTag, - this.sparseEnum, + bool? sparseEnum, this.defaultEnumValue, String? protoName}) : makeDefault = findMakeDefault(type, defaultOrMaker), + sparseEnum = sparseEnum ?? false, check = null, _protoName = protoName, assert(type != 0), assert(!_isGroupOrMessage(type) || subBuilder != null || _isMapField(type)), - assert(!_isEnum(type) || (valueOf != null && sparseEnum != null)); + assert(!_isEnum(type) || valueOf != null); // Represents a field that has been removed by a program transformation. FieldInfo.dummy(this.index) @@ -135,7 +136,7 @@ class FieldInfo { type = 0, makeDefault = null, valueOf = null, - sparseEnum = null, + sparseEnum = false, check = null, enumValues = null, enumValuesByTag = null, @@ -145,15 +146,16 @@ class FieldInfo { FieldInfo.repeated(this.name, this.tagNumber, this.index, this.type, CheckFunc this.check, this.subBuilder, {this.valueOf, - this.sparseEnum, + bool? sparseEnum, this.enumValues, this.enumValuesByTag, this.defaultEnumValue, String? protoName}) : makeDefault = (() => PbList(check: check)), _protoName = protoName, + sparseEnum = sparseEnum ?? false, assert(_isRepeated(type)), - assert(!_isEnum(type) || (valueOf != null && sparseEnum != null)); + assert(!_isEnum(type) || valueOf != null); static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) { if (defaultOrMaker == null) return PbFieldType._defaultForType(type);