Skip to content

[swift2objc] Support optionals #1742

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 7 commits into from
Nov 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ abstract interface class Declaration {
abstract final String name;
}

extension AstDeclaredType<T extends Declaration> on T {
extension AsDeclaredType<T extends Declaration> on T {
DeclaredType<T> get asDeclaredType => DeclaredType(id: id, declaration: this);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import 'type_parameterizable.dart';
/// Describes a function-like entity.
abstract interface class FunctionDeclaration
implements Declaration, Parameterizable, Executable, TypeParameterizable {
abstract final ReferredType? returnType;
abstract final ReferredType returnType;
}
47 changes: 40 additions & 7 deletions pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import '../interfaces/objc_annotatable.dart';
/// entities (e.g a method return type).
/// See `DeclaredType` and `GenericType` for concrete implementation.
sealed class ReferredType {
final String id;
final String name;
abstract final bool isObjCRepresentable;

const ReferredType(this.name, {required this.id});
abstract final String swiftType;

bool sameAs(ReferredType other);

const ReferredType();
}

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

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

@override
String get swiftType => name;

@override
bool sameAs(ReferredType other) => other is DeclaredType && other.id == id;

const DeclaredType({
required this.id,
required this.declaration,
Expand All @@ -53,17 +59,44 @@ class DeclaredType<T extends Declaration> implements ReferredType {
/// Describes a reference of a generic type
/// (e.g a method return type `T` within a generic class).
class GenericType implements ReferredType {
@override
final String id;

@override
final String name;

@override
bool get isObjCRepresentable => false;

@override
String get swiftType => name;

@override
bool sameAs(ReferredType other) => other is GenericType && other.id == id;

const GenericType({
required this.id,
required this.name,
});

@override
String toString() => name;
}

/// An optional type, like Dart's nullable types. Eg `String?`.
class OptionalType implements ReferredType {
final ReferredType child;

@override
bool get isObjCRepresentable => child.isObjCRepresentable;

@override
String get swiftType => '$child?';

@override
bool sameAs(ReferredType other) =>
other is OptionalType && child.sameAs(other.child);

OptionalType(this.child);

@override
String toString() => swiftType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ enum BuiltInDeclaration implements Declaration, ObjCAnnotatable {
required this.name,
});
}

final objectType = BuiltInDeclaration.swiftNSObject.asDeclaredType;
final stringType = BuiltInDeclaration.swiftString.asDeclaredType;
final intType = BuiltInDeclaration.swiftInt.asDeclaredType;
final doubleType = BuiltInDeclaration.swiftDouble.asDeclaredType;
final boolType = BuiltInDeclaration.swiftBool.asDeclaredType;
final voidType = BuiltInDeclaration.swiftVoid.asDeclaredType;
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MethodDeclaration
List<String> statements;

@override
ReferredType? returnType;
ReferredType returnType;

bool isStatic;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class GlobalFunctionDeclaration implements FunctionDeclaration {
List<GenericType> typeParams;

@override
ReferredType? returnType;
ReferredType returnType;

@override
List<String> statements;
Expand Down
2 changes: 1 addition & 1 deletion pkgs/swift2objc/lib/src/generator/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ String generateParameters(List<Parameter> params) {
labels = param.name;
}

return '$labels: ${param.type.name}';
return '$labels: ${param.type.swiftType}';
}).join(', ');
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +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.

import '../../ast/_core/shared/referred_type.dart';
import '../../ast/declarations/built_in/built_in_declaration.dart';
import '../../ast/declarations/compounds/class_declaration.dart';
import '../_core/utils.dart';

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

return 'var ${property.name}: ${property.type.name}';
return 'var ${property.name}: ${property.type.swiftType}';
}

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

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

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

header.write('public var ${property.name}: ${property.type.name} {');
header.write('public var ${property.name}: ${property.type.swiftType} {');

final getterLines = [
'get {',
Expand Down
63 changes: 63 additions & 0 deletions pkgs/swift2objc/lib/src/parser/_core/token_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.

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

/// A slicable list of json tokens from the symbolgraph.
///
/// These token lists appear in the symbolgraph json under keys like
/// 'declarationFragments'. They represent things like argument lists, or
/// variable declarations.
///
/// We have to do a little bit of preprocessing on the raw json list before we
/// 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 {
final List<Json> _list;
final int _start;
final int _end;

TokenList._(this._list, this._start, this._end)
: 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}));
}
} else {
list.add(token);
}
}
return TokenList._(list, 0, list.length);
}

int get length => _end - _start;
bool get isEmpty => length == 0;
Json operator [](int index) => _list[index + _start];

int indexWhere(bool Function(Json element) test) {
for (var i = _start; i < _end; ++i) {
if (test(_list[i])) return i - _start;
}
return -1;
}

