Skip to content

Commit

Permalink
Decoupled parameterized interface ID generation from BoundTypes.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Dec 22, 2023
1 parent f37f88e commit 91047f4
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 109 deletions.
111 changes: 2 additions & 109 deletions Sources/WindowsMetadata/InterfaceID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ public func getInterfaceID(_ typeDefinition: TypeDefinition, genericArgs: [TypeN
throw UnexpectedTypeError(typeDefinition.fullName, context: #function, reason: "Only interfaces and delegates have interface IDs")
}
if let genericArgs = genericArgs, genericArgs.count > 0 {
return try getParameterizedInterfaceID(typeDefinition.bindType(genericArgs: genericArgs))
let signature = try WinRTTypeSignature(typeDefinition.bindType(genericArgs: genericArgs))
return signature.parameterizedID
}
else {
guard let guid = try typeDefinition.findAttribute(GuidAttribute.self) else { throw WinMDError.missingAttribute }
Expand All @@ -16,112 +17,4 @@ public func getInterfaceID(_ typeDefinition: TypeDefinition, genericArgs: [TypeN

public func getInterfaceID(_ type: BoundType) throws -> UUID {
try getInterfaceID(type.definition, genericArgs: type.genericArgs)
}

// https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types
fileprivate let parameterizedInterfaceGuidBytes: [UInt8] = [
0x11, 0xf4, 0x7a, 0xd5,
0x7b, 0x73,
0x42, 0xc0,
0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee
];

public func getParameterizedInterfaceID(_ type: BoundType) throws -> UUID {
var signature: String = ""
try appendSignature(type, to: &signature)

var sha1 = SHA1()
sha1.process(parameterizedInterfaceGuidBytes)
sha1.process(Array(signature.utf8))
let hash = sha1.finalize()
return UUID(uuid: (
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5],
(hash[6] & 0x0F) | 0x50, hash[7],
(hash[8] & 0x3F) | 0x80, hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]))
}

fileprivate func appendGuid(_ guid: UUID, to signature: inout String) {
signature.append("{")
signature.append(guid.uuidString.lowercased())
signature.append("}")
}

fileprivate func appendSignature(_ type: BoundType, to signature: inout String) throws {
if type.genericArgs.count > 0 {
signature.append("pinterface(")
appendGuid(try type.definition.findAttribute(GuidAttribute.self)!, to: &signature)
signature.append(";")
for (index, arg) in type.genericArgs.enumerated() {
if index > 0 { signature.append(";") }
guard case .bound(let arg) = arg else {
throw UnexpectedTypeError(arg.description, context: "WinRT generic argument", reason: "Not a bound type")
}
try appendSignature(arg, to: &signature)
}
signature.append(")")
return
}

let typeDefinition = type.definition
if typeDefinition.namespace == "System" {
switch typeDefinition.name {
case "Boolean": signature.append("b1")
case "Byte": signature.append("u1")
case "SByte": signature.append("i1")
case "Int16": signature.append("i2")
case "UInt16": signature.append("u2")
case "Int32": signature.append("i4")
case "UInt32": signature.append("u4")
case "Int64": signature.append("i8")
case "UInt64": signature.append("u8")
case "Single": signature.append("f4")
case "Double": signature.append("f8")
case "Char": signature.append("c2")
case "String": signature.append("string")
case "Guid": signature.append("g16")
case "Object": signature.append("cinterface(IInspectable)")
default: throw UnexpectedTypeError(typeDefinition.fullName, reason: "Not a well-known WinRT System type")
}

return
}

switch typeDefinition {
case let structDefinition as StructDefinition:
signature.append("struct(")
signature.append(structDefinition.fullName)
signature.append(";")
for (index, field) in structDefinition.fields.enumerated() {
guard field.isInstance else { continue }
if index > 0 { signature.append(";") }
let fieldType = try field.type
guard case .bound(let fieldType) = fieldType else {
throw UnexpectedTypeError(fieldType.description, context: "WinRT struct field", reason: "Not a bound type")
}
try appendSignature(fieldType, to: &signature)
}
signature.append(")")
case let enumDefinition as EnumDefinition:
signature.append("enum(")
signature.append(enumDefinition.fullName)
signature.append(";")
try appendSignature(enumDefinition.underlyingType.bindType(), to: &signature)
signature.append(")")
case let delegateDefinition as DelegateDefinition:
signature.append("delegate(")
appendGuid(try delegateDefinition.findAttribute(GuidAttribute.self)!, to: &signature)
signature.append(")")
case let interfaceDefinition as InterfaceDefinition:
appendGuid(try interfaceDefinition.findAttribute(GuidAttribute.self)!, to: &signature)
case let classDefinition as ClassDefinition:
signature.append("rc(")
signature.append(classDefinition.fullName)
signature.append(";")
try appendSignature(DefaultAttribute.getDefaultInterface(classDefinition)!.asBoundType, to: &signature)
signature.append(")")
default:
fatalError("Unexpected type definition: \(typeDefinition)")
}
}
72 changes: 72 additions & 0 deletions Sources/WindowsMetadata/WinRTTypeSignature+fromBoundType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import DotNetMetadata

extension WinRTTypeSignature {
public init(_ type: BoundType) throws {
self = try WinRTTypeSignature.fromBoundType(type)
}

private static func fromBoundType(_ type: BoundType) throws -> WinRTTypeSignature {
if type.genericArgs.count > 0 {
let id = try type.definition.findAttribute(GuidAttribute.self)!
let args = try type.genericArgs.map {
guard case .bound(let arg) = $0 else {
throw UnexpectedTypeError($0.description, context: "WinRT generic argument", reason: "Not a bound type")
}
return try fromBoundType(arg)
}
return type.definition is DelegateDefinition
? .delegate(id: id, args: args)
: .interface(id: id, args: args)
}

if type.definition.namespace == "System" {
switch type.definition.name {
case "Boolean": return .baseType(.boolean)
case "Byte": return .baseType(.uint8)
case "Int16": return .baseType(.int16)
case "UInt16": return .baseType(.uint16)
case "Int32": return .baseType(.int32)
case "UInt32": return .baseType(.uint32)
case "Int64": return .baseType(.int64)
case "UInt64": return .baseType(.uint64)
case "Single": return .baseType(.single)
case "Double": return .baseType(.double)
case "Char": return .baseType(.char16)
case "String": return .baseType(.string)
case "Guid": return .baseType(.guid)
case "Object": return .comInterface
default: throw UnexpectedTypeError(type.definition.fullName, reason: "Not a well-known WinRT System type")
}
}

switch type.definition {
case is StructDefinition:
let fields = try type.definition.fields.map {
let type = try $0.type
guard case .bound(let arg) = type else {
throw UnexpectedTypeError(type.description, context: "WinRT field", reason: "Not a bound type")
}
return try fromBoundType(arg)
}
return .struct(name: type.definition.fullName, fields: fields)

case is EnumDefinition:
return .enum(name: type.definition.fullName, flags: try type.definition.hasAttribute(FlagsAttribute.self))

case is InterfaceDefinition:
let id = try type.definition.findAttribute(GuidAttribute.self)!
return .interface(id: id)

case is DelegateDefinition:
let id = try type.definition.findAttribute(GuidAttribute.self)!
return .delegate(id: id)

case let classDefinition as ClassDefinition:
let defaultInterface = try DefaultAttribute.getDefaultInterface(classDefinition)!
return .runtimeClass(name: classDefinition.fullName, defaultInterface: try fromBoundType(defaultInterface.asBoundType))

default:
fatalError("Unexpected type definition: \(type.definition)")
}
}
}
32 changes: 32 additions & 0 deletions Sources/WindowsMetadata/WinRTTypeSignature+parameterizedID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import struct Foundation.UUID

// https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types
fileprivate let parameterizedInterfaceGuidBytes: [UInt8] = [
0x11, 0xf4, 0x7a, 0xd5,
0x7b, 0x73,
0x42, 0xc0,
0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee
];

extension WinRTTypeSignature {
public var parameterizedID: UUID {
get {
switch self {
case let .interface(_, args), let .delegate(_, args): precondition(!args.isEmpty)
default: preconditionFailure("Only interfaces and delegates have parameterized IDs")
}

var sha1 = SHA1()
sha1.process(parameterizedInterfaceGuidBytes)
sha1.process(Array(self.toString().utf8))
let hash = sha1.finalize()

return UUID(uuid: (
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5],
(hash[6] & 0x0F) | 0x50, hash[7],
(hash[8] & 0x3F) | 0x80, hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]))
}
}
}
83 changes: 83 additions & 0 deletions Sources/WindowsMetadata/WinRTTypeSignature+string.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import struct Foundation.UUID

extension WinRTTypeSignature {
public func toString() -> String {
var string = ""
appendString(&string)
return string
}

public func appendString(_ string: inout String) {
switch self {
case let .interface(id, args):
if args.isEmpty {
appendGuid(id, to: &string)
}
else {
appendParameterizedInterface(id, args: args, to: &string)
}

case let .delegate(id, args):
if args.isEmpty {
string.append("delegate(")
appendGuid(id, to: &string)
string.append(")")
}
else {
appendParameterizedInterface(id, args: args, to: &string)
}

case let .baseType(baseType):
string.append(baseType.rawValue)

case .comInterface:
string.append("cinterface(IInspectable)")

case let .interfaceGroup(name, defaultInterface):
string.append("ig(")
string.append(name)
string.append(";")
defaultInterface.appendString(&string)
string.append(")")

case let .runtimeClass(name, defaultInterface):
string.append("rc(")
string.append(name)
string.append(";")
defaultInterface.appendString(&string)
string.append(")")

case let .struct(name, fields):
string.append("struct(")
string.append(name)
for field in fields {
string.append(";")
field.appendString(&string)
}
string.append(")")

case let .enum(name, flags):
string.append("enum(")
string.append(name)
string.append(";")
string.append(flags ? "u4" : "i4")
string.append(")")
}
}
}

fileprivate func appendParameterizedInterface(_ id: UUID, args: [WinRTTypeSignature], to string: inout String) {
string.append("pinterface(")
appendGuid(id, to: &string)
for arg in args {
string.append(";")
arg.appendString(&string)
}
string.append(")")
}

fileprivate func appendGuid(_ guid: UUID, to string: inout String) {
string.append("{")
string.append(guid.uuidString.lowercased())
string.append("}")
}
30 changes: 30 additions & 0 deletions Sources/WindowsMetadata/WinRTTypeSignature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import struct Foundation.UUID

/// A WinRT type signature, used to generate GUIDs for parameterized interface and delegate types.
/// See https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types
public enum WinRTTypeSignature: Hashable {
case interface(id: UUID, args: [WinRTTypeSignature] = [])
case delegate(id: UUID, args: [WinRTTypeSignature] = [])
case baseType(BaseType)
case comInterface
indirect case interfaceGroup(name: String, default: WinRTTypeSignature)
indirect case runtimeClass(name: String, defaultInterface: WinRTTypeSignature)
case `struct`(name: String, fields: [WinRTTypeSignature])
case `enum`(name: String, flags: Bool)

public enum BaseType: String, Hashable {
case boolean = "b1"
case uint8 = "u1"
case int16 = "i2" // Undocumented but presumably valid
case uint16 = "u2" // Undocumented but presumably valid
case int32 = "i4"
case uint32 = "u4"
case int64 = "i8"
case uint64 = "u8"
case single = "f4"
case double = "f8"
case char16 = "c2"
case string = "string"
case guid = "g16"
}
}
10 changes: 10 additions & 0 deletions Tests/WindowsMetadata/WinRTTypeSignatureTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import XCTest
import WindowsMetadata

final class WinRTTypeSignatureTests: XCTestCase {
public func testInterfaceID() throws {
let ireferenceInterfaceID = UUID(uuidString: "61c17706-2d65-11e0-9ae8-d48564015472")!
let signature: WinRTTypeSignature = .interface(id: ireferenceInterfaceID, args: [.baseType(.int32)])
XCTAssertEqual(signature.parameterizedID, UUID(uuidString: "548cefbd-bc8a-5fa0-8df2-957440fc8bf4")) // IReference<Int32>
}
}

0 comments on commit 91047f4

Please sign in to comment.