Skip to content

Commit

Permalink
Better leverage Projection.toType(Reference|Expression) (#444)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle authored Dec 25, 2024
1 parent 2b3c2ff commit e178fce
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 73 deletions.
11 changes: 11 additions & 0 deletions Generator/Sources/ProjectionModel/Projection+conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ extension Projection {
return result
}

public func toBindingType(_ type: TypeDefinition) throws -> SwiftType {
.named(try toBindingTypeName(type))
}

public func toBindingType(_ type: BoundType) throws -> SwiftType {
let definitionBindingType = try toBindingType(type.definition)
return type.genericArgs.isEmpty
? definitionBindingType
: definitionBindingType.member(try Projection.toBindingInstantiationTypeName(genericArgs: type.genericArgs))
}

public static func getSwiftAttributes(_ member: any Attributable) throws -> [SwiftAttribute] {
// We recognize any attribute called SwiftAttribute and expect it has a field called Literal,
// ideally that would be a positional argument, but IDL doesn't seem to have a syntax for that.
Expand Down
2 changes: 1 addition & 1 deletion Generator/Sources/ProjectionModel/Projection+params.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension Projection {

public func toParameter(label: String = "_", _ param: Param, genericTypeArgs: [TypeNode] = []) throws -> SwiftParam {
SwiftParam(label: label, name: toParamName(param), `inout`: param.isByRef,
type: try genericTypeArgs.isEmpty ? toType(param.type) : toType(param.type.bindGenericParams(typeArgs: genericTypeArgs)))
type: try toTypeExpression(genericTypeArgs.isEmpty ? param.type : param.type.bindGenericParams(typeArgs: genericTypeArgs)))
}

public func getParamBinding(_ param: ParamBase, genericTypeArgs: [TypeNode] = []) throws -> ParamProjection {
Expand Down
48 changes: 23 additions & 25 deletions Generator/Sources/ProjectionModel/Projection+types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,38 @@ import WindowsMetadata
import CodeWriters

extension Projection {
public func toType(_ type: TypeNode) throws -> SwiftType {
public func toTypeExpression(_ type: TypeNode, outerNullable: Bool = true) throws -> SwiftType {
switch type {
case let .bound(type):
return try toType(type)
case let .bound(boundType):
if let specialTypeBinding = try getSpecialTypeBinding(boundType) {
if boundType.definition.namespace == "System", boundType.definition.name == "Object", !outerNullable {
return specialTypeBinding.swiftType.unwrapOptional()
}
return specialTypeBinding.swiftType
}

let swiftType = try SwiftType.named(
toTypeName(boundType.definition),
genericArgs: boundType.genericArgs.map { try toTypeExpression($0) })
return boundType.definition.isReferenceType && outerNullable ? swiftType.optional() : swiftType
case let .genericParam(param):
return .named(param.name)
case let .array(of: element):
return .array(element: try toType(element))
return .array(element: try toTypeExpression(element))
default:
fatalError("Not implemented: Swift representation of values of type \(type)")
}
}

public func toType(_ boundType: BoundType, nullable: Bool = true) throws -> SwiftType {
public func toTypeReference(_ boundType: BoundType) throws -> SwiftType {
// getSpecialTypeBinding returns a type expression, which includes the optional wrapping.
if let specialTypeBinding = try getSpecialTypeBinding(boundType) {
return specialTypeBinding.swiftType
return specialTypeBinding.swiftType.unwrapOptional()
}

let swiftObjectType: SwiftType = .named(
return .named(
try toTypeName(boundType.definition),
genericArgs: try boundType.genericArgs.map { try toType($0) })
return boundType.definition.isReferenceType && nullable ? swiftObjectType.optional() : swiftObjectType
genericArgs: try boundType.genericArgs.map { try toTypeExpression($0) })
}

public func isPODBinding(_ typeDefinition: TypeDefinition) throws -> Bool {
Expand Down Expand Up @@ -60,9 +70,8 @@ extension Projection {
try enumDefinition.attributes.contains(where: { try $0.type.name == "SwiftEnumAttribute" }) && !enumDefinition.isFlags
}

public func toReturnType(_ type: TypeNode, typeGenericArgs: [TypeNode]? = nil) throws -> SwiftType {
let swiftType = try toType(type.bindGenericParams(typeArgs: typeGenericArgs))
return isNullAsErrorEligible(type) ? swiftType.unwrapOptional() : swiftType
public func toReturnType(_ type: TypeNode) throws -> SwiftType {
try toTypeExpression(type, outerNullable: !isNullAsErrorEligible(type))
}

public func getTypeBinding(_ type: TypeNode) throws -> TypeProjection {
Expand Down Expand Up @@ -108,23 +117,12 @@ extension Projection {
abiType = .unsafeMutablePointer(pointee: abiType).optional()
}

let bindingType: SwiftType = try {
let bindingTypeName = try toBindingTypeName(type.definition)
if type.genericArgs.isEmpty {
return .named(bindingTypeName)
}
else {
return .named(bindingTypeName)
.member(try Projection.toBindingInstantiationTypeName(genericArgs: type.genericArgs))
}
}()

return TypeProjection(
abiType: abiType,
abiDefaultValue: type.definition.isReferenceType ? "nil" : .defaultInitializer,
swiftType: try toType(type.asNode),
swiftType: try toTypeExpression(type.asNode),
swiftDefaultValue: type.definition.isReferenceType ? "nil" : .defaultInitializer,
bindingType: bindingType,
bindingType: try toBindingType(type),
kind: try isPODBinding(type.definition) ? .pod : .allocating)
}

Expand Down
5 changes: 5 additions & 0 deletions Generator/Sources/ProjectionModel/SupportModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ extension SupportModules.COM {
public static var comBinding: SwiftType { moduleType.member("COMBinding") }
public static var comTwoWayBinding: SwiftType { moduleType.member("COMTwoWayBinding") }

public static var comImport: SwiftType { moduleType.member("COMImport") }
public static func comImport(of type: SwiftType) -> SwiftType {
moduleType.member("COMImport", genericArgs: [type])
}

public static var comEmbedding: SwiftType { moduleType.member("COMEmbedding") }

public static var comReference: SwiftType { moduleType.member("COMReference") }
Expand Down
49 changes: 23 additions & 26 deletions Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import struct Foundation.UUID
/// Writes a type or extension providing the ABIBinding conformance for a given projected WinRT type.
internal func writeABIBindingConformance(_ typeDefinition: TypeDefinition, genericArgs: [TypeNode]?, projection: Projection, to writer: SwiftSourceFileWriter) throws {
if SupportModules.WinRT.getBuiltInTypeKind(typeDefinition) == .definitionAndBinding {
// The support module already defines a projection, just import and reexport it.
// The support module already defines a projection, just reexport it.
if typeDefinition.isReferenceType {
let bindingTypeName = try projection.toBindingTypeName(typeDefinition)
writer.writeImport(exported: true, kind: .enum, module: SupportModules.WinRT.moduleName, symbolName: bindingTypeName)
writer.writeImport(exported: true, kind: .enum,
module: SupportModules.WinRT.moduleName,
symbolName: try projection.toBindingTypeName(typeDefinition))
}
else {
// The struct conforms to ABIBinding itself, and we already imported it.
Expand All @@ -30,7 +31,7 @@ internal func writeABIBindingConformance(_ typeDefinition: TypeDefinition, gener
let enumBindingProtocol = try projection.isSwiftEnumEligible(enumDefinition)
? SupportModules.WinRT.closedEnumBinding : SupportModules.WinRT.openEnumBinding
try writer.writeExtension(
type: .named(projection.toTypeName(enumDefinition)),
type: projection.toTypeReference(enumDefinition.bindType()),
protocolConformances: [ enumBindingProtocol ]) { writer in
// public static var typeName: String { "..." }
try writeTypeNameProperty(type: enumDefinition.bindType(), to: writer)
Expand Down Expand Up @@ -59,20 +60,20 @@ internal func writeABIBindingConformance(_ typeDefinition: TypeDefinition, gener
// Non-generic type, create a standard projection type.
// enum IVectorBinding: WinRTBinding... {}
try writeInterfaceOrDelegateBindingType(typeDefinition.bindType(),
projectionName: try projection.toBindingTypeName(typeDefinition), projection: projection, to: writer)
name: try projection.toBindingTypeName(typeDefinition),
projection: projection, to: writer)
}
else if let genericArgs {
// Generic type specialization. Create a projection for the specialization.
// extension IVectorBinding {
// internal final class Boolean: WinRTBinding... {}
// }
try writer.writeExtension(
type: .named(projection.toBindingTypeName(typeDefinition))) { writer in
type: projection.toBindingType(typeDefinition)) { writer in
try writeInterfaceOrDelegateBindingType(
typeDefinition.bindType(genericArgs: genericArgs),
projectionName: try Projection.toBindingInstantiationTypeName(genericArgs: genericArgs),
projection: projection,
to: writer)
name: try Projection.toBindingInstantiationTypeName(genericArgs: genericArgs),
projection: projection, to: writer)
}
}
else {
Expand Down Expand Up @@ -256,21 +257,19 @@ fileprivate func writeClassBindingType(
to writer: SwiftSourceFileWriter) throws {
assert(!classDefinition.isStatic)

let projectionProtocol = try classDefinition.hasAttribute(ComposableAttribute.self)
let bindingProtocol = try classDefinition.hasAttribute(ComposableAttribute.self)
? SupportModules.WinRT.composableClassBinding
: SupportModules.WinRT.runtimeClassBinding

let bindingTypeName = try projection.toBindingTypeName(classDefinition)
try writer.writeClass(
visibility: Projection.toVisibility(classDefinition.visibility),
name: bindingTypeName, protocolConformances: [ projectionProtocol ]) { writer throws in
let typeName = try projection.toTypeName(classDefinition)

name: projection.toBindingTypeName(classDefinition),
protocolConformances: [ bindingProtocol ]) { writer throws in
try writeReferenceTypeBindingConformance(
apiType: classDefinition.bindType(),
abiType: defaultInterface.asBoundType,
wrapImpl: { writer, paramName in
writer.writeStatement("\(typeName)(_wrapping: consume \(paramName))")
writer.writeStatement(".init(_wrapping: consume \(paramName))")
},
projection: projection,
to: writer)
Expand All @@ -289,8 +288,7 @@ fileprivate func writeComposableClassOuterObject(

let baseOuterObjectClass: SwiftType
if let base = try classDefinition.base, try base.definition.base != nil {
baseOuterObjectClass = .named(try projection.toBindingTypeName(base.definition))
.member(outerObjectClassName)
baseOuterObjectClass = try projection.toBindingType(base.definition).member(outerObjectClassName)
} else {
baseOuterObjectClass = SupportModules.WinRT.composableClass_outerObject
}
Expand All @@ -306,8 +304,6 @@ fileprivate func writeComposableClassOuterObject(
return
}

let classSwiftType: SwiftType = .named(try projection.toTypeName(classDefinition))

try writer.writeClass(
visibility: .open,
name: outerObjectClassName,
Expand All @@ -326,7 +322,7 @@ fileprivate func writeComposableClassOuterObject(

// _ifoo.initOwner(owner as! MyClass)
// return _ifoo.toCOM()
writer.writeStatement("\(propertyName).initOwner(owner as! \(classSwiftType))")
writer.writeStatement("\(propertyName).initOwner(owner as! SwiftObject)")
writer.writeReturnStatement(value: "\(propertyName).toCOM()")
}
}
Expand Down Expand Up @@ -356,19 +352,19 @@ fileprivate func writeComposableClassOuterObject(

fileprivate func writeInterfaceOrDelegateBindingType(
_ type: BoundType,
projectionName: String,
name: String,
projection: Projection,
to writer: some SwiftDeclarationWriter) throws {
precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition)
let projectionProtocol = type.definition is InterfaceDefinition
let bindingProtocol = type.definition is InterfaceDefinition
? SupportModules.WinRT.interfaceBinding : SupportModules.WinRT.delegateBinding

// Projections of generic instantiations are not owned by any specific module.
// Making them internal avoids clashes between redundant definitions across modules.
try writer.writeEnum(
visibility: type.genericArgs.isEmpty ? Projection.toVisibility(type.definition.visibility) : .internal,
name: projectionName,
protocolConformances: [ projectionProtocol ]) { writer throws in
name: name,
protocolConformances: [ bindingProtocol ]) { writer throws in

let importClassName = "Import"

Expand Down Expand Up @@ -397,7 +393,8 @@ fileprivate func writeInterfaceOrDelegateBindingType(
}

try writeCOMImportClass(
type, visibility: .private, name: importClassName, projectionName: projectionName,
type, visibility: .private, name: importClassName,
bindingType: .named(name),
projection: projection, to: writer)

// public static var virtualTablePointer: UnsafeRawPointer { .init(withUnsafePointer(to: &virtualTable) { $0 }) }
Expand Down Expand Up @@ -426,7 +423,7 @@ internal func writeReferenceTypeBindingConformance(
projection: Projection,
to writer: SwiftTypeDefinitionWriter) throws {
writer.writeTypeAlias(visibility: .public, name: "SwiftObject",
target: try projection.toType(apiType.asNode).unwrapOptional())
target: try projection.toTypeReference(apiType))
writer.writeTypeAlias(visibility: .public, name: "ABIStruct",
target: try projection.toABIType(abiType))

Expand Down
12 changes: 6 additions & 6 deletions Generator/Sources/SwiftWinRT/Writing/COMImportClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ internal func writeCOMImportClass(
_ type: BoundType,
visibility: SwiftVisibility,
name: String,
projectionName: String,
bindingType: SwiftType,
projection: Projection,
to writer: SwiftTypeDefinitionWriter) throws {
let importBaseTypeName: String
let importBaseType: SwiftType
let protocolConformances: [SwiftType]
switch type.definition {
case let interfaceDefinition as InterfaceDefinition:
importBaseTypeName = "WinRTImport"
importBaseType = SupportModules.WinRT.winRTImport(of: bindingType)
protocolConformances = [.named(try projection.toProtocolName(interfaceDefinition)) ]
case is DelegateDefinition:
importBaseTypeName = "COMImport"
importBaseType = SupportModules.COM.comImport(of: bindingType)
protocolConformances = []
default: fatalError()
}

// private final class Import: WinRTImport<IFooBinding>, IFooProtocol {}
try writer.writeClass(
visibility: visibility, final: true, name: name,
base: .named(importBaseTypeName, genericArgs: [ .named(projectionName) ]),
base: importBaseType,
protocolConformances: protocolConformances) { writer throws in

let interfaces = try type.definition.baseInterfaces.map {
Expand Down Expand Up @@ -92,7 +92,7 @@ internal func writeGenericTypeAliases(interfaces: [BoundInterface], projection:
for (index, genericArg) in interface.genericArgs.enumerated() {
let genericParamName = interface.definition.genericParams[index].name
if typeAliases[genericParamName] == nil {
typeAliases[genericParamName] = try projection.toType(genericArg)
typeAliases[genericParamName] = try projection.toTypeExpression(genericArg)
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ internal func writeClassDefinition(_ classDefinition: ClassDefinition, projectio
return
}

let bindingTypeName = try projection.toBindingTypeName(classDefinition)

// Both composable and activatable classes can have a base class
let base: SwiftType
if let baseClassDefinition = try getRuntimeClassBase(classDefinition) {
base = try projection.toType(baseClassDefinition.bindType(), nullable: false)
base = try projection.toTypeReference(baseClassDefinition.bindType())
} else {
base = classDefinition.isSealed
? SupportModules.WinRT.winRTImport(of: .named(bindingTypeName))
base = try classDefinition.isSealed
? SupportModules.WinRT.winRTImport(of: projection.toBindingType(classDefinition))
: SupportModules.WinRT.composableClass
}

Expand Down Expand Up @@ -259,7 +257,7 @@ fileprivate func writeComposableInitializers(
let propertyName = SecondaryInterfaces.getPropertyName(factoryInterface.bind())

let baseClassDefinition = try getRuntimeClassBase(classDefinition)
let outerObjectType: SwiftType = .named(try projection.toBindingTypeName(classDefinition)).member("OuterObject")
let outerObjectType = try projection.toBindingType(classDefinition).member("OuterObject")

for method in factoryInterface.methods {
// Swift requires "override" on initializers iff the same initializer is defined in the direct base class
Expand Down Expand Up @@ -325,9 +323,9 @@ fileprivate func writeDefaultActivatableInitializer(
override: isOverriding,
throws: true) { writer in
let propertyName = SecondaryInterfaces.getPropertyName(interfaceName: "IActivationFactory")
let projectionClassName = try projection.toBindingTypeName(classDefinition)
let bindingType = try projection.toBindingType(classDefinition)
writer.writeStatement("let _instance = \(SupportModules.COM.comReference)(transferringRef: try Self.\(propertyName)"
+ ".activateInstance(binding: \(projectionClassName).self))")
+ ".activateInstance(binding: \(bindingType).self))")
if try hasComposableBase(classDefinition) {
writer.writeStatement("super.init(_wrapping: _instance.cast()) // Transitively casts down to IInspectable")
}
Expand Down
4 changes: 2 additions & 2 deletions Generator/Sources/SwiftWinRT/Writing/EnumDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fileprivate func writeOpenEnumDefinition(_ enumDefinition: EnumDefinition, proje
name: structName,
protocolConformances: [.named("CStyleEnum") ]) { writer throws in

let rawValueType = try projection.toType(enumDefinition.underlyingType.bindNode())
let rawValueType = try projection.toTypeExpression(enumDefinition.underlyingType.bindNode())
writer.writeStoredProperty(visibility: .public, declarator: .var, name: "rawValue", type: rawValueType)
writer.writeInit(visibility: .public,
params: [ .init(name: "rawValue", type: rawValueType, defaultValue: "0") ]) {
Expand Down Expand Up @@ -90,7 +90,7 @@ fileprivate func writeClosedEnumDefinition(_ enumDefinition: EnumDefinition, pro
documentation: projection.getDocumentationComment(enumDefinition),
visibility: Projection.toVisibility(enumDefinition.visibility),
name: try projection.toTypeName(enumDefinition),
rawValueType: try projection.toType(enumDefinition.underlyingType.bindNode()),
rawValueType: try projection.toTypeExpression(enumDefinition.underlyingType.bindNode()),
protocolConformances: [.named("ClosedEnum") ]) { writer throws in
for field in enumDefinition.fields.filter({ $0.visibility == .public && $0.isStatic }) {
try writer.writeEnumCase(
Expand Down
Loading

0 comments on commit e178fce

Please sign in to comment.