Skip to content

Commit

Permalink
Swift2objc class properties (#1378)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammad3id authored Aug 1, 2024
1 parent 4df0ee6 commit 7e5449f
Show file tree
Hide file tree
Showing 17 changed files with 473 additions and 31 deletions.
3 changes: 3 additions & 0 deletions pkgs/swift2objc/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ coverage/

# Files generated by various OSs.
.DS_Store

# temp directories used during developement to run & test implementation
_temp/
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,17 @@ class ClassPropertyDeclaration
@override
bool hasObjCAnnotation;

List<String> getterStatements;
List<String> setterStatements;

ClassPropertyDeclaration({
required this.id,
required this.name,
required this.type,
this.hasSetter = false,
this.hasObjCAnnotation = false,
this.getterStatements = const [],
this.setterStatements = const [],
});
}

Expand Down
37 changes: 35 additions & 2 deletions pkgs/swift2objc/lib/src/generator/generators/class_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ String generateClass(ClassDeclaration declaration) {
_generateClassHeader(declaration),
[
_generateClassWrappedInstance(declaration),
..._generateClassProperties(declaration),
_generateClassInitializer(declaration),
..._generateClassMethods(declaration),
].join('\n\n').indent(),
Expand All @@ -17,7 +18,7 @@ String generateClass(ClassDeclaration declaration) {
}

String _generateClassHeader(ClassDeclaration declaration) {
var header = StringBuffer();
final header = StringBuffer();

if (declaration.hasObjCAnnotation) {
header.write('@objc ');
Expand Down Expand Up @@ -67,7 +68,7 @@ String? _generateClassInitializer(ClassDeclaration declaration) {
List<String> _generateClassMethods(ClassDeclaration declaration) {
return declaration.methods.map(
(method) {
var header = StringBuffer();
final header = StringBuffer();
if (method.hasObjCAnnotation) {
header.write('@objc ');
}
Expand All @@ -88,3 +89,35 @@ List<String> _generateClassMethods(ClassDeclaration declaration) {
},
).toList();
}

List<String> _generateClassProperties(ClassDeclaration declaration) {
return declaration.properties.map(
(property) {
final header = StringBuffer();
if (property.hasObjCAnnotation) {
header.write('@objc ');
}

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

final getterLines = [
'get {',
property.getterStatements.join('\n').indent(),
'}'
];

final setterLines = [
'set {',
property.setterStatements.join('\n').indent(),
'}'
];

return [
header,
getterLines.join('\n').indent(),
if (property.hasSetter) setterLines.join('\n').indent(),
'}',
].join('\n');
},
).toList();
}
23 changes: 19 additions & 4 deletions pkgs/swift2objc/lib/src/parser/_core/json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,21 @@ class Json extends IterableBase<Json> {
}
if (index >= _json.length) {
throw Exception(
'''Index out of range at "$path" (index: $index, max-length: ${_json.length})''',
'Index out of range at "$path" '
'(index: $index, max-length: ${_json.length})',
);
}
if (index < 0) {
throw Exception(
'Invalid negative index at "$path" (supplied index: $index)');
'Invalid negative index at "$path" (supplied index: $index)',
);
}
return Json(_json[index], [..._pathSegments, '$index']);
}

throw Exception(
'''Invalid subscript type when accessing value at path "$path". Expected an integer index or a string key, got ${index.runtimeType}.''',
'Invalid subscript type when accessing value at path "$path". '
'Expected an integer index or a string key, got ${index.runtimeType}.',
);
}

Expand All @@ -72,7 +75,19 @@ class Json extends IterableBase<Json> {
@override
Iterator<Json> get iterator => _JsonIterator(this);

Json firstWhereKey(String key, dynamic value) {
bool jsonWithKeyExists(String key, [dynamic value]) {
return any((json) {
if (!json[key].exists) return false;

if (value == null) {
return true;
} else {
return json[key].get<dynamic>() == value;
}
});
}

Json firstJsonWhereKey(String key, dynamic value) {
return firstWhere(
(json) {
try {
Expand Down
10 changes: 8 additions & 2 deletions pkgs/swift2objc/lib/src/parser/_core/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@ String parseSymbolId(Json symbolJson) {
final id = idJson.get<String>();
assert(
!id.contains(idDelim),
'''Symbold id at path ${idJson.path} contains a hiphen $idDelim which is not expected''',
'Symbold id at path ${idJson.path} contains a hiphen "$idDelim" '
'which is not expected',
);
return id;
}

String parseSymbolName(Json symbolJson) {
return symbolJson['names']['subHeading']
.firstWhereKey('kind', 'identifier')['spelling']
.firstJsonWhereKey('kind', 'identifier')['spelling']
.get();
}

bool symbolHasObjcAnnotation(Json symbolJson) {
return symbolJson['declarationFragments']
.jsonWithKeyExists('attribute', '@objc');
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import '../../_core/utils.dart';
import '../parse_declarations.dart';

ClassDeclaration parseClassDeclaration(
Json symbolJson,
Json classSymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final classId = parseSymbolId(symbolJson);
final classId = parseSymbolId(classSymbolJson);

final classRelations = symbolgraph.relations[classId] ?? [];

Expand All @@ -27,7 +27,8 @@ ClassDeclaration parseClassDeclaration(
final memberSymbol = symbolgraph.symbols[relation.sourceId];
if (memberSymbol == null) {
throw Exception(
'''Symbol of id "${relation.sourceId}" exist in a relation at path "${relation.json.path}" but does not exist among parsed symbols.''',
'Symbol of id "${relation.sourceId}" exist in a relation at path '
'"${relation.json.path}" but does not exist among parsed symbols.',
);
}
return parseDeclaration(memberSymbol, symbolgraph);
Expand All @@ -36,7 +37,7 @@ ClassDeclaration parseClassDeclaration(

return ClassDeclaration(
id: classId,
name: parseSymbolName(symbolJson),
name: parseSymbolName(classSymbolJson),
methods: memberDeclarations.whereType<ClassMethodDeclaration>().toList(),
properties:
memberDeclarations.whereType<ClassPropertyDeclaration>().toList(),
Expand Down
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 '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/shared/parameter.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../../ast/declarations/compounds/class_declaration.dart';
Expand All @@ -19,6 +20,7 @@ ClassMethodDeclaration parseMethodDeclaration(
name: parseSymbolName(methodSymbolJson),
returnType: _parseMethodReturnType(methodSymbolJson, symbolgraph),
params: _parseMethodParams(methodSymbolJson, symbolgraph),
hasObjCAnnotation: symbolHasObjcAnnotation(methodSymbolJson),
);
}

Expand All @@ -39,7 +41,8 @@ ReferredType? _parseMethodReturnType(

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

Expand All @@ -48,7 +51,7 @@ ReferredType? _parseMethodReturnType(
symbolgraph,
);

return DeclaredType(id: returnTypeId, declaration: returnTypeDeclaration);
return returnTypeDeclaration.asDeclaredType;
}

List<Parameter> _parseMethodParams(
Expand Down Expand Up @@ -77,14 +80,15 @@ ReferredType _parseParamType(
final fragments = paramSymbolJson['declarationFragments'];

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

final paramTypeSymbol = symbolgraph.symbols[paramTypeId];

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import '../../../ast/_core/interfaces/declaration.dart';
import '../../../ast/_core/shared/referred_type.dart';
import '../../../ast/declarations/compounds/class_declaration.dart';
import '../../_core/json.dart';
import '../../_core/parsed_symbolgraph.dart';
import '../../_core/utils.dart';
import '../parse_declarations.dart';

ClassPropertyDeclaration parsePropertyDeclaration(
Json propertySymbolJson,
ParsedSymbolgraph symbolgraph,
) {
return ClassPropertyDeclaration(
id: parseSymbolId(propertySymbolJson),
name: parseSymbolName(propertySymbolJson),
type: _parsePropertyType(propertySymbolJson, symbolgraph),
hasObjCAnnotation: symbolHasObjcAnnotation(propertySymbolJson),
hasSetter: _propertyHasSetter(propertySymbolJson),
);
}

ReferredType _parsePropertyType(
Json propertySymbolJson,
ParsedSymbolgraph symbolgraph,
) {
final subHeadings = propertySymbolJson['names']['subHeading'];

final typeSymbolJson =
subHeadings.firstJsonWhereKey('kind', 'typeIdentifier');
final typeSymbolId = typeSymbolJson['preciseIdentifier'].get<String>();
final typeSymbol = symbolgraph.symbols[typeSymbolId];

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

final typeDeclaration = parseDeclaration(
typeSymbol,
symbolgraph,
);

return typeDeclaration.asDeclaredType;
}

bool _propertyHasSetter(Json propertySymbolJson) {
final fragmentsJson = propertySymbolJson['declarationFragments'];

final declarationKeywordJson = fragmentsJson.firstWhere(
(json) {
if (json['kind'].get<String>() != 'keyword') return false;

final keyword = json['spelling'].get<String>();
if (keyword != 'var' && keyword != 'let') return false;

return true;
},
orElse: () => throw ArgumentError(
'Invalid property declaration fragments at path: ${fragmentsJson.path}. '
'Expected to find "var" or "let" as a keyword, found none',
),
);

final declarationKeyword = declarationKeywordJson['spelling'].get<String>();

if (declarationKeyword == 'var') {
final hasExplicitSetter = fragmentsJson.any(
(json) => json['spelling'].get<String>() == 'set',
);

final hasExplicitGetter = fragmentsJson.any(
(json) => json['spelling'].get<String>() == 'get',
);

if (hasExplicitGetter) {
if (hasExplicitSetter) {
return true;
} else {
return false;
}
}

return true;
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../../ast/_core/interfaces/declaration.dart';
import '../_core/parsed_symbolgraph.dart';
import 'declaration_parsers/parse_class_decalartion.dart';
import 'declaration_parsers/parse_method_declaration.dart';
import 'declaration_parsers/parse_property_declaration.dart';

List<Declaration> parseDeclarations(ParsedSymbolgraph symbolgraph) {
final declarations = <Declaration>[];
Expand Down Expand Up @@ -39,6 +40,7 @@ Declaration parseDeclaration(
parsedSymbol.declaration = switch (symbolType) {
'swift.class' => parseClassDeclaration(symbolJson, symbolgraph),
'swift.method' => parseMethodDeclaration(symbolJson, symbolgraph),
'swift.property' => parsePropertyDeclaration(symbolJson, symbolgraph),
_ => throw Exception(
'Symbol of type $symbolType is not implemented yet.',
),
Expand Down
51 changes: 51 additions & 0 deletions pkgs/swift2objc/lib/src/transformer/_core/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import '../../ast/_core/interfaces/declaration.dart';
import '../../ast/_core/shared/referred_type.dart';
import '../../ast/declarations/compounds/class_declaration.dart';
import '../transform.dart';
import 'unique_namer.dart';

(String value, ReferredType type) maybeWrapValue(
ReferredType type,
String valueIdentifier,
UniqueNamer globalNamer,
TransformationMap transformationMap,
) {
if (type is GenericType) {
throw UnimplementedError('Generic types are not implemented yet');
}

type as DeclaredType;

if (type.isObjCRepresentable) {
return (valueIdentifier, type);
}

final transformedTypeDeclaration = transformDeclaration(
type.declaration,
globalNamer,
transformationMap,
);

return (
'${transformedTypeDeclaration.name}($valueIdentifier)',
transformedTypeDeclaration.asDeclaredType
);
}

(String value, ReferredType type) maybeUnwrapValue(
ReferredType type,
String valueIdentifier,
) {
if (type is GenericType) {
throw UnimplementedError('Generic types are not implemented yet');
}

final declaration = (type as DeclaredType).declaration;

if (declaration is ClassDeclaration && declaration.wrappedInstance != null) {
final wrappedInstance = declaration.wrappedInstance;
return ('$valueIdentifier.${wrappedInstance!.name}', wrappedInstance.type);
} else {
return (valueIdentifier, type);
}
}
Loading

0 comments on commit 7e5449f

Please sign in to comment.