Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SwiftBindings] prototype for SwiftOptional #2896

Open
wants to merge 1 commit into
base: feature/swift-bindings
Choose a base branch
from

Conversation

stephen-hawley
Copy link

@stephen-hawley stephen-hawley commented Dec 18, 2024

This is a prototype of SwiftOptional. It's meant to flesh out issues that we will encounter in binding enums and binding generic types.

What to expect here:

  • ISwiftObject now has methods for marshaling payloads to and from Swift. The from version is effectively a factory method
  • Made the TypeMetadata helper struct public and added the supported methods
  • Added MetadataRequest enum which is necessary for TypeMetadata accessor functions
  • Added SwiftOptional to show how to get cases from an enum and construct enum methods + some convenience methods
  • Put in passing tests (yay!)

What this is missing:

ISwiftEquatable and ISwiftHashable implementations for SwiftOptional. Mostly because those interface definitions and the necessary proxies aren't here (yet).

No memory management. This type should implement IDisposable.

What I like:

  • implementing ISwiftObject explicitly makes the public facing API less cluttered
  • putting the marshaling code in ISwiftObject makes it easier to hide the payload
  • The number of pinvokes is reduced to 1 compared to BTfS
  • Calling the value witness table functions is likely more efficient then pinvoking into a helper (which is what BTfS did)
  • The marshaling code is pretty clean compared to BTfS
  • Using the actual code in the unit tests felt very idiomatic.

What I don't like:

  • the syntax SwiftObjectHelper<SwiftOptional<T>>.GetTypeMetadata() to get the metadata within a class, but I understand that it's the cost of the explicit interface and static methods and keeping only one copy of the type metadata.
  • that MarshalToSwift will return a different pointer value than its passed (see below)
  • I don't like IntPtr in function signatures. I'm thinking that this should probably be SwiftHandle instead.
  • The empty private constructor in SwiftOptional<T>. This is an important point for discussion. What we need is a constructor that has a unique signature that is unpronounceable in Swift so that there will never be conflicts in the bindings. In BTfS I used an enum that was unique to BTfS. We could make the ctor do all the marshaling and take a SwiftHandle (which also shouldn't be pronounceable in Swift).

Why MarshalToSwift returns a different pointer:
For generic arguments to functions, Swift uses a pointer to value types. For heap allocated types, the actual pointer is used NOT a pointer to a pointer. Because of this, a binding to a class will have something that looks like this for the marshaling:

    IntPtr ISwiftObject.MarshalToSwift(IntPtr swiftDest)
    {
        return (IntPtr)_handle;
    }

Other notes:
I added HasValue and Value as those will feel better to C# programmers. In general for binding, we should consider binding types that are represented as classes as partial classes so we can add convenience methods to them that have access to the internals.

The pattern I used for enums follows the documentation that I wrote for binding enums in that:

  • cases are kept in a separate enum named [TYPE]Cases
  • factory methods are named New[CASENAME]
  • the case accessor of the DU is a property named Case
  • the payload accessor(s) are named [CASENAME]

Given C#'s lack of discriminated unions, this seemed reasonable.

@stephen-hawley stephen-hawley added the area-SwiftBindings Swift bindings for .NET label Dec 18, 2024
/// <summary>
/// Marshals this object to a Swift destination
/// </summary>
unsafe IntPtr MarshalToSwift(IntPtr swiftDest);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this has to be unsafe

/// <summary>
/// Creates a new Swift object from a given payload
/// </summary>
public static abstract ISwiftObject NewFromPayload(IntPtr payload);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you that SwiftHandle would feel better


namespace Swift.Runtime;

#nullable enable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a broader discussion, should we enable nullable on all Swft.Runtime code?

public bool HasValue => Case == SwiftOptionalCases.Some;
}

internal static class PInvokesForSwiftOptional {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internal static class PInvokesForSwiftOptional {
internal static class PInvokesForSwiftOptional {

/// <summary>
/// Represents a class for marshaling data to and from Swift
/// </summary>
public static class SwiftMarshal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Sometimes a new line is used before a curly brace and sometimes not


internal static class PInvokesForSwiftOptional {
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSqMa")]
public static extern TypeMetadata _MetadataAccessor(MetadataRequest request, TypeMetadata typeMetadata);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I'm not sure about the naming convention. In the generated code I think we use the "PInvoke" prefix. I dont have strong opinion what convention should we use, just want to be consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-SwiftBindings Swift bindings for .NET
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants