Skip to content

Commit d79f68c

Browse files
authored
[swift2objc] Support throws annotation (#1766)
1 parent f59efc7 commit d79f68c

26 files changed

+500
-105
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// An interface to describe a Swift entity's ability to be annotated
6+
/// with `throws`.
7+
abstract interface class CanThrow {
8+
abstract final bool throws;
9+
}

pkgs/swift2objc/lib/src/ast/_core/interfaces/function_declaration.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import '../shared/referred_type.dart';
6+
import 'can_throw.dart';
67
import 'declaration.dart';
78
import 'executable.dart';
89
import 'parameterizable.dart';
910
import 'type_parameterizable.dart';
1011

1112
/// Describes a function-like entity.
1213
abstract interface class FunctionDeclaration
13-
implements Declaration, Parameterizable, Executable, TypeParameterizable {
14+
implements
15+
Declaration,
16+
Parameterizable,
17+
Executable,
18+
TypeParameterizable,
19+
CanThrow {
1420
abstract final ReferredType returnType;
1521
}

pkgs/swift2objc/lib/src/ast/_core/interfaces/overridable.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
/// An interface to describe a Swift entity's ability to be annotated
6-
/// with `@objc`.
6+
/// with `override`.
77
abstract interface class Overridable {
88
abstract final bool isOverriding;
99
}

pkgs/swift2objc/lib/src/ast/_core/interfaces/variable_declaration.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import '../shared/referred_type.dart';
6+
import 'can_throw.dart';
67
import 'declaration.dart';
78

89
/// Describes a variable-like entity.
9-
abstract interface class VariableDeclaration implements Declaration {
10+
///
11+
/// This declaration [CanThrow] because Swift variables can have explicit
12+
/// getters, which can be marked with `throws`. Such variables may not have a
13+
/// setter.
14+
abstract interface class VariableDeclaration implements Declaration, CanThrow {
1015
abstract final bool isConstant;
1116
abstract final ReferredType type;
1217
}

pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import '../../../_core/interfaces/can_throw.dart';
56
import '../../../_core/interfaces/declaration.dart';
67
import '../../../_core/interfaces/executable.dart';
78
import '../../../_core/interfaces/objc_annotatable.dart';
@@ -16,7 +17,8 @@ class InitializerDeclaration
1617
Executable,
1718
Parameterizable,
1819
ObjCAnnotatable,
19-
Overridable {
20+
Overridable,
21+
CanThrow {
2022
@override
2123
String id;
2224

@@ -29,6 +31,9 @@ class InitializerDeclaration
2931
@override
3032
bool isOverriding;
3133

34+
@override
35+
bool throws;
36+
3237
bool isFailable;
3338

3439
@override
@@ -48,6 +53,7 @@ class InitializerDeclaration
4853
this.statements = const [],
4954
required this.hasObjCAnnotation,
5055
required this.isOverriding,
56+
required this.throws,
5157
required this.isFailable,
5258
});
5359
}

pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class MethodDeclaration
3030
@override
3131
bool isOverriding;
3232

33+
@override
34+
bool throws;
35+
3336
@override
3437
List<String> statements;
3538

@@ -53,5 +56,6 @@ class MethodDeclaration
5356
this.statements = const [],
5457
this.isStatic = false,
5558
this.isOverriding = false,
59+
this.throws = false,
5660
}) : assert(!isStatic || !isOverriding);
5761
}

pkgs/swift2objc/lib/src/ast/declarations/compounds/members/property_declaration.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
2525
@override
2626
bool isConstant;
2727

28+
@override
29+
bool throws;
30+
2831
bool hasSetter;
2932

3033
PropertyStatements? getter;
@@ -42,7 +45,9 @@ class PropertyDeclaration implements VariableDeclaration, ObjCAnnotatable {
4245
this.getter,
4346
this.setter,
4447
this.isStatic = false,
45-
}) : assert(!isConstant || !hasSetter);
48+
this.throws = false,
49+
}) : assert(!(isConstant && hasSetter)),
50+
assert(!(hasSetter && throws));
4651
}
4752

4853
class PropertyStatements implements Executable {

pkgs/swift2objc/lib/src/ast/declarations/globals/globals.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
3333
@override
3434
List<GenericType> typeParams;
3535

36+
@override
37+
bool throws;
38+
3639
@override
3740
ReferredType returnType;
3841

@@ -46,6 +49,7 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
4649
required this.returnType,
4750
this.typeParams = const [],
4851
this.statements = const [],
52+
this.throws = false,
4953
});
5054
}
5155

@@ -63,10 +67,14 @@ class GlobalVariableDeclaration implements VariableDeclaration {
6367
@override
6468
bool isConstant;
6569

70+
@override
71+
bool throws;
72+
6673
GlobalVariableDeclaration({
6774
required this.id,
6875
required this.name,
6976
required this.type,
7077
required this.isConstant,
71-
});
78+
required this.throws,
79+
}) : assert(!(throws && !isConstant));
7280
}

