named optional arguments in constructors for messages (#441)

diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index cfa93cf..29a7741 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 20.0.0-nullsafety.1
+
+* Generate constructors with optional named arguments for prefilling fields.
 ## 20.0.0-nullsafety.0
 
 * Generate null-safe code.
diff --git a/protoc_plugin/lib/base_type.dart b/protoc_plugin/lib/base_type.dart
index 7559c7e..8bb6d8f 100644
--- a/protoc_plugin/lib/base_type.dart
+++ b/protoc_plugin/lib/base_type.dart
@@ -55,6 +55,9 @@
   String getRepeatedDartType(FileGenerator fileGen) =>
       "$_coreImportPrefix.List<${getDartType(fileGen)}>";
 
+  String getRepeatedDartTypeIterable(FileGenerator fileGen) =>
+      "$_coreImportPrefix.Iterable<${getDartType(fileGen)}>";
+
   factory BaseType(FieldDescriptorProto field, GenerationContext ctx) {
     String constSuffix;
 
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index 227eb78..983b6b1 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -357,7 +357,44 @@
       out.printlnAnnotated('${classname}._() : super();', [
         NamedLocation(name: classname, fieldPathSegment: fieldPath, start: 0)
       ]);
-      out.println('factory ${classname}() => create();');
+      out.print('factory $classname(');
+      if (_fieldList.isNotEmpty) {
+        out.println('{');
+        for (final field in _fieldList) {
+          _emitDeprecatedIf(field.isDeprecated, out);
+          if (field.isRepeated && !field.isMapField) {
+            out.println(
+                '  ${field.baseType.getRepeatedDartTypeIterable(fileGen)}? ${field.memberNames.fieldName},');
+          } else {
+            out.println(
+                '  ${field.getDartType(fileGen)}? ${field.memberNames.fieldName},');
+          }
+        }
+        out.print('}');
+      }
+      if (_fieldList.isNotEmpty) {
+        out.println(') {');
+        out.println('  final _result = create();');
+        for (final field in _fieldList) {
+          out.println('  if (${field.memberNames.fieldName} != null) {');
+          if (field.isDeprecated) {
+            out.println(
+                '    // ignore: deprecated_member_use_from_same_package');
+          }
+          if (field.isRepeated || field.isMapField) {
+            out.println(
+                '    _result.${field.memberNames.fieldName}.addAll(${field.memberNames.fieldName});');
+          } else {
+            out.println(
+                '    _result.${field.memberNames.fieldName} = ${field.memberNames.fieldName};');
+          }
+          out.println('  }');
+        }
+        out.println('  return _result;');
+        out.println('}');
+      } else {
+        out.println(') => create();');
+      }
       out.println(
           'factory ${classname}.fromBuffer($_coreImportPrefix.List<$_coreImportPrefix.int> i,'
           ' [$_protobufImportPrefix.ExtensionRegistry r = $_protobufImportPrefix.ExtensionRegistry.EMPTY])'
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index 37f8e6d..114327e 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
 name: protoc_plugin
-version: 20.0.0-nullsafety.0
+version: 20.0.0-nullsafety.1
 description: Protoc compiler plugin to generate Dart code
 homepage: https://github.com/dart-lang/protobuf
 
diff --git a/protoc_plugin/test/generated_message_test.dart b/protoc_plugin/test/generated_message_test.dart
index 34359cb..461fd8c 100755
--- a/protoc_plugin/test/generated_message_test.dart
+++ b/protoc_plugin/test/generated_message_test.dart
@@ -887,4 +887,88 @@
     final value2 = value1.deepCopy();
     assertAllExtensionsSet(value2);
   });
+
+  test('named arguments in constructor', () {
+    final value = TestAllTypes(
+      optionalInt32: 101,
+      optionalInt64: make64(102),
+      optionalUint32: 103,
+      optionalUint64: make64(104),
+      optionalSint32: 105,
+      optionalSint64: make64(106),
+      optionalFixed32: 107,
+      optionalFixed64: make64(108),
+      optionalSfixed32: 109,
+      optionalSfixed64: make64(110),
+      optionalFloat: 111.0,
+      optionalDouble: 112.0,
+      optionalBool: true,
+      optionalString: '115',
+      optionalBytes: '116'.codeUnits,
+      optionalGroup: TestAllTypes_OptionalGroup(a: 117),
+      optionalNestedMessage: TestAllTypes_NestedMessage(bb: 118),
+      optionalForeignMessage: ForeignMessage(c: 119),
+      optionalImportMessage: ImportMessage(d: 120),
+      optionalNestedEnum: TestAllTypes_NestedEnum.BAZ,
+      optionalForeignEnum: ForeignEnum.FOREIGN_BAZ,
+      optionalImportEnum: ImportEnum.IMPORT_BAZ,
+      optionalStringPiece: '124',
+      optionalCord: '125',
+      repeatedInt32: [201, 301],
+      repeatedInt64: [make64(202), make64(302)],
+      repeatedUint32: [203, 303],
+      repeatedUint64: [make64(204), make64(304)],
+      repeatedSint32: [205, 305],
+      repeatedSint64: [make64(206), make64(306)],
+      repeatedFixed32: [207, 307],
+      repeatedFixed64: [make64(208), make64(308)],
+      repeatedSfixed32: [209, 309],
+      repeatedSfixed64: [make64(210), make64(310)],
+      repeatedFloat: [211.0, 311.0],
+      repeatedDouble: [212.0, 312.0],
+      repeatedBool: [true, false],
+      repeatedString: ['215', '315'],
+      repeatedBytes: ['216'.codeUnits, '316'.codeUnits],
+      repeatedGroup: [
+        TestAllTypes_RepeatedGroup(a: 217),
+        TestAllTypes_RepeatedGroup(a: 317)
+      ],
+      repeatedNestedMessage: [
+        TestAllTypes_NestedMessage(bb: 218),
+        TestAllTypes_NestedMessage(bb: 318)
+      ],
+      repeatedForeignMessage: [ForeignMessage(c: 219), ForeignMessage(c: 319)],
+      repeatedImportMessage: [ImportMessage(d: 220), ImportMessage(d: 320)],
+      repeatedNestedEnum: [
+        TestAllTypes_NestedEnum.BAR,
+        TestAllTypes_NestedEnum.BAZ
+      ],
+      repeatedForeignEnum: [ForeignEnum.FOREIGN_BAR, ForeignEnum.FOREIGN_BAZ],
+      repeatedImportEnum: [ImportEnum.IMPORT_BAR, ImportEnum.IMPORT_BAZ],
+      repeatedStringPiece: ['224', '324'],
+      repeatedCord: ['225', '325'],
+      defaultInt32: 401,
+      defaultInt64: make64(402),
+      defaultUint32: 403,
+      defaultUint64: make64(404),
+      defaultSint32: 405,
+      defaultSint64: make64(406),
+      defaultFixed32: 407,
+      defaultFixed64: make64(408),
+      defaultSfixed32: 409,
+      defaultSfixed64: make64(410),
+      defaultFloat: 411.0,
+      defaultDouble: 412.0,
+      defaultBool: false,
+      defaultString: '415',
+      defaultBytes: '416'.codeUnits,
+      defaultNestedEnum: TestAllTypes_NestedEnum.FOO,
+      defaultForeignEnum: ForeignEnum.FOREIGN_FOO,
+      defaultImportEnum: ImportEnum.IMPORT_FOO,
+      defaultStringPiece: '424',
+      defaultCord: '425',
+    );
+
+    assertAllFieldsSet(value);
+  });
 }
