diff --git a/protobuf/lib/meta.dart b/protobuf/lib/meta.dart index 9fe7dd005..f3b999568 100644 --- a/protobuf/lib/meta.dart +++ b/protobuf/lib/meta.dart @@ -47,6 +47,7 @@ const GeneratedMessage_reservedNames = [ 'runtimeType', 'setExtension', 'setField', + 'setFieldNullable', 'toBuilder', 'toDebugString', 'toProto3Json', @@ -59,26 +60,40 @@ const GeneratedMessage_reservedNames = [ r'$_clearField', r'$_ensure', r'$_get', + r'$_getNullable', r'$_getI64', + r'$_getI64Nullable', r'$_getList', r'$_getMap', r'$_getN', r'$_getB', r'$_getBF', + r'$_getBNullable', r'$_getI', r'$_getIZ', + r'$_getINullable', r'$_getS', r'$_getSZ', + r'$_getSNullable', r'$_has', r'$_setBool', + r'$_setBoolNullable', r'$_setBytes', + r'$_setBytesNullable', r'$_setDouble', + r'$_setDoubleNullable', r'$_setField', + r'$_setFieldNullable', r'$_setFloat', + r'$_setFloatNullable', r'$_setInt64', + r'$_setInt64Nullable', r'$_setSignedInt32', + r'$_setSignedInt32Nullable', r'$_setString', + r'$_setStringNullable', r'$_setUnsignedInt32', + r'$_setUnsignedInt32Nullable', r'$_whichOneof', ]; diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart index bd9799116..7713eed04 100644 --- a/protobuf/lib/src/protobuf/field_set.dart +++ b/protobuf/lib/src/protobuf/field_set.dart @@ -279,6 +279,40 @@ class _FieldSet { _setNonExtensionFieldUnchecked(meta, fi, value); } + /// Sets a non-repeated field with error-checking. + /// This method behaves like [_setField], except if `null` is passed as + /// value. In this case, [_clearField] will be called. + /// + /// Works for both extended and non-extended fields. + /// Suitable for public API. + void _setFieldNullable(int tagNumber, Object? value) { + final meta = _meta; + final fi = _nonExtensionInfo(meta, tagNumber); + if (fi == null) { + final extensions = _extensions; + if (extensions == null) { + throw ArgumentError('tag $tagNumber not defined in $_messageName'); + } + if (value == null) { + _clearField(tagNumber); + return; + } + extensions._setField(tagNumber, value); + return; + } + + if (fi.isRepeated) { + throw ArgumentError(_setFieldFailedMessage( + fi, value, 'repeating field (use get + .add())')); + } + if (value == null) { + _clearField(tagNumber); + return; + } + _validateField(fi, value); + _setNonExtensionFieldUnchecked(meta, fi, value); + } + /// Sets a non-repeated field without validating it. /// /// Works for both extended and non-extended fields. @@ -421,6 +455,9 @@ class _FieldSet { /// `false`. bool _$getBF(int index) => _values[index] ?? false; + /// The implementation of a generated getter for nullable `bool` fields. + bool? _$getBNullable(int index) => _values[index]; + /// The implementation of a generated getter for int fields. int _$getI(int index, int? defaultValue) { var value = _values[index]; @@ -435,6 +472,9 @@ class _FieldSet { /// fixed32, sfixed32) that default to `0`. int _$getIZ(int index) => _values[index] ?? 0; + /// The implementation of a generated getter for nullable int fields. + int? _$getINullable(int index) => _values[index]; + /// The implementation of a generated getter for String fields. String _$getS(int index, String? defaultValue) { var value = _values[index]; @@ -449,6 +489,9 @@ class _FieldSet { /// the empty string. String _$getSZ(int index) => _values[index] ?? ''; + /// The implementation of a generated getter for nullable String fields. + String? _$getSNullable(int index) => _values[index]; + /// The implementation of a generated getter for Int64 fields. Int64 _$getI64(int index) { var value = _values[index]; @@ -456,6 +499,9 @@ class _FieldSet { return value; } + /// The implementation of a generated getter for nullable Int64 fields. + Int64? _$getI64Nullable(int index) => _values[index]; + /// The implementation of a generated 'has' method. bool _$has(int index) { final value = _values[index]; @@ -490,11 +536,25 @@ class _FieldSet { _values[index] = value; } + void _$setNullable(int index, Object? value) { + assert(!_nonExtensionInfoByIndex(index).isRepeated); + assert(value == null || _$check(index, value)); + if (value == null) { + _clearField(_meta.byIndex[index].tagNumber); + return; + } + + _$set(index, value); + } + bool _$check(int index, var newValue) { _validateField(_nonExtensionInfoByIndex(index), newValue); return true; // Allows use in an assertion. } + /// The implementation of a generated nullable getter. + T _$getNullable(int index) => _values[index]; + // Bulk operations reading or writing multiple fields void _clear() { diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart index 5a0b51cea..475e5365c 100644 --- a/protobuf/lib/src/protobuf/generated_message.dart +++ b/protobuf/lib/src/protobuf/generated_message.dart @@ -417,6 +417,17 @@ abstract class GeneratedMessage { _fieldSet._setField(tagNumber, value); } + /// Sets the value of a field by its [tagNumber]. + /// This method should be used for optional fields when the nullable option + /// is set. When `null` is passed as value, the field is cleared. + /// + /// Throws an [ArgumentError] if [value] does not match the type associated + /// with [tagNumber]. + @pragma('dart2js:noInline') + void setFieldNullable(int tagNumber, Object? value) { + _fieldSet._setFieldNullable(tagNumber, value); + } + /// For generated code only. /// @nodoc T $_get(int index, T defaultValue) => @@ -426,6 +437,10 @@ abstract class GeneratedMessage { /// @nodoc T $_getN(int index) => _fieldSet._$getND(index); + /// For generated code only. + /// @nodoc + T $_getNullable(int index) => _fieldSet._$getNullable(index); + /// For generated code only. /// @nodoc T $_ensure(int index) => _fieldSet._$ensure(index); @@ -448,6 +463,10 @@ abstract class GeneratedMessage { /// @nodoc bool $_getBF(int index) => _fieldSet._$getBF(index); + /// For generated code only. + /// @nodoc + bool? $_getBNullable(int index) => _fieldSet._$getBNullable(index); + /// For generated code only. /// @nodoc int $_getI(int index, int defaultValue) => @@ -457,6 +476,10 @@ abstract class GeneratedMessage { /// @nodoc int $_getIZ(int index) => _fieldSet._$getIZ(index); + /// For generated code only. + /// @nodoc + int? $_getINullable(int index) => _fieldSet._$getINullable(index); + /// For generated code only. /// @nodoc String $_getS(int index, String defaultValue) => @@ -466,10 +489,18 @@ abstract class GeneratedMessage { /// @nodoc String $_getSZ(int index) => _fieldSet._$getSZ(index); + /// For generated code only. + /// @nodoc + String? $_getSNullable(int index) => _fieldSet._$getSNullable(index); + /// For generated code only. /// @nodoc Int64 $_getI64(int index) => _fieldSet._$getI64(index); + /// For generated code only. + /// @nodoc + Int64? $_getI64Nullable(int index) => _fieldSet._$getI64Nullable(index); + /// For generated code only. /// @nodoc bool $_has(int index) => _fieldSet._$has(index); @@ -478,14 +509,29 @@ abstract class GeneratedMessage { /// @nodoc void $_setBool(int index, bool value) => _fieldSet._$set(index, value); + /// For generated code only. + /// @nodoc + void $_setBoolNullable(int index, bool? value) => + _fieldSet._$setNullable(index, value); + /// For generated code only. /// @nodoc void $_setBytes(int index, List value) => _fieldSet._$set(index, value); + /// For generated code only. + /// @nodoc + void $_setBytesNullable(int index, List? value) => + _fieldSet._$setNullable(index, value); + /// For generated code only. /// @nodoc void $_setString(int index, String value) => _fieldSet._$set(index, value); + /// For generated code only. + /// @nodoc + void $_setStringNullable(int index, String? value) => + _fieldSet._$setNullable(index, value); + /// For generated code only. /// @nodoc void $_setFloat(int index, double value) { @@ -496,10 +542,24 @@ abstract class GeneratedMessage { _fieldSet._$set(index, value); } + /// For generated code only. + /// @nodoc + void $_setFloatNullable(int index, double? value) { + if (value != null && !_isFloat32(value)) { + _fieldSet._$check(index, value); + } + _fieldSet._$setNullable(index, value); + } + /// For generated code only. /// @nodoc void $_setDouble(int index, double value) => _fieldSet._$set(index, value); + /// For generated code only. + /// @nodoc + void $_setDoubleNullable(int index, double? value) => + _fieldSet._$setNullable(index, value); + /// For generated code only. /// @nodoc void $_setSignedInt32(int index, int value) { @@ -510,6 +570,15 @@ abstract class GeneratedMessage { _fieldSet._$set(index, value); } + /// For generated code only. + /// @nodoc + void $_setSignedInt32Nullable(int index, int? value) { + if (value != null && !_isSigned32(value)) { + _fieldSet._$check(index, value); + } + _fieldSet._$setNullable(index, value); + } + /// For generated code only. /// @nodoc void $_setUnsignedInt32(int index, int value) { @@ -520,16 +589,36 @@ abstract class GeneratedMessage { _fieldSet._$set(index, value); } + /// For generated code only. + /// @nodoc + void $_setUnsignedInt32Nullable(int index, int? value) { + if (value != null && !_isUnsigned32(value)) { + _fieldSet._$check(index, value); + } + _fieldSet._$setNullable(index, value); + } + /// For generated code only. /// @nodoc void $_setInt64(int index, Int64 value) => _fieldSet._$set(index, value); + /// For generated code only. + /// @nodoc + void $_setInt64Nullable(int index, Int64? value) => + _fieldSet._$setNullable(index, value); + /// For generated code only. Separate from [setField] to distinguish /// reflective accesses. /// @nodoc void $_setField(int tagNumber, Object value) => _fieldSet._setField(tagNumber, value); + /// For generated code only. Separate from [setFieldNullable] to distinguish + /// reflective accesses. + /// @nodoc + void $_setFieldNullable(int tagNumber, Object? value) => + _fieldSet._setFieldNullable(tagNumber, value); + /// For generated code only. Separate from [clearField] to distinguish /// reflective accesses. /// @nodoc diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile index 0aa7a1312..291cb8478 100644 --- a/protoc_plugin/Makefile +++ b/protoc_plugin/Makefile @@ -18,9 +18,12 @@ TEST_PROTO_LIST = \ google/protobuf/timestamp \ google/protobuf/type \ google/protobuf/unittest_import \ + google/protobuf/unittest_import_proto3 \ + google/protobuf/unittest_import_public_proto3 \ google/protobuf/unittest_optimize_for \ google/protobuf/unittest_well_known_types \ google/protobuf/unittest \ + google/protobuf/unittest_proto3 \ google/protobuf/wrappers \ custom_option \ dart_name \ @@ -99,6 +102,16 @@ $(TEST_PROTO_LIBS): $(PLUGIN_SRC) $(TEST_PROTO_SRCS) --plugin=protoc-gen-dart=$(realpath $(PLUGIN_PATH))\ $(TEST_PROTO_SRCS) + mkdir -p $(TEST_PROTO_DIR)/nullable + + protoc\ + --experimental_allow_proto3_optional\ + --dart_out="nullable:$(TEST_PROTO_DIR)/nullable"\ + -Iprotos\ + -I$(TEST_PROTO_SRC_DIR)\ + --plugin=protoc-gen-dart=$(realpath $(PLUGIN_PATH))\ + $(TEST_PROTO_SRCS) + dart format $(TEST_PROTO_DIR) update-pregenerated: $(PLUGIN_PATH) $(PREGENERATED_SRCS) diff --git a/protoc_plugin/lib/src/file_generator.dart b/protoc_plugin/lib/src/file_generator.dart index e74ae365b..efd3da3b9 100644 --- a/protoc_plugin/lib/src/file_generator.dart +++ b/protoc_plugin/lib/src/file_generator.dart @@ -166,8 +166,14 @@ class FileGenerator extends ProtobufContainer { descriptor.enumType[i], this, usedTopLevelNames, i)); } for (var i = 0; i < descriptor.messageType.length; i++) { - messageGenerators.add(MessageGenerator.topLevel(descriptor.messageType[i], - this, declaredMixins, defaultMixin, usedTopLevelNames, i)); + messageGenerators.add(MessageGenerator.topLevel( + descriptor.messageType[i], + this, + declaredMixins, + defaultMixin, + usedTopLevelNames, + i, + options.useNullable)); } for (var i = 0; i < descriptor.extension.length; i++) { extensionGenerators.add(ExtensionGenerator.topLevel( diff --git a/protoc_plugin/lib/src/message_generator.dart b/protoc_plugin/lib/src/message_generator.dart index b3020c8fc..3242ee31e 100644 --- a/protoc_plugin/lib/src/message_generator.dart +++ b/protoc_plugin/lib/src/message_generator.dart @@ -77,6 +77,8 @@ class MessageGenerator extends ProtobufContainer { Set _usedTopLevelNames; + final bool useNullable; + MessageGenerator._( DescriptorProto descriptor, this.parent, @@ -84,7 +86,8 @@ class MessageGenerator extends ProtobufContainer { PbMixin? defaultMixin, this._usedTopLevelNames, int repeatedFieldIndex, - int fieldIdTag) + int fieldIdTag, + this.useNullable) : _descriptor = descriptor, _fieldPathSegment = [fieldIdTag, repeatedFieldIndex], classname = messageOrEnumClassName(descriptor.name, _usedTopLevelNames, @@ -103,8 +106,8 @@ class MessageGenerator extends ProtobufContainer { for (var i = 0; i < _descriptor.nestedType.length; i++) { final n = _descriptor.nestedType[i]; - _messageGenerators.add(MessageGenerator.nested( - n, this, declaredMixins, defaultMixin, _usedTopLevelNames, i)); + _messageGenerators.add(MessageGenerator.nested(n, this, declaredMixins, + defaultMixin, _usedTopLevelNames, i, useNullable)); } // Extensions within messages won't create top-level classes and don't need @@ -132,9 +135,10 @@ class MessageGenerator extends ProtobufContainer { Map declaredMixins, PbMixin? defaultMixin, Set usedNames, - int repeatedFieldIndex) + int repeatedFieldIndex, + bool useNullable) : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames, - repeatedFieldIndex, _topLevelMessageTag); + repeatedFieldIndex, _topLevelMessageTag, useNullable); MessageGenerator.nested( DescriptorProto descriptor, @@ -142,9 +146,10 @@ class MessageGenerator extends ProtobufContainer { Map declaredMixins, PbMixin? defaultMixin, Set usedNames, - int repeatedFieldIndex) + int repeatedFieldIndex, + bool useNullable) : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames, - repeatedFieldIndex, _nestedMessageTag); + repeatedFieldIndex, _nestedMessageTag, useNullable); @override String get package => parent!.package; @@ -540,7 +545,7 @@ class MessageGenerator extends ProtobufContainer { void generateFieldAccessorsMutators( ProtobufField field, IndentingWriter out, List memberFieldPath) { - final fieldTypeString = field.getDartType(); + var fieldTypeString = field.getDartType(); final defaultExpr = field.getDefaultExpr(); final names = field.memberNames; @@ -549,11 +554,20 @@ class MessageGenerator extends ProtobufContainer { out.println(commentBlock); } + if (useNullable && field.isOptional) { + fieldTypeString += '?'; + } + _emitDeprecatedIf(field.isDeprecated, out); _emitOverrideIf(field.overridesGetter, out); _emitIndexAnnotation(field.number, out); - final getterExpr = _getterExpression(fieldTypeString, field.index!, - defaultExpr, field.isRepeated, field.isMapField); + final getterExpr = _getterExpression( + fieldTypeString, + field.index!, + defaultExpr, + field.isRepeated, + field.isMapField, + useNullable && field.isOptional); out.printlnAnnotated( '$fieldTypeString get ${names!.fieldName} => $getterExpr;', [ @@ -577,11 +591,14 @@ class MessageGenerator extends ProtobufContainer { '${names.clearMethodName}() because it is repeated.'; } } else { - final fastSetter = field.baseType.setter; + var fastSetter = field.baseType.setter; _emitDeprecatedIf(field.isDeprecated, out); _emitOverrideIf(field.overridesSetter, out); _emitIndexAnnotation(field.number, out); if (fastSetter != null) { + if (useNullable && field.isOptional) { + fastSetter += 'Nullable'; + } out.printlnAnnotated( 'set ${names.fieldName}' '($fieldTypeString v) { ' @@ -594,10 +611,13 @@ class MessageGenerator extends ProtobufContainer { start: 'set '.length) ]); } else { + final setterName = + useNullable && field.isOptional ? '\$_setFieldNullable' : '\$_setField'; + out.printlnAnnotated( 'set ${names.fieldName}' '($fieldTypeString v) { ' - '\$_setField(${field.number}, v);' + '$setterName(${field.number}, v);' ' }', [ NamedLocation( @@ -649,7 +669,7 @@ class MessageGenerator extends ProtobufContainer { } String _getterExpression(String fieldType, int index, String defaultExpr, - bool isRepeated, bool isMapField) { + bool isRepeated, bool isMapField, bool isNullable) { if (isMapField) { return '\$_getMap($index)'; } @@ -659,21 +679,36 @@ class MessageGenerator extends ProtobufContainer { } return '\$_getS($index, $defaultExpr)'; } + if (fieldType == '$coreImportPrefix.String?') { + return '\$_getSNullable($index)'; + } if (fieldType == '$coreImportPrefix.bool') { if (defaultExpr == 'false') { return '\$_getBF($index)'; } return '\$_getB($index, $defaultExpr)'; } + if (fieldType == '$coreImportPrefix.bool?') { + return '\$_getBNullable($index)'; + } if (fieldType == '$coreImportPrefix.int') { if (defaultExpr == '0') { return '\$_getIZ($index)'; } return '\$_getI($index, $defaultExpr)'; } + if (fieldType == '$coreImportPrefix.int?') { + return '\$_getINullable($index)'; + } if (fieldType == '$_fixnumImportPrefix.Int64' && defaultExpr == 'null') { return '\$_getI64($index)'; } + if (fieldType == '$_fixnumImportPrefix.Int64?') { + return '\$_getI64Nullable($index)'; + } + if (isNullable) { + return '\$_getNullable($index)'; + } if (defaultExpr == 'null') { return isRepeated ? '\$_getList($index)' : '\$_getN($index)'; } diff --git a/protoc_plugin/lib/src/options.dart b/protoc_plugin/lib/src/options.dart index 25b960b26..99e37919e 100644 --- a/protoc_plugin/lib/src/options.dart +++ b/protoc_plugin/lib/src/options.dart @@ -51,11 +51,13 @@ class GenerationOptions { final bool useGrpc; final bool generateMetadata; final bool disableConstructorArgs; + final bool useNullable; GenerationOptions( {this.useGrpc = false, this.generateMetadata = false, - this.disableConstructorArgs = false}); + this.disableConstructorArgs = false, + this.useNullable = false}); } /// A parser for a name-value pair option. Options parsed in @@ -108,6 +110,19 @@ class DisableConstructorArgsParser implements SingleOptionParser { } } +class NullableOptionParser implements SingleOptionParser { + bool nullableEnabled = false; + + @override + void parse(String name, String? value, OnError onError) { + if (value != null) { + onError('Invalid nullable option. No value expected.'); + return; + } + nullableEnabled = true; + } +} + /// Parser used by the compiler, which supports the `rpc` option (see /// [GrpcOptionParser]) and any additional option added in [parsers]. If /// [parsers] has a key for `rpc`, it will be ignored. @@ -122,6 +137,8 @@ GenerationOptions? parseGenerationOptions( final generateMetadataParser = GenerateMetadataParser(); newParsers['generate_kythe_info'] = generateMetadataParser; + final nullableOptionParser = NullableOptionParser(); + newParsers['nullable'] = nullableOptionParser; final disableConstructorArgsParser = DisableConstructorArgsParser(); newParsers['disable_constructor_args'] = disableConstructorArgsParser; @@ -130,7 +147,8 @@ GenerationOptions? parseGenerationOptions( return GenerationOptions( useGrpc: grpcOptionParser.grpcEnabled, generateMetadata: generateMetadataParser.generateKytheInfo, - disableConstructorArgs: disableConstructorArgsParser.value); + disableConstructorArgs: disableConstructorArgsParser.value, + useNullable: nullableOptionParser.nullableEnabled); } return null; } diff --git a/protoc_plugin/lib/src/protobuf_field.dart b/protoc_plugin/lib/src/protobuf_field.dart index b4ca5d1d0..e7a9e38a2 100644 --- a/protoc_plugin/lib/src/protobuf_field.dart +++ b/protoc_plugin/lib/src/protobuf_field.dart @@ -65,6 +65,12 @@ class ProtobufField { bool get isRepeated => descriptor.label == FieldDescriptorProto_Label.LABEL_REPEATED; + bool get isOptional { + if (isRepeated) return false; + if (isRequired || !descriptor.proto3Optional) return false; + return true; + } + /// Whether a numeric field is repeated and must be encoded with packed /// encoding. /// diff --git a/protoc_plugin/test/message_generator_test.dart b/protoc_plugin/test/message_generator_test.dart index c25fa0ec0..25ab52316 100644 --- a/protoc_plugin/test/message_generator_test.dart +++ b/protoc_plugin/test/message_generator_test.dart @@ -78,7 +78,8 @@ void main() { CodeGeneratorResponse())!; final fg = FileGenerator(fd, options); - final mg = MessageGenerator.topLevel(md, fg, {}, null, {}, 0); + final mg = + MessageGenerator.topLevel(md, fg, {}, null, {}, 0, false); final ctx = GenerationContext(options); mg.register(ctx); @@ -103,7 +104,8 @@ void main() { CodeGeneratorRequest()..parameter = 'disable_constructor_args', CodeGeneratorResponse())!; final fg = FileGenerator(fd, options); - final mg = MessageGenerator.topLevel(md, fg, {}, null, {}, 0); + final mg = + MessageGenerator.topLevel(md, fg, {}, null, {}, 0, false); final ctx = GenerationContext(options); mg.register(ctx); diff --git a/protoc_plugin/test/protos/google/protobuf/unittest_import_proto3.proto b/protoc_plugin/test/protos/google/protobuf/unittest_import_proto3.proto new file mode 100755 index 000000000..49f67ded9 --- /dev/null +++ b/protoc_plugin/test/protos/google/protobuf/unittest_import_proto3.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// A proto file which is imported by unittest_proto3.proto to test importing. + +syntax = "proto3"; + +import public "google/protobuf/unittest_import_public_proto3.proto"; + +package protobuf_unittest_proto3_import; + +message ImportMessage { + int32 d = 1; +} + +enum ImportEnum { + IMPORT_ENUM_UNSPECIFIED = 0; + IMPORT_FOO = 7; + IMPORT_BAR = 8; + IMPORT_BAZ = 9; +} diff --git a/protoc_plugin/test/protos/google/protobuf/unittest_import_public_proto3.proto b/protoc_plugin/test/protos/google/protobuf/unittest_import_public_proto3.proto new file mode 100644 index 000000000..cf135fc69 --- /dev/null +++ b/protoc_plugin/test/protos/google/protobuf/unittest_import_public_proto3.proto @@ -0,0 +1,41 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: liujisi@google.com (Pherl Liu) + +syntax = "proto3"; + +package protobuf_unittest_proto3_import; + +option csharp_namespace = "Google.Protobuf.TestProtos"; + +message PublicImportMessage { + int32 e = 1; +} diff --git a/protoc_plugin/test/protos/google/protobuf/unittest_proto3.proto b/protoc_plugin/test/protos/google/protobuf/unittest_proto3.proto new file mode 100755 index 000000000..7cb6f123c --- /dev/null +++ b/protoc_plugin/test/protos/google/protobuf/unittest_proto3.proto @@ -0,0 +1,376 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// A proto file we will use for unit testing. + +syntax = "proto3"; + +import "google/protobuf/unittest_import_proto3.proto"; + +package protobuf_unittest_proto3; + +// Protos optimized for SPEED use a strict superset of the generated code +// of equivalent ones optimized for CODE_SIZE, so we should optimize all our +// tests for speed unless explicitly testing code size optimization. +option optimize_for = SPEED; + +option java_outer_classname = "UnittestProto"; + +// This proto includes every type of field in both singular and repeated +// forms. +message TestAllTypes { + message NestedMessage { + // The field name "b" fails to compile in proto1 because it conflicts with + // a local variable named "b" in one of the generated methods. Doh. + // This file needs to compile in proto1 to test backwards-compatibility. + int32 bb = 1; + } + + enum NestedEnum { + NESTED_ENUM_UNSPECIFIED = 0; + FOO = 1; + BAR = 2; + BAZ = 3; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 single_int32 = 1; + optional int32 optional_single_int32 = 1001; + int64 single_int64 = 2; + uint32 single_uint32 = 3; + uint64 single_uint64 = 4; + sint32 single_sint32 = 5; + sint64 single_sint64 = 6; + fixed32 single_fixed32 = 7; + fixed64 single_fixed64 = 8; + sfixed32 single_sfixed32 = 9; + sfixed64 single_sfixed64 = 10; + float single_float = 11; + double single_double = 12; + bool single_bool = 13; + string single_string = 14; + bytes single_bytes = 15; + + NestedMessage single_nested_message = 18; + ForeignMessage single_foreign_message = 19; + protobuf_unittest_proto3_import.ImportMessage single_import_message = 20; + + NestedEnum single_nested_enum = 21; + ForeignEnum single_foreign_enum = 22; + protobuf_unittest_proto3_import.ImportEnum single_import_enum = 23; + + // Defined in unittest_import_public.proto + protobuf_unittest_proto3_import.PublicImportMessage single_public_import_message = 26; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessage repeated_foreign_message = 49; + repeated protobuf_unittest_proto3_import.ImportMessage repeated_import_message = 50; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnum repeated_foreign_enum = 52; + repeated protobuf_unittest_proto3_import.ImportEnum repeated_import_enum = 53; + // Defined in unittest_import_public.proto + repeated protobuf_unittest_proto3_import.PublicImportMessage repeated_public_import_message = 54; + + // For oneof test + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + } +} + +// This proto includes a recursively nested message. +message NestedTestAllTypes { + NestedTestAllTypes child = 1; + TestAllTypes payload = 2; + repeated NestedTestAllTypes repeated_child = 3; +} + +message TestDeprecatedFields { + int32 deprecated_int32 = 1 [deprecated=true]; +} + +// Define these after TestAllTypes to make sure the compiler can handle +// that. +message ForeignMessage { + int32 c = 1; +} + +enum ForeignEnum { + FOREIGN_UNSPECIFIED = 0; + FOREIGN_FOO = 4; + FOREIGN_BAR = 5; + FOREIGN_BAZ = 6; +} + +message TestReservedFields { + reserved 2, 15, 9 to 11; + reserved "bar", "baz"; +} + + +// Test that we can use NestedMessage from outside TestAllTypes. +message TestForeignNested { + TestAllTypes.NestedMessage foreign_nested = 1; +} + +// Test that really large tag numbers don't break anything. +message TestReallyLargeTagNumber { + // The largest possible tag number is 2^28 - 1, since the wire format uses + // three bits to communicate wire type. + int32 a = 1; + int32 bb = 268435455; +} + +message TestRecursiveMessage { + TestRecursiveMessage a = 1; + int32 i = 2; +} + +// Test that mutual recursion works. +message TestMutualRecursionA { + TestMutualRecursionB bb = 1; +} + +message TestMutualRecursionB { + TestMutualRecursionA a = 1; + int32 optional_int32 = 2; +} + + +// Test an enum that has multiple values with the same number. +enum TestEnumWithDupValue { + TEST_ENUM_WITH_DUP_VALUE_UNSPECIFIED = 0; + option allow_alias = true; + + FOO1 = 1; + BAR1 = 2; + BAZ = 3; + FOO2 = 1; + BAR2 = 2; +} + +// Test an enum with large, unordered values. +enum TestSparseEnum { + TEST_SPARSE_ENUM_UNSPECIFIED = 0; + SPARSE_A = 123; + SPARSE_B = 62374; + SPARSE_C = 12589234; + SPARSE_D = -15; + SPARSE_E = -53452; + // In proto3, value 0 must be the first one specified + // SPARSE_F = 0; + SPARSE_G = 2; +} + +// Test message with CamelCase field names. This violates Protocol Buffer +// standard style. +message TestCamelCaseFieldNames { + int32 PrimitiveField = 1; + string StringField = 2; + ForeignEnum EnumField = 3; + ForeignMessage MessageField = 4; + + repeated int32 RepeatedPrimitiveField = 7; + repeated string RepeatedStringField = 8; + repeated ForeignEnum RepeatedEnumField = 9; + repeated ForeignMessage RepeatedMessageField = 10; +} + + +// We list fields out of order, to ensure that we're using field number and not +// field index to determine serialization order. +message TestFieldOrderings { + string my_string = 11; + int64 my_int = 1; + float my_float = 101; + message NestedMessage { + int64 oo = 2; + // The field name "b" fails to compile in proto1 because it conflicts with + // a local variable named "b" in one of the generated methods. Doh. + // This file needs to compile in proto1 to test backwards-compatibility. + int32 bb = 1; + } + + NestedMessage single_nested_message = 200; +} + +message SparseEnumMessage { + TestSparseEnum sparse_enum = 1; +} + +// Test String and Bytes: string is for valid UTF-8 strings +message OneString { + string data = 1; +} + +message MoreString { + repeated string data = 1; +} + +message OneBytes { + bytes data = 1; +} + +message MoreBytes { + bytes data = 1; +} + +// Test int32, uint32, int64, uint64, and bool are all compatible +message Int32Message { + int32 data = 1; +} + +message Uint32Message { + uint32 data = 1; +} + +message Int64Message { + int64 data = 1; +} + +message Uint64Message { + uint64 data = 1; +} + +message BoolMessage { + bool data = 1; +} + +// Test oneofs. +message TestOneof { + oneof foo { + int32 foo_int = 1; + string foo_string = 2; + TestAllTypes foo_message = 3; + } +} + +// Test messages for packed fields + +message TestPackedTypes { + repeated int32 packed_int32 = 90 [packed = true]; + repeated int64 packed_int64 = 91 [packed = true]; + repeated uint32 packed_uint32 = 92 [packed = true]; + repeated uint64 packed_uint64 = 93 [packed = true]; + repeated sint32 packed_sint32 = 94 [packed = true]; + repeated sint64 packed_sint64 = 95 [packed = true]; + repeated fixed32 packed_fixed32 = 96 [packed = true]; + repeated fixed64 packed_fixed64 = 97 [packed = true]; + repeated sfixed32 packed_sfixed32 = 98 [packed = true]; + repeated sfixed64 packed_sfixed64 = 99 [packed = true]; + repeated float packed_float = 100 [packed = true]; + repeated double packed_double = 101 [packed = true]; + repeated bool packed_bool = 102 [packed = true]; + repeated ForeignEnum packed_enum = 103 [packed = true]; +} + +// A message with the same fields as TestPackedTypes, but without packing. Used +// to test packed <-> unpacked wire compatibility. +message TestUnpackedTypes { + repeated int32 unpacked_int32 = 90 [packed = false]; + repeated int64 unpacked_int64 = 91 [packed = false]; + repeated uint32 unpacked_uint32 = 92 [packed = false]; + repeated uint64 unpacked_uint64 = 93 [packed = false]; + repeated sint32 unpacked_sint32 = 94 [packed = false]; + repeated sint64 unpacked_sint64 = 95 [packed = false]; + repeated fixed32 unpacked_fixed32 = 96 [packed = false]; + repeated fixed64 unpacked_fixed64 = 97 [packed = false]; + repeated sfixed32 unpacked_sfixed32 = 98 [packed = false]; + repeated sfixed64 unpacked_sfixed64 = 99 [packed = false]; + repeated float unpacked_float = 100 [packed = false]; + repeated double unpacked_double = 101 [packed = false]; + repeated bool unpacked_bool = 102 [packed = false]; + repeated ForeignEnum unpacked_enum = 103 [packed = false]; +} + +message TestRepeatedScalarDifferentTagSizes { + // Parsing repeated fixed size values used to fail. This message needs to be + // used in order to get a tag of the right size; all of the repeated fields + // in TestAllTypes didn't trigger the check. + repeated fixed32 repeated_fixed32 = 12; + // Check for a varint type, just for good measure. + repeated int32 repeated_int32 = 13; + + // These have two-byte tags. + repeated fixed64 repeated_fixed64 = 2046; + repeated int64 repeated_int64 = 2047; + + // Three byte tags. + repeated float repeated_float = 262142; + repeated uint64 repeated_uint64 = 262143; +} + +message TestCommentInjectionMessage { + // */ <- This should not close the generated doc comment + string a = 1; +} + + +// Test that RPC services work. +message FooRequest {} +message FooResponse {} + +message FooClientMessage {} +message FooServerMessage{} + +service TestService { + rpc Foo(FooRequest) returns (FooResponse); + rpc Bar(BarRequest) returns (BarResponse); +} + + +message BarRequest {} +message BarResponse {} + diff --git a/protoc_plugin/test/unittest_proto3_test.dart b/protoc_plugin/test/unittest_proto3_test.dart new file mode 100644 index 000000000..4d9866a99 --- /dev/null +++ b/protoc_plugin/test/unittest_proto3_test.dart @@ -0,0 +1,22 @@ +import 'package:test/test.dart'; +import '../out/protos/nullable/google/protobuf/unittest_proto3.pb.dart'; + +void main() { + group('Optional fields should be nullable for', () { + test('int32', () { + final obj = TestAllTypes.create() + ..singleInt32 = 1 + ..optionalSingleInt32 = 2; + expect(obj.singleInt32, 1); + expect(obj.optionalSingleInt32, 2); + expect(obj.hasOptionalSingleInt32(), true); + + final obj2 = TestAllTypes.create()..singleInt32 = 1; + expect(obj2.singleInt32, 1); + expect(obj2.optionalSingleInt32, null); + // should not generate linting errors + expect(obj2.optionalSingleInt32 ?? 1, 1); + expect(obj2.hasOptionalSingleInt32(), false); + }); + }); +}