Skip to content

Commit ee37f1c

Browse files
[pigeon] Improve C++ data class constructors (flutter#3585)
[pigeon] Improve C++ data class constructors
1 parent 199fc68 commit ee37f1c

File tree

26 files changed

+466
-257
lines changed

26 files changed

+466
-257
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 9.1.3
2+
3+
* [cpp] Requires passing any non-nullable fields of generated data classes as
4+
constructor arguments, similar to what is done in other languages. This may
5+
require updates to existing code that creates data class instances on the
6+
native side.
7+
* [cpp] Adds a convenience constructor to generated data classes to set all
8+
fields during construction.
9+
110
## 9.1.2
211

312
* [cpp] Fixes class parameters to Flutter APIs.

packages/pigeon/lib/cpp_generator.dart

Lines changed: 169 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,24 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
183183
indent, klass.documentationComments, _docCommentSpec,
184184
generatorComments: generatedMessages);
185185

186+
final Iterable<NamedType> orderedFields =
187+
getFieldsInSerializationOrder(klass);
188+
186189
indent.write('class ${klass.name} ');
187190
indent.addScoped('{', '};', () {
188191
indent.addScoped(' public:', '', () {
189-
indent.writeln('${klass.name}();');
190-
for (final NamedType field in getFieldsInSerializationOrder(klass)) {
192+
final Iterable<NamedType> requiredFields =
193+
orderedFields.where((NamedType type) => !type.type.isNullable);
194+
// Minimal constructor, if needed.
195+
if (requiredFields.length != orderedFields.length) {
196+
_writeClassConstructor(root, indent, klass, requiredFields,
197+
'Constructs an object setting all non-nullable fields.');
198+
}
199+
// All-field constructor.
200+
_writeClassConstructor(root, indent, klass, orderedFields,
201+
'Constructs an object setting all fields.');
202+
203+
for (final NamedType field in orderedFields) {
191204
addDocumentationComments(
192205
indent, field.documentationComments, _docCommentSpec);
193206
final HostDatatype baseDatatype = getFieldHostDatatype(
@@ -209,7 +222,8 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
209222
});
210223

211224
indent.addScoped(' private:', '', () {
212-
indent.writeln('${klass.name}(const flutter::EncodableList& list);');
225+
indent.writeln(
226+
'static ${klass.name} FromEncodableList(const flutter::EncodableList& list);');
213227
indent.writeln('flutter::EncodableList ToEncodableList() const;');
214228
for (final Class friend in root.classes) {
215229
if (friend != klass &&
@@ -228,7 +242,7 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
228242
indent.writeln('friend class $testFixtureClass;');
229243
}
230244

231-
for (final NamedType field in getFieldsInSerializationOrder(klass)) {
245+
for (final NamedType field in orderedFields) {
232246
final HostDatatype hostDatatype = getFieldHostDatatype(
233247
field, root.classes, root.enums, _baseCppTypeForBuiltinDartType);
234248
indent.writeln(
@@ -366,6 +380,23 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
366380
}, nestCount: 0);
367381
}
368382

383+
void _writeClassConstructor(Root root, Indent indent, Class klass,
384+
Iterable<NamedType> params, String docComment) {
385+
final String explicit = params.isEmpty ? '' : 'explicit ';
386+
String paramString = params.map((NamedType param) {
387+
final HostDatatype hostDatatype = getFieldHostDatatype(
388+
param, root.classes, root.enums, _baseCppTypeForBuiltinDartType);
389+
return '\t${_hostApiArgumentType(hostDatatype)} ${_makeVariableName(param)}';
390+
}).join(',\n');
391+
if (paramString.isNotEmpty) {
392+
paramString = '\n$paramString';
393+
}
394+
indent.format('''
395+
$_commentPrefix $docComment
396+
$explicit${klass.name}($paramString);
397+
''');
398+
}
399+
369400
void _writeCodec(
370401
CppOptions generatorOptions, Root root, Indent indent, Api api) {
371402
assert(getCodecClasses(api, root).isNotEmpty);
@@ -423,12 +454,10 @@ class FlutterError {
423454
424455
template<class T> class ErrorOr {
425456
public:
426-
\tErrorOr(const T& rhs) { new(&v_) T(rhs); }
427-
\tErrorOr(const T&& rhs) { v_ = std::move(rhs); }
428-
\tErrorOr(const FlutterError& rhs) {
429-
\t\tnew(&v_) FlutterError(rhs);
430-
\t}
431-
\tErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); }
457+
\tErrorOr(const T& rhs) : v_(rhs) {}
458+
\tErrorOr(const T&& rhs) : v_(std::move(rhs)) {}
459+
\tErrorOr(const FlutterError& rhs) : v_(rhs) {}
460+
\tErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {}
432461
433462
\tbool has_error() const { return std::holds_alternative<FlutterError>(v_); }
434463
\tconst T& value() const { return std::get<T>(v_); };
@@ -528,19 +557,26 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
528557
indent.writeln('$_commentPrefix ${klass.name}');
529558
indent.newln();
530559

560+
final Iterable<NamedType> orderedFields =
561+
getFieldsInSerializationOrder(klass);
562+
final Iterable<NamedType> requiredFields =
563+
orderedFields.where((NamedType type) => !type.type.isNullable);
564+
// Minimal constructor, if needed.
565+
if (requiredFields.length != orderedFields.length) {
566+
_writeClassConstructor(root, indent, klass, requiredFields);
567+
}
568+
// All-field constructor.
569+
_writeClassConstructor(root, indent, klass, orderedFields);
570+
531571
// Getters and setters.
532-
for (final NamedType field in getFieldsInSerializationOrder(klass)) {
572+
for (final NamedType field in orderedFields) {
533573
_writeCppSourceClassField(generatorOptions, root, indent, klass, field);
534574
}
535575

536576
// Serialization.
537577
writeClassEncode(generatorOptions, root, indent, klass, customClassNames,
538578
customEnumNames);
539579

540-
// Default constructor.
541-
indent.writeln('${klass.name}::${klass.name}() {}');
542-
indent.newln();
543-
544580
// Deserialization.
545581
writeClassDecode(generatorOptions, root, indent, klass, customClassNames,
546582
customEnumNames);
@@ -581,47 +617,70 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
581617
Set<String> customClassNames,
582618
Set<String> customEnumNames,
583619
) {
584-
indent.write('${klass.name}::${klass.name}(const EncodableList& list) ');
620+
// Returns the expression to convert the given EncodableValue to a field
621+
// value.
622+
String getValueExpression(NamedType field, String encodable) {
623+
if (customEnumNames.contains(field.type.baseName)) {
624+
return '(${field.type.baseName})(std::get<int32_t>($encodable))';
625+
} else if (field.type.baseName == 'int') {
626+
return '$encodable.LongValue()';
627+
} else {
628+
final HostDatatype hostDatatype = getFieldHostDatatype(field,
629+
root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType);
630+
if (!hostDatatype.isBuiltin &&
631+
root.classes
632+
.map((Class x) => x.name)
633+
.contains(field.type.baseName)) {
634+
return '${hostDatatype.datatype}::FromEncodableList(std::get<EncodableList>($encodable))';
635+
} else {
636+
return 'std::get<${hostDatatype.datatype}>($encodable)';
637+
}
638+
}
639+
}
640+
641+
indent.write(
642+
'${klass.name} ${klass.name}::FromEncodableList(const EncodableList& list) ');
585643
indent.addScoped('{', '}', () {
586-
enumerate(getFieldsInSerializationOrder(klass),
587-
(int index, final NamedType field) {
588-
final String instanceVariableName = _makeInstanceVariableName(field);
589-
final String pointerFieldName =
590-
'${_pointerPrefix}_${_makeVariableName(field)}';
644+
const String instanceVariable = 'decoded';
645+
final Iterable<_IndexedField> indexedFields = indexMap(
646+
getFieldsInSerializationOrder(klass),
647+
(int index, NamedType field) => _IndexedField(index, field));
648+
final Iterable<_IndexedField> nullableFields = indexedFields
649+
.where((_IndexedField field) => field.field.type.isNullable);
650+
final Iterable<_IndexedField> nonNullableFields = indexedFields
651+
.where((_IndexedField field) => !field.field.type.isNullable);
652+
653+
// Non-nullable fields must be set via the constructor.
654+
String constructorArgs = nonNullableFields
655+
.map((_IndexedField param) =>
656+
getValueExpression(param.field, 'list[${param.index}]'))
657+
.join(',\n\t');
658+
if (constructorArgs.isNotEmpty) {
659+
constructorArgs = '(\n\t$constructorArgs)';
660+
}
661+
indent.format('${klass.name} $instanceVariable$constructorArgs;');
662+
663+
// Add the nullable fields via setters, since converting the encodable
664+
// values to the pointer types that the convenience constructor uses for
665+
// nullable fields is non-trivial.
666+
for (final _IndexedField entry in nullableFields) {
667+
final NamedType field = entry.field;
668+
final String setterName = _makeSetterName(field);
591669
final String encodableFieldName =
592670
'${_encodablePrefix}_${_makeVariableName(field)}';
593-
indent.writeln('auto& $encodableFieldName = list[$index];');
594-
if (customEnumNames.contains(field.type.baseName)) {
595-
indent.writeln(
596-
'if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))\t$instanceVariableName = (${field.type.baseName})*$pointerFieldName;');
597-
} else {
598-
final HostDatatype hostDatatype = getFieldHostDatatype(field,
599-
root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType);
600-
if (field.type.baseName == 'int') {
601-
indent.format('''
602-
if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))
603-
\t$instanceVariableName = *$pointerFieldName;
604-
else if (const int64_t* ${pointerFieldName}_64 = std::get_if<int64_t>(&$encodableFieldName))
605-
\t$instanceVariableName = *${pointerFieldName}_64;''');
606-
} else if (!hostDatatype.isBuiltin &&
607-
root.classes
608-
.map((Class x) => x.name)
609-
.contains(field.type.baseName)) {
610-
indent.write(
611-
'if (const EncodableList* $pointerFieldName = std::get_if<EncodableList>(&$encodableFieldName)) ');
612-
indent.addScoped('{', '}', () {
613-
indent.writeln(
614-
'$instanceVariableName = ${hostDatatype.datatype}(*$pointerFieldName);');
615-
});
616-
} else {
617-
indent.write(
618-
'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName)) ');
619-
indent.addScoped('{', '}', () {
620-
indent.writeln('$instanceVariableName = *$pointerFieldName;');
621-
});
622-
}
623-
}
624-
});
671+
indent.writeln('auto& $encodableFieldName = list[${entry.index}];');
672+
673+
final String valueExpression =
674+
getValueExpression(field, encodableFieldName);
675+
indent.writeScoped('if (!$encodableFieldName.IsNull()) {', '}', () {
676+
indent.writeln('$instanceVariable.$setterName($valueExpression);');
677+
});
678+
}
679+
680+
// This returns by value, relying on copy elision, since it makes the
681+
// usage more convenient during deserialization than it would be with
682+
// explicit transfer via unique_ptr.
683+
indent.writeln('return $instanceVariable;');
625684
});
626685
}
627686

@@ -868,7 +927,7 @@ EncodableValue ${api.name}::WrapError(const FlutterError& error) {
868927
indent.writeln('case ${customClass.enumeration}:');
869928
indent.nest(1, () {
870929
indent.writeln(
871-
'return CustomEncodableValue(${customClass.name}(std::get<EncodableList>(ReadValue(stream))));');
930+
'return CustomEncodableValue(${customClass.name}::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));');
872931
});
873932
}
874933
indent.writeln('default:');
@@ -901,6 +960,42 @@ EncodableValue ${api.name}::WrapError(const FlutterError& error) {
901960
indent.newln();
902961
}
903962

963+
void _writeClassConstructor(
964+
Root root, Indent indent, Class klass, Iterable<NamedType> params) {
965+
final Iterable<_HostNamedType> hostParams = params.map((NamedType param) {
966+
return _HostNamedType(
967+
_makeVariableName(param),
968+
getFieldHostDatatype(
969+
param,
970+
root.classes,
971+
root.enums,
972+
_shortBaseCppTypeForBuiltinDartType,
973+
),
974+
param.type,
975+
);
976+
});
977+
978+
String paramString = hostParams
979+
.map((_HostNamedType param) =>
980+
'\t${_hostApiArgumentType(param.hostType)} ${param.name}')
981+
.join(',\n');
982+
if (paramString.isNotEmpty) {
983+
paramString = '\n$paramString\n';
984+
}
985+
986+
String initializerString = hostParams
987+
.map((_HostNamedType param) =>
988+
'${param.name}_(${_fieldValueExpression(param.hostType, param.name)})')
989+
.join(',\n\t\t');
990+
if (initializerString.isNotEmpty) {
991+
initializerString = ' : $initializerString';
992+
}
993+
994+
indent.format('''
995+
${klass.name}::${klass.name}($paramString)$initializerString {}
996+
''');
997+
}
998+
904999
void _writeCppSourceClassField(CppOptions generatorOptions, Root root,
9051000
Indent indent, Class klass, NamedType field) {
9061001
final HostDatatype hostDatatype = getFieldHostDatatype(
@@ -918,11 +1013,8 @@ EncodableValue ${api.name}::WrapError(const FlutterError& error) {
9181013
// generating multiple setter variants.
9191014
String makeSetter(HostDatatype type) {
9201015
const String setterArgumentName = 'value_arg';
921-
final String valueExpression = type.isNullable
922-
? '$setterArgumentName ? ${_valueType(type)}(*$setterArgumentName) : std::nullopt'
923-
: setterArgumentName;
9241016
return 'void $qualifiedSetterName(${_unownedArgumentType(type)} $setterArgumentName) '
925-
'{ $instanceVariableName = $valueExpression; }';
1017+
'{ $instanceVariableName = ${_fieldValueExpression(type, setterArgumentName)}; }';
9261018
}
9271019

9281020
indent.writeln(
@@ -938,6 +1030,18 @@ EncodableValue ${api.name}::WrapError(const FlutterError& error) {
9381030
indent.newln();
9391031
}
9401032

1033+
/// Returns the value to use when setting a field of the given type from
1034+
/// an argument of that type.
1035+
///
1036+
/// For non-nullable values this is just the variable itself, but for nullable
1037+
/// values this handles the conversion between an argument type (a pointer)
1038+
/// and the field type (a std::optional).
1039+
String _fieldValueExpression(HostDatatype type, String variable) {
1040+
return type.isNullable
1041+
? '$variable ? ${_valueType(type)}(*$variable) : std::nullopt'
1042+
: variable;
1043+
}
1044+
9411045
String _wrapResponse(Indent indent, Root root, TypeDeclaration returnType,
9421046
{String prefix = ''}) {
9431047
final String nonErrorPath;
@@ -1109,9 +1213,15 @@ class _HostNamedType {
11091213
final TypeDeclaration originalType;
11101214
}
11111215

1216+
/// Contains a class field and its serialization index.
1217+
class _IndexedField {
1218+
const _IndexedField(this.index, this.field);
1219+
final int index;
1220+
final NamedType field;
1221+
}
1222+
11121223
String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer';
11131224

1114-
const String _pointerPrefix = 'pointer';
11151225
const String _encodablePrefix = 'encodable';
11161226

11171227
String _getArgumentName(int count, NamedType argument) =>

packages/pigeon/lib/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import 'ast.dart';
1111
/// The current version of pigeon.
1212
///
1313
/// This must match the version in pubspec.yaml.
14-
const String pigeonVersion = '9.1.2';
14+
const String pigeonVersion = '9.1.3';
1515

1616
/// Read all the content from [stdin] to a String.
1717
String readStdin() {

packages/pigeon/mock_handler_tester/test/message.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v9.1.2), do not edit directly.
5+
// Autogenerated from Pigeon (v9.1.3), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
88

packages/pigeon/mock_handler_tester/test/test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v9.1.2), do not edit directly.
5+
// Autogenerated from Pigeon (v9.1.3), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
88
// ignore_for_file: avoid_relative_lib_imports

packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v9.1.2), do not edit directly.
5+
// Autogenerated from Pigeon (v9.1.3), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77

88
package com.example.alternate_language_test_plugin;

packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v9.1.2), do not edit directly.
5+
// Autogenerated from Pigeon (v9.1.3), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77

88
#import <Foundation/Foundation.h>

packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v9.1.2), do not edit directly.
5+
// Autogenerated from Pigeon (v9.1.3), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77

88
#import "CoreTests.gen.h"

0 commit comments

Comments
 (0)