Skip to content

Commit

Permalink
Implemented structs with String or IReference fields
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Feb 26, 2024
1 parent cc46c94 commit b2bad05
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 62 deletions.
1 change: 1 addition & 0 deletions Generator/Sources/ProjectionModel/SupportModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum SupportModule {

public static var hresult: SwiftType { .chain(comModuleName, "HResult") }

public static var abiProjection: SwiftType { .chain(comModuleName, "ABIProjection") }
public static var abiInertProjection: SwiftType { .chain(comModuleName, "ABIInertProjection") }
public static var boolProjection: SwiftType { .chain(comModuleName, "BoolProjection") }
public static var wideCharProjection: SwiftType { .chain(comModuleName, "WideCharProjection") }
Expand Down
20 changes: 19 additions & 1 deletion Generator/Sources/ProjectionModel/SwiftProjection+types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ extension SwiftProjection {
}
}

public func isProjectionInert(_ typeDefinition: TypeDefinition) throws -> Bool {
switch typeDefinition {
case is InterfaceDefinition, is DelegateDefinition, is ClassDefinition: return false
case let structDefinition as StructDefinition:
return try structDefinition.fields.allSatisfy { field in
guard field.isInstance else { return true }
switch try field.type {
case let .bound(type):
// Careful, primitive types have recursive fields (System.Int32 has a field of type System.Int32)
return try type.definition == typeDefinition || isProjectionInert(type.definition)
default: return false
}
}
case is EnumDefinition: return true
default: fatalError()
}
}

public func isNullAsErrorEligible(_ type: TypeNode) -> Bool {
switch type {
case let .bound(type):
Expand Down Expand Up @@ -101,7 +119,7 @@ extension SwiftProjection {
swiftType: try toType(type.asNode),
swiftDefaultValue: type.definition.isReferenceType ? "nil" : .defaultInitializer,
projectionType: projectionType,
kind: type.definition.isValueType ? .inert : .allocating)
kind: try isProjectionInert(type.definition) ? .inert : .allocating)
}

