From 0df33b30a51928706633b7428ca52dec239bae90 Mon Sep 17 00:00:00 2001 From: Amr Ahmed <97100935+AmrAhmed119@users.noreply.github.com> Date: Thu, 6 Mar 2025 02:34:28 +0200 Subject: [PATCH] [swift2objc] Support wrapper classes for primitives (#1984) --- .../transformer/_core/primitive_wrappers.dart | 65 ++++++++ .../lib/src/transformer/_core/utils.dart | 43 ++++- .../lib/src/transformer/transform.dart | 12 +- .../transformers/transform_compound.dart | 25 +-- .../transformers/transform_function.dart | 38 ++--- .../transformers/transform_variable.dart | 14 +- pkgs/swift2objc/pubspec.yaml | 1 + .../primitive_wrappers_input.swift | 87 ++++++++++ .../primitive_wrappers_output.swift | 150 ++++++++++++++++++ 9 files changed, 379 insertions(+), 56 deletions(-) create mode 100644 pkgs/swift2objc/lib/src/transformer/_core/primitive_wrappers.dart create mode 100644 pkgs/swift2objc/test/integration/primitive_wrappers_input.swift create mode 100644 pkgs/swift2objc/test/integration/primitive_wrappers_output.swift diff --git a/pkgs/swift2objc/lib/src/transformer/_core/primitive_wrappers.dart b/pkgs/swift2objc/lib/src/transformer/_core/primitive_wrappers.dart new file mode 100644 index 0000000000..3335066203 --- /dev/null +++ b/pkgs/swift2objc/lib/src/transformer/_core/primitive_wrappers.dart @@ -0,0 +1,65 @@ +// 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 'package:collection/collection.dart'; + +import '../../ast/_core/interfaces/declaration.dart'; +import '../../ast/_core/shared/referred_type.dart'; +import '../../ast/declarations/built_in/built_in_declaration.dart'; +import '../../ast/declarations/compounds/class_declaration.dart'; +import '../../ast/declarations/compounds/members/property_declaration.dart'; +import '../../parser/_core/utils.dart'; +import '../../transformer/_core/utils.dart'; +import '../transform.dart'; + +final _primitiveWrappers = List<(ReferredType, ReferredType)>.unmodifiable([ + (intType, _createWrapperClass(intType)), + (floatType, _createWrapperClass(floatType)), + (doubleType, _createWrapperClass(doubleType)), + (boolType, _createWrapperClass(boolType)), +]); + +ReferredType _createWrapperClass(DeclaredType primitiveType) { + final property = PropertyDeclaration( + id: primitiveType.id.addIdSuffix('wrappedInstance'), + name: 'wrappedInstance', + type: primitiveType, + ); + return ClassDeclaration( + id: primitiveType.id.addIdSuffix('wrapper'), + name: '${primitiveType.name}Wrapper', + hasObjCAnnotation: true, + superClass: objectType, + isWrapper: true, + wrappedInstance: property, + wrapperInitializer: buildWrapperInitializer(property)) + .asDeclaredType; +} + +// Support Optional primitives as return Type +// TODO(https://github.com/dart-lang/native/issues/1743) + +(ReferredType, bool) maybeGetPrimitiveWrapper( + ReferredType type, + bool shouldWrapPrimitives, + TransformationMap transformationMap, +) { + if (type is! DeclaredType || !shouldWrapPrimitives) { + return (type, false); + } + + final wrapper = _getPrimitiveWrapper(type); + if (wrapper == null) { + return (type, false); + } + + transformationMap[type.declaration] = (wrapper as DeclaredType).declaration; + return (wrapper, true); +} + +ReferredType? _getPrimitiveWrapper(DeclaredType other) { + return _primitiveWrappers + .firstWhereOrNull((pair) => pair.$1.sameAs(other)) + ?.$2; +} diff --git a/pkgs/swift2objc/lib/src/transformer/_core/utils.dart b/pkgs/swift2objc/lib/src/transformer/_core/utils.dart index e594a24ace..4a2ddb5190 100644 --- a/pkgs/swift2objc/lib/src/transformer/_core/utils.dart +++ b/pkgs/swift2objc/lib/src/transformer/_core/utils.dart @@ -3,8 +3,12 @@ // 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'; +import '../../ast/declarations/compounds/members/initializer_declaration.dart'; +import '../../ast/declarations/compounds/members/property_declaration.dart'; +import '../../transformer/_core/primitive_wrappers.dart'; import '../transform.dart'; import 'unique_namer.dart'; @@ -12,12 +16,18 @@ import 'unique_namer.dart'; // probably be methods on ReferredType, but the transformDeclaration call makes // that weird. Refactor this as part of the transformer refactor. -(String value, ReferredType type) maybeWrapValue( - ReferredType type, - String value, - UniqueNamer globalNamer, - TransformationMap transformationMap, -) { +(String value, ReferredType type) maybeWrapValue(ReferredType type, + String value, UniqueNamer globalNamer, TransformationMap transformationMap, + {bool shouldWrapPrimitives = false}) { + final (wrappedPrimitiveType, returnsWrappedPrimitive) = + maybeGetPrimitiveWrapper(type, shouldWrapPrimitives, transformationMap); + if (returnsWrappedPrimitive) { + return ( + '${(wrappedPrimitiveType as DeclaredType).name}($value)', + wrappedPrimitiveType + ); + } + if (type.isObjCRepresentable) { return (value, type); } @@ -77,3 +87,24 @@ import 'unique_namer.dart'; throw UnimplementedError('Unknown type: $type'); } } + +InitializerDeclaration buildWrapperInitializer( + PropertyDeclaration wrappedClassInstance, +) { + return InitializerDeclaration( + id: '', + params: [ + Parameter( + name: '_', + internalName: 'wrappedInstance', + type: wrappedClassInstance.type, + ) + ], + isOverriding: false, + isFailable: false, + throws: false, + async: false, + statements: ['self.${wrappedClassInstance.name} = wrappedInstance'], + hasObjCAnnotation: wrappedClassInstance.hasObjCAnnotation, + ); +} diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 409252c524..fab47a95d5 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -5,6 +5,7 @@ import '../ast/_core/interfaces/compound_declaration.dart'; import '../ast/_core/interfaces/declaration.dart'; import '../ast/_core/interfaces/nestable_declaration.dart'; +import '../ast/declarations/built_in/built_in_declaration.dart'; import '../ast/declarations/compounds/class_declaration.dart'; import '../ast/declarations/compounds/struct_declaration.dart'; import '../ast/declarations/globals/globals.dart'; @@ -51,7 +52,8 @@ List transform(List declarations, transformGlobals(globals, globalNamer, transformationMap), ]; - return transformedDeclarations + return (transformedDeclarations + + _getPrimitiveWrapperClasses(transformationMap)) ..sort((Declaration a, Declaration b) => a.id.compareTo(b.id)); } @@ -80,3 +82,11 @@ Declaration transformDeclaration( _ => throw UnimplementedError(), }; } + +List _getPrimitiveWrapperClasses( + TransformationMap transformationMap) { + return transformationMap.entries + .where((entry) => entry.key is BuiltInDeclaration) + .map((entry) => entry.value) + .toList(); +} diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart index 035c185fc7..e11510d740 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_compound.dart @@ -5,7 +5,6 @@ import '../../ast/_core/interfaces/compound_declaration.dart'; import '../../ast/_core/interfaces/declaration.dart'; import '../../ast/_core/interfaces/nestable_declaration.dart'; -import '../../ast/_core/shared/parameter.dart'; import '../../ast/declarations/built_in/built_in_declaration.dart'; import '../../ast/declarations/compounds/class_declaration.dart'; import '../../ast/declarations/compounds/members/initializer_declaration.dart'; @@ -13,6 +12,7 @@ import '../../ast/declarations/compounds/members/method_declaration.dart'; import '../../ast/declarations/compounds/members/property_declaration.dart'; import '../../parser/_core/utils.dart'; import '../_core/unique_namer.dart'; +import '../_core/utils.dart'; import '../transform.dart'; import 'transform_function.dart'; import 'transform_initializer.dart'; @@ -38,7 +38,7 @@ ClassDeclaration transformCompound( superClass: objectType, isWrapper: true, wrappedInstance: wrappedCompoundInstance, - wrapperInitializer: _buildWrapperInitializer(wrappedCompoundInstance), + wrapperInitializer: buildWrapperInitializer(wrappedCompoundInstance), ); transformationMap[originalCompound] = transformedCompound; @@ -98,24 +98,3 @@ ClassDeclaration transformCompound( return transformedCompound; } - -InitializerDeclaration _buildWrapperInitializer( - PropertyDeclaration wrappedClassInstance, -) { - return InitializerDeclaration( - id: '', - params: [ - Parameter( - name: '_', - internalName: 'wrappedInstance', - type: wrappedClassInstance.type, - ) - ], - isOverriding: false, - isFailable: false, - throws: false, - async: false, - statements: ['self.${wrappedClassInstance.name} = wrappedInstance'], - hasObjCAnnotation: wrappedClassInstance.hasObjCAnnotation, - ); -} diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart index c5e3752826..df8c0a19be 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart @@ -84,16 +84,17 @@ MethodDeclaration _transformFunction( ) .toList(); - final transformedReturnType = transformReferredType( - originalFunction.returnType, - globalNamer, - transformationMap, - ); + final localNamer = UniqueNamer(); + final resultName = localNamer.makeUnique('result'); + + final (wrapperResult, type) = maybeWrapValue( + originalFunction.returnType, resultName, globalNamer, transformationMap, + shouldWrapPrimitives: originalFunction.throws); final transformedMethod = MethodDeclaration( id: originalFunction.id, name: wrapperMethodName, - returnType: transformedReturnType, + returnType: type, params: transformedParams, hasObjCAnnotation: true, isStatic: originalFunction is MethodDeclaration @@ -107,6 +108,9 @@ MethodDeclaration _transformFunction( originalFunction, transformedMethod, globalNamer, + localNamer, + resultName, + wrapperResult, transformationMap, originalCallGenerator: originalCallStatementGenerator, ); @@ -144,10 +148,12 @@ List _generateStatements( FunctionDeclaration originalFunction, MethodDeclaration transformedMethod, UniqueNamer globalNamer, + UniqueNamer localNamer, + String resultName, + String wrappedResult, TransformationMap transformationMap, { required String Function(String arguments) originalCallGenerator, }) { - final localNamer = UniqueNamer(); final arguments = generateInvocationParams( localNamer, originalFunction.params, transformedMethod.params); var originalMethodCall = originalCallGenerator(arguments); @@ -166,22 +172,8 @@ List _generateStatements( throw UnimplementedError('Generic types are not implemented yet'); } - final resultName = localNamer.makeUnique('result'); - final methodCallStmt = 'let $resultName = $originalMethodCall'; - - final (wrappedResult, wrapperType) = maybeWrapValue( - originalFunction.returnType, - resultName, - globalNamer, - transformationMap, - ); - - assert(wrapperType.sameAs(transformedMethod.returnType)); - - final returnStmt = 'return $wrappedResult'; - return [ - methodCallStmt, - returnStmt, + 'let $resultName = $originalMethodCall', + 'return $wrappedResult', ]; } diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart index 6580e64774..235eb3cc89 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_variable.dart @@ -73,24 +73,32 @@ Declaration _transformVariable( ? originalVariable.hasSetter : !originalVariable.isConstant; + // properties that throw or are async need to be wrapped in a method if (originalVariable.throws || originalVariable.async) { final prefix = [ if (originalVariable.throws) 'try', if (originalVariable.async) 'await' ].join(' '); + final localNamer = UniqueNamer(); + final resultName = localNamer.makeUnique('result'); + + final (wrapperResult, type) = maybeWrapValue( + originalVariable.type, resultName, globalNamer, transformationMap, + shouldWrapPrimitives: originalVariable.throws); + return MethodDeclaration( id: originalVariable.id, name: wrapperPropertyName, - returnType: transformedType, + returnType: type, params: [], hasObjCAnnotation: true, isStatic: originalVariable is PropertyDeclaration ? originalVariable.isStatic : true, statements: [ - 'let result = $prefix $variableReferenceExpression', - 'return $transformedType(result)', + 'let $resultName = $prefix $variableReferenceExpression', + 'return $wrapperResult', ], throws: originalVariable.throws, async: originalVariable.async, diff --git a/pkgs/swift2objc/pubspec.yaml b/pkgs/swift2objc/pubspec.yaml index f49adf9839..32bd20a245 100644 --- a/pkgs/swift2objc/pubspec.yaml +++ b/pkgs/swift2objc/pubspec.yaml @@ -15,6 +15,7 @@ topics: - codegen dependencies: + collection: ^1.19.1 logging: ^1.3.0 meta: ^1.16.0 path: ^1.9.0 diff --git a/pkgs/swift2objc/test/integration/primitive_wrappers_input.swift b/pkgs/swift2objc/test/integration/primitive_wrappers_input.swift new file mode 100644 index 0000000000..fdf13c2c10 --- /dev/null +++ b/pkgs/swift2objc/test/integration/primitive_wrappers_input.swift @@ -0,0 +1,87 @@ +import Foundation + +public class MyClass { + public var age : Int { + get throws { + return 10 + } + } + public var name : Float { + get throws { + return 1.0 + } + } + public var height : Double { + get throws { + return 1.0 + } + } + public var isTrue : Bool { + get throws { + return true + } + } + public var str : String { + get throws { + return "Hello" + } + } + + public func intFunc() throws -> Int { + return 10 + } + public func floatFunc() throws -> Float { + return 1.0 + } + public func doubleFunc() throws -> Double { + return 1.0 + } + public func boolFunc() throws -> Bool { + return true + } + public func strFunc() throws -> String { + return "Hello" + } +} + +public var globalVar1 : Int { + get throws { + return 10 + } +} +public var globalVar2 : Float { + get throws { + return 1.0 + } +} +public var globalVar3 : Double { + get throws { + return 1.0 + } +} +public var globalVar4 : Bool { + get throws { + return true + } +} +public var globalVar5 : String { + get throws { + return "Hello" + } +} + +public func intFunc() throws -> Int { + return 10 +} +public func floatFunc() throws -> Float { + return 1.0 +} +public func doubleFunc() throws -> Double { + return 1.0 +} +public func boolFunc() throws -> Bool { + return true +} +public func strFunc() throws -> String { + return "Hello" +} \ No newline at end of file diff --git a/pkgs/swift2objc/test/integration/primitive_wrappers_output.swift b/pkgs/swift2objc/test/integration/primitive_wrappers_output.swift new file mode 100644 index 0000000000..0457b7227b --- /dev/null +++ b/pkgs/swift2objc/test/integration/primitive_wrappers_output.swift @@ -0,0 +1,150 @@ +// Test preamble text + +import Foundation + +@objc public class GlobalsWrapper: NSObject { + @objc static public func doubleFuncWrapper() throws -> DoubleWrapper { + let result = try doubleFunc() + return DoubleWrapper(result) + } + + @objc static public func globalVar1Wrapper() throws -> IntWrapper { + let result = try globalVar1 + return IntWrapper(result) + } + + @objc static public func globalVar2Wrapper() throws -> FloatWrapper { + let result = try globalVar2 + return FloatWrapper(result) + } + + @objc static public func globalVar3Wrapper() throws -> DoubleWrapper { + let result = try globalVar3 + return DoubleWrapper(result) + } + + @objc static public func globalVar4Wrapper() throws -> BoolWrapper { + let result = try globalVar4 + return BoolWrapper(result) + } + + @objc static public func globalVar5Wrapper() throws -> String { + let result = try globalVar5 + return result + } + + @objc static public func intFuncWrapper() throws -> IntWrapper { + let result = try intFunc() + return IntWrapper(result) + } + + @objc static public func strFuncWrapper() throws -> String { + return try strFunc() + } + + @objc static public func boolFuncWrapper() throws -> BoolWrapper { + let result = try boolFunc() + return BoolWrapper(result) + } + + @objc static public func floatFuncWrapper() throws -> FloatWrapper { + let result = try floatFunc() + return FloatWrapper(result) + } + +} + +@objc public class MyClassWrapper: NSObject { + var wrappedInstance: MyClass + + init(_ wrappedInstance: MyClass) { + self.wrappedInstance = wrappedInstance + } + + @objc public func doubleFunc() throws -> DoubleWrapper { + let result = try wrappedInstance.doubleFunc() + return DoubleWrapper(result) + } + + @objc public func age() throws -> IntWrapper { + let result = try wrappedInstance.age + return IntWrapper(result) + } + + @objc public func str() throws -> String { + let result = try wrappedInstance.str + return result + } + + @objc public func name() throws -> FloatWrapper { + let result = try wrappedInstance.name + return FloatWrapper(result) + } + + @objc public func height() throws -> DoubleWrapper { + let result = try wrappedInstance.height + return DoubleWrapper(result) + } + + @objc public func isTrue() throws -> BoolWrapper { + let result = try wrappedInstance.isTrue + return BoolWrapper(result) + } + + @objc public func intFunc() throws -> IntWrapper { + let result = try wrappedInstance.intFunc() + return IntWrapper(result) + } + + @objc public func strFunc() throws -> String { + return try wrappedInstance.strFunc() + } + + @objc public func boolFunc() throws -> BoolWrapper { + let result = try wrappedInstance.boolFunc() + return BoolWrapper(result) + } + + @objc public func floatFunc() throws -> FloatWrapper { + let result = try wrappedInstance.floatFunc() + return FloatWrapper(result) + } + +} + +@objc public class BoolWrapper: NSObject { + var wrappedInstance: Bool + + init(_ wrappedInstance: Bool) { + self.wrappedInstance = wrappedInstance + } + +} + +@objc public class DoubleWrapper: NSObject { + var wrappedInstance: Double + + init(_ wrappedInstance: Double) { + self.wrappedInstance = wrappedInstance + } + +} + +@objc public class FloatWrapper: NSObject { + var wrappedInstance: Float + + init(_ wrappedInstance: Float) { + self.wrappedInstance = wrappedInstance + } + +} + +@objc public class IntWrapper: NSObject { + var wrappedInstance: Int + + init(_ wrappedInstance: Int) { + self.wrappedInstance = wrappedInstance + } + +} +