Skip to content

Commit

Permalink
[runtime] Add support for additional type encodings. Fixes xamarin#18562
Browse files Browse the repository at this point in the history
.

Fixes xamarin#18562.
  • Loading branch information
rolfbjarne committed May 21, 2024
1 parent 8ebceb0 commit 699e94b
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 0 deletions.
21 changes: 21 additions & 0 deletions runtime/runtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,27 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
// COOP: no managed memory access: any mode
switch (type [0]) {
case _C_ID:
type++;
if (*type == '"') {
// https://github.com/xamarin/xamarin-macios/issues/18562
// @"..." is an object with the class name inside the quotes.
// https://github.com/llvm/llvm-project/blob/24a082878f7baec3651de56d54e5aa2b75a21b5f/clang/lib/AST/ASTContext.cpp#L8505-L8516
type++;
while (*type && *type != '"')
type++;
type++;
} else if (*type == '?' && type [1] == '<') {
// https://github.com/xamarin/xamarin-macios/issues/18562
// @?<...> is a block pointer
// https://github.com/llvm/llvm-project/blob/24a082878f7baec3651de56d54e5aa2b75a21b5f/clang/lib/AST/ASTContext.cpp#L8405-L8426
type += 2;
do {
type = objc_skip_type (type);
} while (*type && *type != '>');
if (*type)
type++;
}
return type;
case _C_CLASS:
case _C_SEL:
case _C_CHR:
Expand Down
14 changes: 14 additions & 0 deletions runtime/trampolines.m
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,20 @@
if (desc [1] == '?') {
// Example: [AVAssetImageGenerator generateCGImagesAsynchronouslyForTimes:completionHandler:] = 'v16@0:4@8@?12'
length = 2;
if (desc [2] == '<') {
length = 3;
do {
int nestedLength = get_type_description_length (desc + length);
length += nestedLength;
} while (desc [length] && desc [length] != '>');
if (desc [length] == '>')
length++;
}
} else if (desc [1] == '"') {
length = 2;
while (desc [length] && desc [length] != '"')
length++;
length++;
} else {
length = 1;
}
Expand Down
15 changes: 15 additions & 0 deletions tests/bindings-test/ApiDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,21 @@ interface IProtocolWithBlockProperties { }
interface SwiftTestClass {
[Export ("SayHello")]
string SayHello ();

[Export ("DoSomethingWithMessage:")]
string DoSomething (string message);

[Export ("DoSomethingAsyncWithMessage:completionHandler:")]
void DoSomethingAsync (string message, Action<NSString> completionHandler);

[Export ("DoSomethingComplexAsyncWithMessage:complexParameter:completionHandler:")]
// The type for 'complexParameter' is something like: Func<Func<Int16, Int64>, NSString>
// But the generator can't handle that, it generates code that doesn't compile.
// So just bind it as IntPtr.
// This is not a problem for this test, because the point of this test is to verify that
// we're able to skip the corresponding objc type encoding, and for that we don't need to
// provide an actual argument when calling the method.
void DoSomethingComplexAsync (string message, IntPtr complexParameter, Action<NSString> completionHandler);
}
#endif
}
21 changes: 21 additions & 0 deletions tests/bindings-test/RuntimeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ public void SwiftTest ()
using var obj = new SwiftTestClass ();
Assert.AreEqual ("Hello from Swift", obj.SayHello (), "Hello");
}

[Test]
public void SwiftTypeEncodings ()
{
TestRuntime.AssertXcodeVersion (13, 0);

using var obj = new SwiftTestClass ();

Assert.AreEqual ("42", obj.DoSomething ("42"), "DoSomething");

string asyncResult = null;
obj.DoSomethingAsync ("dolphins", (v) => asyncResult = v);
var done = TestRuntime.RunAsync (TimeSpan.FromSeconds (5), () => asyncResult is not null);
Assert.AreEqual ("dolphins", asyncResult, "DoSomethingAsync");
Assert.IsTrue (done, "Done");

obj.DoSomethingComplexAsync ("fish", IntPtr.Zero, (v) => asyncResult = v);
done = TestRuntime.RunAsync (TimeSpan.FromSeconds (5), () => asyncResult is not null);
Assert.AreEqual ("fish", asyncResult, "DoSomethingComplexAsync");
Assert.IsTrue (done, "Done 2");
}
#endif
}
}
29 changes: 29 additions & 0 deletions tests/test-libraries/libSwiftTest.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
import Foundation

@objc(SwiftTestClass)
@available(iOS 15, tvOS 15, macOS 12, macCatalyst 12, watchOS 8, *)
public class SwiftTestClass : NSObject {
@objc
public func SayHello() -> String {
return "Hello from Swift"
}

@objc
// encoding for 'message': @"NSString"
public func DoSomething(message: String) -> String {
return message
}

@objc
// encoding for 'message': @"NSString"
// objc encoding for implicit callback: @?<v@?@"NSString">
public func DoSomethingAsync(message: String) async -> String {
do {
try await Task.sleep(nanoseconds: 1)
} catch {}
return message;
}

@objc
// objc encoding for 'message': @"NSString"
// objc encoding for 'complexParameter': @?<@"NSString"@?@?<q@?s>>
// in particular this argument has nested <<>>
// objc encoding for implicit callback: @?<v@?@"NSString">
public func DoSomethingComplexAsync(message: String, complexParameter: @escaping ((Int16) -> Int64) -> String?) async -> String {
do {
try await Task.sleep(nanoseconds: 1)
} catch {}
return message;
}
}

0 comments on commit 699e94b

Please sign in to comment.