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
  • Loading branch information
rolfbjarne committed May 23, 2024
1 parent 7bbefa9 commit 153706d
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 5 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 @@ -490,6 +490,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
}
}
5 changes: 5 additions & 0 deletions tests/bindings-test/dotnet/shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<!-- We're warning-free, so let's make sure we stay that way -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>Nullable</WarningsAsErrors>

<DefineConstants>$(DefineConstants);BINDINGS_TEST</DefineConstants>
</PropertyGroup>

<Import Project="$(RootTestsDirectory)/common/shared-dotnet.csproj" />
Expand Down Expand Up @@ -57,6 +59,9 @@
<Compile Include="$(RootTestsDirectory)\common\TestRuntime.cs">
<Link>TestRuntime.cs</Link>
</Compile>
<Compile Include="$(RootTestsDirectory)\common\TestRuntime.RunAsync.cs">
<Link>TestRuntime.RunAsync.cs</Link>
</Compile>
<Compile Include="$(RootTestsDirectory)\..\tools\common\ApplePlatform.cs">
<Link>ApplePlatform.cs</Link>
</Compile>
Expand Down
16 changes: 11 additions & 5 deletions tests/common/TestRuntime.RunAsync.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#if !__WATCHOS__
#if !__WATCHOS__ && !BINDINGS_TEST
#define CAN_SHOW_ASYNC_UI
#endif

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -157,8 +158,13 @@ public void Show (XImage? imageToShow)
var vc = new AsyncController (imageToShow);

#if HAS_UIKIT
window = UIApplication.SharedApplication.KeyWindow;
initialRootViewController = window.RootViewController;
// UIApplication.KeyWindow is deprecated, so we have to do this monstruosity instead (https://stackoverflow.com/a/58031897/183422):
window = UIApplication
.SharedApplication
.ConnectedScenes
.SelectMany<UIScene, UIWindow> (v => (v as UIWindowScene)?.Windows ?? Array.Empty<UIWindow> ())
.Last (v => v.IsKeyWindow);
initialRootViewController = window.RootViewController!;
navigation = initialRootViewController as UINavigationController;

// Pushing something to a navigation controller doesn't seem to work on phones
Expand Down Expand Up @@ -237,7 +243,7 @@ public override void ViewDidLoad ()
}

#if HAS_UIKIT
View.BackgroundColor = backgroundColor;
View!.BackgroundColor = backgroundColor;
#else
View.WantsLayer = true;
View.Layer.BackgroundColor = backgroundColor.CGColor;
Expand All @@ -257,7 +263,7 @@ public override void ViewDidLoad ()
}

namespace Foundation {
public static class NSRunLoop_Extensions {
static class NSRunLoop_Extensions {
// Returns true if task completed before the timeout,
// otherwise returns false
public static bool RunUntil (this NSRunLoop self, Task task, TimeSpan timeout)
Expand Down
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 153706d

Please sign in to comment.