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

[swift2objc] Support the async annotation #1779

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions pkgs/swift2objc/lib/src/ast/_core/interfaces/can_async.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// An interface to describe a Swift entity's ability to be annotated
/// with `async`.
abstract interface class CanAsync {
abstract final bool async;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import '../shared/referred_type.dart';
import 'can_async.dart';
import 'can_throw.dart';
import 'declaration.dart';
import 'executable.dart';
Expand All @@ -16,6 +17,7 @@ abstract interface class FunctionDeclaration
Parameterizable,
Executable,
TypeParameterizable,
CanThrow {
CanThrow,
CanAsync {
abstract final ReferredType returnType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
// BSD-style license that can be found in the LICENSE file.

import '../shared/referred_type.dart';
import 'can_async.dart';
import 'can_throw.dart';
import 'declaration.dart';

/// Describes a variable-like entity.
///
/// This declaration [CanThrow] because Swift variables can have explicit
/// getters, which can be marked with `throws`. Such variables may not have a
/// setter.
abstract interface class VariableDeclaration implements Declaration, CanThrow {
/// This declaration implements [CanThrow] and [CanAsync] because Swift
/// variables can have explicit getters, which can be marked with `throws` and
/// `async`. Such variables may not have a setter.
abstract interface class VariableDeclaration
implements Declaration, CanThrow, CanAsync {
abstract final bool isConstant;
abstract final ReferredType type;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../../../_core/interfaces/can_async.dart';
import '../../../_core/interfaces/can_throw.dart';
import '../../../_core/interfaces/declaration.dart';
import '../../../_core/interfaces/executable.dart';
Expand All @@ -18,7 +19,8 @@ class InitializerDeclaration
Parameterizable,
ObjCAnnotatable,
Overridable,
CanThrow {
CanThrow,
CanAsync {
@override
String id;

Expand All @@ -34,6 +36,9 @@ class InitializerDeclaration
@override
bool throws;

@override
bool async;

bool isFailable;

@override
Expand All @@ -54,6 +59,7 @@ class InitializerDeclaration
required this.hasObjCAnnotation,
required this.isOverriding,
required this.throws,
required this.async,
required this.isFailable,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class MethodDeclaration
@override
bool throws;

@override
bool async;

@override
List<String> statements;

Expand All @@ -57,5 +60,6 @@ class MethodDeclaration
this.isStatic = false,
this.isOverriding = false,
this.throws = false,
this.async = false,
}) : assert(!isStatic || !isOverriding);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
@override
bool throws;

@override
bool async;

bool hasSetter;

PropertyStatements? getter;
Expand All @@ -46,6 +49,7 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
this.setter,
this.isStatic = false,
this.throws = false,
this.async = false,
}) : assert(!(isConstant && hasSetter)),
assert(!(hasSetter && throws));
}
Expand Down
8 changes: 8 additions & 0 deletions pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
@override
bool throws;

@override
bool async;

@override
ReferredType returnType;

Expand All @@ -50,6 +53,7 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
this.typeParams = const [],
this.statements = const [],
this.throws = false,
this.async = false,
});
}

Expand All @@ -70,11 +74,15 @@ class GlobalVariableDeclaration implements VariableDeclaration {
@override
bool throws;

@override
bool async;

GlobalVariableDeclaration({
required this.id,
required this.name,
required this.type,
required this.isConstant,
required this.throws,
required this.async,
}) : assert(!(throws && !isConstant));
}
14 changes: 14 additions & 0 deletions pkgs/swift2objc/lib/src/generator/_core/utils.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import '../../ast/_core/interfaces/can_async.dart';
import '../../ast/_core/interfaces/can_throw.dart';
import '../../ast/_core/interfaces/declaration.dart';
import '../../ast/_core/shared/parameter.dart';

