-
Notifications
You must be signed in to change notification settings - Fork 206
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] Add array support #2948
base: feature/swift-bindings
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on https://developer.apple.com/documentation/swift/array, array is a @frozen
generic struct. Do we plan to remove this projection code (SwiftArray.cs
) when the tooling is able to successfully project any @frozen
generic struct or is the plan that selected Swift types have special projections like this?
I'm just curious about the long-term plan. I think it makes sense for these "often-used" types to have special projections if they need to be handled with caution to satisfy both Swift constraints and .NET developers.
/// Represents a Swift array | ||
/// </summary> | ||
/// <typeparam name="T">the element type</typeparam> | ||
public sealed class SwiftArray<T> : ISwiftObject, IList<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How memory is handled? Who owns the memory?
I wouldn't include it unless it's actually used. Let's review the UX at some point (added to the plan). |
Could you add the manual bindings to the type database xml? |
/// Represents a Swift array | ||
/// </summary> | ||
/// <typeparam name="T">the element type</typeparam> | ||
public sealed class SwiftArray<T> : ISwiftObject, IList<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array conforms to a bunch of protocols in Swift. Assuming that we would like to have protocol constraints projected as generic constraints on c# side, how are we gonna make this work?
E.g. to call https://developer.apple.com/documentation/storekit/product/products(for:) with SwiftArray
we will need to be able to somehow model Collection<T>
conformance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g. to call https://developer.apple.com/documentation/storekit/product/products(for:) with SwiftArray we will need to be able to somehow model Collection conformance
Do we need to model Collection<T>
for this use-case? Is this requirement applied to the method where the conformance is needed? Is the conformance already available in the dynamic library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it depends on how we want the projection to look like.
The pattern used by `products(for:) calls for something like this (I skip async)
public Product products<T> (T identifiers) where T : ICollection<Swift.String>
I guess we could not map ICollection into c#, and have
public Product products<T> (T identifiers)
but then we would have to hardcode some stuff:
- assumption that anything which goes into the function is valid
- we would still somehow need to obtain the protocol conformance descriptor and PWT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assumption that anything which goes into the function is valid
I would assume the input is valid until focus on protocols support.
we would still somehow need to obtain the protocol conformance descriptor and PWT
Since the protocol's mangled name and generic type are known at runtime, can we retrieve the conformance using Swift runtime API?
/// <summary> | ||
/// Constructs a new SwiftArray from the given handle to a Swift array | ||
/// </summary> | ||
public SwiftArray(SwiftHandle handle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be public?
|
||
[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])] | ||
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicis")] | ||
public static unsafe extern void Set(SwiftSelf self, SwiftHandle value, nint index, TypeMetadata elementMetadata); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a bit less confusing order would be SwiftHandle handle, nint index, TypeMetadata elementMetadata, SwiftSelf self
. Current version will compile anyway as self
will go into register and be removed from parameter list before the call, but I think it would be nice to stay consistent with how this looks in llvm-ir where self
is the last parameter. (we enforce that for SwiftSelf<T>
as well) Also applies to others.
|
||
[UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])] | ||
[DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicig")] | ||
public static unsafe extern void Get(SwiftIndirectResult result, nint index, SwiftHandle handle, TypeMetadata elementMetadata); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For non-mutating methods, the instance gets passed in as the first argument (not using the self register) and the type metadata of the generic parameter gets passed in.
The mutating functions look as expected, but the non-mutating are truly interesting. Her a pointer to the copy is technically the second argument. This is something that generated projections would get terribly wrong. Is there any common pattern to the functions which behave like this on other types? Or each case requires manual investigation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is "funcSelfKind": "NonMutating"
property in the ABI.
This pull request adds a binding for
Swift.Array
.This binding demonstrates a fundamental difference between the design of core types in Swift vs. .NET.
Swift uses consistent "copy on write" semantics for pretty much everything. This means that if you write this in Swift:
you will see
This is clearly not what a C# developer would expect and as such this binding doesn't reflect that. Any operation that mutates an instance of the array will effectively throw away the old instance which isn't precisely ideal, but here we are.
This is another example of a generic binding but this is different from
SwiftOptional
in that there are two types of methods: non-mutating (ie, normal methods) and mutating methods. These have very different calling conventions.For non-mutating methods, the instance gets passed in as the first argument (not using the self register) and the type metadata of the generic parameter gets passed in.
For mutating methods, a pointer to a copy of the instance gets passed in in the self register and the type metadata of the array gets passed in.
Like many Swift primitives, this type is...unusual. I think it's technically a value type (it sure is acting like one), but under the hood the actual implementation may be one of several types. As a default, it looks like it's an
NSArray
for most cases.Question: should I include an
IDisposable
implementation here or do we want to put that into a common base class?