diff --git a/protoc_plugin/test/goldens/imports.pb b/protoc_plugin/test/goldens/imports.pb
index 54f84fb..52a4166 100644
--- a/protoc_plugin/test/goldens/imports.pb
+++ b/protoc_plugin/test/goldens/imports.pb
@@ -21,7 +21,23 @@
   ;
 
   M._() : super();
-  factory M() => create();
+  factory M({
+    M? m,
+    $1.M? m1,
+    $2.M? m2,
+  }) {
+    final _result = create();
+    if (m != null) {
+      _result.m = m;
+    }
+    if (m1 != null) {
+      _result.m1 = m1;
+    }
+    if (m2 != null) {
+      _result.m2 = m2;
+    }
+    return _result;
+  }
   factory M.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
   factory M.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
diff --git a/protoc_plugin/test/goldens/int64.pb b/protoc_plugin/test/goldens/int64.pb
index 70782e6..922414b 100644
--- a/protoc_plugin/test/goldens/int64.pb
+++ b/protoc_plugin/test/goldens/int64.pb
@@ -17,7 +17,15 @@
   ;
 
   Int64._() : super();
-  factory Int64() => create();
+  factory Int64({
+    $fixnum.Int64? value,
+  }) {
+    final _result = create();
+    if (value != null) {
+      _result.value = value;
+    }
+    return _result;
+  }
   factory Int64.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
   factory Int64.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