TokenList slice(int startIndex, [int? endIndex]) => TokenList._(
_list, startIndex + _start, endIndex != null ? endIndex + _start : _end);

@override
String toString() => _list.getRange(_start, _end).toString();
}
43 changes: 25 additions & 18 deletions pkgs/swift2objc/lib/src/parser/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import '../../ast/_core/interfaces/declaration.dart';
import '../../ast/_core/interfaces/enum_declaration.dart';
import '../../ast/_core/shared/referred_type.dart';
import '../../ast/declarations/globals/globals.dart';
import '../parsers/parse_declarations.dart';
import '../parsers/parse_type.dart';
import 'json.dart';
import 'parsed_symbolgraph.dart';
import 'token_list.dart';

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

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

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

/// Matches fragments, which look like {"kind": "foo", "spelling": "bar"}.
/// If `fragment['kind'] == kind`, returns `fragment['spelling']`. Otherwise
/// returns null.
String? getSpellingForKind(Json fragment, String kind) =>
fragment['kind'].get<String?>() == kind
? fragment['spelling'].get<String?>()
: null;

/// Matches fragments, which look like `{"kind": "foo", "spelling": "bar"}`.
bool matchFragment(Json fragment, String kind, String spelling) =>
fragment['kind'].get<String?>() == kind &&
fragment['spelling'].get<String?>() == spelling;
getSpellingForKind(fragment, kind) == spelling;

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

ReferredType parseTypeFromId(String typeId, ParsedSymbolgraph symbolgraph) {
final paramTypeSymbol = symbolgraph.symbols[typeId];

if (paramTypeSymbol == null) {
throw Exception(
'Type with id "$typeId" does not exist among parsed symbols.',
);
}

final paramTypeDeclaration = parseDeclaration(paramTypeSymbol, symbolgraph);

return paramTypeDeclaration.asDeclaredType;
}

final class ObsoleteException implements Exception {
final String symbol;
ObsoleteException(this.symbol);
Expand All @@ -107,3 +101,16 @@ extension Deduper<T> on Iterable<T> {
Iterable<T> dedupeBy<K>(K Function(T) id) =>
<K, T>{for (final t in this) id(t): t}.values;
}

ReferredType parseTypeAfterSeparator(
TokenList fragments,
ParsedSymbolgraph symbolgraph,
) {
// fragments = [..., ': ', type tokens...]
final separatorIndex =
fragments.indexWhere((token) => matchFragment(token, 'text', ': '));
final (type, suffix) =
parseType(symbolgraph, fragments.slice(separatorIndex + 1));
assert(suffix.isEmpty, '$suffix');
return type;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
// 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 '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/shared/parameter.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../../ast/declarations/compounds/members/method_declaration.dart';
import '../../../ast/declarations/globals/globals.dart';
import '../../_core/json.dart';
import '../../_core/parsed_symbolgraph.dart';
import '../../_core/token_list.dart';
import '../../_core/utils.dart';
import '../parse_declarations.dart';
import '../parse_type.dart';

GlobalFunctionDeclaration parseGlobalFunctionDeclaration(
Json globalFunctionSymbolJson,
Expand Down Expand Up @@ -39,34 +39,15 @@ MethodDeclaration parseMethodDeclaration(
);
}

ReferredType? _parseFunctionReturnType(
ReferredType _parseFunctionReturnType(
Json methodSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final returnJson = methodSymbolJson['functionSignature']['returns'][0];

// This means there's no return type
if (returnJson['spelling'].get<String>() == '()') {
return null;
}

final returnTypeId = returnJson['preciseIdentifier'].get<String>();

final returnTypeSymbol = symbolgraph.symbols[returnTypeId];

if (returnTypeSymbol == null) {
throw Exception(
'The method at path "${methodSymbolJson.path}" has a return type that '
'does not exist among parsed symbols.',
);
}

final returnTypeDeclaration = parseDeclaration(
returnTypeSymbol,
symbolgraph,
);

return returnTypeDeclaration.asDeclaredType;
final returnJson =
TokenList(methodSymbolJson['functionSignature']['returns']);
final (returnType, unparsed) = parseType(symbolgraph, returnJson);
assert(unparsed.isEmpty);
return returnType;
}

List<Parameter> _parseFunctionParams(
Expand All @@ -91,12 +72,6 @@ List<Parameter> _parseFunctionParams(
ReferredType _parseParamType(
Json paramSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final fragments = paramSymbolJson['declarationFragments'];

final paramTypeId = fragments
.firstJsonWhereKey('kind', 'typeIdentifier')['preciseIdentifier']
.get<String>();

return parseTypeFromId(paramTypeId, symbolgraph);
}
) =>
parseTypeAfterSeparator(
TokenList(paramSymbolJson['declarationFragments']), symbolgraph);
Loading