Skip to content

[swift2objc] Support throws annotation #1766

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

Merged
merged 2 commits into from
Dec 1, 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_throw.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 `throws`.
abstract interface class CanThrow {
abstract final bool throws;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
// BSD-style license that can be found in the LICENSE file.

import '../shared/referred_type.dart';
import 'can_throw.dart';
import 'declaration.dart';
import 'executable.dart';
import 'parameterizable.dart';
import 'type_parameterizable.dart';

/// Describes a function-like entity.
abstract interface class FunctionDeclaration
implements Declaration, Parameterizable, Executable, TypeParameterizable {
implements
Declaration,
Parameterizable,
Executable,
TypeParameterizable,
CanThrow {
abstract final ReferredType returnType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

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

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

/// Describes a variable-like entity.
abstract interface class VariableDeclaration implements Declaration {
///
/// 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 {
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_throw.dart';
import '../../../_core/interfaces/declaration.dart';
import '../../../_core/interfaces/executable.dart';
import '../../../_core/interfaces/objc_annotatable.dart';
Expand All @@ -16,7 +17,8 @@ class InitializerDeclaration
Executable,
Parameterizable,
ObjCAnnotatable,
Overridable {
Overridable,
CanThrow {
@override
String id;

Expand All @@ -29,6 +31,9 @@ class InitializerDeclaration
@override
bool isOverriding;

@override
bool throws;

bool isFailable;

@override
Expand All @@ -48,6 +53,7 @@ class InitializerDeclaration
this.statements = const [],
required this.hasObjCAnnotation,
required this.isOverriding,
required this.throws,
required this.isFailable,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class MethodDeclaration
@override
bool isOverriding;

@override
bool throws;

@override
List<String> statements;

Expand All @@ -53,5 +56,6 @@ class MethodDeclaration
this.statements = const [],
this.isStatic = false,
this.isOverriding = false,
this.throws = false,
}) : assert(!isStatic || !isOverriding);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
@override
bool isConstant;

@override
bool throws;

bool hasSetter;

PropertyStatements? getter;
Expand All @@ -42,7 +45,9 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
this.getter,
this.setter,
this.isStatic = false,
}) : assert(!isConstant || !hasSetter);
this.throws = false,
}) : assert(!(isConstant && hasSetter)),
assert(!(hasSetter && throws));
}

class PropertyStatements implements Executable {
Expand Down
10 changes: 9 additions & 1 deletion pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
@override
List<GenericType> typeParams;

@override
bool throws;

@override
ReferredType returnType;

Expand All @@ -46,6 +49,7 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
required this.returnType,
this.typeParams = const [],
this.statements = const [],
this.throws = false,
});
}

