Skip to content

Commit 4ce4816

Browse files
authored
[swift2objc] Support optionals (#1742)
1 parent 028f89d commit 4ce4816

31 files changed

+833
-300
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ abstract interface class Declaration {
1010
abstract final String name;
1111
}
1212

13-
extension AstDeclaredType<T extends Declaration> on T {
13+
extension AsDeclaredType<T extends Declaration> on T {
1414
DeclaredType<T> get asDeclaredType => DeclaredType(id: id, declaration: this);
1515
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ import 'type_parameterizable.dart';
1111
/// Describes a function-like entity.
1212
abstract interface class FunctionDeclaration
1313
implements Declaration, Parameterizable, Executable, TypeParameterizable {
14-
abstract final ReferredType? returnType;
14+
abstract final ReferredType returnType;
1515
}

pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ import '../interfaces/objc_annotatable.dart';
1010
/// entities (e.g a method return type).
1111
/// See `DeclaredType` and `GenericType` for concrete implementation.
1212
sealed class ReferredType {
13-
final String id;
14-
final String name;
1513
abstract final bool isObjCRepresentable;
1614

17-
const ReferredType(this.name, {required this.id});
15+
abstract final String swiftType;
16+
17+
bool sameAs(ReferredType other);
18+
19+
const ReferredType();
1820
}
1921

2022
/// Describes a reference of a declared type (user-defined or built-in).
2123
class DeclaredType<T extends Declaration> implements ReferredType {
22-
@override
2324
final String id;
2425

25-
@override
2626
String get name {
2727
final decl = declaration;
2828
if (decl is CompoundDeclaration && decl.pathComponents.isNotEmpty) {
@@ -40,6 +40,12 @@ class DeclaredType<T extends Declaration> implements ReferredType {
4040
declaration is ObjCAnnotatable &&
4141
(declaration as ObjCAnnotatable).hasObjCAnnotation;
4242

43+
@override
44+
String get swiftType => name;
45+
46+
@override
47+
bool sameAs(ReferredType other) => other is DeclaredType && other.id == id;
48+
4349
const DeclaredType({
4450
required this.id,
4551
required this.declaration,
@@ -53,17 +59,44 @@ class DeclaredType<T extends Declaration> implements ReferredType {
5359
/// Describes a reference of a generic type
5460
/// (e.g a method return type `T` within a generic class).
5561
class GenericType implements ReferredType {
56-
@override
5762
final String id;
5863

59-
@override
6064
final String name;
6165

6266
@override
6367
bool get isObjCRepresentable => false;
6468

69+
@override
70+
String get swiftType => name;
71+
72+
@override
73+
bool sameAs(ReferredType other) => other is GenericType && other.id == id;
74+
6575
const GenericType({
6676
required this.id,
6777
required this.name,
6878
});
79+
80+
@override
81+
String toString() => name;
82+
}
83+
84+
/// An optional type, like Dart's nullable types. Eg `String?`.
85+
class OptionalType implements ReferredType {
86+
final ReferredType child;
87+
88+
@override
89+
bool get isObjCRepresentable => child.isObjCRepresentable;
90+
91+
@override
92+
String get swiftType => '$child?';
93+
94+
@override
95+
bool sameAs(ReferredType other) =>
96+
other is OptionalType && child.sameAs(other.child);
97+
98+
OptionalType(this.child);
99+
100+
@override
101+
String toString() => swiftType;
69102
}

pkgs/swift2objc/lib/src/ast/declarations/built_in/built_in_declaration.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ enum BuiltInDeclaration implements Declaration, ObjCAnnotatable {
2828
required this.name,
2929
});
3030
}
31+
32+
final objectType = BuiltInDeclaration.swiftNSObject.asDeclaredType;
33+
final stringType = BuiltInDeclaration.swiftString.asDeclaredType;
34+
final intType = BuiltInDeclaration.swiftInt.asDeclaredType;
35+
final doubleType = BuiltInDeclaration.swiftDouble.asDeclaredType;
36+
final boolType = BuiltInDeclaration.swiftBool.asDeclaredType;
37+
final voidType = BuiltInDeclaration.swiftVoid.asDeclaredType;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class MethodDeclaration
3434
List<String> statements;
3535

3636
@override
37-
ReferredType? returnType;
37+
ReferredType returnType;
3838

3939
bool isStatic;
4040

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
3434
List<GenericType> typeParams;
3535

3636
@override
37-
ReferredType? returnType;
37+
ReferredType returnType;
3838

3939
@override
4040
List<String> statements;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ String generateParameters(List<Parameter> params) {
1111
labels = param.name;
1212
}
1313

14-
return '$labels: ${param.type.name}';
14+
return '$labels: ${param.type.swiftType}';
1515
}).join(', ');
1616
}
1717

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +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+
15
import '../../ast/_core/shared/referred_type.dart';
6+
import '../../ast/declarations/built_in/built_in_declaration.dart';
27
import '../../ast/declarations/compounds/class_declaration.dart';
38
import '../_core/utils.dart';
49

@@ -47,7 +52,7 @@ String? _generateClassWrappedInstance(ClassDeclaration declaration) {
4752
"Wrapped instance can't have a generic type",
4853
);
4954

50-
return 'var ${property.name}: ${property.type.name}';
55+
return 'var ${property.name}: ${property.type.swiftType}';
5156
}
5257

5358
List<String> _generateInitializers(ClassDeclaration declaration) {
@@ -102,8 +107,8 @@ List<String> _generateClassMethods(ClassDeclaration declaration) {
102107
'public func ${method.name}(${generateParameters(method.params)})',
103108
);
104109

105-
if (method.returnType != null) {
106-
header.write(' -> ${method.returnType!.name}');
110+
if (!method.returnType.sameAs(voidType)) {
111+
header.write(' -> ${method.returnType.swiftType}');
107112
}
108113

109114
return [
@@ -127,7 +132,7 @@ List<String> _generateClassProperties(ClassDeclaration declaration) {
127132
header.write('static ');
128133
}
129134

130-
header.write('public var ${property.name}: ${property.type.name} {');
135+
header.write('public var ${property.name}: ${property.type.swiftType} {');
131136

132137
final getterLines = [
133138
'get {',
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
import 'json.dart';
6+
import 'utils.dart';
7+
8+
/// A slicable list of json tokens from the symbolgraph.
9+
///
10+
/// These token lists appear in the symbolgraph json under keys like
11+
/// 'declarationFragments'. They represent things like argument lists, or
12+
/// variable declarations.
13+
///
14+
/// We have to do a little bit of preprocessing on the raw json list before we
15+
/// can pass it to parseType, because certain tokens get concatenated by the
16+
/// swift compiler. This class performs that preprocessing, as well as providing
17+
/// convenience methods for parsing, like slicing.
18+
class TokenList {
19+
final List<Json> _list;
20+
final int _start;
21+
final int _end;
22+
23+
TokenList._(this._list, this._start, this._end)
24+
: assert(0 <= _start && _start <= _end && _end <= _list.length);
25+
26+
factory TokenList(Json fragments) {
27+
const splits = {
28+
'?(': ['?', '('],
29+
'?)': ['?', ')'],
30+
'?, ': ['?', ', '],
31+
};
32+
33+
final list = <Json>[];
34+
for (final token in fragments) {
35+
final split = splits[getSpellingForKind(token, 'text')];
36+
if (split != null) {
37+
for (final sub in split) {
38+
list.add(Json({'kind': 'text', 'spelling': sub}));
39+
}
40+
} else {
41+
list.add(token);
42+
}
43+
}
44+
return TokenList._(list, 0, list.length);
45+
}
46+
47+
int get length => _end - _start;
48+
bool get isEmpty => length == 0;
49+
Json operator [](int index) => _list[index + _start];
50+
51+
int indexWhere(bool Function(Json element) test) {
52+
for (var i = _start; i < _end; ++i) {
53+
if (test(_list[i])) return i - _start;
54+
}
55+
return -1;
56+
}
57+
58+
TokenList slice(int startIndex, [int? endIndex]) => TokenList._(
59+
_list, startIndex + _start, endIndex != null ? endIndex + _start : _end);
60+
61+
@override
62+
String toString() => _list.getRange(_start, _end).toString();
63+
}

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

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import '../../ast/_core/interfaces/declaration.dart';
1010
import '../../ast/_core/interfaces/enum_declaration.dart';
1111
import '../../ast/_core/shared/referred_type.dart';
1212
import '../../ast/declarations/globals/globals.dart';
13-
import '../parsers/parse_declarations.dart';
13+
import '../parsers/parse_type.dart';
1414
import 'json.dart';
1515
import 'parsed_symbolgraph.dart';
16+
import 'token_list.dart';
1617

1718
typedef ParsedSymbolsMap = Map<String, ParsedSymbol>;
1819
typedef ParsedRelationsMap = Map<String, List<ParsedRelation>>;
@@ -22,6 +23,7 @@ Json readJsonFile(String jsonFilePath) {
2223
return Json(jsonDecode(jsonStr));
2324
}
2425

26+
// Valid ID characters seen in symbolgraphs: 0-9A-Za-z_():@
2527
const idDelim = '-';
2628

2729
extension AddIdSuffix on String {
@@ -38,10 +40,16 @@ extension TopLevelOnly<T extends Declaration> on List<T> {
3840
).toList();
3941
}
4042

41-
/// Matches fragments, which look like {"kind": "foo", "spelling": "bar"}.
43+
/// If `fragment['kind'] == kind`, returns `fragment['spelling']`. Otherwise
44+
/// returns null.
45+
String? getSpellingForKind(Json fragment, String kind) =>
46+
fragment['kind'].get<String?>() == kind
47+
? fragment['spelling'].get<String?>()
48+
: null;
49+
50+
/// Matches fragments, which look like `{"kind": "foo", "spelling": "bar"}`.
4251
bool matchFragment(Json fragment, String kind, String spelling) =>
43-
fragment['kind'].get<String?>() == kind &&
44-
fragment['spelling'].get<String?>() == spelling;
52+
getSpellingForKind(fragment, kind) == spelling;
4553

4654
String parseSymbolId(Json symbolJson) {
4755
final idJson = symbolJson['identifier']['precise'];
@@ -70,20 +78,6 @@ bool parseIsOverriding(Json symbolJson) {
7078
.any((json) => matchFragment(json, 'keyword', 'override'));
7179
}
7280

73-
ReferredType parseTypeFromId(String typeId, ParsedSymbolgraph symbolgraph) {
74-
final paramTypeSymbol = symbolgraph.symbols[typeId];
75-
76-
if (paramTypeSymbol == null) {
77-
throw Exception(
78-
'Type with id "$typeId" does not exist among parsed symbols.',
79-
);
80-
}
81-
82-
final paramTypeDeclaration = parseDeclaration(paramTypeSymbol, symbolgraph);
83-
84-
return paramTypeDeclaration.asDeclaredType;
85-
}
86-
8781
final class ObsoleteException implements Exception {
8882
final String symbol;
8983
ObsoleteException(this.symbol);
@@ -107,3 +101,16 @@ extension Deduper<T> on Iterable<T> {
107101
Iterable<T> dedupeBy<K>(K Function(T) id) =>
108102
<K, T>{for (final t in this) id(t): t}.values;
109103
}
104+
105+
ReferredType parseTypeAfterSeparator(
106+
TokenList fragments,
107+
ParsedSymbolgraph symbolgraph,
108+
) {
109+
// fragments = [..., ': ', type tokens...]
110+
final separatorIndex =
111+
fragments.indexWhere((token) => matchFragment(token, 'text', ': '));
112+
final (type, suffix) =
113+
parseType(symbolgraph, fragments.slice(separatorIndex + 1));
114+
assert(suffix.isEmpty, '$suffix');
115+
return type;
116+
}

0 commit comments

Comments
 (0)