diff --git a/protoc_plugin/test/goldens/messageGenerator b/protoc_plugin/test/goldens/messageGenerator
index 6840a22..151559c 100644
--- a/protoc_plugin/test/goldens/messageGenerator
+++ b/protoc_plugin/test/goldens/messageGenerator
@@ -7,7 +7,29 @@
   ;
 
   PhoneNumber._() : super();
-  factory PhoneNumber() => create();
+  factory PhoneNumber({
+    $core.String? number,
+    PhoneNumber_PhoneType? type,
+    $core.String? name,
+  @$core.Deprecated('This field is deprecated.')
+    $core.String? deprecatedField,
+  }) {
+    final _result = create();
+    if (number != null) {
+      _result.number = number;
+    }
+    if (type != null) {
+      _result.type = type;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (deprecatedField != null) {
+      // ignore: deprecated_member_use_from_same_package
+      _result.deprecatedField = deprecatedField;
+    }
+    return _result;
+  }
   factory PhoneNumber.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
   factory PhoneNumber.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
index e852feb..2fd6f55 100644
--- a/protoc_plugin/test/goldens/messageGenerator.meta
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -18,8 +18,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 2263
-  end: 2269
+  begin: 2802
+  end: 2808
 }
 annotation: {
   path: 4
@@ -27,8 +27,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 2311
-  end: 2317
+  begin: 2850
+  end: 2856
 }
 annotation: {
   path: 4
@@ -36,8 +36,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 2390
-  end: 2399
+  begin: 2929
+  end: 2938
 }
 annotation: {
   path: 4
@@ -45,8 +45,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 2442
-  end: 2453
+  begin: 2981
+  end: 2992
 }
 annotation: {
   path: 4
@@ -54,8 +54,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 2523
-  end: 2527
+  begin: 3062
+  end: 3066
 }
 annotation: {
   path: 4
@@ -63,8 +63,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 2568
-  end: 2572
+  begin: 3107
+  end: 3111
 }
 annotation: {
   path: 4
@@ -72,8 +72,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 2651
-  end: 2658
+  begin: 3190
+  end: 3197
 }
 annotation: {
   path: 4
@@ -81,8 +81,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 2701
-  end: 2710
+  begin: 3240
+  end: 3249
 }
 annotation: {
   path: 4
@@ -90,8 +90,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 2771
-  end: 2775
+  begin: 3310
+  end: 3314
 }
 annotation: {
   path: 4
@@ -99,8 +99,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 2822
-  end: 2826
+  begin: 3361
+  end: 3365
 }
 annotation: {
   path: 4
@@ -108,8 +108,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 2899
-  end: 2906
+  begin: 3438
+  end: 3445
 }
 annotation: {
   path: 4
@@ -117,8 +117,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 2949
-  end: 2958
+  begin: 3488
+  end: 3497
 }
 annotation: {
   path: 4
@@ -126,8 +126,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 3068
-  end: 3083
+  begin: 3607
+  end: 3622
 }
 annotation: {
   path: 4
@@ -135,8 +135,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 3174
-  end: 3189
+  begin: 3713
+  end: 3728
 }
 annotation: {
   path: 4
@@ -144,8 +144,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 3311
-  end: 3329
+  begin: 3850
+  end: 3868
 }
 annotation: {
   path: 4
@@ -153,6 +153,6 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 3421
-  end: 3441
+  begin: 3960
+  end: 3980
 }
diff --git a/protoc_plugin/test/goldens/oneMessage.pb b/protoc_plugin/test/goldens/oneMessage.pb
index dbe5523..f07c350 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb
+++ b/protoc_plugin/test/goldens/oneMessage.pb
@@ -17,7 +17,23 @@
   ;
 
   PhoneNumber._() : super();
