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 c99e1802e3..f4a43859cf 100644 --- a/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart +++ b/pkgs/swift2objc/lib/src/ast/_core/shared/referred_type.dart @@ -143,3 +143,35 @@ class OptionalType extends AstNode implements ReferredType { visitor.visit(child); } } + +/// An inout type, like Swift's pass-by-reference params. Eg `inout Int`. +class InoutType extends AstNode implements ReferredType { + final ReferredType child; + + @override + bool get isObjCRepresentable => false; + + @override + String get swiftType => child.swiftType; + + @override + bool _sameAs(ReferredType other) => + other is InoutType && child.sameAs(other.child); + + @override + ReferredType get aliasedType => InoutType(child.aliasedType); + + InoutType(this.child); + + @override + String toString() => 'inout $child'; + + @override + void visit(Visitation visitation) => visitation.visitInoutType(this); + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(child); + } +} diff --git a/pkgs/swift2objc/lib/src/ast/visitor.dart b/pkgs/swift2objc/lib/src/ast/visitor.dart index 6c4a60ea4c..b486f08b3b 100644 --- a/pkgs/swift2objc/lib/src/ast/visitor.dart +++ b/pkgs/swift2objc/lib/src/ast/visitor.dart @@ -78,6 +78,7 @@ abstract class Visitation { void visitDeclaredType(DeclaredType node) => visitReferredType(node); void visitGenericType(GenericType node) => visitReferredType(node); void visitOptionalType(OptionalType node) => visitReferredType(node); + void visitInoutType(InoutType node) => visitReferredType(node); void visitDeclaration(Declaration node) => visitAstNode(node); void visitBuiltInDeclaration(BuiltInDeclaration node) => visitDeclaration(node); diff --git a/pkgs/swift2objc/lib/src/parser/parsers/parse_type.dart b/pkgs/swift2objc/lib/src/parser/parsers/parse_type.dart index 52ce80b474..7f3d7275d5 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/parse_type.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/parse_type.dart @@ -145,9 +145,8 @@ typedef PrefixParselet = if (_tokenId(fragments[0]) == 'text: ') { fragments = fragments.slice(1); } - // TODO(https://github.com/dart-lang/native/issues/1754): Mark the returned - // type as inout (maybe wrap it in a new InOutType AST node?). - return parseType(context, symbolgraph, fragments); + final (type, suffix) = parseType(context, symbolgraph, fragments); + return (InoutType(type), suffix); } Map _prefixParsets = { diff --git a/pkgs/swift2objc/lib/src/transformer/_core/utils.dart b/pkgs/swift2objc/lib/src/transformer/_core/utils.dart index 35204c8be9..e4938de933 100644 --- a/pkgs/swift2objc/lib/src/transformer/_core/utils.dart +++ b/pkgs/swift2objc/lib/src/transformer/_core/utils.dart @@ -24,6 +24,17 @@ import 'unique_namer.dart'; TransformationState state, { bool shouldWrapPrimitives = false, }) { + if (type is InoutType) { + final (newValue, newType) = maybeWrapValue( + type.child, + value, + globalNamer, + state, + shouldWrapPrimitives: shouldWrapPrimitives, + ); + return (newValue, InoutType(newType)); + } + final (wrappedPrimitiveType, returnsWrappedPrimitive) = maybeGetPrimitiveWrapper(type, shouldWrapPrimitives, state); if (returnsWrappedPrimitive) { @@ -78,6 +89,11 @@ import 'unique_namer.dart'; ReferredType type, String value, ) { + if (type is InoutType) { + final (newValue, newType) = maybeUnwrapValue(type.child, value); + return (newValue, InoutType(newType)); + } + if (!type.isObjCRepresentable) { return (value, type); } diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart index b78eaf913c..6cd7edbf26 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_function.dart @@ -165,11 +165,14 @@ String generateInvocationParams( ); assert(unwrappedType.sameAs(originalParam.type)); + final invocationValue = originalParam.type is InoutType + ? '&$unwrappedParamValue' + : unwrappedParamValue; argumentsList.add( originalParam.name.isEmpty || originalParam.name == '_' - ? unwrappedParamValue - : '${originalParam.name}: $unwrappedParamValue', + ? invocationValue + : '${originalParam.name}: $invocationValue', ); } return argumentsList.join(', '); diff --git a/pkgs/swift2objc/lib/src/transformer/transformers/transform_referred_type.dart b/pkgs/swift2objc/lib/src/transformer/transformers/transform_referred_type.dart index 6793d2e829..decd5f98d2 100644 --- a/pkgs/swift2objc/lib/src/transformer/transformers/transform_referred_type.dart +++ b/pkgs/swift2objc/lib/src/transformer/transformers/transform_referred_type.dart @@ -5,6 +5,7 @@ import '../../ast/_core/interfaces/declaration.dart'; import '../../ast/_core/shared/referred_type.dart'; import '../../ast/declarations/typealias_declaration.dart'; +import '../_core/primitive_wrappers.dart'; import '../_core/unique_namer.dart'; import '../transform.dart'; @@ -16,6 +17,18 @@ ReferredType transformReferredType( UniqueNamer globalNamer, TransformationState state, ) { + if (type is InoutType) { + final (wrappedPrimitive, hasWrappedPrimitive) = maybeGetPrimitiveWrapper( + type.child, + true, + state, + ); + if (hasWrappedPrimitive) { + return InoutType(wrappedPrimitive); + } + return InoutType(transformReferredType(type.child, globalNamer, state)); + } + if (type.isObjCRepresentable) return type; if (type is GenericType) { diff --git a/pkgs/swift2objc/test/integration/inout_input.swift b/pkgs/swift2objc/test/integration/inout_input.swift new file mode 100644 index 0000000000..7677bd2014 --- /dev/null +++ b/pkgs/swift2objc/test/integration/inout_input.swift @@ -0,0 +1,21 @@ +import Foundation + +public class MyClass { + public func update(_ value: inout Int) { + value += 1 + } +} + +public class MyOtherClass { + public init() {} +} + +public func swapTwoInts(_ a: inout Int, _ b: inout Int) { + let temp = a + a = b + b = temp +} + +public func replaceOther(_ value: inout MyOtherClass) { + value = MyOtherClass() +} diff --git a/pkgs/swift2objc/test/integration/inout_output.swift b/pkgs/swift2objc/test/integration/inout_output.swift new file mode 100644 index 0000000000..9f02e66594 --- /dev/null +++ b/pkgs/swift2objc/test/integration/inout_output.swift @@ -0,0 +1,50 @@ +// Test preamble text + +import Foundation + +@objc public class GlobalsWrapper: NSObject { + @objc static public func swapTwoIntsWrapper(_ a: IntWrapper, _ b: IntWrapper) { + return swapTwoInts(&a.wrappedInstance, &b.wrappedInstance) + } + + @objc static public func replaceOtherWrapper(_ value: MyOtherClassWrapper) { + return replaceOther(&value.wrappedInstance) + } + +} + +@objc public class MyOtherClassWrapper: NSObject { + var wrappedInstance: MyOtherClass + + init(_ wrappedInstance: MyOtherClass) { + self.wrappedInstance = wrappedInstance + } + + @objc override public init() { + wrappedInstance = MyOtherClass() + } + +} + +@objc public class MyClassWrapper: NSObject { + var wrappedInstance: MyClass + + init(_ wrappedInstance: MyClass) { + self.wrappedInstance = wrappedInstance + } + + @objc public func update(_ value: IntWrapper) { + return wrappedInstance.update(&value.wrappedInstance) + } + +} + +@objc public class IntWrapper: NSObject { + var wrappedInstance: Int + + init(_ wrappedInstance: Int) { + self.wrappedInstance = wrappedInstance + } + +} + diff --git a/pkgs/swift2objc/test/unit/parse_type_test.dart b/pkgs/swift2objc/test/unit/parse_type_test.dart index 491f1b3399..d2f3b86440 100644 --- a/pkgs/swift2objc/test/unit/parse_type_test.dart +++ b/pkgs/swift2objc/test/unit/parse_type_test.dart @@ -67,6 +67,68 @@ void main() { expect(remaining.length, 0); }); + test('Inout', () { + final fragments = Json( + jsonDecode(''' + [ + { + "kind": "keyword", + "spelling": "inout" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Int", + "preciseIdentifier": "s:Si" + } + ] + '''), + ); + + final (type, remaining) = parseType( + context, + parsedSymbols, + TokenList(fragments), + ); + + expect(type.sameAs(InoutType(intType)), isTrue); + expect(remaining.length, 0); + }); + + test('Inout non-primitive', () { + final fragments = Json( + jsonDecode(''' + [ + { + "kind": "keyword", + "spelling": "inout" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "spelling": "Foo", + "preciseIdentifier": "Foo" + } + ] + '''), + ); + + final (type, remaining) = parseType( + context, + parsedSymbols, + TokenList(fragments), + ); + + expect(type.sameAs(InoutType(classFoo.asDeclaredType)), isTrue); + expect(remaining.length, 0); + }); + test('Empty tuple', () { final fragments = Json( jsonDecode('''