diff --git a/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart b/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart index 9bbd4080b4..24d913efa4 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import '../../ast_node.dart'; +import '../../declarations/compounds/protocol_declaration.dart'; import '../interfaces/declaration.dart'; import '../interfaces/nestable_declaration.dart'; import '../interfaces/objc_annotatable.dart'; @@ -97,6 +98,33 @@ class GenericType extends AstNode implements ReferredType { void visit(Visitation visitation) => visitation.visitGenericType(this); } +/// Associated Types are similar to Generics, but in the context of protocols. +/// They are declared with an `associatedType` keyword inside a protocol. +/// +/// For more information: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types +class AssociatedType extends AstNode implements ReferredType { + final String id; + + final String name; + + @override + bool get isObjCRepresentable => false; + + @override + String get swiftType => name; + + List> conformedProtocols; + + @override + bool sameAs(ReferredType other) => other is AssociatedType && other.id == id; + + AssociatedType( + {required this.id, required this.name, required this.conformedProtocols}); + + @override + String toString() => name; +} + /// An optional type, like Dart's nullable types. Eg `String?`. class OptionalType extends AstNode implements ReferredType { final ReferredType child; diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/associated_type_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/associated_type_declaration.dart new file mode 100644 index 0000000000..555ec9c2ca --- /dev/null +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/associated_type_declaration.dart @@ -0,0 +1,22 @@ +import '../../../_core/interfaces/declaration.dart'; +import '../../../_core/shared/referred_type.dart'; +import '../../../ast_node.dart'; +import '../protocol_declaration.dart'; + +class AssociatedTypeDeclaration extends AstNode implements Declaration { + @override + String id; + + @override + String name; + + List> conformedProtocols; + + AssociatedTypeDeclaration( + {required this.id, required this.name, required this.conformedProtocols}); +} + +extension AsAssociatedType on T { + AssociatedType asType() => AssociatedType( + id: id, name: name, conformedProtocols: conformedProtocols); +} diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart index ce4bf5685f..94b1fc453a 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/initializer_declaration.dart @@ -13,6 +13,7 @@ import '../../../_core/shared/parameter.dart'; import '../../../ast_node.dart'; /// Describes an initializer for a Swift compound entity (e.g, class, structs) +/// TODO(https://github.com/dart-lang/native/issues/2048): Convenience and Required Initializers class InitializerDeclaration extends AstNode implements Declaration, diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart index 9cda87ba33..2b9c348c85 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/members/method_declaration.dart @@ -11,6 +11,7 @@ import '../../../ast_node.dart'; /// Describes a method declaration for a Swift compound entity /// (e.g, class, structs) +/// TODO(https://github.com/dart-lang/native/issues/2049): Add support for private(set) functions class MethodDeclaration extends AstNode implements FunctionDeclaration, ObjCAnnotatable, Overridable { @override @@ -45,6 +46,8 @@ class MethodDeclaration extends AstNode bool isStatic; + bool public; + bool mutating; String get fullName => [ @@ -52,6 +55,7 @@ class MethodDeclaration extends AstNode for (final p in params) p.name, ].join(':'); + MethodDeclaration( {required this.id, required this.name, @@ -64,6 +68,7 @@ class MethodDeclaration extends AstNode this.isOverriding = false, this.throws = false, this.async = false, + this.public = false, this.mutating = false}) : assert(!isStatic || !isOverriding); diff --git a/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart b/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart index 5ae9467816..15ce6d252d 100644 --- a/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart +++ b/pkgs/swift2objc/lib/src/ast/declarations/compounds/protocol_declaration.dart @@ -4,6 +4,7 @@ import '../../_core/interfaces/compound_declaration.dart'; import '../../_core/interfaces/nestable_declaration.dart'; +import '../../_core/interfaces/objc_annotatable.dart'; import '../../_core/shared/referred_type.dart'; import '../../ast_node.dart'; import 'members/initializer_declaration.dart'; @@ -11,7 +12,8 @@ import 'members/method_declaration.dart'; import 'members/property_declaration.dart'; /// Describes the declaration of a Swift protocol. -class ProtocolDeclaration extends AstNode implements CompoundDeclaration { +class ProtocolDeclaration extends AstNode + implements CompoundDeclaration, ObjCAnnotatable { @override String id; @@ -24,11 +26,21 @@ class ProtocolDeclaration extends AstNode implements CompoundDeclaration { @override covariant List methods; + /// Only present if indicated with `@objc` + List optionalProperties; + + /// Only present if indicated with `@objc` + List optionalMethods; + + /// Associated types used with this declaration. They are similar to generic + /// types, but only designated for protocol declarations. + List associatedTypes; + @override List> conformedProtocols; @override - List typeParams; + bool hasObjCAnnotation; @override List initializers; @@ -46,10 +58,12 @@ class ProtocolDeclaration extends AstNode implements CompoundDeclaration { required this.methods, required this.initializers, required this.conformedProtocols, - required this.typeParams, + this.hasObjCAnnotation = false, this.nestingParent, - this.nestedDeclarations = const [], - }); + }) : associatedTypes = [], + nestedDeclarations = [], + optionalMethods = [], + optionalProperties = []; @override void visit(Visitation visitation) => @@ -61,9 +75,13 @@ class ProtocolDeclaration extends AstNode implements CompoundDeclaration { visitor.visitAll(properties); visitor.visitAll(methods); visitor.visitAll(conformedProtocols); - visitor.visitAll(typeParams); visitor.visitAll(initializers); visitor.visit(nestingParent); visitor.visitAll(nestedDeclarations); } + + @override + List get typeParams => + throw Exception('Protocols do not have type params: ' + 'Did you mean to use `associatedTypes` instead?'); } diff --git a/pkgs/swift2objc/lib/src/generator/generator.dart b/pkgs/swift2objc/lib/src/generator/generator.dart index 86f6a5454a..789ac321d6 100644 --- a/pkgs/swift2objc/lib/src/generator/generator.dart +++ b/pkgs/swift2objc/lib/src/generator/generator.dart @@ -1,6 +1,8 @@ import '../ast/_core/interfaces/declaration.dart'; import '../ast/declarations/compounds/class_declaration.dart'; +import '../ast/declarations/compounds/protocol_declaration.dart'; import 'generators/class_generator.dart'; +import 'generators/protocol_generator.dart'; String generate( List declarations, { @@ -19,6 +21,7 @@ String generate( List generateDeclaration(Declaration declaration) { return switch (declaration) { ClassDeclaration() => generateClass(declaration), + ProtocolDeclaration() => generateProtocol(declaration), _ => throw UnimplementedError( "$declaration generation isn't implemented yet", ), diff --git a/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart b/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart index b5161bfb2c..83e58f96b9 100644 --- a/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart +++ b/pkgs/swift2objc/lib/src/generator/generators/class_generator.dart @@ -134,6 +134,7 @@ List _generateClassProperties(ClassDeclaration declaration) => [ ..._generateClassProperty(property), ]; +// TODO(https://github.com/dart-lang/native/pull/2056): Generate Class properties for constants (i.e let) List _generateClassProperty(PropertyDeclaration property) { final header = StringBuffer(); diff --git a/pkgs/swift2objc/lib/src/generator/generators/protocol_generator.dart b/pkgs/swift2objc/lib/src/generator/generators/protocol_generator.dart new file mode 100644 index 0000000000..5dd195d8fd --- /dev/null +++ b/pkgs/swift2objc/lib/src/generator/generators/protocol_generator.dart @@ -0,0 +1,123 @@ +import '../../ast/declarations/built_in/built_in_declaration.dart'; +import '../../ast/declarations/compounds/members/initializer_declaration.dart'; +import '../../ast/declarations/compounds/members/method_declaration.dart'; +import '../../ast/declarations/compounds/members/property_declaration.dart'; +import '../../ast/declarations/compounds/protocol_declaration.dart'; +import '../_core/utils.dart'; + +List generateProtocol(ProtocolDeclaration declaration) { + return [ + '${_generateProtocolHeader(declaration)} {', + ...[ + ..._generateProtocolProperties(declaration), + ..._generateInitializers(declaration), + ..._generateProtocolMethods(declaration) + ].nonNulls.indent(), + '}\n', + ]; +} + +String _generateProtocolHeader(ProtocolDeclaration declaration) { + final header = StringBuffer(); + + if (declaration.hasObjCAnnotation) { + header.write('@objc '); + } + + header.write('public protocol ${declaration.name}'); + + final superClassAndProtocols = [ + ...declaration.conformedProtocols + .map((protocol) => protocol.declaration.name), + ].nonNulls; + + if (superClassAndProtocols.isNotEmpty) { + header.write(': ${superClassAndProtocols.join(", ")}'); + } + + return header.toString(); +} + +List _generateProtocolMethods(ProtocolDeclaration declaration) => [ + for (final method in declaration.methods) + ..._generateProtocolMethod(method) + ]; + +List _generateProtocolMethod(MethodDeclaration method) { + final header = StringBuffer(); + + if (method.hasObjCAnnotation) { + header.write('@objc '); + } + + if (method.isStatic) { + header.write('static '); + } + + header.write( + 'func ${method.name}(${generateParameters(method.params)}) ', + ); + + header.write(generateAnnotations(method)); + + if (!method.returnType.sameAs(voidType)) { + header.write('-> ${method.returnType.swiftType} '); + } + + return ['$header']; +} + +List _generateProtocolProperties(ProtocolDeclaration declaration) => [ + for (final property in declaration.properties) + ..._generateProtocolProperty(property), + ]; + +List _generateProtocolProperty(PropertyDeclaration property) { + final header = StringBuffer(); + + if (property.hasObjCAnnotation) { + header.write('@objc '); + } + + if (property.isStatic) { + header.write('static '); + } + + header.write(property.isConstant ? 'let' : 'var'); + header.write(' ${property.name}: ${property.type.swiftType}'); + header.write(' { ${[ + if (property.getter != null) 'get', + if (property.setter != null) 'set' + ].join(' ')} }'); + + return [ + header.toString(), + ]; +} + +List _generateInitializers(ProtocolDeclaration declaration) { + final initializers = [ + ...declaration.initializers, + ].nonNulls; + return [for (final init in initializers) ..._generateInitializer(init)]; +} + +List _generateInitializer(InitializerDeclaration initializer) { + final header = StringBuffer(); + + if (initializer.hasObjCAnnotation) { + header.write('@objc '); + } + + header.write('init'); + + if (initializer.isFailable) { + header.write('?'); + } + + header.write('(${generateParameters(initializer.params)})'); + + return [ + '$header ${generateAnnotations(initializer)}', + ]; +} diff --git a/pkgs/swift2objc/lib/src/parser/_core/parsed_symbolgraph.dart b/pkgs/swift2objc/lib/src/parser/_core/parsed_symbolgraph.dart index e980107196..88684dc665 100644 --- a/pkgs/swift2objc/lib/src/parser/_core/parsed_symbolgraph.dart +++ b/pkgs/swift2objc/lib/src/parser/_core/parsed_symbolgraph.dart @@ -35,6 +35,10 @@ class ParsedRelation { } enum ParsedRelationKind { + requirementOf, + defaultImplementationOf, + optionalRequirementOf, + conformsTo, memberOf; static final _supportedRelationKindsMap = { diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_associated_type_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_associated_type_declaration.dart new file mode 100644 index 0000000000..9309ba8cd0 --- /dev/null +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_associated_type_declaration.dart @@ -0,0 +1,64 @@ +// 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/interfaces/declaration.dart'; +import '../../../ast/_core/shared/referred_type.dart'; +import '../../../ast/declarations/compounds/members/associated_type_declaration.dart'; +import '../../../ast/declarations/compounds/protocol_declaration.dart'; +import '../../_core/json.dart'; +import '../../_core/parsed_symbolgraph.dart'; +import '../../_core/utils.dart'; +import '../parse_declarations.dart'; + +AssociatedTypeDeclaration parseAssociatedTypeDeclaration( + Json symbolJson, ParsedSymbolgraph symbolGraph) { + final id = parseSymbolId(symbolJson); + final name = parseSymbolName(symbolJson); + + final conformedProtocols = _parseConformedProtocols(symbolJson, symbolGraph); + + return AssociatedTypeDeclaration( + id: id, name: name, conformedProtocols: conformedProtocols); +} + +List> _parseConformedProtocols( + Json symbolJson, ParsedSymbolgraph symbolGraph) { + final conformDecls = >[]; + + final identifierIndex = symbolJson['declarationFragments'] + .toList() + .indexWhere((t) => + t['kind'].get() == 'identifier' && + t['spelling'].get() == 'Element'); + + if (symbolJson['declarationFragments'].length - 1 > identifierIndex && + symbolJson['declarationFragments'][identifierIndex + 1]['spelling'] + .get() + .trim() == + ':') { + // the associated type contains + final conformList = symbolJson['declarationFragments'] + .toList() + .sublist(identifierIndex + 2); + conformList.removeWhere((t) => t['spelling'].get().trim() == ','); + + // go through and find, then add + for (final proto in conformList) { + final protoId = proto['preciseIdentifier'].get(); + + final protoSymbol = symbolGraph.symbols[protoId]; + + if (protoSymbol == null) { + // return null; + continue; + } + + conformDecls.add( + (tryParseDeclaration(protoSymbol, symbolGraph) as ProtocolDeclaration) + .asDeclaredType); + } + } + + return conformDecls; +} diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_compound_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_compound_declaration.dart index cf3e9a27a6..aba046a49b 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_compound_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_compound_declaration.dart @@ -3,11 +3,14 @@ // BSD-style license that can be found in the LICENSE file. import '../../../ast/_core/interfaces/compound_declaration.dart'; +import '../../../ast/_core/interfaces/declaration.dart'; import '../../../ast/_core/interfaces/nestable_declaration.dart'; import '../../../ast/declarations/compounds/class_declaration.dart'; +import '../../../ast/declarations/compounds/members/associated_type_declaration.dart'; import '../../../ast/declarations/compounds/members/initializer_declaration.dart'; import '../../../ast/declarations/compounds/members/method_declaration.dart'; import '../../../ast/declarations/compounds/members/property_declaration.dart'; +import '../../../ast/declarations/compounds/protocol_declaration.dart'; import '../../../ast/declarations/compounds/struct_declaration.dart'; import '../../_core/parsed_symbolgraph.dart'; import '../../_core/utils.dart'; @@ -107,3 +110,127 @@ StructDeclaration parseStructDeclaration( symbolgraph, ); } + +// TODO(https://github.com/dart-lang/native/issues/1815): Implement extensions before adding support for default implementations +ProtocolDeclaration parseProtocolDeclaration( + ParsedSymbol protocolSymbol, ParsedSymbolgraph symbolgraph) { + final compoundId = parseSymbolId(protocolSymbol.json); + final compoundRelations = symbolgraph.relations[compoundId] ?? []; + + // construct protocol + final protocol = ProtocolDeclaration( + id: compoundId, + name: parseSymbolName(protocolSymbol.json), + properties: [], + methods: [], + initializers: [], + conformedProtocols: []); + + // get optional member declarations if any + final optionalMemberDeclarations = compoundRelations + .where((relation) { + final isOptionalRequirementRelation = + relation.kind == ParsedRelationKind.optionalRequirementOf; + final isMemberOfProtocol = relation.targetId == compoundId; + return isMemberOfProtocol && isOptionalRequirementRelation; + }) + .map((relation) { + final memberSymbol = symbolgraph.symbols[relation.sourceId]; + if (memberSymbol == null) { + return null; + } + return tryParseDeclaration(memberSymbol, symbolgraph); + }) + .nonNulls + .dedupeBy((decl) => decl.id) + .toList(); + + // get normal member declarations + final memberDeclarations = compoundRelations + .where((relation) { + final isOptionalRequirementRelation = + relation.kind == ParsedRelationKind.requirementOf && + relation.kind != ParsedRelationKind.optionalRequirementOf; + final isMemberOfProtocol = relation.targetId == compoundId; + return isMemberOfProtocol && isOptionalRequirementRelation; + }) + .map((relation) { + final memberSymbol = symbolgraph.symbols[relation.sourceId]; + + if (memberSymbol == null) { + return null; + } + + return tryParseDeclaration(memberSymbol, symbolgraph); + }) + .nonNulls + .dedupeBy((decl) => decl.id) + .toList(); + + // get conformed protocols + final conformedProtocolDeclarations = compoundRelations + .where((relation) { + final isOptionalRequirementRelation = + relation.kind == ParsedRelationKind.conformsTo; + final isMemberOfProtocol = relation.sourceId == compoundId; + return isMemberOfProtocol && isOptionalRequirementRelation; + }) + .map((relation) { + final memberSymbol = symbolgraph.symbols[relation.targetId]; + if (memberSymbol == null) { + return null; + } + var conformedDecl = tryParseDeclaration(memberSymbol, symbolgraph) + as ProtocolDeclaration; + return conformedDecl.asDeclaredType; + }) + .nonNulls + .dedupeBy((decl) => decl.id) + .toList(); + + // If the protocol has optional members, it must be annotated with `@objc` + if (optionalMemberDeclarations.isNotEmpty) { + protocol.hasObjCAnnotation = true; + } + + protocol.associatedTypes.addAll(memberDeclarations + .whereType() + .map((decl) => decl.asType()) + .dedupeBy((m) => m.name)); + + protocol.methods.addAll( + memberDeclarations + .whereType() + .dedupeBy((m) => m.fullName), + ); + + protocol.properties.addAll( + memberDeclarations.whereType(), + ); + + protocol.optionalMethods.addAll( + optionalMemberDeclarations + .whereType() + .dedupeBy((m) => m.fullName), + ); + + protocol.optionalProperties.addAll( + optionalMemberDeclarations.whereType(), + ); + + protocol.conformedProtocols.addAll(conformedProtocolDeclarations); + + protocol.initializers.addAll( + memberDeclarations + .whereType() + .dedupeBy((m) => m.fullName), + ); + + protocol.nestedDeclarations.addAll( + memberDeclarations.whereType(), + ); + + protocol.nestedDeclarations.fillNestingParents(protocol); + + return protocol; +} diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_function_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_function_declaration.dart index b02ede83f0..fa79d793f3 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_function_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_function_declaration.dart @@ -163,6 +163,9 @@ ParsedFunctionInfo parseFunctionInfo( ); } +// TODO(https://github.com/dart-lang/native/issues/1931): Function Return Type does not support nested types +// (e.g String.UTF8, Self.Element +// (necessary when making use of protocol associated types)) ReferredType _parseFunctionReturnType( Json methodSymbolJson, ParsedSymbolgraph symbolgraph, diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart index 23a3fee39b..275eb93486 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart @@ -10,6 +10,7 @@ import '../../_core/parsed_symbolgraph.dart'; import '../../_core/token_list.dart'; import '../../_core/utils.dart'; +// TODO(https://github.com/dart-lang/native/issues/1932): Add support for parsing getters and setters in property declarations PropertyDeclaration parsePropertyDeclaration( Json propertySymbolJson, ParsedSymbolgraph symbolgraph, { diff --git a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart index af3f8e443a..b4728bcf9f 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart @@ -7,6 +7,7 @@ import 'package:logging/logging.dart'; import '../../ast/_core/interfaces/declaration.dart'; import '../_core/parsed_symbolgraph.dart'; import '../_core/utils.dart'; +import 'declaration_parsers/parse_associated_type_declaration.dart'; import 'declaration_parsers/parse_compound_declaration.dart'; import 'declaration_parsers/parse_function_declaration.dart'; import 'declaration_parsers/parse_initializer_declaration.dart'; @@ -56,6 +57,9 @@ Declaration parseDeclaration( 'swift.init' => parseInitializerDeclaration(symbolJson, symbolgraph), 'swift.func' => parseGlobalFunctionDeclaration(symbolJson, symbolgraph), 'swift.var' => parseGlobalVariableDeclaration(symbolJson, symbolgraph), + 'swift.protocol' => parseProtocolDeclaration(parsedSymbol, symbolgraph), + 'swift.associatedtype' => + parseAssociatedTypeDeclaration(symbolJson, symbolgraph), _ => throw Exception( 'Symbol of type $symbolType is not implemented yet.', ), diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 409252c524..ce9fa1d20f 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -6,6 +6,7 @@ import '../ast/_core/interfaces/compound_declaration.dart'; import '../ast/_core/interfaces/declaration.dart'; import '../ast/_core/interfaces/nestable_declaration.dart'; import '../ast/declarations/compounds/class_declaration.dart'; +import '../ast/declarations/compounds/protocol_declaration.dart'; import '../ast/declarations/compounds/struct_declaration.dart'; import '../ast/declarations/globals/globals.dart'; import '../ast/visitor.dart'; @@ -13,9 +14,16 @@ import '_core/dependencies.dart'; import '_core/unique_namer.dart'; import 'transformers/transform_compound.dart'; import 'transformers/transform_globals.dart'; +import 'transformers/transform_protocol.dart'; typedef TransformationMap = Map; +extension TransformationMapUtils on TransformationMap { + Declaration? findByOriginalId(String id) { + return this[keys.where((k) => k.id == id).firstOrNull]; + } +} + Set generateDependencies(Iterable decls) => visit(DependencyVisitation(), decls).topLevelDeclarations; @@ -77,6 +85,8 @@ Declaration transformDeclaration( parentNamer, transformationMap, ), + ProtocolDeclaration() => + transformProtocol(declaration, parentNamer, transformationMap), _ => throw UnimplementedError(), }; } diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart index 035c185fc7..27942bc6d3 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart @@ -11,6 +11,7 @@ import '../../ast/declarations/compounds/class_declaration.dart'; import '../../ast/declarations/compounds/members/initializer_declaration.dart'; import '../../ast/declarations/compounds/members/method_declaration.dart'; import '../../ast/declarations/compounds/members/property_declaration.dart'; +import '../../ast/declarations/compounds/protocol_declaration.dart'; import '../../parser/_core/utils.dart'; import '../_core/unique_namer.dart'; import '../transform.dart'; @@ -31,17 +32,31 @@ ClassDeclaration transformCompound( type: originalCompound.asDeclaredType, ); + final superClass = originalCompound is ClassDeclaration + ? (originalCompound.superClass == null + ? null + : transformationMap.findByOriginalId(originalCompound.superClass!.id)) + : null; + final transformedCompound = ClassDeclaration( id: originalCompound.id.addIdSuffix('wrapper'), name: parentNamer.makeUnique('${originalCompound.name}Wrapper'), hasObjCAnnotation: true, - superClass: objectType, + superClass: superClass?.asDeclaredType ?? objectType, isWrapper: true, wrappedInstance: wrappedCompoundInstance, wrapperInitializer: _buildWrapperInitializer(wrappedCompoundInstance), ); - transformationMap[originalCompound] = transformedCompound; + transformedCompound.conformedProtocols.addAll( + originalCompound.conformedProtocols.map((p) { + return ( + transformDeclaration(p.declaration, compoundNamer, transformationMap) + as ProtocolDeclaration + ) + .asDeclaredType; + }) + ); transformedCompound.nestedDeclarations = originalCompound.nestedDeclarations .map((nested) => transformDeclaration( @@ -86,6 +101,8 @@ ClassDeclaration transformCompound( .toList() ..sort((Declaration a, Declaration b) => a.id.compareTo(b.id)); + transformationMap[originalCompound] = transformedCompound; + transformedCompound.initializers = transformedInitializers .whereType() .toList() diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_protocol.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_protocol.dart new file mode 100644 index 0000000000..c42b3704a5 --- /dev/null +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_protocol.dart @@ -0,0 +1,45 @@ +// 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/interfaces/declaration.dart'; +import '../../ast/declarations/compounds/protocol_declaration.dart'; +import '../../parser/_core/utils.dart'; +import '../_core/unique_namer.dart'; +import '../transform.dart'; + +// TODO(https://github.com/dart-lang/native/pull/2056): Update TransformationMap to make referencing types easy. +// Would want to just add wrapper suffix but due to unique naming, +// can't take chances +ProtocolDeclaration transformProtocol(ProtocolDeclaration originalProtocol, + UniqueNamer parentNamer, TransformationMap transformationMap) { + final compoundNamer = UniqueNamer.inCompound(originalProtocol); + + if (originalProtocol.associatedTypes.isNotEmpty) { + throw Exception('Associated types are not exportable to Objective-C'); + } + + final transformedProtocol = ProtocolDeclaration( + id: originalProtocol.id.addIdSuffix('wrapper'), + name: parentNamer.makeUnique('${originalProtocol.name}Wrapper'), + properties: originalProtocol.properties, + methods: originalProtocol.methods, + initializers: originalProtocol.initializers, + conformedProtocols: [], + hasObjCAnnotation: true, + nestingParent: originalProtocol.nestingParent); + + transformedProtocol.conformedProtocols.addAll( + originalProtocol.conformedProtocols.map((p) { + return ( + transformDeclaration(p.declaration, compoundNamer, transformationMap) + as ProtocolDeclaration + ) + .asDeclaredType; + }) + ); + + transformationMap[originalProtocol] = transformedProtocol; + + return transformedProtocol; +} diff --git a/pkgs/swift2objc/test/integration/protocol_input.swift b/pkgs/swift2objc/test/integration/protocol_input.swift new file mode 100644 index 0000000000..9aa30bdd57 --- /dev/null +++ b/pkgs/swift2objc/test/integration/protocol_input.swift @@ -0,0 +1,26 @@ +import Foundation + +public protocol Greetable { + var name: String { get } + func greet() -> String +} + + +public protocol Identifiable { + var id: String { get } +} + +public protocol User: Identifiable { + static var configurationName: String { get } + static func configure() + var currentState: String { get set } + var isActive: Bool { get } + var email: String { get } + func displayInfo() -> String +} + +@objc public protocol OptionallyResponds { + @objc optional func optionalMethod() + @objc optional var response: String { get set } + func requiredMethod() +} \ No newline at end of file diff --git a/pkgs/swift2objc/test/integration/protocol_output.swift b/pkgs/swift2objc/test/integration/protocol_output.swift new file mode 100644 index 0000000000..e1645510ee --- /dev/null +++ b/pkgs/swift2objc/test/integration/protocol_output.swift @@ -0,0 +1,28 @@ +// Test preamble text + +import Foundation + +@objc public protocol OptionallyRespondsWrapper { + @objc var response: String { } + @objc func optionalMethod() + func requiredMethod() +} + +@objc public protocol IdentifiableWrapper { + var id: String { } +} + +@objc public protocol UserWrapper { + var email: String { } + var currentState: String { } + static var configurationName: String { } + var isActive: Bool { } + static func configure() + func displayInfo() -> String +} + +@objc public protocol GreetableWrapper { + var name: String { } + func greet() -> String +} + diff --git a/pkgs/swift2objc/test/unit/json/parse_protocol_symbol.json b/pkgs/swift2objc/test/unit/json/parse_protocol_symbol.json new file mode 100644 index 0000000000..0e67c36a3c --- /dev/null +++ b/pkgs/swift2objc/test/unit/json/parse_protocol_symbol.json @@ -0,0 +1,2128 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 6, + "patch": 0 + }, + "generator": "Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)" + }, + "module": { + "name": "proto", + "platform": { + "architecture": "arm64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 15, + "minor": 0 + } + } + } + }, + "symbols": [ + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "s:5proto12IdentifiableP", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Identifiable" + ], + "names": { + "title": "Identifiable", + "navigator": [ + { + "kind": "identifier", + "spelling": "Identifiable" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Identifiable" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Identifiable" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 15, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "s:5proto12IdentifiableP2idSSvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Identifiable", + "id" + ], + "names": { + "title": "id", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "id" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "id" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 16, + "character": 8 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "s:5proto4UserP5emailSSvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "email" + ], + "names": { + "title": "email", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "email" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "email" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 24, + "character": 8 + } + } + }, + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "c:@M@proto@objc(pl)OptionallyResponds", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "OptionallyResponds" + ], + "names": { + "title": "OptionallyResponds", + "navigator": [ + { + "kind": "identifier", + "spelling": "OptionallyResponds" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "OptionallyResponds" + } + ] + }, + "declarationFragments": [ + { + "kind": "attribute", + "spelling": "@objc" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "OptionallyResponds" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 28, + "character": 22 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "s:5proto9GreetableP4nameSSvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Greetable", + "name" + ], + "names": { + "title": "name", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "name" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "name" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 3, + "character": 8 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto18PrintableStackableP3pop7ElementQzSgyF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "PrintableStackable", + "pop()" + ], + "names": { + "title": "pop()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "pop" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "spelling": "Self" + }, + { + "kind": "text", + "spelling": "." + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "mutating" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "pop" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 38, + "character": 18 + } + } + }, + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "s:5proto18PrintableStackableP", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "PrintableStackable" + ], + "names": { + "title": "PrintableStackable", + "navigator": [ + { + "kind": "identifier", + "spelling": "PrintableStackable" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "PrintableStackable" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "PrintableStackable" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 35, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "c:@M@proto@objc(pl)OptionallyResponds(im)optionalMethod", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "OptionallyResponds", + "optionalMethod()" + ], + "names": { + "title": "optionalMethod()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "optionalMethod" + }, + { + "kind": "text", + "spelling": "()" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "declarationFragments": [ + { + "kind": "attribute", + "spelling": "@objc" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "optional" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "optionalMethod" + }, + { + "kind": "text", + "spelling": "()" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 29, + "character": 24 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "c:@M@proto@objc(pl)OptionallyResponds(py)response", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "OptionallyResponds", + "response" + ], + "names": { + "title": "response", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "response" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "attribute", + "spelling": "@objc" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "optional" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "response" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "set" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 30, + "character": 23 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto18PrintableStackableP4pushyy7ElementQzF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "PrintableStackable", + "push(_:)" + ], + "names": { + "title": "push(_:)", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "push" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + }, + { + "kind": "text", + "spelling": ")" + } + ] + }, + "functionSignature": { + "parameters": [ + { + "name": "element", + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "element" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + } + ] + } + ], + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "mutating" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "push" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "externalParam", + "spelling": "_" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "element" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto18PrintableStackableP7ElementQa" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 37, + "character": 18 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "c:@M@proto@objc(pl)OptionallyResponds(im)requiredMethod", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "OptionallyResponds", + "requiredMethod()" + ], + "names": { + "title": "requiredMethod()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "requiredMethod" + }, + { + "kind": "text", + "spelling": "()" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "requiredMethod" + }, + { + "kind": "text", + "spelling": "()" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 31, + "character": 9 + } + } + }, + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "s:5proto9GreetableP", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Greetable" + ], + "names": { + "title": "Greetable", + "navigator": [ + { + "kind": "identifier", + "spelling": "Greetable" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Greetable" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Greetable" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 2, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto9StackableP4pushyy7ElementQzF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Stackable", + "push(_:)" + ], + "names": { + "title": "push(_:)", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "push" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + }, + { + "kind": "text", + "spelling": ")" + } + ] + }, + "functionSignature": { + "parameters": [ + { + "name": "element", + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "element" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + } + ] + } + ], + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "mutating" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "push" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "externalParam", + "spelling": "_" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "element" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 10, + "character": 18 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto4UserP11displayInfoSSyF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "displayInfo()" + ], + "names": { + "title": "displayInfo()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "displayInfo" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "displayInfo" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 25, + "character": 9 + } + } + }, + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "s:5proto9StackableP", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Stackable" + ], + "names": { + "title": "Stackable", + "navigator": [ + { + "kind": "identifier", + "spelling": "Stackable" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Stackable" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Stackable" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 8, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.protocol", + "displayName": "Protocol" + }, + "identifier": { + "precise": "s:5proto4UserP", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User" + ], + "names": { + "title": "User", + "navigator": [ + { + "kind": "identifier", + "spelling": "User" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "User" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "protocol" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "User" + }, + { + "kind": "text", + "spelling": " : " + }, + { + "kind": "typeIdentifier", + "spelling": "Identifiable", + "preciseIdentifier": "s:5proto12IdentifiableP" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 19, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto9GreetableP5greetSSyF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Greetable", + "greet()" + ], + "names": { + "title": "greet()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "greet" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "greet" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 4, + "character": 9 + } + } + }, + { + "kind": { + "identifier": "swift.type.method", + "displayName": "Type Method" + }, + "identifier": { + "precise": "s:5proto4UserP9configureyyFZ", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "configure()" + ], + "names": { + "title": "configure()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "static" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "configure" + }, + { + "kind": "text", + "spelling": "()" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "text", + "spelling": "()" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "static" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "configure" + }, + { + "kind": "text", + "spelling": "()" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 21, + "character": 16 + } + } + }, + { + "kind": { + "identifier": "swift.associatedtype", + "displayName": "Associated Type" + }, + "identifier": { + "precise": "s:5proto18PrintableStackableP7ElementQa", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "PrintableStackable", + "Element" + ], + "names": { + "title": "Element", + "subHeading": [ + { + "kind": "keyword", + "spelling": "associatedtype" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Element" + }, + { + "kind": "text", + "spelling": " : " + }, + { + "kind": "typeIdentifier", + "spelling": "Identifiable", + "preciseIdentifier": "s:5proto12IdentifiableP" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "associatedtype" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Element" + }, + { + "kind": "text", + "spelling": " : " + }, + { + "kind": "typeIdentifier", + "spelling": "Identifiable", + "preciseIdentifier": "s:5proto12IdentifiableP" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 36, + "character": 19 + } + } + }, + { + "kind": { + "identifier": "swift.associatedtype", + "displayName": "Associated Type" + }, + "identifier": { + "precise": "s:5proto9StackableP7ElementQa", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Stackable", + "Element" + ], + "names": { + "title": "Element", + "subHeading": [ + { + "kind": "keyword", + "spelling": "associatedtype" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Element" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "associatedtype" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Element" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 9, + "character": 19 + } + } + }, + { + "kind": { + "identifier": "swift.method", + "displayName": "Instance Method" + }, + "identifier": { + "precise": "s:5proto9StackableP3pop7ElementQzSgyF", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Stackable", + "pop()" + ], + "names": { + "title": "pop()", + "subHeading": [ + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "pop" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ] + }, + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "spelling": "Self" + }, + { + "kind": "text", + "spelling": "." + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "mutating" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "func" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "pop" + }, + { + "kind": "text", + "spelling": "() -> " + }, + { + "kind": "typeIdentifier", + "spelling": "Element", + "preciseIdentifier": "s:5proto9StackableP7ElementQa" + }, + { + "kind": "text", + "spelling": "?" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 11, + "character": 18 + } + } + }, + { + "kind": { + "identifier": "swift.type.property", + "displayName": "Type Property" + }, + "identifier": { + "precise": "s:5proto4UserP17configurationNameSSvpZ", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "configurationName" + ], + "names": { + "title": "configurationName", + "subHeading": [ + { + "kind": "keyword", + "spelling": "static" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "configurationName" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "static" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "configurationName" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 20, + "character": 15 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "s:5proto4UserP8isActiveSbvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "isActive" + ], + "names": { + "title": "isActive", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "isActive" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Bool", + "preciseIdentifier": "s:Sb" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "isActive" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "Bool", + "preciseIdentifier": "s:Sb" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 23, + "character": 8 + } + } + }, + { + "kind": { + "identifier": "swift.property", + "displayName": "Instance Property" + }, + "identifier": { + "precise": "s:5proto4UserP12currentStateSSvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "User", + "currentState" + ], + "names": { + "title": "currentState", + "subHeading": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "currentState" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "var" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "currentState" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "spelling": "String", + "preciseIdentifier": "s:SS" + }, + { + "kind": "text", + "spelling": " { " + }, + { + "kind": "keyword", + "spelling": "get" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "set" + }, + { + "kind": "text", + "spelling": " }" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://protocol.swift", + "position": { + "line": 22, + "character": 8 + } + } + } + ], + "relationships": [ + { + "kind": "requirementOf", + "source": "s:5proto12IdentifiableP2idSSvp", + "target": "s:5proto12IdentifiableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto18PrintableStackableP4pushyy7ElementQzF", + "target": "s:5proto18PrintableStackableP" + }, + { + "kind": "optionalRequirementOf", + "source": "c:@M@proto@objc(pl)OptionallyResponds(im)optionalMethod", + "target": "c:@M@proto@objc(pl)OptionallyResponds" + }, + { + "kind": "optionalRequirementOf", + "source": "c:@M@proto@objc(pl)OptionallyResponds(py)response", + "target": "c:@M@proto@objc(pl)OptionallyResponds" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP9configureyyFZ", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "s:5proto18PrintableStackableP3pop7ElementQzSgyF", + "target": "s:5proto18PrintableStackableP" + }, + { + "kind": "requirementOf", + "source": "c:@M@proto@objc(pl)OptionallyResponds(im)optionalMethod", + "target": "c:@M@proto@objc(pl)OptionallyResponds" + }, + { + "kind": "requirementOf", + "source": "c:@M@proto@objc(pl)OptionallyResponds(py)response", + "target": "c:@M@proto@objc(pl)OptionallyResponds" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP12currentStateSSvp", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "s:5proto9StackableP7ElementQa", + "target": "s:5proto9StackableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto9GreetableP4nameSSvp", + "target": "s:5proto9GreetableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP17configurationNameSSvpZ", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "s:5proto9StackableP3pop7ElementQzSgyF", + "target": "s:5proto9StackableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP8isActiveSbvp", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "c:@M@proto@objc(pl)OptionallyResponds(im)requiredMethod", + "target": "c:@M@proto@objc(pl)OptionallyResponds" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP5emailSSvp", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "s:5proto18PrintableStackableP7ElementQa", + "target": "s:5proto18PrintableStackableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto9StackableP4pushyy7ElementQzF", + "target": "s:5proto9StackableP" + }, + { + "kind": "conformsTo", + "source": "s:5proto4UserP", + "target": "s:5proto12IdentifiableP" + }, + { + "kind": "requirementOf", + "source": "s:5proto4UserP11displayInfoSSyF", + "target": "s:5proto4UserP" + }, + { + "kind": "requirementOf", + "source": "s:5proto9GreetableP5greetSSyF", + "target": "s:5proto9GreetableP" + } + ] +} \ No newline at end of file diff --git a/pkgs/swift2objc/test/unit/parse_protocol_test.dart b/pkgs/swift2objc/test/unit/parse_protocol_test.dart new file mode 100644 index 0000000000..5eed76e7df --- /dev/null +++ b/pkgs/swift2objc/test/unit/parse_protocol_test.dart @@ -0,0 +1,100 @@ +import 'package:path/path.dart' as p; +import 'package:swift2objc/src/ast/declarations/compounds/protocol_declaration.dart'; +import 'package:swift2objc/src/parser/_core/parsed_symbolgraph.dart'; +import 'package:swift2objc/src/parser/_core/utils.dart'; +import 'package:swift2objc/src/parser/parsers/parse_declarations.dart'; +import 'package:swift2objc/src/parser/parsers/parse_relations_map.dart'; +import 'package:swift2objc/src/parser/parsers/parse_symbols_map.dart'; +import 'package:test/test.dart'; + +void main() { + group('Protocol Testing', () { + final jsonFile = + p.join('test', 'unit', 'json', 'parse_protocol_symbol.json'); + final inputJson = readJsonFile(jsonFile); + final symbolgraph = ParsedSymbolgraph( + parseSymbolsMap(inputJson), + parseRelationsMap(inputJson), + ); + final declarations = parseDeclarations(symbolgraph); + + test('Basic Protocols', () { + final declOne = declarations.firstWhere((t) => t.name == 'Greetable') + as ProtocolDeclaration; + + expect(declOne.properties, hasLength(1)); + expect(declOne.methods, hasLength(1)); + + expect(declOne.properties.single.name, equalsIgnoringCase('name')); + + var declMethod = declOne.methods.single; + expect(declMethod.name, equalsIgnoringCase('greet')); + expect(declMethod.returnType.swiftType, equalsIgnoringCase('String')); + }); + + test('Protocols with Static Properties and Methods', () { + final testDecl = declarations.firstWhere((t) => t.name == 'User') + as ProtocolDeclaration; + + expect(testDecl.properties, hasLength(4)); + expect(testDecl.methods, hasLength(2)); + + expect(testDecl.methods.where((m) => m.isStatic), hasLength(1)); + expect(testDecl.properties.where((m) => m.isStatic), hasLength(1)); + + final declProp = testDecl.properties.where((m) => m.isStatic).single; + // final declMethod = testDecl.methods.where((m) => m.isStatic).single; + + expect(declProp.name, equalsIgnoringCase('configurationName')); + }); + + test('Nested Protocols', () { + final testDecl = declarations.firstWhere((t) => t.name == 'User') + as ProtocolDeclaration; + + expect(testDecl.conformedProtocols, hasLength(1)); + + final conformedProtocol = testDecl.conformedProtocols.single.declaration; + + expect(conformedProtocol.name, equalsIgnoringCase('Identifiable')); + expect(conformedProtocol.properties, hasLength(1)); + + var conformedProtocolProp = conformedProtocol.properties.single; + expect(conformedProtocolProp.name, equalsIgnoringCase('id')); + }); + + test('Protocols with associated types', () { + final testDecl = declarations.firstWhere((t) => t.name == 'Stackable') + as ProtocolDeclaration; + + expect(testDecl.associatedTypes, hasLength(1)); + expect(testDecl.methods.map((m) => m.name), contains('push')); + + expect( + testDecl.methods + .firstWhere((m) => m.name == 'push') + .returnType + .swiftType, + equalsIgnoringCase('Void')); + }); + + test('Protocols with associated types that conform', () { + final testDecl = + declarations.firstWhere((t) => t.name == 'PrintableStackable') + as ProtocolDeclaration; + + expect(testDecl.associatedTypes, hasLength(1)); + + final associatedType = testDecl.associatedTypes.single; + + expect(associatedType.name, equalsIgnoringCase('Element')); + expect(associatedType.conformedProtocols, hasLength(1)); + + final associatedTypeConformedProtocol = + associatedType.conformedProtocols.single.declaration; + + expect(associatedTypeConformedProtocol.name, + equalsIgnoringCase('Identifiable')); + }); + }); +}