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

Refactor WinRT projection protocols and implement delegate boxing #94

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Generator/Sources/ProjectionModel/SupportModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ extension SupportModules.WinRT {
public static var iinspectablePointer: SwiftType { .chain(moduleName, "IInspectablePointer") }
public static var iinspectableProjection: SwiftType { .chain(moduleName, "IInspectableProjection") }

public static var winRTProjection: SwiftType { .chain(moduleName, "WinRTProjection") }
public static var winRTTwoWayProjection: SwiftType { .chain(moduleName, "WinRTTwoWayProjection") }
public static var winRTBoxableProjection: SwiftType { .chain(moduleName, "WinRTBoxableProjection") }
public static var winRTEnumProjection: SwiftType { .chain(moduleName, "WinRTEnumProjection") }
public static var winRTStructProjection: SwiftType { .chain(moduleName, "WinRTStructProjection") }
public static var winRTInterfaceProjection: SwiftType { .chain(moduleName, "WinRTInterfaceProjection") }
public static var winRTDelegateProjection: SwiftType { .chain(moduleName, "WinRTDelegateProjection") }
public static var winRTClassProjection: SwiftType { .chain(moduleName, "WinRTClassProjection") }

public static func winRTArrayProjection(of type: SwiftType) -> SwiftType {
.chain([ .init(moduleName), .init("WinRTArrayProjection", genericArgs: [type]) ])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,12 @@ extension SwiftProjection {
let primitiveType = WinRTPrimitiveType(fromSystemNamespaceType: type.definition.name) {
projectionType = SupportModules.WinRT.ireferenceUnboxingProjection(of: primitiveType)
}
else {
else if type.definition is EnumDefinition || type.definition is StructDefinition || type.definition is DelegateDefinition {
projectionType = SupportModules.WinRT.ireferenceUnboxingProjection(of: typeProjection.projectionType)
}
else {
return nil
}

return TypeProjection(
abiType: .optional(wrapped: .unsafeMutablePointer(to: .chain(abiModuleName, CAbi.ireferenceName))),
Expand Down
49 changes: 30 additions & 19 deletions Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ internal func writeABIProjectionConformance(_ typeDefinition: TypeDefinition, ge

if let enumDefinition = typeDefinition as? EnumDefinition {
assert(genericArgs == nil)
let protocolConformances = [
SupportModules.WinRT.winRTBoxableProjection,
SwiftType.chain("WindowsRuntime", "IntegerEnumProjection")
]
try writer.writeExtension(
type: .identifier(projection.toTypeName(enumDefinition)),
protocolConformances: protocolConformances) { writer in
protocolConformances: [ SupportModules.WinRT.winRTEnumProjection ]) { writer in
// public static var typeName: String { "..." }
try writeTypeNameProperty(type: enumDefinition.bindType(), to: writer)

// public static var ireferenceID: COM.COMInterfaceID { .init(...) }
try writeBoxableIReferenceID(boxableType: enumDefinition.bindType(), to: writer)
try writeIReferenceIDProperty(boxableType: enumDefinition.bindType(), to: writer)
}
return
}
Expand Down Expand Up @@ -106,7 +105,7 @@ fileprivate func writeStructProjectionExtension(
to writer: SwiftSourceFileWriter) throws {
let isInert = try projection.isProjectionInert(structDefinition)

var protocolConformances = [SupportModules.WinRT.winRTBoxableProjection]
var protocolConformances = [SupportModules.WinRT.winRTStructProjection]
if isInert {
protocolConformances.append(SupportModules.COM.abiInertProjection)
}
Expand All @@ -124,8 +123,11 @@ fileprivate func writeStructProjectionExtension(
// public typealias ABIValue = <abi-type>
writer.writeTypeAlias(visibility: .public, name: "ABIValue", target: abiType)

// public static var typeName: String { "..." }
try writeTypeNameProperty(type: structDefinition.bindType(), to: writer)

// public static var ireferenceID: COM.COMInterfaceID { .init(...) }
try writeBoxableIReferenceID(boxableType: structDefinition.bindType(), to: writer)
try writeIReferenceIDProperty(boxableType: structDefinition.bindType(), to: writer)

// public static var abiDefaultValue: ABIValue { .init() }
writer.writeComputedProperty(
Expand Down Expand Up @@ -239,7 +241,7 @@ fileprivate func writeStructSwiftToABIInitializerParam(
}
}

fileprivate func writeBoxableIReferenceID(boxableType: BoundType, to writer: SwiftTypeDefinitionWriter) throws {
fileprivate func writeIReferenceIDProperty(boxableType: BoundType, to writer: SwiftTypeDefinitionWriter) throws {
let ireferenceParameterizedInterfaceID = UUID(uuidString: "61c17706-2d65-11e0-9ae8-d48564015472")!

// public static var ireferenceID: COM.COMInterfaceID { UUID(...) }
Expand All @@ -262,11 +264,12 @@ fileprivate func writeClassProjectionType(
let projectionTypeName = try projection.toProjectionTypeName(classDefinition)
try writer.writeEnum(
visibility: SwiftProjection.toVisibility(classDefinition.visibility),
name: projectionTypeName, protocolConformances: [ SupportModules.WinRT.winRTProjection ]) { writer throws in
name: projectionTypeName,
protocolConformances: [ SupportModules.WinRT.winRTClassProjection ]) { writer throws in
let typeName = try projection.toTypeName(classDefinition)
let composable = try classDefinition.hasAttribute(ComposableAttribute.self)

try writeCOMProjectionConformance(
try writeReferenceTypeProjectionConformance(
apiType: classDefinition.bindType(),
abiType: defaultInterface.asBoundType,
toSwiftBody: { writer, paramName in
Expand Down Expand Up @@ -315,7 +318,7 @@ fileprivate func writeInterfaceOrDelegateProjectionType(
to writer: some SwiftDeclarationWriter) throws {
precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition)
let projectionProtocol = type.definition is InterfaceDefinition
? SupportModules.WinRT.winRTTwoWayProjection : SupportModules.COM.comTwoWayProjection
? SupportModules.WinRT.winRTInterfaceProjection : SupportModules.WinRT.winRTDelegateProjection

try writer.writeEnum(
visibility: SwiftProjection.toVisibility(type.definition.visibility),
Expand All @@ -324,7 +327,7 @@ fileprivate func writeInterfaceOrDelegateProjectionType(

let importClassName = "Import"

try writeCOMProjectionConformance(
try writeReferenceTypeProjectionConformance(
apiType: type, abiType: type,
toSwiftBody: { writer, paramName in
if type.definition is InterfaceDefinition {
Expand Down Expand Up @@ -365,8 +368,14 @@ fileprivate func writeInterfaceOrDelegateProjectionType(
}
}

internal func writeTypeNameProperty(type: BoundType, to writer: SwiftTypeDefinitionWriter) throws {
let typeName = try WinRTTypeName.from(type: type).description
writer.writeStoredProperty(visibility: .public, static: true, declarator: .let, name: "typeName",
initialValue: "\"\(typeName)\"")
}

/// Writes members implementing the COMProjection or WinRTProjection protocol
internal func writeCOMProjectionConformance(
internal func writeReferenceTypeProjectionConformance(
apiType: BoundType, abiType: BoundType,
toSwiftBody: (_ writer: inout SwiftStatementWriter, _ paramName: String) throws -> Void,
toCOMBody: (_ writer: inout SwiftStatementWriter, _ paramName: String) throws -> Void,
Expand All @@ -379,16 +388,18 @@ internal func writeCOMProjectionConformance(
writer.writeTypeAlias(visibility: .public, name: "COMVirtualTable",
target: try projection.toABIVirtualTableType(abiType))

// public static var typeName: String { "..." }
try writeTypeNameProperty(type: apiType, to: writer)

// public static var interfaceID: COM.COMInterfaceID { COMInterface.iid }
writer.writeComputedProperty(visibility: .public, static: true, name: "interfaceID", type: SupportModules.COM.comInterfaceID) { writer in
writer.writeStatement("COMInterface.iid")
}

if !(abiType.definition is DelegateDefinition) {
// Delegates are IUnknown whereas interfaces are IInspectable
let runtimeClassName = try WinRTTypeName.from(type: apiType).description
writer.writeStoredProperty(visibility: .public, static: true, declarator: .let, name: "runtimeClassName",
initialValue: "\"\(runtimeClassName)\"")
if apiType.definition is DelegateDefinition {
// Delegates can be boxed to IReference<T>
// public static var ireferenceID: COM.COMInterfaceID { .init(...) }
try writeIReferenceIDProperty(boxableType: apiType, to: writer)
}

let comReferenceType = SupportModules.COM.comReference(to: .identifier("COMInterface"))
Expand Down
34 changes: 30 additions & 4 deletions InteropTests/Tests/InspectableBoxingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,57 @@ class InspectableBoxingTests: WinRTTestCase {
typealias CppBoxing = WinRTComponent.InspectableBoxing
typealias SwiftBoxing = WindowsRuntime.IInspectableBoxing

func testRoundTripOfPrimitiveTypeWithIdentityProjection() throws {
func testRoundTripOfPrimitiveWithIdentityProjection() throws {
let original = Int32(42)
XCTAssertEqual(try SwiftBoxing.unboxInt32(SwiftBoxing.box(original)), original)
XCTAssertEqual(try SwiftBoxing.unboxInt32(CppBoxing.boxInt32(original)), original)
XCTAssertEqual(try CppBoxing.unboxInt32(SwiftBoxing.box(original)), original)
}

func testRoundTripOfPrimitiveTypeWithAllocatingProjection() throws {
func testRoundTripOfPrimitiveWithAllocatingProjection() throws {
let original = "Hello"
XCTAssertEqual(try SwiftBoxing.unboxString(SwiftBoxing.box(original)), original)
XCTAssertEqual(try SwiftBoxing.unboxString(CppBoxing.boxString(original)), original)
XCTAssertEqual(try CppBoxing.unboxString(SwiftBoxing.box(original)), original)
}

func testRoundTripOfEnumType() throws {
func testRoundTripOfEnum() throws {
let original = MinimalEnum.one
XCTAssertEqual(try SwiftBoxing.unbox(SwiftBoxing.box(original), projection: MinimalEnum.self), original)
XCTAssertEqual(try SwiftBoxing.unbox(CppBoxing.boxMinimalEnum(original), projection: MinimalEnum.self), original)
XCTAssertEqual(try CppBoxing.unboxMinimalEnum(SwiftBoxing.box(original)), original)
}

func testRoundTripOfStructType() throws {
func testRoundTripOfStruct() throws {
let original = MinimalStruct(field: 42)
XCTAssertEqual(try SwiftBoxing.unbox(SwiftBoxing.box(original), projection: MinimalStruct.self), original)
XCTAssertEqual(try SwiftBoxing.unbox(CppBoxing.boxMinimalStruct(original), projection: MinimalStruct.self), original)
XCTAssertEqual(try CppBoxing.unboxMinimalStruct(SwiftBoxing.box(original)), original)
}

func testRoundTripOfDelegate() throws {
func assertRoundTrip(roundtrip: (@escaping MinimalDelegate) throws -> MinimalDelegate) throws {
var invoked = false
let original: MinimalDelegate = { invoked = true }
let roundtripped = try roundtrip(original)
XCTAssertFalse(invoked)
try roundtripped()
XCTAssertTrue(invoked)
}

try assertRoundTrip {
let boxed = try SwiftBoxing.box($0, projection: MinimalDelegateProjection.self)
return try XCTUnwrap(SwiftBoxing.unbox(boxed, projection: MinimalDelegateProjection.self))
}

try assertRoundTrip {
let boxed = try CppBoxing.boxMinimalDelegate($0)
return try XCTUnwrap(SwiftBoxing.unbox(boxed, projection: MinimalDelegateProjection.self))
}

try assertRoundTrip {
let boxed = try SwiftBoxing.box($0, projection: MinimalDelegateProjection.self)
return try CppBoxing.unboxMinimalDelegate(boxed)
}
}
}
6 changes: 3 additions & 3 deletions InteropTests/Tests/ReferenceBoxingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import WinRTComponent
import XCTest

class ReferenceBoxingTests: WinRTTestCase {
func testRoundTripOfPrimitiveTypeWithIdentityProjection() throws {
func testRoundTripOfPrimitiveWithIdentityProjection() throws {
let original = Int32(42)
XCTAssertEqual(try XCTUnwrap(ReferenceBoxing.boxInt32(original)), original)
XCTAssertEqual(try ReferenceBoxing.unboxInt32(Optional(original)), original)
XCTAssertThrowsError(try ReferenceBoxing.unboxInt32(nil))
}

func testRoundTripOfEnumType() throws {
func testRoundTripOfEnum() throws {
let original = MinimalEnum.one
XCTAssertEqual(try XCTUnwrap(ReferenceBoxing.boxMinimalEnum(original)), original)
XCTAssertEqual(try ReferenceBoxing.unboxMinimalEnum(Optional(original)), original)
XCTAssertThrowsError(try ReferenceBoxing.unboxMinimalEnum(nil))
}

func testRoundTripOfStructType() throws {
func testRoundTripOfStruct() throws {
let original = MinimalStruct(field: 42)
XCTAssertEqual(try XCTUnwrap(ReferenceBoxing.boxMinimalStruct(original)), original)
XCTAssertEqual(try ReferenceBoxing.unboxMinimalStruct(Optional(original)), original)
Expand Down
8 changes: 8 additions & 0 deletions InteropTests/WinRTComponent/InspectableBoxing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ namespace winrt::WinRTComponent::implementation
{
return winrt::unbox_value<winrt::WinRTComponent::MinimalStruct>(value);
}
winrt::Windows::Foundation::IInspectable InspectableBoxing::BoxMinimalDelegate(winrt::WinRTComponent::MinimalDelegate const& value)
{
return winrt::box_value(value);
}
winrt::WinRTComponent::MinimalDelegate InspectableBoxing::UnboxMinimalDelegate(winrt::Windows::Foundation::IInspectable const& value)
{
return winrt::unbox_value<winrt::WinRTComponent::MinimalDelegate>(value);
}
}
2 changes: 2 additions & 0 deletions InteropTests/WinRTComponent/InspectableBoxing.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace winrt::WinRTComponent::implementation
static winrt::WinRTComponent::MinimalEnum UnboxMinimalEnum(winrt::Windows::Foundation::IInspectable const& value);
static winrt::Windows::Foundation::IInspectable BoxMinimalStruct(winrt::WinRTComponent::MinimalStruct const& value);
static winrt::WinRTComponent::MinimalStruct UnboxMinimalStruct(winrt::Windows::Foundation::IInspectable const& value);
static winrt::Windows::Foundation::IInspectable BoxMinimalDelegate(winrt::WinRTComponent::MinimalDelegate const& value);
static winrt::WinRTComponent::MinimalDelegate UnboxMinimalDelegate(winrt::Windows::Foundation::IInspectable const& value);
};
}
namespace winrt::WinRTComponent::factory_implementation
Expand Down
2 changes: 2 additions & 0 deletions InteropTests/WinRTComponent/InspectableBoxing.idl
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ namespace WinRTComponent
static MinimalEnum UnboxMinimalEnum(IInspectable value);
static IInspectable BoxMinimalStruct(MinimalStruct value);
static MinimalStruct UnboxMinimalStruct(IInspectable value);
static IInspectable BoxMinimalDelegate(MinimalDelegate value);
static MinimalDelegate UnboxMinimalDelegate(IInspectable value);
};
}
7 changes: 4 additions & 3 deletions Support/Sources/WindowsRuntime/IActivationFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ public protocol IActivationFactoryProtocol: IInspectableProtocol {
func activateInstance() throws -> IInspectable
}

public enum IActivationFactoryProjection: WinRTProjection {
public enum IActivationFactoryProjection: WinRTInterfaceProjection {
public typealias SwiftObject = IActivationFactory
public typealias COMInterface = WindowsRuntime_ABI.SWRT_IActivationFactory
public typealias COMVirtualTable = WindowsRuntime_ABI.SWRT_IActivationFactoryVTable

public static var typeName: String { "IActivationFactory" }
public static var interfaceID: COMInterfaceID { COMInterface.iid }
public static var runtimeClassName: String { "IActivationFactory" }
public static var virtualTablePointer: COMVirtualTablePointer { fatalError("Not implemented: \(#function)") }

public static func toSwift(_ reference: consuming COMReference<COMInterface>) -> SwiftObject {
Import.toSwift(reference)
Expand Down Expand Up @@ -47,7 +48,7 @@ extension COMInterop where Interface == WindowsRuntime_ABI.SWRT_IActivationFacto
}

// TODO: Move elsewhere to keep COMInterop only for bridging.
public func activateInstance<Projection: WinRTProjection>(projection: Projection.Type) throws -> Projection.COMPointer {
public func activateInstance<Projection: WinRTProjection & COMProjection>(projection: Projection.Type) throws -> Projection.COMPointer {
var inspectable = IInspectableProjection.abiDefaultValue
try WinRTError.throwIfFailed(this.pointee.lpVtbl.pointee.ActivateInstance(this, &inspectable))
defer { IInspectableProjection.release(&inspectable) }
Expand Down
4 changes: 2 additions & 2 deletions Support/Sources/WindowsRuntime/IInspectable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ public protocol IInspectableProtocol: IUnknownProtocol {
func getTrustLevel() throws -> TrustLevel
}

public enum IInspectableProjection: WinRTTwoWayProjection {
public enum IInspectableProjection: WinRTInterfaceProjection {
public typealias SwiftObject = IInspectable
public typealias COMInterface = WindowsRuntime_ABI.SWRT_IInspectable
public typealias COMVirtualTable = WindowsRuntime_ABI.SWRT_IInspectableVTable

public static var typeName: String { "IInspectable" }
public static var interfaceID: COMInterfaceID { COMInterface.iid }
public static var virtualTablePointer: COMVirtualTablePointer { withUnsafePointer(to: &virtualTable) { $0 } }
public static var runtimeClassName: String { "IInspectable" }

public static func toSwift(_ reference: consuming COMReference<COMInterface>) -> SwiftObject {
Import.toSwift(reference)
Expand Down
13 changes: 10 additions & 3 deletions Support/Sources/WindowsRuntime/IInspectableBoxing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ public enum IInspectableBoxing {
public static func box(_ value: Char16) throws -> IInspectable { try WinRTPrimitiveProjection.Char16.box(value) }
public static func box(_ value: String) throws -> IInspectable { try WinRTPrimitiveProjection.String.box(value) }
public static func box(_ value: UUID) throws -> IInspectable { try WinRTPrimitiveProjection.Guid.box(value) }
public static func box<BoxableValue: WinRTBoxableProjection>(_ value: BoxableValue) throws -> IInspectable
where BoxableValue.SwiftValue == BoxableValue {

public static func box<BoxableValue: WinRTValueTypeProjection>(_ value: BoxableValue) throws -> IInspectable {
try BoxableValue.box(value)
}

public static func box<Projection: WinRTDelegateProjection>(_ value: Projection.SwiftObject, projection: Projection.Type) throws -> IInspectable {
try Projection.box(value)
}

public static func unboxBoolean(_ inspectable: IInspectable) -> Bool? {
WinRTPrimitiveProjection.Boolean.unbox(inspectable)
}
Expand Down Expand Up @@ -59,7 +63,10 @@ public enum IInspectableBoxing {
public static func unboxGuid(_ inspectable: IInspectable) -> UUID? {
WinRTPrimitiveProjection.Guid.unbox(inspectable)
}
public static func unbox<Projection: WinRTBoxableProjection>(_ inspectable: IInspectable, projection: Projection.Type) -> Projection.SwiftValue? {
public static func unbox<Projection: WinRTValueTypeProjection>(_ inspectable: IInspectable, projection: Projection.Type) -> Projection.SwiftValue? {
Projection.unbox(inspectable)
}
public static func unbox<Projection: WinRTDelegateProjection>(_ inspectable: IInspectable, projection: Projection.Type) -> Projection.SwiftObject? {
Projection.unbox(inspectable).flatMap { $0! } // If we unboxed, the inner optional is non-nil
}
}
Loading
Loading