Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pigeon] Adds SwiftFunction annotation #2304

Merged
merged 37 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4337cd6
Add SwiftFunction annotation
ailtonvivaz Jul 3, 2022
d3eb40d
Bump version to 3.2.4
ailtonvivaz Jul 3, 2022
2920bbf
Remove unused imports
ailtonvivaz Jul 3, 2022
ac02d63
Improve methods map
ailtonvivaz Jul 5, 2022
f52ab0c
Remove unnecessary print
ailtonvivaz Jul 8, 2022
b5ec8ae
Force cast match of SwiftFunction
ailtonvivaz Jul 9, 2022
ac2b51b
Update packages/pigeon/lib/pigeon_lib.dart
ailtonvivaz Jul 27, 2022
f0bbf86
Merge branch 'main' into swift-function-annotation
ailtonvivaz Jul 27, 2022
5f70ea2
Improve documentation of function to parse method with SwiftFunction
ailtonvivaz Jul 27, 2022
718c1b3
Merge branch 'main' into swift-function-annotation
ailtonvivaz Aug 24, 2022
b034144
Merge branch 'main' into swift-function-annotation
ailtonvivaz Aug 25, 2022
519ff35
Merge branch 'main' into swift-function-annotation
ailtonvivaz Aug 27, 2022
9f86539
Fix some dartdocs
ailtonvivaz Aug 31, 2022
d754a58
Merge branch 'main' into swift-function-annotation
ailtonvivaz Sep 22, 2022
d8bf6d4
Merge branch 'swift-function-annotation' of github.com:ailtonvivaz/pa…
tarrinneal Jan 17, 2023
d4175a9
gen
tarrinneal Jan 17, 2023
09ebdea
analyze
tarrinneal Jan 17, 2023
6db9d1b
Improve SwiftFunction application
ailtonvivaz Jan 18, 2023
22af130
Merge branch 'main' into swift-function-annotation
ailtonvivaz Jan 18, 2023
399e4ae
Add type annotation
ailtonvivaz Jan 18, 2023
608b5ef
format
tarrinneal Jan 18, 2023
4bc40a3
Run format
ailtonvivaz Jan 18, 2023
a8c90f0
Update macos Swift tests
ailtonvivaz Jan 18, 2023
1bdde71
Bump version to 7.0.0
ailtonvivaz Jan 18, 2023
3da3698
Merge branch 'swift-function-annotation' of github.com:ailtonvivaz/pa…
tarrinneal Jan 18, 2023
b13b7ca
revert version change
tarrinneal Jan 18, 2023
ef9b5d0
Improve some code of SwiftGenerator
ailtonvivaz Jan 18, 2023
62b3163
Merge branch 'swift-function-annotation' of github.com:ailtonvivaz/pa…
ailtonvivaz Jan 18, 2023
a44aa18
Bump version to 6.1.0
ailtonvivaz Jan 18, 2023
5aa2197
Improve echo functions for Swift
ailtonvivaz Jan 18, 2023
9176579
Match order of parameters
ailtonvivaz Jan 18, 2023
372d7b7
Documents _SwiftFunctionComponents.fromMethod and _SwiftFunctionArgument
ailtonvivaz Jan 18, 2023
e734c28
Merge branch 'main' into swift-function-annotation
ailtonvivaz Jan 24, 2023
0039165
Improve doc comments
ailtonvivaz Jan 24, 2023
651f9fa
Fix tests
ailtonvivaz Jan 24, 2023
217e4f7
Merge branch 'main' of github.com:flutter/packages into swift-functio…
tarrinneal Jan 25, 2023
245a327
Fix SwiftFunction documentation
ailtonvivaz Jan 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 6.1.0

* Adds `@SwiftFunction` annotation for specifying custom swift function signature.

## 6.0.3

