Skip to content
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

Swift2objc fixes #1668

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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 @@ -17,4 +17,7 @@ abstract interface class CompoundDeclaration
abstract List<PropertyDeclaration> properties;
abstract List<MethodDeclaration> methods;
abstract List<InitializerDeclaration> initializers;

/// For handling nested classes
abstract List<String> pathComponents;
}
9 changes: 9 additions & 0 deletions pkgs/swift2objc/lib/src/ast/_core/interfaces/overridable.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 `@objc`.
abstract interface class Overridable {
abstract final bool isOverriding;
}
10 changes: 9 additions & 1 deletion pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart
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 '../interfaces/compound_declaration.dart';
import '../interfaces/declaration.dart';
import '../interfaces/objc_annotatable.dart';

Expand All @@ -22,7 +23,14 @@ class DeclaredType<T extends Declaration> implements ReferredType {
final String id;

@override
String get name => declaration.name;
String get name {
final decl = declaration;
if (decl is CompoundDeclaration && decl.pathComponents.isNotEmpty) {
return decl.pathComponents.join('.');
}

return declaration.name;
}

final T declaration;
final List<ReferredType> typeParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum BuiltInDeclaration implements Declaration, ObjCAnnotatable {
swiftString(id: 's:SS', name: 'String'),
swiftInt(id: 's:Si', name: 'Int'),
swiftDouble(id: 's:Sd', name: 'Double'),
swiftBool(id: 's:Sb', name: 'Bool'),
swiftVoid(id: 's:s4Voida', name: 'Void');

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ class ClassDeclaration implements CompoundDeclaration, ObjCAnnotatable {
@override
List<InitializerDeclaration> initializers;

@override
List<String> pathComponents;

ClassDeclaration({
required this.id,
required this.name,
this.properties = const [],
this.methods = const [],
this.pathComponents = const [],
this.conformedProtocols = const [],
this.typeParams = const [],
this.hasObjCAnnotation = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,30 @@
import '../../../_core/interfaces/declaration.dart';
import '../../../_core/interfaces/executable.dart';
import '../../../_core/interfaces/objc_annotatable.dart';
import '../../../_core/interfaces/overridable.dart';
import '../../../_core/interfaces/parameterizable.dart';
import '../../../_core/shared/parameter.dart';

/// Describes an initializer for a Swift compound entity (e.g, class, structs)
class InitializerDeclaration
implements Declaration, Executable, Parameterizable, ObjCAnnotatable {
implements
Declaration,
Executable,
Parameterizable,
ObjCAnnotatable,
Overridable {
@override
String id;

@override
String get name => 'init';

@override
bool hasObjCAnnotation;

@override
bool isOverriding;

@override
List<Parameter> params;

Expand All @@ -22,14 +40,6 @@ class InitializerDeclaration
required this.params,
this.statements = const [],
required this.hasObjCAnnotation,
required this.isOverriding,
});

@override
String id;

@override
String get name => 'init';

@override
bool hasObjCAnnotation;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

import '../../../_core/interfaces/function_declaration.dart';
import '../../../_core/interfaces/objc_annotatable.dart';
import '../../../_core/interfaces/overridable.dart';
import '../../../_core/shared/parameter.dart';
import '../../../_core/shared/referred_type.dart';

/// Describes a method declaration for a Swift compound entity
/// (e.g, class, structs)
class MethodDeclaration implements FunctionDeclaration, ObjCAnnotatable {
class MethodDeclaration
implements FunctionDeclaration, ObjCAnnotatable, Overridable {
@override
String id;

Expand All @@ -25,6 +27,9 @@ class MethodDeclaration implements FunctionDeclaration, ObjCAnnotatable {
@override
bool hasObjCAnnotation;

@override
bool isOverriding;

@override
List<String> statements;

Expand All @@ -42,5 +47,6 @@ class MethodDeclaration implements FunctionDeclaration, ObjCAnnotatable {
this.hasObjCAnnotation = false,
this.statements = const [],
this.isStatic = false,
});
this.isOverriding = false,
}) : assert(!isStatic || !isOverriding);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class ProtocolDeclaration implements CompoundDeclaration {
@override
List<InitializerDeclaration> initializers;

@override
List<String> pathComponents;

ProtocolDeclaration({
required this.id,
required this.name,
Expand All @@ -39,5 +42,6 @@ class ProtocolDeclaration implements CompoundDeclaration {
required this.initializers,
required this.conformedProtocols,
required this.typeParams,
required this.pathComponents,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class StructDeclaration implements CompoundDeclaration {
@override
List<InitializerDeclaration> initializers;

@override
List<String> pathComponents;

StructDeclaration({
required this.id,
required this.name,
Expand All @@ -40,5 +43,6 @@ class StructDeclaration implements CompoundDeclaration {
this.initializers = const [],
this.conformedProtocols = const [],
this.typeParams = const [],
this.pathComponents = const [],
});
}
17 changes: 14 additions & 3 deletions pkgs/swift2objc/lib/src/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Config {
/// Used to specify the inputs in the `config` object.
/// See `FilesInputConfig` and `ModuleInputConfig` for concrete implementation;
sealed class InputConfig {
Command get symbolgraphCommand;
Command? get symbolgraphCommand;
}

/// Used to generate a objc wrapper for one or more swift files
Expand All @@ -60,7 +60,7 @@ class FilesInputConfig implements InputConfig {
});

@override
Command get symbolgraphCommand => Command(
Command? get symbolgraphCommand => Command(
executable: 'swiftc',
args: [
...files.map((uri) => path.absolute(uri.path)),
Expand Down Expand Up @@ -95,7 +95,7 @@ class ModuleInputConfig implements InputConfig {
});

@override
Command get symbolgraphCommand => Command(
Command? get symbolgraphCommand => Command(
executable: 'swiftc',
args: [
'symbolgraph-extract',
Expand All @@ -110,3 +110,14 @@ class ModuleInputConfig implements InputConfig {
],
);
}

/// Used to generate wrappers directly from a JSON symbolgraph, for debugging.
class JsonFileInputConfig implements InputConfig {
/// The JSON symbolgraph file.
final Uri jsonFile;

JsonFileInputConfig({required this.jsonFile});

@override
Command? get symbolgraphCommand => null;
}
23 changes: 17 additions & 6 deletions pkgs/swift2objc/lib/src/generate_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:path/path.dart' as path;

import 'config.dart';
import 'generator/generator.dart';
import 'parser/_core/utils.dart';
import 'parser/parser.dart';
import 'transformer/transform.dart';

Expand All @@ -22,20 +23,30 @@ Future<void> generateWrapper(Config config) async {

final input = config.input;

await _generateSymbolgraphJson(
input.symbolgraphCommand,
tempDir,
);
final symbolgraphCommand = input.symbolgraphCommand;
if (symbolgraphCommand != null) {
await _generateSymbolgraphJson(
symbolgraphCommand,
tempDir,
);
}

final symbolgraphFileName = switch (input) {
FilesInputConfig() => '${input.generatedModuleName}$symbolgraphFileSuffix',
ModuleInputConfig() => '${input.module}$symbolgraphFileSuffix',
JsonFileInputConfig() => path.absolute(input.jsonFile.path),
};
final symbolgraphJsonPath = path.join(tempDir.path, symbolgraphFileName);
final symbolgraphJson = readJsonFile(symbolgraphJsonPath);

final declarations = parseAst(symbolgraphJsonPath);
final declarations = parseAst(symbolgraphJson);
final transformedDeclarations = transform(declarations);
final wrapperCode = generate(transformedDeclarations, config.preamble);

final wrapperCode = generate(
transformedDeclarations,
moduleName: parseModuleName(symbolgraphJson),
preamble: config.preamble,
);

File.fromUri(config.outputFile).writeAsStringSync(wrapperCode);

Expand Down
7 changes: 6 additions & 1 deletion pkgs/swift2objc/lib/src/generator/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import '../ast/_core/interfaces/declaration.dart';
import '../ast/declarations/compounds/class_declaration.dart';
import 'generators/class_generator.dart';

String generate(List<Declaration> declarations, [String? preamble]) {
String generate(
List<Declaration> declarations, {
String? moduleName,
String? preamble,
}) {
return '${[
preamble,
if (moduleName != null) 'import $moduleName',
'import Foundation',
...declarations.map(generateDeclaration),
].nonNulls.join('\n\n')}\n';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ List<String> _generateInitializers(ClassDeclaration declaration) {
header.write('@objc ');
}

if (initializer.isOverriding) {
header.write('override ');
}

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

return ['$header {', initializer.statements.join('\n').indent(), '}']
Expand All @@ -84,6 +88,10 @@ List<String> _generateClassMethods(ClassDeclaration declaration) {
header.write('static ');
}

if (method.isOverriding) {
header.write('override ');
}

header.write(
'public func ${method.name}(${generateParameters(method.params)})',
);
Expand Down
10 changes: 10 additions & 0 deletions pkgs/swift2objc/lib/src/parser/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ bool parseSymbolHasObjcAnnotation(Json symbolJson) {
);
}

bool parseIsOverriding(Json symbolJson) {
return symbolJson['declarationFragments'].any(
(json) =>
json['kind'].exists &&
json['kind'].get<String>() == 'keyword' &&
json['spelling'].exists &&
json['spelling'].get<String>() == 'override',
);
}

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

Expand Down
9 changes: 5 additions & 4 deletions pkgs/swift2objc/lib/src/parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
// BSD-style license that can be found in the LICENSE file.

import '../ast/_core/interfaces/declaration.dart';
import '_core/json.dart';
import '_core/parsed_symbolgraph.dart';

import '_core/utils.dart';
import 'parsers/parse_declarations.dart';
import 'parsers/parse_relations_map.dart';
import 'parsers/parse_symbols_map.dart';

List<Declaration> parseAst(String symbolgraphJsonPath) {
final symbolgraphJson = readJsonFile(symbolgraphJsonPath);

List<Declaration> parseAst(Json symbolgraphJson) {
final symbolgraph = ParsedSymbolgraph(
parseSymbolsMap(symbolgraphJson),
parseRelationsMap(symbolgraphJson),
);

return parseDeclarations(symbolgraph);
}

String? parseModuleName(Json symbolgraphJson) =>
symbolgraphJson['module']['name'].get();
Loading
Loading