Expand All @@ -63,10 +67,14 @@ class GlobalVariableDeclaration implements VariableDeclaration {
@override
bool isConstant;

@override
bool throws;

GlobalVariableDeclaration({
required this.id,
required this.name,
required this.type,
required this.isConstant,
});
required this.throws,
}) : assert(!(throws && !isConstant));
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ List<String> _generateInitializer(InitializerDeclaration initializer) {

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

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

return [
'$header {',
...initializer.statements.indent(),
Expand Down Expand Up @@ -116,6 +120,10 @@ List<String> _generateClassMethod(MethodDeclaration method) {
'public func ${method.name}(${generateParameters(method.params)})',
);

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

if (!method.returnType.sameAs(voidType)) {
header.write(' -> ${method.returnType.swiftType}');
}
Expand Down
15 changes: 7 additions & 8 deletions pkgs/swift2objc/lib/src/parser/_core/json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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 'dart:collection';
import 'dart:convert';

/// This is a helper class that helps with parsing Json values. It supports
Expand All @@ -18,13 +17,13 @@ import 'dart:convert';
/// The class is also an `Iterable` so if the json is an array, you can directly
/// iterate over it with a `for` loop. If the json isn't an array, attempting to
/// iterate over it will throw an error.
class Json extends IterableBase<Json> {
final List<String> _pathSegments;
class Json extends Iterable<Json> {
final List<String> pathSegments;
final dynamic _json;

String get path => _pathSegments.join('/');
String get path => pathSegments.join('/');

Json(this._json, [this._pathSegments = const []]);
Json(this._json, [this.pathSegments = const []]);

/// The subscript syntax is intended to access a value at a field of a map or
/// at an index if an array, and thus, the `index` parameter here can either
Expand All @@ -37,7 +36,7 @@ class Json extends IterableBase<Json> {
'Expected a map at "$path", found a ${_json.runtimeType}',
);
}
return Json(_json[index], [..._pathSegments, index]);
return Json(_json[index], [...pathSegments, index]);
}

if (index is int) {
Expand All @@ -57,7 +56,7 @@ class Json extends IterableBase<Json> {
'Invalid negative index at "$path" (supplied index: $index)',
);
}
return Json(_json[index], [..._pathSegments, '$index']);
return Json(_json[index], [...pathSegments, '$index']);
}

throw Exception(
Expand Down Expand Up @@ -115,7 +114,7 @@ class _JsonIterator implements Iterator<Json> {
_JsonIterator(this._json) : _list = _json.get();

@override
Json get current => Json(_list[_index], [..._json._pathSegments, '$_index']);
Json get current => Json(_list[_index], [..._json.pathSegments, '$_index']);

@override
bool moveNext() {
Expand Down
81 changes: 62 additions & 19 deletions pkgs/swift2objc/lib/src/parser/_core/token_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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 'package:meta/meta.dart';

import 'json.dart';
import 'utils.dart';

Expand All @@ -15,7 +17,7 @@ import 'utils.dart';
/// can pass it to parseType, because certain tokens get concatenated by the
/// swift compiler. This class performs that preprocessing, as well as providing
/// convenience methods for parsing, like slicing.
class TokenList {
class TokenList extends Iterable<Json> {
final List<Json> _list;
final int _start;
final int _end;
Expand All @@ -24,30 +26,55 @@ class TokenList {
: assert(0 <= _start && _start <= _end && _end <= _list.length);

factory TokenList(Json fragments) {
const splits = {
'?(': ['?', '('],
'?)': ['?', ')'],
'?, ': ['?', ', '],
') -> ': [')', '->'],
'?) -> ': ['?', ')', '->'],
};

final list = <Json>[];
for (final token in fragments) {
final split = splits[getSpellingForKind(token, 'text')];
if (split != null) {
for (final sub in split) {
list.add(Json({'kind': 'text', 'spelling': sub}));
final list = [for (final token in fragments) ...splitToken(token)];
return TokenList._(list, 0, list.length);
}

@visibleForTesting
static Iterable<Json> splitToken(Json token) sync* {
const splittables = ['(', ')', '?', ',', '->'];
Json textToken(String text) =>
Json({'kind': 'text', 'spelling': text}, token.pathSegments);

final text = getSpellingForKind(token, 'text')?.trim();
if (text == null) {
// Not a text token. Pass it though unchanged.
yield token;
return;
}

if (text.isEmpty) {
// Input text token was nothing but whitespace. The loop below would yield
// nothing, but we still need it as a separator.
yield textToken(text);
return;
}

var suffix = text;
while (true) {
var any = false;
for (final prefix in splittables) {
if (suffix.startsWith(prefix)) {
yield textToken(prefix);
suffix = suffix.substring(prefix.length).trim();
any = true;
break;
}
} else {
list.add(token);
}
if (!any) {
// Remaining text isn't splittable.
if (suffix.isNotEmpty) yield textToken(suffix);
break;
}
}
return TokenList._(list, 0, list.length);
}

@override
int get length => _end - _start;
bool get isEmpty => length == 0;

@override
Iterator<Json> get iterator => _TokenListIterator(this);

Json operator [](int index) => _list[index + _start];

int indexWhere(bool Function(Json element) test) {
Expand All @@ -63,3 +90,19 @@ class TokenList {
@override
String toString() => _list.getRange(_start, _end).toString();
}

class _TokenListIterator implements Iterator<Json> {
final TokenList _list;
var _index = -1;

_TokenListIterator(this._list);

@override
Json get current => _list[_index];

@override
bool moveNext() {
_index++;
return _index < _list.length;
}
}
2 changes: 1 addition & 1 deletion pkgs/swift2objc/lib/src/parser/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ ReferredType parseTypeAfterSeparator(
) {
// fragments = [..., ': ', type tokens...]
final separatorIndex =
fragments.indexWhere((token) => matchFragment(token, 'text', ': '));
fragments.indexWhere((token) => matchFragment(token, 'text', ':'));
final (type, suffix) =
parseType(symbolgraph, fragments.slice(separatorIndex + 1));
assert(suffix.isEmpty, '$suffix');
Expand Down
Loading