* [docs] Updates README.md.
Expand Down
8 changes: 7 additions & 1 deletion packages/pigeon/lib/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Method extends Node {
this.isAsynchronous = false,
this.offset,
this.objcSelector = '',
this.swiftFunction = '',
this.taskQueueType = TaskQueueType.serial,
this.documentationComments = const <String>[],
});
Expand All @@ -54,6 +55,9 @@ class Method extends Node {
/// An override for the generated objc selector (ex. "divideNumber:by:").
String objcSelector;

/// An override for the generated swift function signature (ex. "divideNumber(_:by:)").
String swiftFunction;

/// Specifies how handlers are dispatched with respect to threading.
TaskQueueType taskQueueType;

Expand All @@ -68,7 +72,9 @@ class Method extends Node {
String toString() {
final String objcSelectorStr =
objcSelector.isEmpty ? '' : ' objcSelector:$objcSelector';
return '(Method name:$name returnType:$returnType arguments:$arguments isAsynchronous:$isAsynchronous$objcSelectorStr documentationComments:$documentationComments)';
final String swiftFunctionStr =
swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction';
return '(Method name:$name returnType:$returnType arguments:$arguments isAsynchronous:$isAsynchronous$objcSelectorStr$swiftFunctionStr documentationComments:$documentationComments)';
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'dart:mirrors';
import 'ast.dart';

/// The current version of pigeon. This must match the version in pubspec.yaml.
const String pigeonVersion = '6.0.3';
const String pigeonVersion = '6.1.0';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down
32 changes: 32 additions & 0 deletions packages/pigeon/lib/pigeon_lib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ class ObjCSelector {
final String value;
}

/// Metadata to annotate methods to control the signature used for Swift output.
/// The number of components in the provided signature must match the number of
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
/// arguments in the annotated method.
/// For example:
/// @SwiftFunction('setValue(_:for:)') double divide(int value, String key);
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
class SwiftFunction {
/// Constructor.
const SwiftFunction(this.value);

/// The string representation of the function signature.
final String value;
}

/// Type of TaskQueue which determines how handlers are dispatched for
/// HostApi's.
enum TaskQueueType {
Expand Down Expand Up @@ -724,6 +737,17 @@ List<Error> _validateAst(Root root, String source) {
));
}
}
if (method.swiftFunction.isNotEmpty) {
final RegExp signatureRegex =
RegExp('\\w+ *\\((\\w+:){${method.arguments.length}}\\)');
if (!signatureRegex.hasMatch(method.swiftFunction)) {
result.add(Error(
message:
'Invalid function signature, expected ${method.arguments.length} arguments.',
lineNumber: _calculateLineNumberNullable(source, method.offset),
));
}
}
if (method.taskQueueType != TaskQueueType.serial &&
api.location != ApiLocation.host) {
result.add(Error(
Expand Down Expand Up @@ -1026,6 +1050,13 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
.asNullable<dart_ast.SimpleStringLiteral>()
?.value ??
'';
final String swiftFunction = _findMetadata(node.metadata, 'SwiftFunction')
?.arguments
?.arguments
.first
.asNullable<dart_ast.SimpleStringLiteral>()
?.value ??
'';
final dart_ast.ArgumentList? taskQueueArguments =
_findMetadata(node.metadata, 'TaskQueue')?.arguments;
final String? taskQueueTypeName = taskQueueArguments == null
Expand Down Expand Up @@ -1054,6 +1085,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
arguments: arguments,
isAsynchronous: isAsynchronous,
objcSelector: objcSelector,
swiftFunction: swiftFunction,
offset: node.offset,
taskQueueType: taskQueueType,
documentationComments:
Expand Down
143 changes: 119 additions & 24 deletions packages/pigeon/lib/swift_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ import FlutterMacOS
});
}
for (final Method func in api.methods) {
final _SwiftFunctionComponents components =
_SwiftFunctionComponents.fromMethod(func);

final String channelName = makeChannelName(api, func);
final String returnType = func.returnType.isVoid
? ''
Expand All @@ -309,8 +312,11 @@ import FlutterMacOS
} else {
final Iterable<String> argTypes = func.arguments
.map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type));
final Iterable<String> argLabels =
indexMap(func.arguments, _getArgumentName);
final Iterable<String> argLabels = indexMap(components.arguments,
(int index, _SwiftFunctionArgument argument) {
return argument.label ??
_getArgumentName(index, argument.namedType);
});
final Iterable<String> argNames =
indexMap(func.arguments, _getSafeArgumentName);
sendArgument = '[${argNames.join(', ')}] as [Any?]';
Expand All @@ -322,10 +328,10 @@ import FlutterMacOS
'$label $name: $type').join(', ');
if (func.returnType.isVoid) {
indent.write(
'func ${func.name}($argsSignature, completion: @escaping () -> Void) ');
'func ${components.name}($argsSignature, completion: @escaping () -> Void) ');
} else {
indent.write(
'func ${func.name}($argsSignature, completion: @escaping ($returnType) -> Void) ');
'func ${components.name}($argsSignature, completion: @escaping ($returnType) -> Void) ');
}
}
indent.scoped('{', '}', () {
Expand Down Expand Up @@ -369,7 +375,6 @@ import FlutterMacOS
if (isCustomCodec) {
_writeCodec(indent, api, root);
}

const List<String> generatedComments = <String>[
' Generated protocol from Pigeon that represents a handler of messages from Flutter.'
];
Expand All @@ -379,17 +384,15 @@ import FlutterMacOS
indent.write('protocol $apiName ');
indent.scoped('{', '}', () {
for (final Method method in api.methods) {
final List<String> argSignature = <String>[];
if (method.arguments.isNotEmpty) {
final Iterable<String> argTypes = method.arguments
.map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type));
final Iterable<String> argNames =
method.arguments.map((NamedType e) => e.name);
argSignature.addAll(
map2(argTypes, argNames, (String argType, String argName) {
return '$argName: $argType';
}));
}
final _SwiftFunctionComponents components =
_SwiftFunctionComponents.fromMethod(method);
final List<String> argSignature =
components.arguments.map((_SwiftFunctionArgument argument) {
final String? label = argument.label;
final String name = argument.name;
final String type = _nullsafeSwiftTypeForDartType(argument.type);
return '${label == null ? '' : '$label '}$name: $type';
}).toList();

final String returnType = method.returnType.isVoid
? ''
Expand All @@ -399,12 +402,12 @@ import FlutterMacOS

if (method.isAsynchronous) {
argSignature.add('completion: @escaping ($returnType) -> Void');
indent.writeln('func ${method.name}(${argSignature.join(', ')})');
indent.writeln('func ${components.name}(${argSignature.join(', ')})');
} else if (method.returnType.isVoid) {
indent.writeln('func ${method.name}(${argSignature.join(', ')})');
indent.writeln('func ${components.name}(${argSignature.join(', ')})');
} else {
indent.writeln(
'func ${method.name}(${argSignature.join(', ')}) -> $returnType');
'func ${components.name}(${argSignature.join(', ')}) -> $returnType');
}
}
});
Expand All @@ -428,6 +431,9 @@ import FlutterMacOS
'static func setUp(binaryMessenger: FlutterBinaryMessenger, api: $apiName?) ');
indent.scoped('{', '}', () {
for (final Method method in api.methods) {
final _SwiftFunctionComponents components =
_SwiftFunctionComponents.fromMethod(method);

final String channelName = makeChannelName(api, method);
final String varChannelName = '${method.name}Channel';
addDocumentationComments(
Expand All @@ -442,18 +448,25 @@ import FlutterMacOS
method.arguments.isNotEmpty ? 'message' : '_';
indent.scoped('{ $messageVarName, reply in', '}', () {
final List<String> methodArgument = <String>[];
if (method.arguments.isNotEmpty) {
if (components.arguments.isNotEmpty) {
indent.writeln('let args = message as! [Any?]');
enumerate(method.arguments, (int index, NamedType arg) {
final String argName = _getSafeArgumentName(index, arg);
enumerate(components.arguments,
(int index, _SwiftFunctionArgument arg) {
final String argName =
_getSafeArgumentName(index, arg.namedType);
final String argIndex = 'args[$index]';
indent.writeln(
'let $argName = ${_castForceUnwrap(argIndex, arg.type, root)}');
methodArgument.add('${arg.name}: $argName');

if (arg.label == '_') {
methodArgument.add(argName);
} else {
methodArgument.add('${arg.label ?? arg.name}: $argName');
}
});
}
final String call =
'api.${method.name}(${methodArgument.join(', ')})';
'api.${components.name}(${methodArgument.join(', ')})';
if (method.isAsynchronous) {
indent.write('$call ');
if (method.returnType.isVoid) {
Expand Down Expand Up @@ -694,3 +707,85 @@ String _nullsafeSwiftTypeForDartType(TypeDeclaration type) {
final String nullSafe = type.isNullable ? '?' : '';
return '${_swiftTypeForDartType(type)}$nullSafe';
}

/// A class that represents a Swift function argument.
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
/// The [name] is the name of the argument.
/// The [type] is the type of the argument.
/// The [namedType] is the [NamedType] that this argument is generated from.
/// The [label] is the label of the argument.
class _SwiftFunctionArgument {
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
_SwiftFunctionArgument({
required this.name,
required this.type,
required this.namedType,
this.label,
});

final String name;
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
final TypeDeclaration type;
final NamedType namedType;
final String? label;
}

/// A class that represents a Swift function signature.
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
/// The [name] is the name of the function.
/// The [arguments] are the arguments of the function.
/// The [returnType] is the return type of the function.
/// The [method] is the method that this function signature is generated from.
class _SwiftFunctionComponents {
ailtonvivaz marked this conversation as resolved.
Show resolved Hide resolved
_SwiftFunctionComponents._({
required this.name,
required this.arguments,
required this.returnType,
required this.method,
});

/// Constructor that generates a [_SwiftFunctionComponents] from a [Method].
factory _SwiftFunctionComponents.fromMethod(Method method) {
if (method.swiftFunction.isEmpty) {
return _SwiftFunctionComponents._(
name: method.name,
returnType: method.returnType,
arguments: method.arguments
.map((NamedType field) => _SwiftFunctionArgument(
name: field.name,
type: field.type,
namedType: field,
))
.toList(),
method: method,
);
}

final String argsExtractor =
repeat(r'(\w+):', method.arguments.length).join();
final RegExp signatureRegex = RegExp(r'(\w+) *\(' + argsExtractor + r'\)');
final RegExpMatch match = signatureRegex.firstMatch(method.swiftFunction)!;

final Iterable<String> labels = match
.groups(List<int>.generate(
method.arguments.length, (int index) => index + 2))
.whereType();

return _SwiftFunctionComponents._(
name: match.group(1)!,
returnType: method.returnType,
arguments: map2(
method.arguments,
labels,
(NamedType field, String label) => _SwiftFunctionArgument(
name: field.name,
label: label == field.name ? null : label,
type: field.type,
namedType: field,
),
).toList(),
method: method,
);
}

final String name;
final List<_SwiftFunctionArgument> arguments;
final TypeDeclaration returnType;
final Method method;
}
2 changes: 1 addition & 1 deletion packages/pigeon/mock_handler_tester/test/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v6.0.3), do not edit directly.
// Autogenerated from Pigeon (v6.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// 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

Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/mock_handler_tester/test/test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v6.0.3), do not edit directly.
// Autogenerated from Pigeon (v6.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
// ignore_for_file: avoid_relative_lib_imports
Expand Down
Loading