Skip to content

Commit

Permalink
issue(123867): added ability to specify swift throws in modern async
Browse files Browse the repository at this point in the history
  • Loading branch information
feduke-nukem committed Dec 24, 2024
1 parent 583d6d7 commit 934c5c7
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ private class PigeonApiImplementation : ExampleHostApi {
throw FlutterError("code", "message", "details")
}

delay(2000)

return true
}
}
Expand Down
8 changes: 1 addition & 7 deletions packages/pigeon/example/app/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@ private class PigeonApiImplementation: ExampleHostApi {
completion(.success(true))
}

func sendMessageModernAsync(message: MessageData) async throws -> Bool {
try? await Task.sleep(nanoseconds: 2_000_000_000)

if message.code == Code.one {
throw PigeonError(code: "code", message: "message", details: "details")
}

func sendMessageModernAsync(message: MessageData) async -> Bool {
return true
}
}
Expand Down
69 changes: 21 additions & 48 deletions packages/pigeon/example/app/ios/Runner/Messages.g.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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.
// Autogenerated from Pigeon, do not edit directly.
// Autogenerated from Pigeon (v22.7.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation
Expand Down Expand Up @@ -29,7 +29,7 @@ final class PigeonError: Error {
var localizedDescription: String {
return
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
}
}
}

