Skip to content

Commit 660bbc8

Browse files
authored
Edition support (#1052)
This is mainly cl/611142449, cl/802300883, and a few other small cleanup CLs. (This is not my code, just a sync of internal changes.) Closes #945.
1 parent 82fc34f commit 660bbc8

25 files changed

+808
-107
lines changed

protoc_plugin/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
Note: this version requires protobuf 5.0.0.
44

5+
* Support protobuf editions. ([#1052])
56
* Update generated code for protobuf 5.0.0.
67
* Update generated `clone` members to take advantage of faster `deepCopy`
78
implementation in protobuf 5.0.0. ([#742])
89
* Code size improvements for enum fields. ([#1047])
910

1011
[#742]: https://github.com/google/protobuf.dart/pull/742
1112
[#1047]: https://github.com/google/protobuf.dart/pull/1047
13+
[#1052]: https://github.com/google/protobuf.dart/pull/1052
1214

1315
## 22.5.0
1416

protoc_plugin/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ TEST_PROTO_LIST = \
3939
foo \
4040
high_tagnumber \
4141
import_clash \
42+
import_option \
4243
import_public \
4344
json_name \
4445
map_api \

protoc_plugin/lib/names.dart

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,18 @@ String singleQuote(String input) {
117117
}
118118

119119
/// Chooses the Dart name of an extension.
120-
String extensionName(FieldDescriptorProto descriptor, Set<String> usedNames) {
121-
return _unusedMemberNames(descriptor, null, null, usedNames).fieldName;
120+
String extensionName(
121+
FieldDescriptorProto descriptor,
122+
Set<String> usedNames,
123+
bool lowercaseGroupNames,
124+
) {
125+
return _unusedMemberNames(
126+
descriptor,
127+
null,
128+
null,
129+
usedNames,
130+
lowercaseGroupNames,
131+
).fieldName;
122132
}
123133

124134
Iterable<String> extensionSuffixes() sync* {
@@ -281,6 +291,7 @@ MemberNames messageMemberNames(
281291
String parentClassName,
282292
Set<String> usedTopLevelNames, {
283293
Iterable<String> reserved = const [],
294+
bool lowercaseGroupNames = false,
284295
}) {
285296
final fieldList = List<FieldDescriptorProto>.from(descriptor.field);
286297
final sourcePositions = fieldList.asMap().map(
@@ -340,7 +351,13 @@ MemberNames messageMemberNames(
340351
final index = indexes[field.name]!;
341352
final sourcePosition = sourcePositions[field.name];
342353
takeFieldNames(
343-
_unusedMemberNames(field, index, sourcePosition, existingNames),
354+
_unusedMemberNames(
355+
field,
356+
index,
357+
sourcePosition,
358+
existingNames,
359+
lowercaseGroupNames,
360+
),
344361
);
345362
}
346363
}
@@ -470,14 +487,15 @@ FieldNames _unusedMemberNames(
470487
int? index,
471488
int? sourcePosition,
472489
Set<String> existingNames,
490+
bool lowercaseGroupNames,
473491
) {
474492
if (_isRepeated(field)) {
475493
return FieldNames(
476494
field,
477495
index,
478496
sourcePosition,
479497
disambiguateName(
480-
_defaultFieldName(_fieldMethodSuffix(field)),
498+
_defaultFieldName(_fieldMethodSuffix(field, lowercaseGroupNames)),
481499
existingNames,
482500
_memberNamesSuffix(field.number),
483501
),
@@ -498,7 +516,7 @@ FieldNames _unusedMemberNames(
498516
}
499517

500518
final name = disambiguateName(
501-
_fieldMethodSuffix(field),
519+
_fieldMethodSuffix(field, lowercaseGroupNames),
502520
existingNames,
503521
_memberNamesSuffix(field.number),
504522
generateVariants: generateNameVariants,
@@ -535,11 +553,15 @@ String _defaultEnsureMethodName(String fieldMethodSuffix) =>
535553

536554
/// The suffix to use for this field in Dart method names.
537555
/// (It should be camelcase and begin with an uppercase letter.)
538-
String _fieldMethodSuffix(FieldDescriptorProto field) {
556+
String _fieldMethodSuffix(
557+
FieldDescriptorProto field,
558+
bool lowercaseGroupNames,
559+
) {
539560
var name = _nameOption(field)!;
540561
if (name.isNotEmpty) return _capitalize(name);
541562

542-
if (field.type != FieldDescriptorProto_Type.TYPE_GROUP) {
563+
if (field.type != FieldDescriptorProto_Type.TYPE_GROUP ||
564+
lowercaseGroupNames) {
543565
return underscoresToCamelCase(field.name);
544566
}
545567

protoc_plugin/lib/protoc.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'src/gen/dart_options.pb.dart';
1515
import 'src/gen/google/api/client.pb.dart';
1616
import 'src/gen/google/protobuf/compiler/plugin.pb.dart';
1717
import 'src/gen/google/protobuf/descriptor.pb.dart';
18+
import 'src/gen/google/protobuf/dart_edition_defaults.pb.dart';
1819
import 'src/linker.dart';
1920
import 'src/options.dart';
2021
import 'src/output_config.dart';
@@ -34,3 +35,12 @@ part 'src/paths.dart';
3435
part 'src/protobuf_field.dart';
3536
part 'src/service_generator.dart';
3637
part 'src/well_known_types.dart';
38+
39+
final FeatureSetDefaults pluginFeatureSetDefaults =
40+
FeatureSetDefaults.fromBuffer(
41+
base64Decode(ProtobufInternalDartEditionDefaults),
42+
);
43+
44+
const Edition pluginMinSupportedEdition = Edition.EDITION_PROTO2;
45+
46+
const Edition pluginMaxSupportedEdition = Edition.EDITION_2024;

protoc_plugin/lib/src/base_type.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,13 @@ class BaseType {
6565
String getRepeatedDartTypeIterable(FileGenerator fileGen) =>
6666
'$coreImportPrefix.Iterable<${getDartType(fileGen)}>';
6767

68-
factory BaseType(FieldDescriptorProto field, GenerationContext ctx) {
68+
factory BaseType(
69+
FieldDescriptorProto field,
70+
FeatureSet features,
71+
GenerationContext ctx,
72+
) {
6973
String constSuffix;
74+
FieldDescriptorProto_Type type;
7075

7176
switch (field.type) {
7277
case FieldDescriptorProto_Type.TYPE_BOOL:
@@ -191,14 +196,17 @@ class BaseType {
191196
);
192197

193198
case FieldDescriptorProto_Type.TYPE_GROUP:
194-
constSuffix = 'G';
195-
break;
196199
case FieldDescriptorProto_Type.TYPE_MESSAGE:
197-
constSuffix = 'M';
198-
break;
200+
if (features.messageEncoding == FeatureSet_MessageEncoding.DELIMITED) {
201+
constSuffix = 'G';
202+
type = FieldDescriptorProto_Type.TYPE_GROUP;
203+
} else {
204+
constSuffix = 'M';
205+
type = FieldDescriptorProto_Type.TYPE_MESSAGE;
206+
}
199207
case FieldDescriptorProto_Type.TYPE_ENUM:
200208
constSuffix = 'E';
201-
break;
209+
type = FieldDescriptorProto_Type.TYPE_ENUM;
202210

203211
default:
204212
throw ArgumentError('unimplemented type: ${field.type.name}');
@@ -210,7 +218,7 @@ class BaseType {
210218
}
211219

212220
return BaseType._raw(
213-
field.type,
221+
type,
214222
constSuffix,
215223
generator.classname!,
216224
null,

protoc_plugin/lib/src/code_generator.dart

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ import 'package:fixnum/fixnum.dart';
1010
import 'package:protobuf/protobuf.dart';
1111

1212
import '../names.dart' show lowerCaseFirstLetter;
13-
import '../protoc.dart' show FileGenerator;
13+
import '../protoc.dart'
14+
show
15+
FileGenerator,
16+
pluginFeatureSetDefaults,
17+
pluginMinSupportedEdition,
18+
pluginMaxSupportedEdition;
1419
import 'gen/dart_options.pb.dart';
1520
import 'gen/google/api/client.pb.dart';
1621
import 'gen/google/protobuf/compiler/plugin.pb.dart';
22+
import 'gen/google/protobuf/descriptor.pb.dart';
1723
import 'linker.dart';
1824
import 'options.dart';
1925
import 'output_config.dart';
@@ -58,6 +64,8 @@ abstract class ProtobufContainer {
5864
// The generator containing this entity.
5965
ProtobufContainer? get parent;
6066

67+
FeatureSet get features;
68+
6169
/// The top-level parent of this entity, or itself if it is a top-level
6270
/// entity.
6371
ProtobufContainer? get toplevelParent {
@@ -86,8 +94,8 @@ class CodeGenerator {
8694
Map<String, SingleOptionParser>? optionParsers,
8795
OutputConfiguration config = const DefaultOutputConfiguration(),
8896
}) async {
97+
final editionDefaults = pluginFeatureSetDefaults;
8998
final extensions = ExtensionRegistry();
90-
9199
Dart_options.registerAllExtensions(extensions);
92100
Client.registerAllExtensions(extensions);
93101

@@ -118,7 +126,7 @@ class CodeGenerator {
118126
// (We may import it even if we don't generate the .pb.dart file.)
119127
final generators = <FileGenerator>[];
120128
for (final file in request.protoFile) {
121-
generators.add(FileGenerator(file, options));
129+
generators.add(FileGenerator(editionDefaults, file, options));
122130
}
123131

124132
// Collect field types and importable files.
@@ -131,9 +139,12 @@ class CodeGenerator {
131139
response.file.addAll(gen.generateFiles(config));
132140
}
133141
}
134-
response.supportedFeatures = Int64(
135-
CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL.value,
136-
);
142+
response.supportedFeatures =
143+
Int64(CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL.value) |
144+
Int64(CodeGeneratorResponse_Feature.FEATURE_SUPPORTS_EDITIONS.value);
145+
response.minimumEdition = pluginMinSupportedEdition.value;
146+
response.maximumEdition = pluginMaxSupportedEdition.value;
147+
137148
_streamOut.add(response.writeToBuffer());
138149
}
139150
}

protoc_plugin/lib/src/enum_generator.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class EnumGenerator extends ProtobufContainer {
1414
@override
1515
final ProtobufContainer parent;
1616

17+
@override
18+
final FeatureSet features;
19+
1720
@override
1821
final String classname;
1922

@@ -50,7 +53,8 @@ class EnumGenerator extends ProtobufContainer {
5053
parent.fullName == ''
5154
? descriptor.name
5255
: '${parent.fullName}.${descriptor.name}',
53-
_descriptor = descriptor {
56+
_descriptor = descriptor,
57+
features = resolveFeatures(parent.features, descriptor.options.features) {
5458
final usedNames = {...reservedEnumNames};
5559
for (var i = 0; i < descriptor.value.length; i++) {
5660
final value = descriptor.value[i];

protoc_plugin/lib/src/extension_generator.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ExtensionGenerator {
2424
Set<String> usedNames,
2525
int repeatedFieldIndex,
2626
int fieldIdTag,
27-
) : _extensionName = extensionName(_descriptor, usedNames),
27+
) : _extensionName = extensionName(_descriptor, usedNames, false),
2828
_fieldPathSegment = [fieldIdTag, repeatedFieldIndex];
2929

3030
static const _topLevelFieldTag = 7;
@@ -71,6 +71,8 @@ class ExtensionGenerator {
7171
/// The generator of the .pb.dart file where this extension will be defined.
7272
FileGenerator? get fileGen => _parent.fileGen;
7373

74+
FeatureSet get features => _field.features;
75+
7476
String get name {
7577
if (!_resolved) throw StateError('resolve not called');
7678
final name = _extensionName;

protoc_plugin/lib/src/file_generator.dart

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ const String _protobufImportUrl = 'package:protobuf/protobuf.dart';
1818
const String _typedDataImportPrefix = r'$typed_data';
1919
const String _typedDataImportUrl = 'dart:typed_data';
2020

21-
enum ProtoSyntax { proto2, proto3 }
22-
2321
/// Generates the Dart output files for one .proto input file.
2422
///
2523
/// Outputs include .pb.dart, pbenum.dart, and .pbjson.dart.
@@ -140,14 +138,21 @@ class FileGenerator extends ProtobufContainer {
140138
/// Whether cross-references have been resolved.
141139
bool _linked = false;
142140

143-
final ProtoSyntax syntax;
141+
final Edition edition;
144142

145-
FileGenerator(this.descriptor, this.options)
146-
: protoFileUri = Uri.file(descriptor.name),
147-
syntax =
148-
descriptor.syntax == 'proto3'
149-
? ProtoSyntax.proto3
150-
: ProtoSyntax.proto2 {
143+
@override
144+
final FeatureSet features;
145+
146+
FileGenerator(
147+
FeatureSetDefaults editionDefaults,
148+
this.descriptor,
149+
this.options,
150+
) : protoFileUri = Uri.file(descriptor.name),
151+
edition = _getEdition(descriptor),
152+
features = resolveFeatures(
153+
_getEditionDefaults(editionDefaults, _getEdition(descriptor)),
154+
descriptor.options.features,
155+
) {
151156
if (protoFileUri.isAbsolute) {
152157
// protoc should never generate an import with an absolute path.
153158
throw 'FAILURE: Import with absolute path is not supported';
@@ -825,6 +830,51 @@ class ConditionalConstDefinition {
825830
}
826831
}
827832

833+
Edition _getEdition(FileDescriptorProto file) {
834+
if (file.edition != Edition.EDITION_UNKNOWN) {
835+
return file.edition;
836+
}
837+
if (file.syntax == 'proto3') {
838+
return Edition.EDITION_PROTO3;
839+
}
840+
return Edition.EDITION_PROTO2;
841+
}
842+
843+
FeatureSet resolveFeatures(FeatureSet parent, FeatureSet child) {
844+
final result = parent.deepCopy();
845+
result.mergeFromMessage(child);
846+
return result;
847+
}
848+
849+
FeatureSet _getEditionDefaults(
850+
FeatureSetDefaults editionDefaults,
851+
Edition edition,
852+
) {
853+
if (edition.value < editionDefaults.minimumEdition.value) {
854+
throw ArgumentError(
855+
'Edition $edition is earlier than the minimum supported edition ${editionDefaults.minimumEdition}!',
856+
);
857+
}
858+
if (edition.value > editionDefaults.maximumEdition.value) {
859+
throw ArgumentError(
860+
'Edition $edition is later than the maximum supported edition ${editionDefaults.maximumEdition}!',
861+
);
862+
}
863+
FeatureSetDefaults_FeatureSetEditionDefault? found;
864+
for (final d in editionDefaults.defaults) {
865+
if (d.edition.value > edition.value) {
866+
break;
867+
}
868+
found = d;
869+
}
870+
if (found == null) {
871+
throw ArgumentError('No default found for edition $edition!');
872+
}
873+
final defaults = found.fixedFeatures.deepCopy();
874+
defaults.mergeFromMessage(found.overridableFeatures);
875+
return defaults;
876+
}
877+
828878
const _fileIgnores = {
829879
'annotate_overrides',
830880
'camel_case_types',
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Generated with third_party/dart/protoc_plugin/tool/regenerate.sh.
2+
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
3+
// for details. All rights reserved. Use of this source code is governed by a
4+
// BSD-style license that can be found in the LICENSE file.
5+
6+
// ignore_for_file: constant_identifier_names
7+
8+
const ProtobufInternalDartEditionDefaults =
9+
'ChcYhAciACoQCAEQAhgCIAMoATACOAJAAQoXGOcHIgAqEAgCEAEYASACKAEwATgCQAEKFxjoByIMCAEQARgBIAIoATABKgQ4AkABChcY6QciEAgBEAEYASACKAEwATgBQAIqACDmByjpBw==';

0 commit comments

Comments
 (0)