[pigeon] Fix C++ generator's handling of non-class host APIs (#2270)
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index f784aa0..27ad31c 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 3.2.4
+
+* [c++] Fixes most non-class arguments and return values in host APIs. The
+ types of arguments and return values have changed, so this may require updates
+ to existing code.
+
## 3.2.3
* Adds `unnecessary_import` to linter ignore list in generated dart tests.
diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart
index 8cf965c..ad4758a 100644
--- a/packages/pigeon/lib/cpp_generator.dart
+++ b/packages/pigeon/lib/cpp_generator.dart
@@ -57,8 +57,8 @@
String _getCodecName(Api api) => '${api.name}CodecSerializer';
-String _pointerPrefix = 'pointer';
-String _encodablePrefix = 'encodable';
+const String _pointerPrefix = 'pointer';
+const String _encodablePrefix = 'encodable';
void _writeCodecHeader(Indent indent, Api api, Root root) {
final String codecName = _getCodecName(api);
@@ -134,39 +134,43 @@
}
}
-void _writeErrorOr(Indent indent) {
+void _writeErrorOr(Indent indent,
+ {Iterable<String> friends = const <String>[]}) {
+ final String friendLines = friends
+ .map((String className) => '\tfriend class $className;')
+ .join('\n');
indent.format('''
class FlutterError {
public:
-\tFlutterError();
\tFlutterError(const std::string& arg_code)
-\t\t: code(arg_code) {};
+\t\t: code(arg_code) {}
\tFlutterError(const std::string& arg_code, const std::string& arg_message)
-\t\t: code(arg_code), message(arg_message) {};
+\t\t: code(arg_code), message(arg_message) {}
\tFlutterError(const std::string& arg_code, const std::string& arg_message, const flutter::EncodableValue& arg_details)
-\t\t: code(arg_code), message(arg_message), details(arg_details) {};
+\t\t: code(arg_code), message(arg_message), details(arg_details) {}
\tstd::string code;
\tstd::string message;
\tflutter::EncodableValue details;
};
+
template<class T> class ErrorOr {
-\tstd::variant<std::unique_ptr<T>, T, FlutterError> v;
+\tstd::variant<T, FlutterError> v;
public:
\tErrorOr(const T& rhs) { new(&v) T(rhs); }
+\tErrorOr(const T&& rhs) { v = std::move(rhs); }
\tErrorOr(const FlutterError& rhs) {
\t\tnew(&v) FlutterError(rhs);
\t}
-\tstatic ErrorOr<std::unique_ptr<T>> MakeWithUniquePtr(std::unique_ptr<T> rhs) {
-\t\tErrorOr<std::unique_ptr<T>> ret = ErrorOr<std::unique_ptr<T>>();
-\t\tret.v = std::move(rhs);
-\t\treturn ret;
-\t}
+\tErrorOr(const FlutterError&& rhs) { v = std::move(rhs); }
+
\tbool hasError() const { return std::holds_alternative<FlutterError>(v); }
\tconst T& value() const { return std::get<T>(v); };
\tconst FlutterError& error() const { return std::get<FlutterError>(v); };
+
private:
+$friendLines
\tErrorOr() = default;
-\tfriend class ErrorOr;
+\tT TakeValue() && { return std::get<T>(std::move(v)); }
};
''');
}
@@ -185,11 +189,11 @@
indent.scoped(' public:', '', () {
indent.writeln('${klass.name}();');
for (final NamedType field in klass.fields) {
- final HostDatatype baseDatatype = getHostDatatype(
+ final HostDatatype baseDatatype = getFieldHostDatatype(
field,
root.classes,
root.enums,
- (NamedType x) => _baseCppTypeForBuiltinDartType(x.type));
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
indent.writeln(
'${_getterReturnType(baseDatatype)} ${_makeGetterName(field)}() const;');
indent.writeln(
@@ -227,11 +231,11 @@
}
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(
+ final HostDatatype hostDatatype = getFieldHostDatatype(
field,
root.classes,
root.enums,
- (NamedType x) => _baseCppTypeForBuiltinDartType(x.type));
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
indent.writeln(
'${_valueType(hostDatatype)} ${_makeInstanceVariableName(field)};');
}
@@ -256,8 +260,8 @@
// Getters and setters.
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
- root.enums, (NamedType x) => _baseCppTypeForBuiltinDartType(x.type));
+ final HostDatatype hostDatatype = getFieldHostDatatype(field, root.classes,
+ root.enums, (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
final String instanceVariableName = _makeInstanceVariableName(field);
final String qualifiedGetterName =
'${klass.name}::${_makeGetterName(field)}';
@@ -296,11 +300,11 @@
indent.scoped('{', '}', () {
indent.scoped('return flutter::EncodableMap{', '};', () {
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(
+ final HostDatatype hostDatatype = getFieldHostDatatype(
field,
root.classes,
root.enums,
- (NamedType x) => _baseCppTypeForBuiltinDartType(x.type));
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
final String instanceVariable = _makeInstanceVariableName(field);
@@ -346,16 +350,16 @@
final String encodableFieldName =
'${_encodablePrefix}_${_makeVariableName(field)}';
indent.writeln(
- 'auto $encodableFieldName = map.at(flutter::EncodableValue("${field.name}"));');
+ 'auto& $encodableFieldName = map.at(flutter::EncodableValue("${field.name}"));');
if (rootEnumNameSet.contains(field.type.baseName)) {
indent.writeln(
'if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))\t$instanceVariableName = (${field.type.baseName})*$pointerFieldName;');
} else {
- final HostDatatype hostDatatype = getHostDatatype(
+ final HostDatatype hostDatatype = getFieldHostDatatype(
field,
root.classes,
root.enums,
- (NamedType x) => _baseCppTypeForBuiltinDartType(x.type));
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
if (field.type.baseName == 'int') {
indent.format('''
if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))
@@ -385,7 +389,7 @@
indent.addln('');
}
-void _writeHostApiHeader(Indent indent, Api api) {
+void _writeHostApiHeader(Indent indent, Api api, Root root) {
assert(api.location == ApiLocation.host);
indent.writeln(
@@ -397,14 +401,24 @@
indent.writeln('${api.name}& operator=(const ${api.name}&) = delete;');
indent.writeln('virtual ~${api.name}() { };');
for (final Method method in api.methods) {
- final String returnTypeName = method.returnType.isVoid
- ? 'std::optional<FlutterError>'
- : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
+ final HostDatatype returnType = getHostDatatype(
+ method.returnType,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
+ final String returnTypeName = _apiReturnType(returnType);
final List<String> argSignature = <String>[];
if (method.arguments.isNotEmpty) {
- final Iterable<String> argTypes = method.arguments
- .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+ final Iterable<String> argTypes =
+ method.arguments.map((NamedType arg) {
+ final HostDatatype hostType = getFieldHostDatatype(
+ arg,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
+ return _hostApiArgumentType(hostType);
+ });
final Iterable<String> argNames =
method.arguments.map((NamedType e) => _makeVariableName(e));
argSignature.addAll(
@@ -439,7 +453,7 @@
}, nestCount: 0);
}
-void _writeHostApiSource(Indent indent, Api api) {
+void _writeHostApiSource(Indent indent, Api api, Root root) {
assert(api.location == ApiLocation.host);
final String codecName = _getCodecName(api);
@@ -470,67 +484,136 @@
indent.write(
'channel->SetMessageHandler([api](const flutter::EncodableValue& message, const flutter::MessageReply<flutter::EncodableValue>& reply) ');
indent.scoped('{', '});', () {
- final String returnTypeName = method.returnType.isVoid
- ? 'std::optional<FlutterError>'
- : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
indent.writeln('flutter::EncodableMap wrapped;');
indent.write('try ');
indent.scoped('{', '}', () {
final List<String> methodArgument = <String>[];
if (method.arguments.isNotEmpty) {
indent.writeln(
- 'auto args = std::get<flutter::EncodableList>(message);');
+ 'const auto& args = std::get<flutter::EncodableList>(message);');
+
+ // Writes the code to declare and populate a variable called
+ // [argName] to use as a parameter to an API method call from
+ // an existing EncodablValue variable called [encodableArgName]
+ // which corresponds to [arg] in the API definition.
+ void extractEncodedArgument(
+ String argName,
+ String encodableArgName,
+ NamedType arg,
+ HostDatatype hostType) {
+ if (arg.type.isNullable) {
+ // Nullable arguments are always pointers, with nullptr
+ // corresponding to null.
+ if (hostType.datatype == 'int64_t') {
+ // The EncodableValue will either be an int32_t or an
+ // int64_t depending on the value, but the generated API
+ // requires an int64_t so that it can handle any case.
+ // Create a local variable for the 64-bit value...
+ final String valueVarName = '${argName}_value';
+ indent.writeln(
+ 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();');
+ // ... then declare the arg as a reference to that local.
+ indent.writeln(
+ 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &$valueVarName;');
+ } else if (hostType.isBuiltin) {
+ indent.writeln(
+ 'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);');
+ } else {
+ indent.writeln(
+ 'const auto* $argName = &(std::any_cast<const ${hostType.datatype}&>(std::get<flutter::CustomEncodableValue>($encodableArgName)));');
+ }
+ } else {
+ // Non-nullable arguments are either passed by value or
+ // reference, but the extraction doesn't need to distinguish
+ // since those are the same at the call site.
+ if (hostType.datatype == 'int64_t') {
+ // The EncodableValue will either be an int32_t or an
+ // int64_t depending on the value, but the generated API
+ // requires an int64_t so that it can handle any case.
+ indent.writeln(
+ 'const int64_t $argName = $encodableArgName.LongValue();');
+ } else if (hostType.isBuiltin) {
+ indent.writeln(
+ 'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);');
+ } else {
+ indent.writeln(
+ 'const auto& $argName = std::any_cast<const ${hostType.datatype}&>(std::get<flutter::CustomEncodableValue>($encodableArgName));');
+ }
+ }
+ }
+
enumerate(method.arguments, (int index, NamedType arg) {
- final String argType = _nullSafeCppTypeForDartType(arg.type);
+ final HostDatatype hostType = getHostDatatype(
+ arg.type,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
final String argName = _getSafeArgumentName(index, arg);
final String encodableArgName =
'${_encodablePrefix}_$argName';
- indent.writeln('auto $encodableArgName = args.at($index);');
+ indent.writeln(
+ 'const auto& $encodableArgName = args.at($index);');
if (!arg.type.isNullable) {
indent.write('if ($encodableArgName.IsNull()) ');
indent.scoped('{', '}', () {
indent.writeln(
- 'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("$argName unexpectedly null.")));');
+ 'wrapped.emplace(flutter::EncodableValue("${Keys.error}"), WrapError("$argName unexpectedly null."));');
indent.writeln('reply(wrapped);');
indent.writeln('return;');
});
}
- indent.writeln(
- '$argType $argName = std::any_cast<$argType>(std::get<flutter::CustomEncodableValue>($encodableArgName));');
+ extractEncodedArgument(
+ argName, encodableArgName, arg, hostType);
methodArgument.add(argName);
});
}
String _wrapResponse(String reply, TypeDeclaration returnType) {
- final bool isReferenceReturnType = _isReferenceType(
- _baseCppTypeForDartType(method.returnType));
String elseBody = '';
final String ifCondition;
final String errorGetter;
final String prefix = (reply != '') ? '\t' : '';
+
+ const String resultKey =
+ 'flutter::EncodableValue("${Keys.result}")';
+ const String errorKey =
+ 'flutter::EncodableValue("${Keys.error}")';
+ const String nullValue = 'flutter::EncodableValue()';
if (returnType.isVoid) {
elseBody =
- '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::EncodableValue()));${indent.newline}';
+ '$prefix\twrapped.emplace($resultKey, $nullValue);${indent.newline}';
ifCondition = 'output.has_value()';
errorGetter = 'value';
} else {
- if (isReferenceReturnType && !returnType.isNullable) {
+ final HostDatatype hostType = getHostDatatype(
+ returnType,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
+ const String extractedValue = 'std::move(output).TakeValue()';
+ final String wrapperType = hostType.isBuiltin
+ ? 'flutter::EncodableValue'
+ : 'flutter::CustomEncodableValue';
+ if (returnType.isNullable) {
+ // The value is a std::optional, so needs an extra layer of
+ // handling.
elseBody = '''
-$prefix\tif (!output.value()) {
-$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("output is unexpectedly null.")));
+$prefix\tauto output_optional = $extractedValue;
+$prefix\tif (output_optional) {
+$prefix\t\twrapped.emplace($resultKey, $wrapperType(std::move(output_optional).value()));
$prefix\t} else {
-$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(*output.value().get())));
+$prefix\t\twrapped.emplace($resultKey, $nullValue);
$prefix\t}${indent.newline}''';
} else {
elseBody =
- '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(output.value())));${indent.newline}';
+ '$prefix\twrapped.emplace($resultKey, $wrapperType($extractedValue));${indent.newline}';
}
ifCondition = 'output.hasError()';
errorGetter = 'error';
}
return '${prefix}if ($ifCondition) {${indent.newline}'
- '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(output.$errorGetter())));${indent.newline}'
+ '$prefix\twrapped.emplace($errorKey, WrapError(output.$errorGetter()));${indent.newline}'
'$prefix$reply'
'$prefix} else {${indent.newline}'
'$elseBody'
@@ -538,9 +621,15 @@
'$prefix}';
}
+ final HostDatatype returnType = getHostDatatype(
+ method.returnType,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x));
+ final String returnTypeName = _apiReturnType(returnType);
if (method.isAsynchronous) {
methodArgument.add(
- '[&wrapped, &reply]($returnTypeName output) {${indent.newline}'
+ '[&wrapped, &reply]($returnTypeName&& output) {${indent.newline}'
'${_wrapResponse('\treply(wrapped);${indent.newline}', method.returnType)}'
'}',
);
@@ -557,7 +646,7 @@
indent.write('catch (const std::exception& exception) ');
indent.scoped('{', '}', () {
indent.writeln(
- 'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(exception.what())));');
+ 'wrapped.emplace(flutter::EncodableValue("${Keys.error}"), WrapError(exception.what()));');
if (method.isAsynchronous) {
indent.writeln('reply(wrapped);');
}
@@ -685,7 +774,7 @@
} else {
indent.writeln('$returnTypeName $output{};');
}
- final String pointerVariable = '${_pointerPrefix}_$output';
+ const String pointerVariable = '${_pointerPrefix}_$output';
if (func.returnType.baseName == 'int') {
indent.format('''
if (const int32_t* $pointerVariable = std::get_if<int32_t>(&args))
@@ -767,6 +856,7 @@
String? _baseCppTypeForBuiltinDartType(TypeDeclaration type) {
const Map<String, String> cppTypeForDartTypeMap = <String, String>{
+ 'void': 'void',
'bool': 'bool',
'int': 'int64_t',
'String': 'std::string',
@@ -809,6 +899,18 @@
return type.isNullable ? 'const $baseType*' : 'const $baseType&';
}
+/// Returns the C++ type to use for arguments to a host API. This is slightly
+/// different from [_unownedArgumentType] since passing `std::string_view*` in
+/// to the host API implementation when the actual type is `std::string*` is
+/// needlessly complicated, so it uses `std::string` directly.
+String _hostApiArgumentType(HostDatatype type) {
+ final String baseType = type.datatype;
+ if (_isPodType(type)) {
+ return type.isNullable ? 'const $baseType*' : baseType;
+ }
+ return type.isNullable ? 'const $baseType*' : 'const $baseType&';
+}
+
/// Returns the C++ type to use for the return of a getter for a field of type
/// [type].
String _getterReturnType(HostDatatype type) {
@@ -822,6 +924,19 @@
return type.isNullable ? 'const $baseType*' : 'const $baseType&';
}
+/// Returns the C++ type to use for the return of an API method retutrning
+/// [type].
+String _apiReturnType(HostDatatype type) {
+ if (type.datatype == 'void') {
+ return 'std::optional<FlutterError>';
+ }
+ String valueType = type.datatype;
+ if (type.isNullable) {
+ valueType = 'std::optional<$valueType>';
+ }
+ return 'ErrorOr<$valueType>';
+}
+
// TODO(stuartmorgan): Audit all uses of this and convert them to context-based
// methods like those above. Code still using this method may well have bugs.
String _nullSafeCppTypeForDartType(TypeDeclaration type,
@@ -919,7 +1034,7 @@
indent.addln('');
- _writeErrorOr(indent);
+ _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name));
for (final Class klass in root.classes) {
_writeDataClassDeclaration(indent, klass, root,
@@ -932,7 +1047,7 @@
_writeCodecHeader(indent, api, root);
indent.addln('');
if (api.location == ApiLocation.host) {
- _writeHostApiHeader(indent, api);
+ _writeHostApiHeader(indent, api, root);
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApiHeader(indent, api);
}
@@ -986,13 +1101,13 @@
_writeCodecSource(indent, api, root);
indent.addln('');
if (api.location == ApiLocation.host) {
- _writeHostApiSource(indent, api);
+ _writeHostApiSource(indent, api, root);
indent.addln('');
indent.format('''
flutter::EncodableMap ${api.name}::WrapError(std::string_view error_message) {
\treturn flutter::EncodableMap({
-\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(std::string(error_message).data())},
+\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(std::string(error_message))},
\t\t{flutter::EncodableValue("${Keys.errorCode}"), flutter::EncodableValue("Error")},
\t\t{flutter::EncodableValue("${Keys.errorDetails}"), flutter::EncodableValue()}
\t});
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 63f63d7..e935fb1 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -9,7 +9,7 @@
import 'ast.dart';
/// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '3.2.3';
+const String pigeonVersion = '3.2.4';
/// Read all the content from [stdin] to a String.
String readStdin() {
@@ -172,39 +172,58 @@
final bool isNullable;
}
-/// Calculates the [HostDatatype] for the provided [NamedType]. It will check
-/// the field against [classes], the list of custom classes, to check if it is a
-/// builtin type. [builtinResolver] will return the host datatype for the Dart
-/// datatype for builtin types. [customResolver] can modify the datatype of
-/// custom types.
-HostDatatype getHostDatatype(NamedType field, List<Class> classes,
- List<Enum> enums, String? Function(NamedType) builtinResolver,
+/// Calculates the [HostDatatype] for the provided [NamedType].
+///
+/// It will check the field against [classes], the list of custom classes, to
+/// check if it is a builtin type. [builtinResolver] will return the host
+/// datatype for the Dart datatype for builtin types.
+///
+/// [customResolver] can modify the datatype of custom types.
+HostDatatype getFieldHostDatatype(NamedType field, List<Class> classes,
+ List<Enum> enums, String? Function(TypeDeclaration) builtinResolver,
{String Function(String)? customResolver}) {
- final String? datatype = builtinResolver(field);
+ return _getHostDatatype(field.type, classes, enums, builtinResolver,
+ customResolver: customResolver, fieldName: field.name);
+}
+
+/// Calculates the [HostDatatype] for the provided [TypeDeclaration].
+///
+/// It will check the field against [classes], the list of custom classes, to
+/// check if it is a builtin type. [builtinResolver] will return the host
+/// datatype for the Dart datatype for builtin types.
+///
+/// [customResolver] can modify the datatype of custom types.
+HostDatatype getHostDatatype(TypeDeclaration type, List<Class> classes,
+ List<Enum> enums, String? Function(TypeDeclaration) builtinResolver,
+ {String Function(String)? customResolver}) {
+ return _getHostDatatype(type, classes, enums, builtinResolver,
+ customResolver: customResolver);
+}
+
+HostDatatype _getHostDatatype(TypeDeclaration type, List<Class> classes,
+ List<Enum> enums, String? Function(TypeDeclaration) builtinResolver,
+ {String Function(String)? customResolver, String? fieldName}) {
+ final String? datatype = builtinResolver(type);
if (datatype == null) {
- if (classes.map((Class x) => x.name).contains(field.type.baseName)) {
+ if (classes.map((Class x) => x.name).contains(type.baseName)) {
final String customName = customResolver != null
- ? customResolver(field.type.baseName)
- : field.type.baseName;
+ ? customResolver(type.baseName)
+ : type.baseName;
return HostDatatype(
- datatype: customName,
- isBuiltin: false,
- isNullable: field.type.isNullable);
- } else if (enums.map((Enum x) => x.name).contains(field.type.baseName)) {
+ datatype: customName, isBuiltin: false, isNullable: type.isNullable);
+ } else if (enums.map((Enum x) => x.name).contains(type.baseName)) {
final String customName = customResolver != null
- ? customResolver(field.type.baseName)
- : field.type.baseName;
+ ? customResolver(type.baseName)
+ : type.baseName;
return HostDatatype(
- datatype: customName,
- isBuiltin: false,
- isNullable: field.type.isNullable);
+ datatype: customName, isBuiltin: false, isNullable: type.isNullable);
} else {
throw Exception(
- 'unrecognized datatype for field:"${field.name}" of type:"${field.type.baseName}"');
+ 'unrecognized datatype ${fieldName == null ? '' : 'for field:"$fieldName" '}of type:"${type.baseName}"');
}
} else {
return HostDatatype(
- datatype: datatype, isBuiltin: true, isNullable: field.type.isNullable);
+ datatype: datatype, isBuiltin: true, isNullable: type.isNullable);
}
}
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index 7b6d741..c155482 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -451,8 +451,8 @@
/// object.
String _castObject(
NamedType field, List<Class> classes, List<Enum> enums, String varName) {
- final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
- (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ final HostDatatype hostDatatype = getFieldHostDatatype(field, classes, enums,
+ (TypeDeclaration x) => _javaTypeForBuiltinDartType(x));
if (field.type.baseName == 'int') {
return '($varName == null) ? null : (($varName instanceof Integer) ? (Integer)$varName : (${hostDatatype.datatype})$varName)';
} else if (!hostDatatype.isBuiltin &&
@@ -521,8 +521,11 @@
void writeDataClass(Class klass) {
void writeField(NamedType field) {
- final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
- root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ final HostDatatype hostDatatype = getFieldHostDatatype(
+ field,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _javaTypeForBuiltinDartType(x));
final String nullability =
field.type.isNullable ? '@Nullable' : '@NonNull';
indent.writeln(
@@ -547,8 +550,11 @@
indent.scoped('{', '}', () {
indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
- root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ final HostDatatype hostDatatype = getFieldHostDatatype(
+ field,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _javaTypeForBuiltinDartType(x));
String toWriteValue = '';
final String fieldName = field.name;
if (!hostDatatype.isBuiltin &&
@@ -592,8 +598,11 @@
indent.write('public static final class Builder ');
indent.scoped('{', '}', () {
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
- root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ final HostDatatype hostDatatype = getFieldHostDatatype(
+ field,
+ root.classes,
+ root.enums,
+ (TypeDeclaration x) => _javaTypeForBuiltinDartType(x));
final String nullability =
field.type.isNullable ? '@Nullable' : '@NonNull';
indent.writeln(
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index dc1661b..84dc5cd 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -107,9 +107,10 @@
return result;
}
-String? _objcTypePtrForPrimitiveDartType(String? classPrefix, NamedType field) {
- return _objcTypeForDartTypeMap.containsKey(field.type.baseName)
- ? _objcTypeForDartType(classPrefix, field.type).ptr
+String? _objcTypePtrForPrimitiveDartType(
+ String? classPrefix, TypeDeclaration type) {
+ return _objcTypeForDartTypeMap.containsKey(type.baseName)
+ ? _objcTypeForDartType(classPrefix, type).ptr
: null;
}
@@ -170,8 +171,11 @@
indent.write(x);
};
isFirst = false;
- final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
- (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
+ final HostDatatype hostDatatype = getFieldHostDatatype(
+ field,
+ classes,
+ enums,
+ (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x),
customResolver: enumNames.contains(field.type.baseName)
? (String x) => _className(prefix, x)
: (String x) => '${_className(prefix, x)} *');
@@ -205,8 +209,11 @@
indent.addln(';');
}
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
- (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
+ final HostDatatype hostDatatype = getFieldHostDatatype(
+ field,
+ classes,
+ enums,
+ (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x),
customResolver: enumNames.contains(field.type.baseName)
? (String x) => _className(prefix, x)
: (String x) => '${_className(prefix, x)} *');
diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart
index 8a7d6ec..8be4278 100644
--- a/packages/pigeon/lib/swift_generator.dart
+++ b/packages/pigeon/lib/swift_generator.dart
@@ -408,8 +408,8 @@
final Indent indent = Indent(sink);
HostDatatype _getHostDatatype(NamedType field) {
- return getHostDatatype(field, root.classes, root.enums,
- (NamedType x) => _swiftTypeForBuiltinDartType(x.type));
+ return getFieldHostDatatype(field, root.classes, root.enums,
+ (TypeDeclaration x) => _swiftTypeForBuiltinDartType(x));
}
void writeHeader() {
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
index 73e559a..db289d0 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
@@ -52,9 +52,14 @@
# The plugin's C API is not very useful for unit testing, so build the sources
# directly into the test binary rather than using the DLL.
add_executable(${TEST_RUNNER}
+ # Tests.
+ test/multiple_arity_test.cpp
test/non_null_fields_test.cpp
+ test/nullable_returns_test.cpp
test/null_fields_test.cpp
test/pigeon_test.cpp
+ test/primitive_test.cpp
+ # Generated sources.
test/all_datatypes.g.cpp
test/all_datatypes.g.h
test/all_void.g.cpp
@@ -87,6 +92,11 @@
test/voidflutter.g.h
test/voidhost.g.cpp
test/voidhost.g.h
+ # Test utilities.
+ test/utils/echo_messenger.cpp
+ test/utils/echo_messenger.h
+ test/utils/fake_host_messenger.cpp
+ test/utils/fake_host_messenger.h
${PLUGIN_SOURCES}
)
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/multiple_arity_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/multiple_arity_test.cpp
new file mode 100644
index 0000000..8434164
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/multiple_arity_test.cpp
@@ -0,0 +1,52 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "test/multiple_arity.g.h"
+#include "test/utils/fake_host_messenger.h"
+
+namespace multiple_arity_pigeontest {
+
+namespace {
+using flutter::EncodableList;
+using flutter::EncodableMap;
+using flutter::EncodableValue;
+using testing::FakeHostMessenger;
+
+class TestHostApi : public MultipleArityHostApi {
+ public:
+ TestHostApi() {}
+ virtual ~TestHostApi() {}
+
+ protected:
+ ErrorOr<int64_t> Subtract(int64_t x, int64_t y) override { return x - y; }
+};
+
+const EncodableValue& GetResult(const EncodableValue& pigeon_response) {
+ return std::get<EncodableMap>(pigeon_response).at(EncodableValue("result"));
+}
+} // namespace
+
+TEST(MultipleArity, HostSimple) {
+ FakeHostMessenger messenger(&MultipleArityHostApi::GetCodec());
+ TestHostApi api;
+ MultipleArityHostApi::SetUp(&messenger, &api);
+
+ int64_t result = 0;
+ messenger.SendHostMessage("dev.flutter.pigeon.MultipleArityHostApi.subtract",
+ EncodableValue(EncodableList({
+ EncodableValue(30),
+ EncodableValue(10),
+ })),
+ [&result](const EncodableValue& reply) {
+ result = GetResult(reply).LongValue();
+ });
+
+ EXPECT_EQ(result, 20);
+}
+
+// TODO(stuartmorgan): Add a FlutterApi version of the test.
+
+} // namespace multiple_arity_pigeontest
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/nullable_returns_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/nullable_returns_test.cpp
new file mode 100644
index 0000000..4804e1a
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/nullable_returns_test.cpp
@@ -0,0 +1,112 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include <optional>
+
+#include "test/nullable_returns.g.h"
+#include "test/utils/fake_host_messenger.h"
+
+namespace nullable_returns_pigeontest {
+
+namespace {
+using flutter::EncodableList;
+using flutter::EncodableMap;
+using flutter::EncodableValue;
+using testing::FakeHostMessenger;
+
+class TestNullableArgHostApi : public NullableArgHostApi {
+ public:
+ TestNullableArgHostApi() {}
+ virtual ~TestNullableArgHostApi() {}
+
+ protected:
+ ErrorOr<int64_t> Doit(const int64_t* x) override {
+ return x == nullptr ? 42 : *x;
+ }
+};
+
+class TestNullableReturnHostApi : public NullableReturnHostApi {
+ public:
+ TestNullableReturnHostApi(std::optional<int64_t> return_value)
+ : value_(return_value) {}
+ virtual ~TestNullableReturnHostApi() {}
+
+ protected:
+ ErrorOr<std::optional<int64_t>> Doit() override { return value_; }
+
+ private:
+ std::optional<int64_t> value_;
+};
+
+const EncodableValue& GetResult(const EncodableValue& pigeon_response) {
+ return std::get<EncodableMap>(pigeon_response).at(EncodableValue("result"));
+}
+} // namespace
+
+TEST(NullableReturns, HostNullableArgNull) {
+ FakeHostMessenger messenger(&NullableArgHostApi::GetCodec());
+ TestNullableArgHostApi api;
+ NullableArgHostApi::SetUp(&messenger, &api);
+
+ int64_t result = 0;
+ messenger.SendHostMessage("dev.flutter.pigeon.NullableArgHostApi.doit",
+ EncodableValue(EncodableList({EncodableValue()})),
+ [&result](const EncodableValue& reply) {
+ result = GetResult(reply).LongValue();
+ });
+
+ EXPECT_EQ(result, 42);
+}
+
+TEST(NullableReturns, HostNullableArgNonNull) {
+ FakeHostMessenger messenger(&NullableArgHostApi::GetCodec());
+ TestNullableArgHostApi api;
+ NullableArgHostApi::SetUp(&messenger, &api);
+
+ int64_t result = 0;
+ messenger.SendHostMessage("dev.flutter.pigeon.NullableArgHostApi.doit",
+ EncodableValue(EncodableList({EncodableValue(7)})),
+ [&result](const EncodableValue& reply) {
+ result = GetResult(reply).LongValue();
+ });
+
+ EXPECT_EQ(result, 7);
+}
+
+TEST(NullableReturns, HostNullableReturnNull) {
+ FakeHostMessenger messenger(&NullableReturnHostApi::GetCodec());
+ TestNullableReturnHostApi api(std::nullopt);
+ NullableReturnHostApi::SetUp(&messenger, &api);
+
+ // Initialize to a non-null value to ensure that it's actually set to null,
+ // rather than just never set.
+ EncodableValue result(true);
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.NullableReturnHostApi.doit",
+ EncodableValue(EncodableList({})),
+ [&result](const EncodableValue& reply) { result = GetResult(reply); });
+
+ EXPECT_TRUE(result.IsNull());
+}
+
+TEST(NullableReturns, HostNullableReturnNonNull) {
+ FakeHostMessenger messenger(&NullableReturnHostApi::GetCodec());
+ TestNullableReturnHostApi api(42);
+ NullableReturnHostApi::SetUp(&messenger, &api);
+
+ EncodableValue result;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.NullableReturnHostApi.doit",
+ EncodableValue(EncodableList({})),
+ [&result](const EncodableValue& reply) { result = GetResult(reply); });
+
+ EXPECT_FALSE(result.IsNull());
+ EXPECT_EQ(result.LongValue(), 42);
+}
+
+// TODO(stuartmorgan): Add FlutterApi versions of the tests.
+
+} // namespace nullable_returns_pigeontest
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
index 558d9d6..60c1f1f 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
@@ -59,7 +59,7 @@
~MockApi() = default;
MOCK_METHOD(std::optional<FlutterError>, Initialize, (), (override));
- MOCK_METHOD(ErrorOr<std::unique_ptr<MessageSearchReply>>, Search,
+ MOCK_METHOD(ErrorOr<MessageSearchReply>, Search,
(const MessageSearchRequest&), (override));
};
@@ -130,8 +130,8 @@
.Times(1)
.WillOnce(testing::SaveArg<1>(&handler));
EXPECT_CALL(mock_api, Search(testing::_))
- .WillOnce(Return(ByMove(ErrorOr<MessageSearchReply>::MakeWithUniquePtr(
- std::make_unique<MessageSearchReply>()))));
+ .WillOnce(
+ Return(ByMove(ErrorOr<MessageSearchReply>(MessageSearchReply()))));
MessageApi::SetUp(&mock_messenger, &mock_api);
bool did_call_reply = false;
flutter::BinaryReply reply = [&did_call_reply](const uint8_t* data,
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/primitive_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/primitive_test.cpp
new file mode 100644
index 0000000..68b4dfe
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/primitive_test.cpp
@@ -0,0 +1,158 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "test/primitive.g.h"
+#include "test/utils/fake_host_messenger.h"
+
+namespace primitive_pigeontest {
+
+namespace {
+using flutter::EncodableList;
+using flutter::EncodableMap;
+using flutter::EncodableValue;
+using testing::FakeHostMessenger;
+
+class TestHostApi : public PrimitiveHostApi {
+ public:
+ TestHostApi() {}
+ virtual ~TestHostApi() {}
+
+ protected:
+ ErrorOr<int64_t> AnInt(int64_t value) override { return value; }
+ ErrorOr<bool> ABool(bool value) override { return value; }
+ ErrorOr<std::string> AString(const std::string& value) override {
+ return std::string(value);
+ }
+ ErrorOr<double> ADouble(double value) override { return value; }
+ ErrorOr<flutter::EncodableMap> AMap(
+ const flutter::EncodableMap& value) override {
+ return value;
+ }
+ ErrorOr<flutter::EncodableList> AList(
+ const flutter::EncodableList& value) override {
+ return value;
+ }
+ ErrorOr<std::vector<int32_t>> AnInt32List(
+ const std::vector<int32_t>& value) override {
+ return value;
+ }
+ ErrorOr<flutter::EncodableList> ABoolList(
+ const flutter::EncodableList& value) override {
+ return value;
+ }
+ ErrorOr<flutter::EncodableMap> AStringIntMap(
+ const flutter::EncodableMap& value) override {
+ return value;
+ }
+};
+
+const EncodableValue& GetResult(const EncodableValue& pigeon_response) {
+ return std::get<EncodableMap>(pigeon_response).at(EncodableValue("result"));
+}
+} // namespace
+
+TEST(Primitive, HostInt) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ int64_t result = 0;
+ messenger.SendHostMessage("dev.flutter.pigeon.PrimitiveHostApi.anInt",
+ EncodableValue(EncodableList({EncodableValue(7)})),
+ [&result](const EncodableValue& reply) {
+ result = GetResult(reply).LongValue();
+ });
+
+ EXPECT_EQ(result, 7);
+}
+
+TEST(Primitive, HostBool) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ bool result = false;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.PrimitiveHostApi.aBool",
+ EncodableValue(EncodableList({EncodableValue(true)})),
+ [&result](const EncodableValue& reply) {
+ result = std::get<bool>(GetResult(reply));
+ });
+
+ EXPECT_EQ(result, true);
+}
+
+TEST(Primitive, HostDouble) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ double result = 0.0;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.PrimitiveHostApi.aDouble",
+ EncodableValue(EncodableList({EncodableValue(3.0)})),
+ [&result](const EncodableValue& reply) {
+ result = std::get<double>(GetResult(reply));
+ });
+
+ EXPECT_EQ(result, 3.0);
+}
+
+TEST(Primitive, HostString) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ std::string result;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.PrimitiveHostApi.aString",
+ EncodableValue(EncodableList({EncodableValue("hello")})),
+ [&result](const EncodableValue& reply) {
+ result = std::get<std::string>(GetResult(reply));
+ });
+
+ EXPECT_EQ(result, "hello");
+}
+
+TEST(Primitive, HostList) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ EncodableList result;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.PrimitiveHostApi.aList",
+ EncodableValue(EncodableList({EncodableValue(EncodableList({1, 2, 3}))})),
+ [&result](const EncodableValue& reply) {
+ result = std::get<EncodableList>(GetResult(reply));
+ });
+
+ EXPECT_EQ(result.size(), 3);
+ EXPECT_EQ(result[2].LongValue(), 3);
+}
+
+TEST(Primitive, HostMap) {
+ FakeHostMessenger messenger(&PrimitiveHostApi::GetCodec());
+ TestHostApi api;
+ PrimitiveHostApi::SetUp(&messenger, &api);
+
+ EncodableMap result;
+ messenger.SendHostMessage(
+ "dev.flutter.pigeon.PrimitiveHostApi.aMap",
+ EncodableValue(EncodableList({EncodableValue(EncodableMap({
+ {EncodableValue("foo"), EncodableValue("bar")},
+ }))})),
+ [&result](const EncodableValue& reply) {
+ result = std::get<EncodableMap>(GetResult(reply));
+ });
+
+ EXPECT_EQ(result.size(), 1);
+ EXPECT_EQ(result[EncodableValue("foo")], EncodableValue("bar"));
+}
+
+// TODO(stuartmorgan): Add FlutterApi versions of the tests.
+
+} // namespace primitive_pigeontest
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.cpp
new file mode 100644
index 0000000..18da6c9
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.cpp
@@ -0,0 +1,32 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "echo_messenger.h"
+
+#include <flutter/encodable_value.h>
+#include <flutter/message_codec.h>
+
+namespace testing {
+
+EchoMessenger::EchoMessenger(
+ const flutter::MessageCodec<flutter::EncodableValue>* codec)
+ : codec_(codec) {}
+EchoMessenger::~EchoMessenger() {}
+
+// flutter::BinaryMessenger:
+void EchoMessenger::Send(const std::string& channel, const uint8_t* message,
+ size_t message_size,
+ flutter::BinaryReply reply) const {
+ std::unique_ptr<flutter::EncodableValue> arg_value =
+ codec_->DecodeMessage(message, message_size);
+ const auto& args = std::get<flutter::EncodableList>(*arg_value);
+ std::unique_ptr<std::vector<uint8_t>> reply_data =
+ codec_->EncodeMessage(args[0]);
+ reply(reply_data->data(), reply_data->size());
+}
+
+void EchoMessenger::SetMessageHandler(const std::string& channel,
+ flutter::BinaryMessageHandler handler) {}
+
+} // namespace testing
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.h b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.h
new file mode 100644
index 0000000..7bc4625
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/echo_messenger.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_ECHO_MESSENGER_H_
+#define PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_ECHO_MESSENGER_H_
+
+#include <flutter/binary_messenger.h>
+#include <flutter/encodable_value.h>
+#include <flutter/message_codec.h>
+
+namespace testing {
+
+// A BinaryMessenger that replies with the first argument sent to it.
+class EchoMessenger : public flutter::BinaryMessenger {
+ public:
+ // Creates an echo messenger that expects MessageCalls encoded with the given
+ // codec.
+ EchoMessenger(const flutter::MessageCodec<flutter::EncodableValue>* codec);
+ virtual ~EchoMessenger();
+
+ // flutter::BinaryMessenger:
+ void Send(const std::string& channel, const uint8_t* message,
+ size_t message_size,
+ flutter::BinaryReply reply = nullptr) const override;
+ void SetMessageHandler(const std::string& channel,
+ flutter::BinaryMessageHandler handler) override;
+
+ private:
+ const flutter::MessageCodec<flutter::EncodableValue>* codec_;
+};
+
+} // namespace testing
+
+#endif // PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_ECHO_MESSENGER_H_
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.cpp
new file mode 100644
index 0000000..a2b10b5
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.cpp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fake_host_messenger.h"
+
+#include <flutter/encodable_value.h>
+#include <flutter/message_codec.h>
+
+#include <memory>
+#include <vector>
+
+namespace testing {
+
+FakeHostMessenger::FakeHostMessenger(
+ const flutter::MessageCodec<flutter::EncodableValue>* codec)
+ : codec_(codec) {}
+FakeHostMessenger::~FakeHostMessenger() {}
+
+void FakeHostMessenger::SendHostMessage(const std::string& channel,
+
+ const flutter::EncodableValue& message,
+ HostMessageReply reply_handler) {
+ const auto* codec = codec_;
+ flutter::BinaryReply binary_handler = [reply_handler, codec, channel](
+ const uint8_t* reply_data,
+ size_t reply_size) {
+ std::unique_ptr<flutter::EncodableValue> reply =
+ codec->DecodeMessage(reply_data, reply_size);
+ reply_handler(*reply);
+ };
+
+ std::unique_ptr<std::vector<uint8_t>> data = codec_->EncodeMessage(message);
+ handlers_[channel](data->data(), data->size(), std::move(binary_handler));
+}
+
+void FakeHostMessenger::Send(const std::string& channel, const uint8_t* message,
+ size_t message_size,
+ flutter::BinaryReply reply) const {}
+
+void FakeHostMessenger::SetMessageHandler(
+ const std::string& channel, flutter::BinaryMessageHandler handler) {
+ handlers_[channel] = std::move(handler);
+}
+
+} // namespace testing
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.h b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.h
new file mode 100644
index 0000000..fd31426
--- /dev/null
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/utils/fake_host_messenger.h
@@ -0,0 +1,51 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_FAKE_HOST_MESSENGER_H_
+#define PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_FAKE_HOST_MESSENGER_H_
+
+#include <flutter/binary_messenger.h>
+#include <flutter/encodable_value.h>
+#include <flutter/message_codec.h>
+
+#include <map>
+
+namespace testing {
+
+typedef std::function<void(const flutter::EncodableValue& reply)>
+ HostMessageReply;
+
+// A BinaryMessenger that allows tests to act as the engine to call host APIs.
+class FakeHostMessenger : public flutter::BinaryMessenger {
+ public:
+ // Creates an messenger that can send and receive responses with the given
+ // codec.
+ FakeHostMessenger(
+ const flutter::MessageCodec<flutter::EncodableValue>* codec);
+ virtual ~FakeHostMessenger();
+
+ // Calls the registered handler for the given channel, and calls reply_handler
+ // with the response.
+ //
+ // This allows a test to simulate a message from the Dart side, exercising the
+ // encoding and decoding logic generated for a host API.
+ void SendHostMessage(const std::string& channel,
+ const flutter::EncodableValue& message,
+ HostMessageReply reply_handler);
+
+ // flutter::BinaryMessenger:
+ void Send(const std::string& channel, const uint8_t* message,
+ size_t message_size,
+ flutter::BinaryReply reply = nullptr) const override;
+ void SetMessageHandler(const std::string& channel,
+ flutter::BinaryMessageHandler handler) override;
+
+ private:
+ const flutter::MessageCodec<flutter::EncodableValue>* codec_;
+ std::map<std::string, flutter::BinaryMessageHandler> handlers_;
+};
+
+} // namespace testing
+
+#endif // PLATFORM_TESTS_WINDOWS_UNIT_TESTS_WINDOWS_TEST_UTILS_FAKE_HOST_MESSENGER_H_
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 4826d6c..6d81137 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 3.2.3 # This must match the version in lib/generator_tools.dart
+version: 3.2.4 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart
index 0b2c93c..c720503 100644
--- a/packages/pigeon/test/cpp_generator_test.dart
+++ b/packages/pigeon/test/cpp_generator_test.dart
@@ -525,6 +525,482 @@
}
});
+ test('host nullable return types map correctly', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'returnNullableBool',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnNullableInt',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnNullableString',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnNullableList',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'List',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ )
+ ],
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnNullableMap',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'Map',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ ),
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ )
+ ],
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnNullableDataClass',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'ReturnData',
+ isNullable: true,
+ ),
+ isAsynchronous: false,
+ ),
+ ])
+ ], classes: <Class>[
+ Class(name: 'ReturnData', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ ),
+ name: 'aValue',
+ offset: null),
+ ]),
+ ], enums: <Enum>[]);
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppHeader('', const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(
+ code, contains('ErrorOr<std::optional<bool>> ReturnNullableBool()'));
+ expect(code,
+ contains('ErrorOr<std::optional<int64_t>> ReturnNullableInt()'));
+ expect(
+ code,
+ contains(
+ 'ErrorOr<std::optional<std::string>> ReturnNullableString()'));
+ expect(
+ code,
+ contains(
+ 'ErrorOr<std::optional<flutter::EncodableList>> ReturnNullableList()'));
+ expect(
+ code,
+ contains(
+ 'ErrorOr<std::optional<flutter::EncodableMap>> ReturnNullableMap()'));
+ expect(
+ code,
+ contains(
+ 'ErrorOr<std::optional<ReturnData>> ReturnNullableDataClass()'));
+ }
+ });
+
+ test('host non-nullable return types map correctly', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'returnBool',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnInt',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnString',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnList',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'List',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ )
+ ],
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnMap',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'Map',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ ),
+ TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ )
+ ],
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ Method(
+ name: 'returnDataClass',
+ arguments: <NamedType>[],
+ returnType: const TypeDeclaration(
+ baseName: 'ReturnData',
+ isNullable: false,
+ ),
+ isAsynchronous: false,
+ ),
+ ])
+ ], classes: <Class>[
+ Class(name: 'ReturnData', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ ),
+ name: 'aValue',
+ offset: null),
+ ]),
+ ], enums: <Enum>[]);
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppHeader('', const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('ErrorOr<bool> ReturnBool()'));
+ expect(code, contains('ErrorOr<int64_t> ReturnInt()'));
+ expect(code, contains('ErrorOr<std::string> ReturnString()'));
+ expect(code, contains('ErrorOr<flutter::EncodableList> ReturnList()'));
+ expect(code, contains('ErrorOr<flutter::EncodableMap> ReturnMap()'));
+ expect(code, contains('ErrorOr<ReturnData> ReturnDataClass()'));
+ }
+ });
+
+ test('host nullable arguments map correctly', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doSomething',
+ arguments: <NamedType>[
+ NamedType(
+ name: 'aBool',
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: true,
+ )),
+ NamedType(
+ name: 'anInt',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ NamedType(
+ name: 'aString',
+ type: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ )),
+ NamedType(
+ name: 'aList',
+ type: const TypeDeclaration(
+ baseName: 'List',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(baseName: 'Object', isNullable: true)
+ ],
+ isNullable: true,
+ )),
+ NamedType(
+ name: 'aMap',
+ type: const TypeDeclaration(
+ baseName: 'Map',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(baseName: 'String', isNullable: true),
+ TypeDeclaration(baseName: 'Object', isNullable: true),
+ ],
+ isNullable: true,
+ )),
+ NamedType(
+ name: 'anObject',
+ type: const TypeDeclaration(
+ baseName: 'ParameterObject',
+ isNullable: true,
+ )),
+ ],
+ returnType: const TypeDeclaration.voidDeclaration(),
+ isAsynchronous: false,
+ ),
+ ])
+ ], classes: <Class>[
+ Class(name: 'ParameterObject', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ ),
+ name: 'aValue',
+ offset: null),
+ ]),
+ ], enums: <Enum>[]);
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppHeader('', const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ contains('DoSomething(const bool* a_bool, '
+ 'const int64_t* an_int, '
+ 'const std::string* a_string, '
+ 'const flutter::EncodableList* a_list, '
+ 'const flutter::EncodableMap* a_map, '
+ 'const ParameterObject* an_object)'));
+ }
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppSource(const CppOptions(), root, sink);
+ final String code = sink.toString();
+ // Most types should just use get_if, since the parameter is a pointer,
+ // and get_if will automatically handle null values (since a null
+ // EncodableValue will not match the queried type, so get_if will return
+ // nullptr).
+ expect(
+ code,
+ contains(
+ 'const auto* a_bool_arg = std::get_if<bool>(&encodable_a_bool_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto* a_string_arg = std::get_if<std::string>(&encodable_a_string_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto* a_list_arg = std::get_if<flutter::EncodableList>(&encodable_a_list_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto* a_map_arg = std::get_if<flutter::EncodableMap>(&encodable_a_map_arg);'));
+ // Ints are complicated since there are two possible pointer types, but
+ // the paramter always needs an int64_t*.
+ expect(
+ code,
+ contains(
+ 'const int64_t an_int_arg_value = encodable_an_int_arg.IsNull() ? 0 : encodable_an_int_arg.LongValue();'));
+ expect(
+ code,
+ contains(
+ 'const auto* an_int_arg = encodable_an_int_arg.IsNull() ? nullptr : &an_int_arg_value;'));
+ // Custom class types require an extra layer of extraction.
+ expect(
+ code,
+ contains(
+ 'const auto* an_object_arg = &(std::any_cast<const ParameterObject&>(std::get<flutter::CustomEncodableValue>(encodable_an_object_arg)));'));
+ }
+ });
+
+ test('host non-nullable arguments map correctly', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doSomething',
+ arguments: <NamedType>[
+ NamedType(
+ name: 'aBool',
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ )),
+ NamedType(
+ name: 'anInt',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: false,
+ )),
+ NamedType(
+ name: 'aString',
+ type: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: false,
+ )),
+ NamedType(
+ name: 'aList',
+ type: const TypeDeclaration(
+ baseName: 'List',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(baseName: 'Object', isNullable: true)
+ ],
+ isNullable: false,
+ )),
+ NamedType(
+ name: 'aMap',
+ type: const TypeDeclaration(
+ baseName: 'Map',
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(baseName: 'String', isNullable: true),
+ TypeDeclaration(baseName: 'Object', isNullable: true),
+ ],
+ isNullable: false,
+ )),
+ NamedType(
+ name: 'anObject',
+ type: const TypeDeclaration(
+ baseName: 'ParameterObject',
+ isNullable: false,
+ )),
+ ],
+ returnType: const TypeDeclaration.voidDeclaration(),
+ isAsynchronous: false,
+ ),
+ ])
+ ], classes: <Class>[
+ Class(name: 'ParameterObject', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'bool',
+ isNullable: false,
+ ),
+ name: 'aValue',
+ offset: null),
+ ]),
+ ], enums: <Enum>[]);
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppHeader('', const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ contains('DoSomething(bool a_bool, '
+ 'int64_t an_int, '
+ 'const std::string& a_string, '
+ 'const flutter::EncodableList& a_list, '
+ 'const flutter::EncodableMap& a_map, '
+ 'const ParameterObject& an_object)'));
+ }
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppSource(const CppOptions(), root, sink);
+ final String code = sink.toString();
+ // Most types should extract references. Since the type is non-nullable,
+ // there's only one possible type.
+ expect(
+ code,
+ contains(
+ 'const auto& a_bool_arg = std::get<bool>(encodable_a_bool_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto& a_string_arg = std::get<std::string>(encodable_a_string_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto& a_list_arg = std::get<flutter::EncodableList>(encodable_a_list_arg);'));
+ expect(
+ code,
+ contains(
+ 'const auto& a_map_arg = std::get<flutter::EncodableMap>(encodable_a_map_arg);'));
+ // Ints use a copy since there are two possible reference types, but
+ // the paramter always needs an int64_t.
+ expect(
+ code,
+ contains(
+ 'const int64_t an_int_arg = encodable_an_int_arg.LongValue();',
+ ));
+ // Custom class types require an extra layer of extraction.
+ expect(
+ code,
+ contains(
+ 'const auto& an_object_arg = std::any_cast<const ParameterObject&>(std::get<flutter::CustomEncodableValue>(encodable_an_object_arg));'));
+ }
+ });
+
+ test('host API argument extraction uses references', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doSomething',
+ arguments: <NamedType>[
+ NamedType(
+ name: 'anArg',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: false,
+ )),
+ ],
+ returnType: const TypeDeclaration.voidDeclaration(),
+ isAsynchronous: false,
+ ),
+ ])
+ ], classes: <Class>[], enums: <Enum>[]);
+
+ final StringBuffer sink = StringBuffer();
+ generateCppSource(const CppOptions(), root, sink);
+ final String code = sink.toString();
+ // A bare 'auto' here would create a copy, not a reference, which is
+ // ineffecient.
+ expect(
+ code,
+ contains(
+ 'const auto& args = std::get<flutter::EncodableList>(message);'));
+ expect(code, contains('const auto& encodable_an_arg_arg = args.at(0);'));
+ });
+
test('enum argument', () {
final Root root = Root(
apis: <Api>[