Skip to content

Commit

Permalink
[swift2objc] Support the async annotation (#1779)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe authored Dec 3, 2024
1 parent 4b03d95 commit 3210a79
Show file tree
Hide file tree
Showing 19 changed files with 249 additions and 21 deletions.
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) {
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

0 comments on commit 3210a79

Please sign in to comment.