-  factory PhoneNumber() => create();
+  factory PhoneNumber({
+    $core.String? number,
+    $core.int? type,
+    $core.String? name,
+  }) {
+    final _result = create();
+    if (number != null) {
+      _result.number = number;
+    }
+    if (type != null) {
+      _result.type = type;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    return _result;
+  }
   factory PhoneNumber.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
   factory PhoneNumber.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
index 70e4ab4..72f03dc 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb.meta
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -18,8 +18,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 2390
-  end: 2396
+  begin: 2686
+  end: 2692
 }
 annotation: {
   path: 4
@@ -27,8 +27,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 2438
-  end: 2444
+  begin: 2734
+  end: 2740
 }
 annotation: {
   path: 4
@@ -36,51 +36,6 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 2517
-  end: 2526
-}
-annotation: {
-  path: 4
-  path: 0
-  path: 2
-  path: 0
-  sourceFile: test
-  begin: 2569
-  end: 2580
-}
-annotation: {
-  path: 4
-  path: 0
-  path: 2
-  path: 1
-  sourceFile: test
-  begin: 2638
-  end: 2642
-}
-annotation: {
-  path: 4
-  path: 0
-  path: 2
-  path: 1
-  sourceFile: test
-  begin: 2684
-  end: 2688
-}
-annotation: {
-  path: 4
-  path: 0
-  path: 2
-  path: 1
-  sourceFile: test
-  begin: 2763
-  end: 2770
-}
-annotation: {
-  path: 4
-  path: 0
-  path: 2
-  path: 1
-  sourceFile: test
   begin: 2813
   end: 2822
 }
@@ -88,16 +43,16 @@
   path: 4
   path: 0
   path: 2
-  path: 2
+  path: 0
   sourceFile: test
-  begin: 2883
-  end: 2887
+  begin: 2865
+  end: 2876
 }
 annotation: {
   path: 4
   path: 0
   path: 2
-  path: 2
+  path: 1
   sourceFile: test
   begin: 2934
   end: 2938
@@ -106,10 +61,28 @@
   path: 4
   path: 0
   path: 2
-  path: 2
+  path: 1
   sourceFile: test
-  begin: 3011
-  end: 3018
+  begin: 2980
+  end: 2984
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 2
+  path: 1
+  sourceFile: test
+  begin: 3059
+  end: 3066
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 2
+  path: 1
+  sourceFile: test
+  begin: 3109
+  end: 3118
 }
 annotation: {
   path: 4
@@ -117,6 +90,33 @@
   path: 2
   path: 2
   sourceFile: test
-  begin: 3061
-  end: 3070
+  begin: 3179
+  end: 3183
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 2
+  path: 2
+  sourceFile: test
+  begin: 3230
+  end: 3234
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 2
+  path: 2
+  sourceFile: test
+  begin: 3307
+  end: 3314
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 2
+  path: 2
+  sourceFile: test
+  begin: 3357
+  end: 3366
 }
diff --git a/protoc_plugin/test/map_field_test.dart b/protoc_plugin/test/map_field_test.dart
index ff94157..ec17f35 100644
--- a/protoc_plugin/test/map_field_test.dart
+++ b/protoc_plugin/test/map_field_test.dart
@@ -341,4 +341,28 @@
     final value = testMap.getField(mapFieldInfo.tagNumber);
     expect(value is Map<int, List<int>>, true);
   });
+
+  test('named optional arguments in cosntructor', () {
+    final testMap = TestMap(
+      int32ToInt32Field: {1: 11, 2: 22, 3: 33},
+      int32ToStringField: {1: '11', 2: '22', 3: '33'},
+      int32ToBytesField: {
+        1: utf8.encode('11'),
+        2: utf8.encode('22'),
+        3: utf8.encode('33')
+      },
+      int32ToEnumField: {
+        1: TestMap_EnumValue.DEFAULT,
+        2: TestMap_EnumValue.BAR,
+        3: TestMap_EnumValue.BAZ
+      },
+      int32ToMessageField: {
+        1: TestMap_MessageValue(value: 11),
+        2: TestMap_MessageValue(value: 22),
+        3: TestMap_MessageValue(value: 33)
+      },
+      stringToInt32Field: {'1': 11, '2': 22, '3': 33},
+    );
+    _expectMapValuesSet(testMap);
+  });
 }