private func getSpecialTypeProjection(_ type: BoundType) throws -> TypeProjection? {
Expand Down
136 changes: 91 additions & 45 deletions Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,16 @@ fileprivate func writeStructProjectionExtension(
_ structDefinition: StructDefinition,
projection: SwiftProjection,
to writer: SwiftSourceFileWriter) throws {
let abiType = try projection.toABIType(structDefinition.bindType())
let isInert = try projection.isProjectionInert(structDefinition)
let abiProjectionProtocol = isInert ? SupportModule.abiInertProjection : SupportModule.abiProjection

// TODO: Support strings and IReference<T> field types (non-inert)
// extension <struct>: ABIInertProjection
try writer.writeExtension(
type: .identifier(projection.toTypeName(structDefinition)),
protocolConformances: [SupportModule.abiInertProjection]) { writer in
protocolConformances: [abiProjectionProtocol]) { writer in

let abiType = try projection.toABIType(structDefinition.bindType())

// public typealias SwiftValue = Self
writer.writeTypeAlias(visibility: .public, name: "SwiftValue", target: .`self`)
Expand All @@ -117,69 +120,112 @@ fileprivate func writeStructProjectionExtension(
writer.writeStatement(".init()")
}

let fields = structDefinition.fields.filter { $0.isInstance }

// public static func toSwift(_ value: ABIValue) -> SwiftValue { .init(field: value.Field, ...) }
try writer.writeFunc(
visibility: .public, static: true, name: "toSwift",
params: [.init(label: "_", name: "value", type: abiType)],
params: [.init(label: "_", name: "value", type: abiType)],
returnType: .`self`) { writer in
var expression = ".init("
for (index, field) in structDefinition.fields.enumerated() {
guard field.isInstance else { continue }
if index > 0 { expression += ", " }

SwiftIdentifier.write(SwiftProjection.toMemberName(field), to: &expression)
expression += ": "

let typeProjection = try projection.getTypeProjection(field.type)
if typeProjection.kind == .identity {
expression += "value."
SwiftIdentifier.write(field.name, to: &expression)
}
else {
typeProjection.projectionType.write(to: &expression)
expression += ".toSwift("
expression += "value."
SwiftIdentifier.write(field.name, to: &expression)
expression += ")"
if fields.isEmpty {
writer.writeStatement(".init()")
return
}

let output = writer.output
try output.writeIndentedBlock(header: ".init(") {
for (index, field) in fields.enumerated() {
if index > 0 { output.write(",", endLine: true) }
try writeStructABIToSwiftInitializerParam(
abiValueName: "value", abiFieldName: field.name, swiftFieldName: SwiftProjection.toMemberName(field),
typeProjection: projection.getTypeProjection(field.type), to: output)
}
}
expression += ")"
writer.writeStatement(expression)
output.write(")", endLine: true)
}

// public static func toABI(_ value: SwiftValue) -> ABIValue { .init(Field: value.field, ...) }
try writer.writeFunc(
visibility: .public, static: true, name: "toABI",
params: [.init(label: "_", name: "value", type: .`self`)],
throws: !isInert,
returnType: abiType) { writer in
var expression = ".init("
for (index, field) in structDefinition.fields.enumerated() {
guard field.isInstance else { continue }
if index > 0 { expression += ", " }

SwiftIdentifier.write(field.name, to: &expression)
expression += ": "

let typeProjection = try projection.getTypeProjection(field.type)
if typeProjection.kind == .identity {
expression += "value."
SwiftIdentifier.write(SwiftProjection.toMemberName(field), to: &expression)
if fields.isEmpty {
writer.writeStatement(".init()")
return
}

let output = writer.output
try output.writeIndentedBlock(header: ".init(") {
for (index, field) in fields.enumerated() {
if index > 0 { output.write(",", endLine: true) }
try writeStructSwiftToABIInitializerParam(
swiftValueName: "value", swiftFieldName: SwiftProjection.toMemberName(field), abiFieldName: field.name,
typeProjection: projection.getTypeProjection(field.type), to: output)
}
else {
if typeProjection.kind != .inert { expression.append("try! ") }
typeProjection.projectionType.write(to: &expression)
expression += ".toABI("
expression += "value."
SwiftIdentifier.write(SwiftProjection.toMemberName(field), to: &expression)
expression += ")"
}
output.write(")", endLine: true)
}

if !isInert {
// public static func release(_ value: inout ABIValue) {}
try writer.writeFunc(
visibility: .public, static: true, name: "release",
params: [.init(label: "_", name: "value", `inout`: true, type: abiType)]) { writer in
for field in fields {
let typeProjection = try projection.getTypeProjection(field.type)
if typeProjection.kind == .allocating {
writer.writeStatement("\(typeProjection.projectionType).release(&value.\(field.name))")
}
}
}
expression += ")"
writer.writeStatement(expression)
}
}
}

fileprivate func writeStructABIToSwiftInitializerParam(
abiValueName: String, abiFieldName: String, swiftFieldName: String,
typeProjection: TypeProjection, to output: IndentedTextOutputStream) throws {
var output = output
SwiftIdentifier.write(swiftFieldName, to: &output)
output.write(": ")

if typeProjection.kind != .identity {
typeProjection.projectionType.write(to: &output)
output.write(".toSwift(")
}

SwiftIdentifier.write(abiValueName, to: &output)
output.write(".")
SwiftIdentifier.write(abiFieldName, to: &output)

if typeProjection.kind != .identity {
output.write(")")
}
}

fileprivate func writeStructSwiftToABIInitializerParam(
swiftValueName: String, swiftFieldName: String, abiFieldName: String,
typeProjection: TypeProjection, to output: IndentedTextOutputStream) throws {
var output = output
SwiftIdentifier.write(abiFieldName, to: &output)
output.write(": ")

if typeProjection.kind != .identity {
if typeProjection.kind != .inert { output.write("try ") }
typeProjection.projectionType.write(to: &output)
output.write(".toABI(")
}

SwiftIdentifier.write(swiftValueName, to: &output)
output.write(".")
SwiftIdentifier.write(swiftFieldName, to: &output)

if typeProjection.kind != .identity {
output.write(")")
}
}

/// Writes a type providing the ABIProjection conformance for a WinRT class.
fileprivate func writeClassProjectionType(
_ classDefinition: ClassDefinition,
Expand Down
16 changes: 12 additions & 4 deletions InteropTests/Tests/StructTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,46 @@ import WinRTComponent

class StructTests: WinRTTestCase {
func testAsReturnValue() throws {
let result = try Structs.make(1, "a", LeafStruct(int32: 2, string: "b"))
let result = try Structs.make(1, "a", 11, LeafStruct(int32: 2, string: "b", reference: 22))
XCTAssertEqual(result.int32, 1)
XCTAssertEqual(result.string, "a")
XCTAssertEqual(result.reference, 11)
XCTAssertEqual(result.nested.int32, 2)
XCTAssertEqual(result.nested.string, "b")
XCTAssertEqual(result.nested.reference, 22)
}

func testAsArgument() throws {
let value = Struct(int32: 1, string: "a", nested: LeafStruct(int32: 2, string: "b"))
let value = Struct(int32: 1, string: "a", reference: 11, nested: LeafStruct(int32: 2, string: "b", reference: 22))
XCTAssertEqual(try Structs.getInt32(value), 1)
XCTAssertEqual(try Structs.getString(value), "a")
XCTAssertEqual(try Structs.getReference(value), 11)
let nested = try Structs.getNested(value)
XCTAssertEqual(nested.int32, 2)
XCTAssertEqual(nested.string, "b")
XCTAssertEqual(nested.reference, 22)
}

func testAsOutParam() throws {
var result: Struct = .init()
try Structs.output(1, "a", LeafStruct(int32: 2, string: "b"), &result)
try Structs.output(1, "a", 11, LeafStruct(int32: 2, string: "b", reference: 22), &result)
XCTAssertEqual(result.int32, 1)
XCTAssertEqual(result.string, "a")
XCTAssertEqual(result.reference, 11)
XCTAssertEqual(result.nested.int32, 2)
XCTAssertEqual(result.nested.string, "b")
XCTAssertEqual(result.nested.reference, 22)
}

func testAsConstByRefArgument() throws {
// Currently "ref const" maps to in params
let value = Struct(int32: 1, string: "a", nested: LeafStruct(int32: 2, string: "b"))
let value = Struct(int32: 1, string: "a", reference: 11, nested: LeafStruct(int32: 2, string: "b", reference: 22))
let roundtripped = try Structs.returnRefConstArgument(value)
XCTAssertEqual(roundtripped.int32, 1)
XCTAssertEqual(roundtripped.string, "a")
XCTAssertEqual(roundtripped.reference, 11)
XCTAssertEqual(roundtripped.nested.int32, 2)
XCTAssertEqual(roundtripped.nested.string, "b")
XCTAssertEqual(roundtripped.nested.reference, 22)
}
}
12 changes: 8 additions & 4 deletions InteropTests/WinRTComponent/Structs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace winrt::WinRTComponent::implementation
{
winrt::WinRTComponent::Struct Structs::Make(int32_t a, hstring const& b, winrt::WinRTComponent::LeafStruct const& c)
winrt::WinRTComponent::Struct Structs::Make(int32_t int32, hstring const& string, winrt::Windows::Foundation::IReference<int32_t> const& reference, winrt::WinRTComponent::LeafStruct const& nested)
{
return { a, b, c };
return { int32, string, reference, nested };
}
int32_t Structs::GetInt32(winrt::WinRTComponent::Struct const& value)
{
Expand All @@ -16,13 +16,17 @@ namespace winrt::WinRTComponent::implementation
{
return value.String;
}
winrt::Windows::Foundation::IReference<int32_t> Structs::GetReference(winrt::WinRTComponent::Struct const& value)
{
return value.Reference;
}
winrt::WinRTComponent::LeafStruct Structs::GetNested(winrt::WinRTComponent::Struct const& value)
{
return value.Nested;
}
void Structs::Output(int32_t a, hstring const& b, winrt::WinRTComponent::LeafStruct const& c, winrt::WinRTComponent::Struct& value)
void Structs::Output(int32_t int32, hstring const& string, winrt::Windows::Foundation::IReference<int32_t> const& reference, winrt::WinRTComponent::LeafStruct const& nested, winrt::WinRTComponent::Struct& value)
{
value = { a, b, c };
value = { int32, string, reference, nested };
}
winrt::WinRTComponent::Struct Structs::ReturnRefConstArgument(const winrt::WinRTComponent::Struct& value)
{
Expand Down
5 changes: 3 additions & 2 deletions InteropTests/WinRTComponent/Structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ namespace winrt::WinRTComponent::implementation
{
Structs() = default;

static winrt::WinRTComponent::Struct Make(int32_t a, hstring const& b, winrt::WinRTComponent::LeafStruct const& c);
static winrt::WinRTComponent::Struct Make(int32_t int32, hstring const& string, winrt::Windows::Foundation::IReference<int32_t> const& reference, winrt::WinRTComponent::LeafStruct const& nested);
static int32_t GetInt32(winrt::WinRTComponent::Struct const& value);
static hstring GetString(winrt::WinRTComponent::Struct const& value);
static winrt::Windows::Foundation::IReference<int32_t> GetReference(winrt::WinRTComponent::Struct const& value);
static winrt::WinRTComponent::LeafStruct GetNested(winrt::WinRTComponent::Struct const& value);
static void Output(int32_t a, hstring const& b, winrt::WinRTComponent::LeafStruct const& c, winrt::WinRTComponent::Struct& value);
static void Output(int32_t int32, hstring const& string, winrt::Windows::Foundation::IReference<int32_t> const& reference, winrt::WinRTComponent::LeafStruct const& nested, winrt::WinRTComponent::Struct& value);
static winrt::WinRTComponent::Struct ReturnRefConstArgument(const winrt::WinRTComponent::Struct& value);
};
}
Expand Down
11 changes: 5 additions & 6 deletions InteropTests/WinRTComponent/Structs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@ namespace WinRTComponent
{
Int32 Int32;
String String;
// TODO(#6): Support IReference<T> projection
// Windows.Foundation.IReference<Int32> Reference;
Windows.Foundation.IReference<Int32> Reference;
};

struct Struct
{
Int32 Int32;
String String;
// TODO(#6): Support IReference<T> projection
// Windows.Foundation.IReference<Int32> Reference;
Windows.Foundation.IReference<Int32> Reference;
LeafStruct Nested;
};

static runtimeclass Structs
{
static Struct Make(Int32 a, String b, LeafStruct d);
static Struct Make(Int32 int32, String string, Windows.Foundation.IReference<Int32> reference, LeafStruct nested);

static Int32 GetInt32(Struct value);
static String GetString(Struct value);
static Windows.Foundation.IReference<Int32> GetReference(Struct value);
static LeafStruct GetNested(Struct value);

static void Output(Int32 a, String b, LeafStruct d, out Struct value);
static void Output(Int32 int32, String string, Windows.Foundation.IReference<Int32> reference, LeafStruct nested, out Struct value);

static Struct ReturnRefConstArgument(ref const Struct value);
};
Expand Down

0 comments on commit b2bad05

Please sign in to comment.