Skip to content

Commit

Permalink
[pigeon] primitive enums (#4580)
Browse files Browse the repository at this point in the history
Adds support for enums as parameters and return types.

Dart, C++, Java, Kotlin, Objective-C, and swift:
Primitive synchronous non-null
Primitive synchronous nullable
Primitive asynchronous non-null
Primitive asynchronous nullable
Primitive Flutter api non-null
Primitive flutter api nullable

Objective-C:
Class property nullable 

Also fixes an issue with nullable enums in classes on objc. This fix required a breaking change that nested all nullable enums in a wrapper class to allow nullability. 

Also adds the ability to format files before tests run (to make my life better)

Also replaces #4756

Also adds objc prefixes to enums

fixes: flutter/flutter#87307
fixes: flutter/flutter#118733
  • Loading branch information
tarrinneal committed Aug 28, 2023
1 parent bab7bb0 commit 94ba82c
Show file tree
Hide file tree
Showing 49 changed files with 3,058 additions and 351 deletions.
2 changes: 1 addition & 1 deletion packages/pigeon/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ gradlew.bat
local.properties
gradle-wrapper.jar
bin/pigeon.dart.dill

**/subdirectory/does/not/exist/**
8 changes: 6 additions & 2 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## NEXT
## 11.0.0

* Adds primitive enum support.
* Fixes Objective-C nullable enums.
* **Breaking Change** Changes all nullable enums in Objective-C to be wrapped in custom classes.
* **Breaking Change** Changes all enums names in Objective-C to have class prefix.
* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.

## 10.1.6
Expand Down Expand Up @@ -91,7 +95,7 @@
## 9.1.1

* [swift] Removes experimental tags.
* [kotin] Removes experimental tags.
* [kotlin] Removes experimental tags.

## 9.1.0

Expand Down
7 changes: 2 additions & 5 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@ Currently pigeon supports generating:
Pigeon uses the `StandardMessageCodec` so it supports
[any datatype platform channels support](https://flutter.dev/docs/development/platform-integration/platform-channels#codec).

Custom classes and nested datatypes are also supported.
Custom classes, nested datatypes, and enums are also supported.

#### Enums

Pigeon currently supports enum generation in class fields only.
See issue: [87307](https://github.com/flutter/flutter/issues/87307).
Nullable enums in Objective-C generated code will be wrapped in a class to allow for nullability.

### Synchronous and Asynchronous methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ ArrayList<Object> toList() {
Object description = list.get(1);
pigeonResult.setDescription((String) description);
Object code = list.get(2);
pigeonResult.setCode(code == null ? null : Code.values()[(int) code]);
pigeonResult.setCode(Code.values()[(int) code]);
Object data = list.get(3);
pigeonResult.setData((Map<String, String>) data);
return pigeonResult;
Expand Down
4 changes: 4 additions & 0 deletions packages/pigeon/example/app/ios/Runner/Messages.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import FlutterMacOS
#error("Unsupported platform.")
#endif

private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}

private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
Expand Down
6 changes: 6 additions & 0 deletions packages/pigeon/example/app/macos/Runner/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ typedef NS_ENUM(NSUInteger, PGNCode) {
PGNCodeTwo = 1,
};

/// Wrapper for PGNCode to allow for nullability.
@interface PGNCodeBox : NSObject
@property(nonatomic, assign) PGNCode value;
- (instancetype)initWithValue:(PGNCode)value;
@end

@class PGNMessageData;

@interface PGNMessageData : NSObject
Expand Down
10 changes: 10 additions & 0 deletions packages/pigeon/example/app/macos/Runner/messages.g.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
#error File requires ARC to be enabled.
#endif

@implementation PGNCodeBox
- (instancetype)initWithValue:(PGNCode)value {
self = [super init];
if (self) {
_value = value;
}
return self;
}
@end

static NSArray *wrapResult(id result, FlutterError *error) {
if (error) {
return @[
Expand Down
68 changes: 57 additions & 11 deletions packages/pigeon/lib/cpp_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -888,9 +888,14 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
indent.writeln(
'std::unique_ptr<EncodableValue> response = GetCodec().DecodeMessage(reply, reply_size);');
indent.writeln('const auto& $encodedReplyName = *response;');
_writeEncodableValueArgumentUnwrapping(indent, returnType,
argName: successCallbackArgument,
encodableArgName: encodedReplyName);
_writeEncodableValueArgumentUnwrapping(
indent,
root,
returnType,
argName: successCallbackArgument,
encodableArgName: encodedReplyName,
apiType: ApiType.flutter,
);
}
indent.writeln('on_success($successCallbackArgument);');
});
Expand Down Expand Up @@ -968,9 +973,19 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
indent.writeln('return;');
});
}
_writeEncodableValueArgumentUnwrapping(indent, hostType,
argName: argName, encodableArgName: encodableArgName);
methodArgument.add(argName);
_writeEncodableValueArgumentUnwrapping(
indent,
root,
hostType,
argName: argName,
encodableArgName: encodableArgName,
apiType: ApiType.host,
);
final String unwrapEnum =
isEnum(root, arg.type) && arg.type.isNullable
? ' ? &(*$argName) : nullptr'
: '';
methodArgument.add('$argName$unwrapEnum');
});
}

Expand Down Expand Up @@ -1198,29 +1213,35 @@ return EncodableValue(EncodableList{
final String errorGetter;

const String nullValue = 'EncodableValue()';
String enumPrefix = '';
if (isEnum(root, returnType)) {
enumPrefix = '(int) ';
}
if (returnType.isVoid) {
nonErrorPath = '${prefix}wrapped.push_back($nullValue);';
errorCondition = 'output.has_value()';
errorGetter = 'value';
} else {
final HostDatatype hostType = getHostDatatype(returnType, root.classes,
root.enums, _shortBaseCppTypeForBuiltinDartType);

const String extractedValue = 'std::move(output).TakeValue()';
final String wrapperType =
hostType.isBuiltin ? 'EncodableValue' : 'CustomEncodableValue';
final String wrapperType = hostType.isBuiltin || isEnum(root, returnType)
? 'EncodableValue'
: 'CustomEncodableValue';
if (returnType.isNullable) {
// The value is a std::optional, so needs an extra layer of
// handling.
nonErrorPath = '''
${prefix}auto output_optional = $extractedValue;
${prefix}if (output_optional) {
$prefix\twrapped.push_back($wrapperType(std::move(output_optional).value()));
$prefix\twrapped.push_back($wrapperType(${enumPrefix}std::move(output_optional).value()));
$prefix} else {
$prefix\twrapped.push_back($nullValue);
$prefix}''';
} else {
nonErrorPath =
'${prefix}wrapped.push_back($wrapperType($extractedValue));';
'${prefix}wrapped.push_back($wrapperType($enumPrefix$extractedValue));';
}
errorCondition = 'output.has_error()';
errorGetter = 'error';
Expand Down Expand Up @@ -1297,9 +1318,11 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));''';
/// existing EncodableValue variable called [encodableArgName].
void _writeEncodableValueArgumentUnwrapping(
Indent indent,
Root root,
HostDatatype hostType, {
required String argName,
required String encodableArgName,
required ApiType apiType,
}) {
if (hostType.isNullable) {
// Nullable arguments are always pointers, with nullptr corresponding to
Expand All @@ -1320,6 +1343,22 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));''';
} else if (hostType.isBuiltin) {
indent.writeln(
'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);');
} else if (hostType.isEnum) {
if (hostType.isNullable) {
final String valueVarName = '${argName}_value';
indent.writeln(
'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();');
if (apiType == ApiType.flutter) {
indent.writeln(
'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &(${hostType.datatype})$valueVarName;');
} else {
indent.writeln(
'const auto $argName = $encodableArgName.IsNull() ? std::nullopt : std::make_optional<${hostType.datatype}>(static_cast<${hostType.datatype}>(${argName}_value));');
}
} else {
indent.writeln(
'const auto* $argName = &((${hostType.datatype})std::get<int>($encodableArgName));');
}
} else {
indent.writeln(
'const auto* $argName = &(std::any_cast<const ${hostType.datatype}&>(std::get<CustomEncodableValue>($encodableArgName)));');
Expand All @@ -1342,6 +1381,9 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));''';
} else if (hostType.isBuiltin) {
indent.writeln(
'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);');
} else if (hostType.isEnum) {
indent.writeln(
'const ${hostType.datatype}& $argName = (${hostType.datatype})$encodableArgName.LongValue();');
} else {
indent.writeln(
'const auto& $argName = std::any_cast<const ${hostType.datatype}&>(std::get<CustomEncodableValue>($encodableArgName));');
Expand Down Expand Up @@ -1390,7 +1432,11 @@ String _getSafeArgumentName(int count, NamedType argument) =>
/// Returns a non-nullable variant of [type].
HostDatatype _nonNullableType(HostDatatype type) {
return HostDatatype(
datatype: type.datatype, isBuiltin: type.isBuiltin, isNullable: false);
datatype: type.datatype,
isBuiltin: type.isBuiltin,
isNullable: false,
isEnum: type.isEnum,
);
}

String _pascalCaseFromCamelCase(String camelCase) =>
Expand Down
31 changes: 25 additions & 6 deletions packages/pigeon/lib/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ $resultAt != null
final String leftHandSide = 'final $argType? $argName';
if (customEnumNames.contains(arg.type.baseName)) {
indent.writeln(
'$leftHandSide = $argsArray[$count] == null ? null : $argType.values[$argsArray[$count] as int];');
'$leftHandSide = $argsArray[$count] == null ? null : $argType.values[$argsArray[$count]! as int];');
} else {
indent.writeln(
'$leftHandSide = ($argsArray[$count] as $genericArgType?)${castCall.isEmpty ? '' : '?$castCall'};');
Expand Down Expand Up @@ -442,10 +442,15 @@ $resultAt != null
} else {
indent.writeln('final $returnType output = $call;');
}

const String returnExpression = 'output';
final String nullability =
func.returnType.isNullable ? '?' : '';
final String valueExtraction =
isEnum(root, func.returnType) ? '$nullability.index' : '';
final String returnStatement = isMockHandler
? 'return <Object?>[$returnExpression];'
: 'return $returnExpression;';
? 'return <Object?>[$returnExpression$valueExtraction];'
: 'return $returnExpression$valueExtraction;';
indent.writeln(returnStatement);
}
});
Expand Down Expand Up @@ -487,6 +492,8 @@ $resultAt != null
codecName = _getCodecName(api);
_writeCodec(indent, codecName, api, root);
}
final List<String> customEnumNames =
root.enums.map((Enum x) => x.name).toList();
indent.newln();
bool first = true;
addDocumentationComments(
Expand Down Expand Up @@ -553,9 +560,21 @@ final BinaryMessenger? _binaryMessenger;
final String nullHandler = func.returnType.isNullable
? (genericCastCall.isEmpty ? '' : '?')
: '!';
final String returnStatement = func.returnType.isVoid
? 'return;'
: 'return $nullablyTypedAccessor$nullHandler$genericCastCall;';
String returnStatement = 'return';
if (customEnumNames.contains(returnType)) {
if (func.returnType.isNullable) {
returnStatement =
'$returnStatement ($accessor as int?) == null ? null : $returnType.values[$accessor! as int]';
} else {
returnStatement =
'$returnStatement $returnType.values[$accessor! as int]';
}
} else if (!func.returnType.isVoid) {
returnStatement =
'$returnStatement $nullablyTypedAccessor$nullHandler$genericCastCall';
}
returnStatement = '$returnStatement;';

indent.format('''
final List<Object?>? replyList =
\t\tawait channel.send($sendArgument) as List<Object?>?;
Expand Down
33 changes: 29 additions & 4 deletions packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'ast.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '10.1.6';
const String pigeonVersion = '11.0.0';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down Expand Up @@ -176,6 +176,7 @@ class HostDatatype {
required this.datatype,
required this.isBuiltin,
required this.isNullable,
required this.isEnum,
});

/// The [String] that can be printed into host code to represent the type.
Expand All @@ -186,6 +187,9 @@ class HostDatatype {

/// `true` if the type corresponds to a nullable Dart datatype.
final bool isNullable;

/// `true if the type is a custom enum.
final bool isEnum;
}

/// Calculates the [HostDatatype] for the provided [NamedType].
Expand Down Expand Up @@ -226,20 +230,32 @@ HostDatatype _getHostDatatype(TypeDeclaration type, List<Class> classes,
? customResolver(type.baseName)
: type.baseName;
return HostDatatype(
datatype: customName, isBuiltin: false, isNullable: type.isNullable);
datatype: customName,
isBuiltin: false,
isNullable: type.isNullable,
isEnum: false,
);
} else if (enums.map((Enum x) => x.name).contains(type.baseName)) {
final String customName = customResolver != null
? customResolver(type.baseName)
: type.baseName;
return HostDatatype(
datatype: customName, isBuiltin: false, isNullable: type.isNullable);
datatype: customName,
isBuiltin: false,
isNullable: type.isNullable,
isEnum: true,
);
} else {
throw Exception(
'unrecognized datatype ${fieldName == null ? '' : 'for field:"$fieldName" '}of type:"${type.baseName}"');
}
} else {
return HostDatatype(
datatype: datatype, isBuiltin: true, isNullable: type.isNullable);
datatype: datatype,
isBuiltin: true,
isNullable: type.isNullable,
isEnum: false,
);
}
}

Expand Down Expand Up @@ -574,6 +590,15 @@ String? deducePackageName(String mainDartFile) {
}
}

/// Enum to specify api type when generating code.
enum ApiType {
/// Flutter api.
flutter,

/// Host api.
host,
}

/// Enum to specify which file will be generated for multi-file generators
enum FileType {
/// header file.
Expand Down
Loading

0 comments on commit 94ba82c

Please sign in to comment.