Skip to content

Commit

Permalink
Support for Any (flutter#112)
Browse files Browse the repository at this point in the history
* Support for Any
  • Loading branch information
sigurdm authored Aug 31, 2018
1 parent f2874bd commit 3e8f116
Show file tree
Hide file tree
Showing 28 changed files with 559 additions and 182 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.idea/
.packages
.pub
.idea
out
packages
pubspec.lock
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.10.0

* Breaking change: Support for [any](https://developers.google.com/protocol-buffers/docs/proto3#any) messages.
Generated files require package:protobuf version 0.10.1 or newer.
`BuilderInfo.messageName` will now be the fully qualified name for generated messages.

## 0.9.0

* Breaking change: Add `copyWith()` to message classes and update `getDefault()` to use `freeze()`.
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PLUGIN_PATH=bin/$(PLUGIN_NAME)
BENCHMARK_PROTOS = $(wildcard benchmark/protos/*.proto)

TEST_PROTO_LIST = \
google/protobuf/any \
google/protobuf/unittest_import \
google/protobuf/unittest_optimize_for \
google/protobuf/unittest \
Expand All @@ -36,7 +37,8 @@ TEST_PROTO_LIST = \
service2 \
service3 \
toplevel_import \
toplevel
toplevel \
using_any
TEST_PROTO_DIR=$(OUTPUT_DIR)/protos
TEST_PROTO_LIBS=$(foreach f, $(TEST_PROTO_LIST), \
$(TEST_PROTO_DIR)/$(f).pb.dart \
Expand Down
10 changes: 8 additions & 2 deletions lib/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ part of protoc;
abstract class ProtobufContainer {
String get package;
String get classname;
String get fqname;
String get fullName;

/// The fully qualified name with a leading '.'.
///
/// This exists because names from protoc come like this.
String get dottedName => '.$fullName';

String get packageImportPrefix =>
_cachedImportPrefix ??= _calculateImportPrefix();

Expand Down Expand Up @@ -83,6 +89,6 @@ class CodeGenerator extends ProtobufContainer {

String get package => '';
String get classname => null;
String get fqname => '';
String get fullName => '';
get fileGen => null;
}
15 changes: 7 additions & 8 deletions lib/enum_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,21 @@ class EnumAlias {
class EnumGenerator extends ProtobufContainer {
final ProtobufContainer _parent;
final String classname;
final String fqname;
final String fullName;
final EnumDescriptorProto _descriptor;
final List<EnumValueDescriptorProto> _canonicalValues =
<EnumValueDescriptorProto>[];
final List<EnumAlias> _aliases = <EnumAlias>[];

EnumGenerator(EnumDescriptorProto descriptor, ProtobufContainer parent)
: _parent = parent,
classname = (parent == null || parent is FileGenerator)
: assert(parent != null),
_parent = parent,
classname = (parent is FileGenerator)
? descriptor.name
: '${parent.classname}_${descriptor.name}',
fqname = (parent == null || parent.fqname == null)
fullName = parent.fullName == ''
? descriptor.name
: (parent.fqname == '.'
? '.${descriptor.name}'
: '${parent.fqname}.${descriptor.name}'),
: '${parent.fullName}.${descriptor.name}',
_descriptor = descriptor {
for (EnumValueDescriptorProto value in descriptor.value) {
EnumValueDescriptorProto canonicalValue =
Expand All @@ -46,7 +45,7 @@ class EnumGenerator extends ProtobufContainer {

/// Make this enum available as a field type.
void register(GenerationContext ctx) {
ctx.registerFieldType(fqname, this);
ctx.registerFieldType(this);
}

/// Returns a const expression that evaluates to the JSON for this message.
Expand Down
8 changes: 4 additions & 4 deletions lib/extension_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ExtensionGenerator {
// populated by resolve()
ProtobufField _field;
String _extensionName;
String _extendedClassName = "";
String _extendedFullName = "";

ExtensionGenerator(this._descriptor, this._parent);

Expand All @@ -22,7 +22,7 @@ class ExtensionGenerator {
ProtobufContainer extendedType = ctx.getFieldType(_descriptor.extendee);
// TODO(skybrian) When would this be null?
if (extendedType != null) {
_extendedClassName = extendedType.classname;
_extendedFullName = extendedType.fullName;
}
}

Expand Down Expand Up @@ -80,7 +80,7 @@ class ExtensionGenerator {

if (_field.isRepeated) {
out.print('static final $_protobufImportPrefix.Extension $name = '
'new $_protobufImportPrefix.Extension<$dartType>.repeated(\'$_extendedClassName\','
'new $_protobufImportPrefix.Extension<$dartType>.repeated(\'$_extendedFullName\','
' \'$name\', ${_field.number}, ${_field.typeConstant}');
if (type.isMessage || type.isGroup) {
out.println(', $dartType.$checkItem, $dartType.create);');
Expand All @@ -95,7 +95,7 @@ class ExtensionGenerator {
}

out.print('static final $_protobufImportPrefix.Extension $name = '
'new $_protobufImportPrefix.Extension<$dartType>(\'$_extendedClassName\', \'$name\', '
'new $_protobufImportPrefix.Extension<$dartType>(\'$_extendedFullName\', \'$name\', '
'${_field.number}, ${_field.typeConstant}');

String initializer = _field.generateDefaultFunction(package);
Expand Down
2 changes: 1 addition & 1 deletion lib/file_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class FileGenerator extends ProtobufContainer {

String get package => descriptor.package;
String get classname => '';
String get fqname => '.${descriptor.package}';
String get fullName => descriptor.package;
FileGenerator get fileGen => this;

/// Generates all the Dart files for this .proto file.
Expand Down
2 changes: 1 addition & 1 deletion lib/grpc_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class GrpcServiceGenerator {
return;
}
mg.checkResolved();
_deps[mg.fqname] = mg;
_deps[mg.dottedName] = mg;
}

/// Adds dependencies of [generate] to [imports].
Expand Down
6 changes: 4 additions & 2 deletions lib/linker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ class GenerationContext {
}

/// Makes a message, group, or enum available for reference.
void registerFieldType(String name, ProtobufContainer type) {
_typeRegistry[name] = type;
void registerFieldType(ProtobufContainer type) {
// Register the name with a leading '.' to be compatible with input from
// protoc.
_typeRegistry[type.dottedName] = type;
}

/// Returns info about a .pb.dart being imported,
Expand Down
94 changes: 78 additions & 16 deletions lib/message_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,27 @@ class MessageGenerator extends ProtobufContainer {
/// The name of the Dart class to generate.
final String classname;

/// The fully-qualified name of the message type.
/// The fully-qualified name of the message (without any leading '.').
final String fullName;

/// The part of the fully qualified name that comes after the package prefix.
///
/// For nested messages this will include the names of the parents.
///
/// (Used as a unique key and in error messages, not in Dart code.)
final String fqname;
/// For example:
/// ```
/// package foo;
///
/// message Container {
/// message Nested {
/// int32 int32_value = 1;
/// }
/// }
/// ```
/// The nested message will have a `fullName` of 'foo.Container.Nested', and a
/// `messageName` of 'Container.Nested'.
String get messageName =>
fullName.substring(package.length == 0 ? 0 : package.length + 1);

final PbMixin mixin;

Expand All @@ -48,11 +65,10 @@ class MessageGenerator extends ProtobufContainer {
: _descriptor = descriptor,
_parent = parent,
classname = messageClassName(descriptor, parent: parent.classname),
fqname = (parent == null || parent.fqname == null)
assert(parent != null),
fullName = parent.fullName == ''
? descriptor.name
: (parent.fqname == '.'
? '.${descriptor.name}'
: '${parent.fqname}.${descriptor.name}'),
: '${parent.fullName}.${descriptor.name}',
mixin = _getMixin(descriptor, parent.fileGen.descriptor, declaredMixins,
defaultMixin) {
for (EnumDescriptorProto e in _descriptor.enumType) {
Expand All @@ -77,7 +93,7 @@ class MessageGenerator extends ProtobufContainer {
/// Throws an exception if [resolve] hasn't been called yet.
void checkResolved() {
if (_fieldList == null) {
throw new StateError("message not resolved: ${fqname}");
throw new StateError("message not resolved: ${fullName}");
}
}

Expand All @@ -103,7 +119,7 @@ class MessageGenerator extends ProtobufContainer {

// Registers message and enum types that can be used elsewhere.
void register(GenerationContext ctx) {
ctx.registerFieldType(fqname, this);
ctx.registerFieldType(this);
for (var m in _messageGenerators) {
m.register(ctx);
}
Expand Down Expand Up @@ -205,11 +221,15 @@ class MessageGenerator extends ProtobufContainer {
mixinClause = ' with ${mixinNames.join(", ")}';
}

String packageClause = package == ''
? ''
: ', package: const $_protobufImportPrefix.PackageName(\'$package\')';
out.addBlock(
'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
'}', () {
out.addBlock(
'static final $_protobufImportPrefix.BuilderInfo _i = new $_protobufImportPrefix.BuilderInfo(\'${classname}\')',
'static final $_protobufImportPrefix.BuilderInfo _i = '
'new $_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause)',
';', () {
for (ProtobufField field in _fieldList) {
var dartFieldName = field.memberNames.fieldName;
Expand Down Expand Up @@ -255,9 +275,12 @@ class MessageGenerator extends ProtobufContainer {
out.println('static ${classname} _defaultInstance;');
out.addBlock('static void $checkItem($classname v) {', '}', () {
out.println('if (v is! $classname)'
" $_protobufImportPrefix.checkItemFailed(v, '$classname');");
" $_protobufImportPrefix.checkItemFailed(v, _i.messageName);");
});
generateFieldsAccessorsMutators(out);
if (fullName == 'google.protobuf.Any') {
generateAnyMethods(out);
}
});
out.println();
}
Expand All @@ -271,7 +294,7 @@ class MessageGenerator extends ProtobufContainer {
bool _hasRequiredFields(MessageGenerator type, Set alreadySeen) {
if (type._fieldList == null) throw new StateError("message not resolved");

if (alreadySeen.contains(type.fqname)) {
if (alreadySeen.contains(type.fullName)) {
// The type is already in cache. This means that either:
// a. The type has no required fields.
// b. We are in the midst of checking if the type has required fields,
Expand All @@ -282,7 +305,7 @@ class MessageGenerator extends ProtobufContainer {
// here.
return false;
}
alreadySeen.add(type.fqname);
alreadySeen.add(type.fullName);
// If the type has extensions, an extension with message type could contain
// required fields, so we have to be conservative and assume such an
// extension exists.
Expand All @@ -304,6 +327,45 @@ class MessageGenerator extends ProtobufContainer {
return false;
}

/// Generates methods for the Any message class for packing and unpacking
/// values.
void generateAnyMethods(IndentingWriter out) {
out.println('''
/// Unpacks the message in [value] into [instance].
///
/// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond
/// to the type of [instance].
///
/// A typical usage would be `any.unpackInto(new Message())`.
///
/// Returns [instance].
T unpackInto<T extends $_protobufImportPrefix.GeneratedMessage>(T instance,
{$_protobufImportPrefix.ExtensionRegistry extensionRegistry = $_protobufImportPrefix.ExtensionRegistry.EMPTY}) {
$_protobufImportPrefix.unpackIntoHelper(value, instance, typeUrl,
extensionRegistry: extensionRegistry);
return instance;
}
/// Returns `true` if the encoded message matches the type of [instance].
///
/// Can be used with a default instance:
/// `any.canUnpackInto(Message.getDefault())`
bool canUnpackInto($_protobufImportPrefix.GeneratedMessage instance) {
return $_protobufImportPrefix.canUnpackIntoHelper(instance, typeUrl);
}
/// Creates a new [Any] encoding [message].
///
/// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is
/// the fully qualified name of the type of [message].
static Any pack($_protobufImportPrefix.GeneratedMessage message,
{String typeUrlPrefix = 'type.googleapis.com'}) {
return new Any()
..value = message.writeToBuffer()
..typeUrl = '\${typeUrlPrefix}/\${message.info_.messageName}';
}''');
}

void generateFieldsAccessorsMutators(IndentingWriter out) {
for (ProtobufField field in _fieldList) {
out.println();
Expand All @@ -324,15 +386,15 @@ class MessageGenerator extends ProtobufContainer {

if (field.isRepeated) {
if (field.overridesSetter) {
throw 'Field ${field.fqname} cannot override a setter for '
throw 'Field ${field.fullName} cannot override a setter for '
'${names.fieldName} because it is repeated.';
}
if (field.overridesHasMethod) {
throw 'Field ${field.fqname} cannot override '
throw 'Field ${field.fullName} cannot override '
'${names.hasMethodName}() because it is repeated.';
}
if (field.overridesClearMethod) {
throw 'Field ${field.fqname} cannot override '
throw 'Field ${field.fullName} cannot override '
'${names.clearMethodName}() because it is repeated.';
}
} else {
Expand Down
8 changes: 4 additions & 4 deletions lib/protobuf_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ProtobufField {
/// Dart names within a GeneratedMessage or `null` for an extension.
final MemberNames memberNames;

final String fqname;
final String fullName;
final BaseType baseType;

ProtobufField.message(
Expand All @@ -37,7 +37,7 @@ class ProtobufField {
ProtobufContainer parent, GenerationContext ctx)
: this.descriptor = descriptor,
this.memberNames = dartNames,
fqname = '${parent.fqname}.${descriptor.name}',
fullName = '${parent.fullName}.${descriptor.name}',
baseType = new BaseType(descriptor, ctx);

/// The index of this field in MessageGenerator.fieldList.
Expand Down Expand Up @@ -290,9 +290,9 @@ class ProtobufField {

get _invalidDefaultValue => "dart-protoc-plugin:"
" invalid default value (${descriptor.defaultValue})"
" found in field $fqname";
" found in field $fullName";

_typeNotImplemented(String methodName) => "dart-protoc-plugin:"
" $methodName not implemented for type (${descriptor.type})"
" found in field $fqname";
" found in field $fullName";
}
Loading

0 comments on commit 3e8f116

Please sign in to comment.