private func wrapResult(_ result: Any?) -> [Any?] {
Expand Down Expand Up @@ -59,9 +59,7 @@ private func wrapError(_ error: Any) -> [Any?] {
}

private func createConnectionError(withChannelName channelName: String) -> PigeonError {
return PigeonError(
code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.",
details: "")
return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
}

private func isNullish(_ value: Any?) -> Bool {
Expand All @@ -85,6 +83,7 @@ struct MessageData {
var code: Code
var data: [String: String]


// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> MessageData? {
let name: String? = nilOrValue(pigeonVar_list[0])
Expand Down Expand Up @@ -154,29 +153,25 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
}


/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol ExampleHostApi {
func getHostLanguage() throws -> String
func add(_ a: Int64, to b: Int64) throws -> Int64
func sendMessage(message: MessageData, completion: @escaping (Result<Bool, Error>) -> Void)
func sendMessageModernAsync(message: MessageData) async throws -> Bool
func sendMessageModernAsync(message: MessageData) async -> Bool
}

/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class ExampleHostApiSetup {
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
/// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`.
static func setUp(
binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = ""
) {
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let getHostLanguageChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let getHostLanguageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getHostLanguageChannel.setMessageHandler { _, reply in
do {
do {
let result = try api.getHostLanguage()
reply(wrapResult(result))
} catch {
Expand All @@ -186,15 +181,13 @@ class ExampleHostApiSetup {
} else {
getHostLanguageChannel.setMessageHandler(nil)
}
let addChannel = FlutterBasicMessageChannel(
name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let addChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
addChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let aArg = args[0] as! Int64
let bArg = args[1] as! Int64
do {
do {
let result = try api.add(aArg, to: bArg)
reply(wrapResult(result))
} catch {
Expand All @@ -204,9 +197,7 @@ class ExampleHostApiSetup {
} else {
addChannel.setMessageHandler(nil)
}
let sendMessageChannel = FlutterBasicMessageChannel(
name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let sendMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
sendMessageChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
Expand All @@ -223,24 +214,15 @@ class ExampleHostApiSetup {
} else {
sendMessageChannel.setMessageHandler(nil)
}
let sendMessageModernAsyncChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let sendMessageModernAsyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
sendMessageModernAsyncChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let messageArg = args[0] as! MessageData
Task {
do {
let result = try await api.sendMessageModernAsync(message: messageArg)
await MainActor.run {
reply(wrapResult(result))
}
} catch {
await MainActor.run {
reply(wrapError(error))
}
let result = await api.sendMessageModernAsync(message: messageArg)
await MainActor.run {
reply(wrapResult(result))
}
}
}
Expand All @@ -251,8 +233,7 @@ class ExampleHostApiSetup {
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol MessageFlutterApiProtocol {
func flutterMethod(
aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void)
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void)
}
class MessageFlutterApi: MessageFlutterApiProtocol {
private let binaryMessenger: FlutterBinaryMessenger
Expand All @@ -264,13 +245,9 @@ class MessageFlutterApi: MessageFlutterApiProtocol {
var codec: MessagesPigeonCodec {
return MessagesPigeonCodec.shared
}
func flutterMethod(
aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void
) {
let channelName: String =
"dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(
name: channelName, binaryMessenger: binaryMessenger, codec: codec)
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([aStringArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
Expand All @@ -282,11 +259,7 @@ class MessageFlutterApi: MessageFlutterApiProtocol {
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else if listResponse[0] == nil {
completion(
.failure(
PigeonError(
code: "null-error",
message: "Flutter api returned null value for non-null return value.", details: "")))
completion(.failure(PigeonError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! String
completion(.success(result))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ private class PigeonApiImplementation: ExampleHostApi {
completion(.success(true))
}

func sendMessageModernAsync(message: MessageData) async throws -> Bool {
try? await Task.sleep(nanoseconds: 2_000_000_000)

if message.code == Code.one {
throw PigeonError(code: "code", message: "message", details: "details")
}

func sendMessageModernAsync(message: MessageData) async -> Bool {
return true
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/example/app/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ abstract class ExampleHostApi {
@async
bool sendMessage(MessageData message);

@modernAsync
@ModernAsync(isSwiftThrows: false)
bool sendMessageModernAsync(MessageData message);
}
// #enddocregion host-definitions
Expand Down
79 changes: 54 additions & 25 deletions packages/pigeon/lib/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,64 @@ enum ApiLocation {
flutter,
}

/// Enum that represents the type of asynchronous a method is.
enum AsynchronousType {
/// No asynchronous implementation.
none,
/// Represents the type of asynchronous api will be used.
sealed class AsynchronousType {
/// Constructor for [AsynchronousType].
const AsynchronousType();

/// Basic callback implementation.
callback,
/// No asynchronous.
static const NoAsynchronous none = NoAsynchronous();

/// Modern async implementation.
///
/// * Swift - async.
/// * Kotlin - suspend.
modern;
/// Callback asynchronous.
static const CallbackAsynchronous callback = CallbackAsynchronous();

/// Returns true if the [AsynchronousType] is [CallbackAsynchronous].
bool get isCallback => this is CallbackAsynchronous;

/// Returns true if the [AsynchronousType] is [ModernAsynchronous].
bool get isModern => this is ModernAsynchronous;

/// Returns true if the [AsynchronousType] is [NoAsynchronous].
bool get isNone => this is NoAsynchronous;
}

/// Returns true if the [AsynchronousType] is [AsynchronousType.none].
bool get isNone => this == AsynchronousType.none;
/// Represents a callback asynchronous api will be used.
class CallbackAsynchronous extends AsynchronousType {
/// Constructor for [CallbackAsynchronous].
const CallbackAsynchronous();
}

/// Returns true if the [AsynchronousType] is [AsynchronousType.callback].
bool get isCallback => this == AsynchronousType.callback;
/// Represents a modern asynchronous api will be used.
///
/// * Swift - async.
/// * Kotlin - suspend.
class ModernAsynchronous extends AsynchronousType {
/// Constructor for [ModernAsynchronous].
const ModernAsynchronous({
required this.swiftOptions,
});

/// Returns true if the [AsynchronousType] is [AsynchronousType.modern].
bool get isModern => this == AsynchronousType.modern;
/// {@macro ast.swift_modern_asynchronous_options}
final SwiftModernAsynchronousOptions swiftOptions;
}

/// Represents a no asynchronous api will be used.
class NoAsynchronous extends AsynchronousType {
/// Constructor for [NoAsynchronous].
const NoAsynchronous();
}

/// {@template ast.swift_modern_asynchronous_options}
/// Options for Swift modern asynchronous.
/// {@endtemplate}
class SwiftModernAsynchronousOptions {
/// Constructor for [SwiftModernAsynchronousOptions].
const SwiftModernAsynchronousOptions({
required this.throws,
});

/// Whether the function throws an exception or not.
final bool throws;
}

/// Superclass for all AST nodes.
Expand Down Expand Up @@ -112,14 +148,7 @@ class Method extends Node {
AsynchronousType asynchronousType;

/// Whether this method is asynchronous.
bool get isAsynchronous => asynchronousType != AsynchronousType.none;

/// Whether this method is asynchronous with callback.
bool get isCallbackAsynchronous =>
asynchronousType == AsynchronousType.callback;

/// Whether this method is asynchronous with modern Api.
bool get isModernAsynchronous => asynchronousType == AsynchronousType.modern;
bool get isAsynchronous => !asynchronousType.isNone;

@override
String toString() {
Expand Down
7 changes: 3 additions & 4 deletions packages/pigeon/lib/kotlin_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class KotlinGenerator extends StructuredGenerator<KotlinOptions> {
indent.writeln('import java.io.ByteArrayOutputStream');
indent.writeln('import java.nio.ByteBuffer');
if (root.apis.any((Api api) => api.methods.any((Method it) =>
it.isModernAsynchronous && it.location == ApiLocation.host))) {
it.asynchronousType.isModern && it.location == ApiLocation.host))) {
indent.writeln('import kotlinx.coroutines.launch');
indent.writeln('import kotlinx.coroutines.CoroutineScope');
indent.writeln('import kotlinx.coroutines.Dispatchers');
Expand Down Expand Up @@ -345,8 +345,7 @@ class KotlinGenerator extends StructuredGenerator<KotlinOptions> {
}) {
if (root.apis.any((Api api) =>
api is AstHostApi &&
api.methods.any((Method it) =>
it.isCallbackAsynchronous || it.isModernAsynchronous))) {
api.methods.any((Method it) => it.isAsynchronous))) {
indent.newln();
}
super.writeApis(generatorOptions, root, indent,
Expand Down Expand Up @@ -646,7 +645,7 @@ if (wrapped == null) {
'/** Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`. */');
indent.writeln('@JvmOverloads');
final String coroutineScope =
api.methods.any((Method method) => method.isModernAsynchronous)
api.methods.any((Method method) => method.asynchronousType.isModern)
? ', coroutineScope: CoroutineScope'
: '';
indent.write(
Expand Down
Loading

0 comments on commit 934c5c7

Please sign in to comment.