Add ensureX for each message field (#290)
diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index 4797d80..4ff3e89 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,5 +1,9 @@
## 0.14.4
+* Added 'ensureX' methods on GeneratedMessage classes for each message field X.
+
+ The method `ensureX()` will set X to an empty instance if `hasX()` returns false and then returns the value of X.
+
* Add specialized getters for `String`, `int`, and `bool` with usual default values.
* Shrink dart2js generated code for `getDefault()`.
diff --git a/protobuf/lib/meta.dart b/protobuf/lib/meta.dart
index d8fbf3d..9ccf448 100644
--- a/protobuf/lib/meta.dart
+++ b/protobuf/lib/meta.dart
@@ -59,6 +59,7 @@
'writeToJson',
'writeToJsonMap',
r'$_defaultFor',
+ r'$_ensure',
r'$_get',
r'$_getI64',
r'$_getList',
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 2e2b12e..d45a97e 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -379,6 +379,17 @@
return _getDefault(_nonExtensionInfoByIndex(index));
}
+ T _$ensure<T>(int index) {
+ if (!_$has(index)) {
+ dynamic value = _nonExtensionInfoByIndex(index).subBuilder();
+ _$set(index, value);
+ return value;
+ }
+ // The implicit downcast at the return is always correct by construction
+ // from the protoc generator. See `GeneratedMessage.$_getN` for details.
+ return _$getND(index);
+ }
+
/// The implementation of a generated getter for repeated fields.
List<T> _$getList<T>(int index) {
var value = _values[index];
diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart
index f2f0026..88b54ae 100644
--- a/protobuf/lib/src/protobuf/generated_message.dart
+++ b/protobuf/lib/src/protobuf/generated_message.dart
@@ -402,6 +402,11 @@
}
/// For generated code only.
+ T $_ensure<T>(int index) {
+ return _fieldSet._$ensure<T>(index);
+ }
+
+ /// For generated code only.
List<T> $_getList<T>(int index) => _fieldSet._$getList<T>(index);
/// For generated code only.
diff --git a/protobuf/pubspec.yaml b/protobuf/pubspec.yaml
index d3ced8c..4cd2d7e 100644
--- a/protobuf/pubspec.yaml
+++ b/protobuf/pubspec.yaml
@@ -1,5 +1,5 @@
name: protobuf
-version: 0.14.4
+version: 0.14.4-dev
author: Dart Team <misc@dartlang.org>
description: >
Runtime library for protocol buffers support.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index 9c631ae..56960e1 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,7 +1,7 @@
## 19.0.0-dev
-
-* Breaking: Add specialized getters for `String`, `int`, and `bool` with usual default values.
-* Breaking: Shrink dart2js generated code for `getDefault()`.
+* Breaking: Generates code that requires at least `protobuf` 0.14.4.
+ - GeneratedMessage classes now have methods `ensureX` for each message field X.
+ - Add specialized getters for `String`, `int`, and `bool` with usual default values.
## 18.0.2
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index 8ed90e0..d38f7d8 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -527,6 +527,17 @@
fieldPathSegment: memberFieldPath,
start: 'void '.length)
]);
+ if (field.baseType.isMessage) {
+ out.printlnAnnotated(
+ '${fieldTypeString} ${names.ensureMethodName}() => '
+ '\$_ensure(${field.index});',
+ <NamedLocation>[
+ NamedLocation(
+ name: names.ensureMethodName,
+ fieldPathSegment: memberFieldPath,
+ start: '${fieldTypeString} '.length)
+ ]);
+ }
}
}
diff --git a/protoc_plugin/lib/names.dart b/protoc_plugin/lib/names.dart
index 0373187..56bbf74 100644
--- a/protoc_plugin/lib/names.dart
+++ b/protoc_plugin/lib/names.dart
@@ -38,8 +38,13 @@
/// `null` for repeated fields.
final String clearMethodName;
+ // Identifier for the generated ensureX() method, without braces.
+ //
+ //'null' for scalar, repeated, and map fields.
+ final String ensureMethodName;
+
FieldNames(this.descriptor, this.index, this.sourcePosition, this.fieldName,
- {this.hasMethodName, this.clearMethodName});
+ {this.hasMethodName, this.clearMethodName, this.ensureMethodName});
}
/// The Dart names associated with a oneof declaration.
@@ -359,8 +364,16 @@
String clearMethod = "clear${_capitalize(name)}";
checkAvailable(clearMethod);
+ String ensureMethod;
+
+ if (_isGroupOrMessage(field)) {
+ ensureMethod = 'ensure${_capitalize(name)}';
+ checkAvailable(ensureMethod);
+ }
return FieldNames(field, index, sourcePosition, name,
- hasMethodName: hasMethod, clearMethodName: clearMethod);
+ hasMethodName: hasMethod,
+ clearMethodName: clearMethod,
+ ensureMethodName: ensureMethod);
}
Iterable<String> _memberNamesSuffix(int number) sync* {
@@ -383,19 +396,27 @@
}
List<String> generateNameVariants(String name) {
- return [
+ List<String> result = [
_defaultFieldName(name),
_defaultHasMethodName(name),
- _defaultClearMethodName(name)
+ _defaultClearMethodName(name),
];
+
+ // TODO(zarah): Use 'collection if' when sdk dependency is updated.
+ if (_isGroupOrMessage(field)) result.add(_defaultEnsureMethodName(name));
+
+ return result;
}
String name = disambiguateName(_fieldMethodSuffix(field), existingNames,
_memberNamesSuffix(field.number),
generateVariants: generateNameVariants);
+
return FieldNames(field, index, sourcePosition, _defaultFieldName(name),
hasMethodName: _defaultHasMethodName(name),
- clearMethodName: _defaultClearMethodName(name));
+ clearMethodName: _defaultClearMethodName(name),
+ ensureMethodName:
+ _isGroupOrMessage(field) ? _defaultEnsureMethodName(name) : null);
}
/// The name to use by default for the Dart getter and setter.
@@ -413,6 +434,9 @@
String _defaultWhichMethodName(String oneofMethodSuffix) =>
'which$oneofMethodSuffix';
+String _defaultEnsureMethodName(String fieldMethodSuffix) =>
+ 'ensure$fieldMethodSuffix';
+
/// The suffix to use for this field in Dart method names.
/// (It should be camelcase and begin with an uppercase letter.)
String _fieldMethodSuffix(FieldDescriptorProto field) {
@@ -440,6 +464,10 @@
bool _isRepeated(FieldDescriptorProto field) =>
field.label == FieldDescriptorProto_Label.LABEL_REPEATED;
+bool _isGroupOrMessage(FieldDescriptorProto field) =>
+ field.type == FieldDescriptorProto_Type.TYPE_MESSAGE ||
+ field.type == FieldDescriptorProto_Type.TYPE_GROUP;
+
String _nameOption(FieldDescriptorProto field) =>
field.options.getExtension(Dart_options.dartName);
diff --git a/protoc_plugin/test/generated_message_test.dart b/protoc_plugin/test/generated_message_test.dart
index c0db210..d9d0426 100755
--- a/protoc_plugin/test/generated_message_test.dart
+++ b/protoc_plugin/test/generated_message_test.dart
@@ -215,6 +215,13 @@
assertClear(message);
});
+ test('test ensure method', () {
+ TestAllTypes message = TestAllTypes();
+ expect(message.hasOptionalNestedMessage(), isFalse);
+ expect(message.ensureOptionalNestedMessage(), TestAllTypes_NestedMessage());
+ expect(message.hasOptionalNestedMessage(), isTrue);
+ });
+
// void testReflectionGetters() {} // UNSUPPORTED -- until reflection
// void testReflectionSetters() {} // UNSUPPORTED -- until reflection
// void testReflectionSettersRejectNull() {} // UNSUPPORTED - reflection
diff --git a/protoc_plugin/test/goldens/imports.pb b/protoc_plugin/test/goldens/imports.pb
index e038ee8..00eb417 100644
--- a/protoc_plugin/test/goldens/imports.pb
+++ b/protoc_plugin/test/goldens/imports.pb
@@ -39,15 +39,18 @@
set m(M v) { setField(1, v); }
$core.bool hasM() => $_has(0);
void clearM() => clearField(1);
+ M ensureM() => $_ensure(0);
$1.M get m1 => $_getN(1);
set m1($1.M v) { setField(2, v); }
$core.bool hasM1() => $_has(1);
void clearM1() => clearField(2);
+ $1.M ensureM1() => $_ensure(1);
$2.M get m2 => $_getN(2);
set m2($2.M v) { setField(3, v); }
$core.bool hasM2() => $_has(2);
void clearM2() => clearField(3);
+ $2.M ensureM2() => $_ensure(2);
}
diff --git a/protoc_plugin/test/oneof_test.dart b/protoc_plugin/test/oneof_test.dart
index d59a1cb..f6659cf 100644
--- a/protoc_plugin/test/oneof_test.dart
+++ b/protoc_plugin/test/oneof_test.dart
@@ -171,6 +171,18 @@
Foo copy2 = foo.copyWith((_) {});
expectFirstSet(copy2);
});
+
+ test('oneof semantics is preserved when using ensure method', () {
+ Foo foo = Foo();
+ foo.first = 'oneof';
+ expectFirstSet(foo);
+ foo.ensureIndex();
+ expect(foo.hasFirst(), false);
+ expect(foo.first, '');
+ expect(foo.whichOneofField(), Foo_OneofField.index_);
+ expect(foo.hasIndex(), true);
+ expect(foo.index, Bar());
+ });
}
void expectSecondSet(Foo foo) {