String generateParameters(List<Parameter> params) {
Expand Down Expand Up @@ -34,3 +37,14 @@ void outputNextToFile({

File(outputPath).writeAsStringSync(content);
}

String generateAnnotations(Declaration decl) {
final annotations = StringBuffer();
if (decl is CanAsync && (decl as CanAsync).async) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promotion isn't working here for some reason, so need the as.

Ideally I'd be able to do this with generics, like String generateAnnotations<T extends CanAsync, CanThrow> (T decl) {, but Dart doesn't allow that yet: dart-lang/language#2709

annotations.write('async ');
}
if (decl is CanThrow && (decl as CanThrow).throws) {
annotations.write('throws ');
}
return annotations.toString();
}
18 changes: 6 additions & 12 deletions pkgs/swift2objc/lib/src/generator/generators/class_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,8 @@ List<String> _generateInitializer(InitializerDeclaration initializer) {

header.write('(${generateParameters(initializer.params)})');

if (initializer.throws) {
header.write(' throws');
}

return [
'$header {',
'$header ${generateAnnotations(initializer)}{',
...initializer.statements.indent(),
'}\n',
];
Expand All @@ -117,19 +113,17 @@ List<String> _generateClassMethod(MethodDeclaration method) {
}

header.write(
'public func ${method.name}(${generateParameters(method.params)})',
'public func ${method.name}(${generateParameters(method.params)}) ',
);

if (method.throws) {
header.write(' throws');
}
header.write(generateAnnotations(method));

if (!method.returnType.sameAs(voidType)) {
header.write(' -> ${method.returnType.swiftType}');
header.write('-> ${method.returnType.swiftType} ');
}

return [
'$header {',
'$header{',
...method.statements.indent(),
'}\n',
];
Expand All @@ -154,7 +148,7 @@ List<String> _generateClassProperty(PropertyDeclaration property) {
header.write('public var ${property.name}: ${property.type.swiftType} {');

final getterLines = [
'get {',
'get ${generateAnnotations(property)}{',
...(property.getter?.statements.indent() ?? <String>[]),
'}'
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ GlobalFunctionDeclaration parseGlobalFunctionDeclaration(
returnType: _parseFunctionReturnType(globalFunctionSymbolJson, symbolgraph),
params: info.params,
throws: info.throws,
async: info.async,
);
}

Expand All @@ -42,12 +43,14 @@ MethodDeclaration parseMethodDeclaration(
hasObjCAnnotation: parseSymbolHasObjcAnnotation(methodSymbolJson),
isStatic: isStatic,
throws: info.throws,
async: info.async,
);
}

typedef ParsedFunctionInfo = ({
List<Parameter> params,
bool throws,
bool async,
});

ParsedFunctionInfo parseFunctionInfo(
Expand Down Expand Up @@ -120,17 +123,22 @@ ParsedFunctionInfo parseFunctionInfo(
}
}

// Parse annotations until we run out.
// Parse annotations until we run out. The annotations are keywords separated
// by whitespace tokens.
final annotations = <String>{};
while (true) {
final keyword = maybeConsume('keyword');
if (keyword == null) break;
annotations.add(keyword);
if (keyword == null) {
if (maybeConsume('text') != '') break;
} else {
annotations.add(keyword);
}
}

return (
params: parameters,
throws: annotations.contains('throws'),
async: annotations.contains('async'),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,22 @@ InitializerDeclaration parseInitializerDeclaration(
}

final info = parseFunctionInfo(declarationFragments, symbolgraph);

if (info.async) {
// TODO(https://github.com/dart-lang/native/issues/1778): Support async
// initializerse.
throw Exception("Async initializers aren't supported yet, at "
'${initializerSymbolJson.path}');
}

return InitializerDeclaration(
id: id,
params: info.params,
hasObjCAnnotation: parseSymbolHasObjcAnnotation(initializerSymbolJson),
isOverriding: parseIsOverriding(initializerSymbolJson),
isFailable: parseIsFailableInit(id, declarationFragments),
throws: info.throws,
async: info.async,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ PropertyDeclaration parsePropertyDeclaration(
hasSetter: isConstant ? false : _parsePropertyHasSetter(propertySymbolJson),
isStatic: isStatic,
throws: _parseVariableThrows(propertySymbolJson),
async: _parseVariableAsync(propertySymbolJson),
);
}

Expand All @@ -41,6 +42,7 @@ GlobalVariableDeclaration parseGlobalVariableDeclaration(
type: _parseVariableType(variableSymbolJson, symbolgraph),
isConstant: isConstant || !hasSetter,
throws: _parseVariableThrows(variableSymbolJson),
async: _parseVariableAsync(variableSymbolJson),
);
}

Expand Down Expand Up @@ -78,6 +80,17 @@ bool _parseVariableThrows(Json json) {
return throws;
}

bool _parseVariableAsync(Json json) {
final async = json['declarationFragments']
.any((frag) => matchFragment(frag, 'keyword', 'async'));
if (async) {
// TODO(https://github.com/dart-lang/native/issues/1778): Support async
// getters.
throw Exception("Async getters aren't supported yet, at ${json.path}");
}
return async;
}

bool _parsePropertyHasSetter(Json propertySymbolJson) {
final fragmentsJson = propertySymbolJson['declarationFragments'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ InitializerDeclaration _buildWrapperInitializer(
isOverriding: false,
isFailable: false,
throws: false,
async: false,
statements: ['self.${wrappedClassInstance.name} = wrappedInstance'],
hasObjCAnnotation: wrappedClassInstance.hasObjCAnnotation,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ MethodDeclaration _transformFunction(
? originalFunction.isStatic
: true,
throws: originalFunction.throws,
async: originalFunction.async,
);

transformedMethod.statements = _generateStatements(
Expand Down Expand Up @@ -150,6 +151,9 @@ List<String> _generateStatements(
final arguments = generateInvocationParams(
localNamer, originalFunction.params, transformedMethod.params);
var originalMethodCall = originalCallGenerator(arguments);
if (transformedMethod.async) {
originalMethodCall = 'await $originalMethodCall';
}
if (transformedMethod.throws) {
originalMethodCall = 'try $originalMethodCall';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ InitializerDeclaration transformInitializer(
hasObjCAnnotation: true,
isFailable: originalInitializer.isFailable,
throws: originalInitializer.throws,
async: originalInitializer.async,
// Because the wrapper class extends NSObject that has an initializer with
// no parameters. If we make a similar parameterless initializer we need
// to add `override` keyword.
Expand All @@ -60,6 +61,9 @@ List<String> _generateInitializerStatements(
localNamer, originalInitializer.params, transformedInitializer.params);
var instanceConstruction =
'${wrappedClassInstance.type.swiftType}($arguments)';
if (transformedInitializer.async) {
instanceConstruction = 'await $instanceConstruction';
}
if (transformedInitializer.throws) {
instanceConstruction = 'try $instanceConstruction';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ PropertyDeclaration _transformVariable(
: true,
isConstant: originalVariable.isConstant,
throws: originalVariable.throws,
async: originalVariable.async,
);

final getterStatements = _generateGetterStatements(
Expand Down
12 changes: 12 additions & 0 deletions pkgs/swift2objc/test/integration/async_input.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

public class MyClass {
public func voidMethod() async {}
public func intMethod(y: Int) async -> MyClass { return MyClass() }
public func asyncThrowsMethod(y: Int) async throws -> MyClass {
return MyClass()
}
}

public func voidFunc(x: Int, y: Int) async {}
public func intFunc() async -> MyClass { return MyClass() }
Loading
Loading