pkgs/swift2objc/lib/src/generator/generators/class_generator.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ List<String> _generateInitializer(InitializerDeclaration initializer) {
8787

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

90+
if (initializer.throws) {
91+
header.write(' throws');
92+
}
93+
9094
return [
9195
'$header {',
9296
...initializer.statements.indent(),
@@ -116,6 +120,10 @@ List<String> _generateClassMethod(MethodDeclaration method) {
116120
'public func ${method.name}(${generateParameters(method.params)})',
117121
);
118122

123+
if (method.throws) {
124+
header.write(' throws');
125+
}
126+
119127
if (!method.returnType.sameAs(voidType)) {
120128
header.write(' -> ${method.returnType.swiftType}');
121129
}

pkgs/swift2objc/lib/src/parser/_core/json.dart

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:collection';
65
import 'dart:convert';
76

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

25-
String get path => _pathSegments.join('/');
24+
String get path => pathSegments.join('/');
2625

27-
Json(this._json, [this._pathSegments = const []]);
26+
Json(this._json, [this.pathSegments = const []]);
2827

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

4342
if (index is int) {
@@ -57,7 +56,7 @@ class Json extends IterableBase<Json> {
5756
'Invalid negative index at "$path" (supplied index: $index)',
5857
);
5958
}
60-
return Json(_json[index], [..._pathSegments, '$index']);
59+
return Json(_json[index], [...pathSegments, '$index']);
6160
}
6261

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

117116
@override
118-
Json get current => Json(_list[_index], [..._json._pathSegments, '$_index']);
117+
Json get current => Json(_list[_index], [..._json.pathSegments, '$_index']);
119118

120119
@override
121120
bool moveNext() {

pkgs/swift2objc/lib/src/parser/_core/token_list.dart

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:meta/meta.dart';
6+
57
import 'json.dart';
68
import 'utils.dart';
79

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

2628
factory TokenList(Json fragments) {
27-
const splits = {
28-
'?(': ['?', '('],
29-
'?)': ['?', ')'],
30-
'?, ': ['?', ', '],
31-
') -> ': [')', '->'],
32-
'?) -> ': ['?', ')', '->'],
33-
};
34-
35-
final list = <Json>[];
36-
for (final token in fragments) {
37-
final split = splits[getSpellingForKind(token, 'text')];
38-
if (split != null) {
39-
for (final sub in split) {
40-
list.add(Json({'kind': 'text', 'spelling': sub}));
29+
final list = [for (final token in fragments) ...splitToken(token)];
30+
return TokenList._(list, 0, list.length);
31+
}
32+
33+
@visibleForTesting
34+
static Iterable<Json> splitToken(Json token) sync* {
35+
const splittables = ['(', ')', '?', ',', '->'];
36+
Json textToken(String text) =>
37+
Json({'kind': 'text', 'spelling': text}, token.pathSegments);
38+
39+
final text = getSpellingForKind(token, 'text')?.trim();
40+
if (text == null) {
41+
// Not a text token. Pass it though unchanged.
42+
yield token;
43+
return;
44+
}
45+
46+
if (text.isEmpty) {
47+
// Input text token was nothing but whitespace. The loop below would yield
48+
// nothing, but we still need it as a separator.
49+
yield textToken(text);
50+
return;
51+
}
52+
53+
var suffix = text;
54+
while (true) {
55+
var any = false;
56+
for (final prefix in splittables) {
57+
if (suffix.startsWith(prefix)) {
58+
yield textToken(prefix);
59+
suffix = suffix.substring(prefix.length).trim();
60+
any = true;
61+
break;
4162
}
42-
} else {
43-
list.add(token);
63+
}
64+
if (!any) {
65+
// Remaining text isn't splittable.
66+
if (suffix.isNotEmpty) yield textToken(suffix);
67+
break;
4468
}
4569
}
46-
return TokenList._(list, 0, list.length);
4770
}
4871

72+
@override
4973
int get length => _end - _start;
50-
bool get isEmpty => length == 0;
74+
75+
@override
76+
Iterator<Json> get iterator => _TokenListIterator(this);
77+
5178
Json operator [](int index) => _list[index + _start];
5279

5380
int indexWhere(bool Function(Json element) test) {
@@ -63,3 +90,19 @@ class TokenList {
6390
@override
6491
String toString() => _list.getRange(_start, _end).toString();
6592
}
93+
94+
class _TokenListIterator implements Iterator<Json> {
95+
final TokenList _list;
96+
var _index = -1;
97+
98+
_TokenListIterator(this._list);
99+
100+
@override
101+
Json get current => _list[_index];
102+
103+
@override
104+
bool moveNext() {
105+
_index++;
106+
return _index < _list.length;
107+
}
108+
}

pkgs/swift2objc/lib/src/parser/_core/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ ReferredType parseTypeAfterSeparator(
107107
) {
108108
// fragments = [..., ': ', type tokens...]
109109
final separatorIndex =
110-
fragments.indexWhere((token) => matchFragment(token, 'text', ': '));
110+
fragments.indexWhere((token) => matchFragment(token, 'text', ':'));
111111
final (type, suffix) =
112112
parseType(symbolgraph, fragments.slice(separatorIndex + 1));
113113
assert(suffix.isEmpty, '$suffix');

0 commit comments

Comments
 (0)