Skip to content

[Suggestion] Replace symbol hacks with AssociatedTypeRequirementsVisitor hacks #3

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ let package = Package(
],
dependencies: [
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1"),
.package(url: "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", from: "0.2.0"),
],
targets: [
.target(
name: "CSymbols",
dependencies: []),
.target(
name: "SwiftShortcuts",
dependencies: ["CSymbols"]),
dependencies: ["AssociatedTypeRequirementsKit"]),
.testTarget(
name: "SwiftShortcutsTests",
dependencies: ["SwiftShortcuts", "SnapshotTesting"],
Expand Down
11 changes: 0 additions & 11 deletions Sources/CSymbols/CSymbols.c

This file was deleted.

7 changes: 0 additions & 7 deletions Sources/CSymbols/include/CSymbols.h

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/SwiftShortcuts/Internal/ActionDecomposable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,6 @@ extension Optional: ActionDecomposable where Wrapped: Shortcut {
extension TupleShortcut: ActionDecomposable {
func decompose() -> [Action] {
let mirror = Mirror(reflecting: value)
return mirror.children.flatMap { _, value in AnyShortcut(_fromValue: value)!.decompose() }
return mirror.children.flatMap { _, value in actionComponents(from: value)! }
}
}
139 changes: 22 additions & 117 deletions Sources/SwiftShortcuts/Internal/FromAny.swift
Original file line number Diff line number Diff line change
@@ -1,142 +1,47 @@
#if os(Linux)
import CSymbols
#else
import Foundation
#endif

import AssociatedTypeRequirementsVisitor

private let typeEraser = ShortcutTypeEraser()
private let decomposer = ShortcutDecomposer()

// MARK: - AnyShortcut from Any

extension AnyShortcut {
init?(_fromValue value: Any) {
// Synthesize a fake protocol conformance record to AnyShortcutConvertible
let conformance = ProtocolConformanceRecord(type: type(of: value), witnessTable: 0)
let type = unsafeBitCast(conformance, to: AnyShortcutConvertible.Type.self)

guard let action = type.anyShortcut(from: value) else {
return nil
}
guard let action = typeEraser(value) else { return nil }

self = action
}
}

// MARK: - AnyViewConvertible

private let actionMetadata: ProtocolMetadata = {
let module = "SwiftShortcuts"
let name = "Shortcut"
let postfix = "_p"
let mangled = "\(module.count)\(module)\(name.count)\(name)\(postfix)"
return ProtocolMetadata(type: _typeByName(mangled)!)
}()

private protocol AnyShortcutConvertible {}

extension AnyShortcutConvertible {
static func anyShortcut(from action: Any) -> AnyShortcut? {
guard let witnessTable = _conformsToProtocol(Self.self, actionMetadata.protocolDescriptorVector) else {
return nil
}

let conformanceRecord = ProtocolConformanceRecord(type: Self.self, witnessTable: Int(bitPattern: witnessTable))
return withUnsafePointer(to: action as! Self) { pointer in makeAnyShortcut(pointer, conformanceRecord) }
}
}
// MARK: - Actions from Any

@_silgen_name("_swift_shortcuts_makeAnyShortcut")
@available(*, unavailable)
public func makeAnyShortcut<S: Shortcut>(from shortcut: S) -> AnyShortcut {
return AnyShortcut(shortcut)
func actionComponents(from value: Any) -> [Action]? {
return decomposer(value)
}

private typealias ShortcutToAnyShortcutFunction = @convention(thin) (UnsafeRawPointer, ProtocolConformanceRecord) -> AnyShortcut

#if os(Linux)
private let makeAnyShortcut = unsafeBitCast(makeAnyShortcutSymbol(), to: ShortcutToAnyShortcutFunction.self)
#else
private let makeAnyShortcut: ShortcutToAnyShortcutFunction = {
let symbolName = "_swift_shortcuts_makeAnyShortcut"
let handle = dlopen(nil, RTLD_GLOBAL)
let pointer = dlsym(handle, symbolName)
return unsafeBitCast(pointer, to: ShortcutToAnyShortcutFunction.self)
}()
#endif

// MARK: - [ActionComponent] from Any

private protocol ActionsConvertible {}

extension ActionsConvertible {
static func actions(from shortcut: Any) -> [Action]? {
guard let witnessTable = _conformsToProtocol(Self.self, actionMetadata.protocolDescriptorVector) else {
return nil
}
// MARK: - ShortcutVisitor

let conformanceRecord = ProtocolConformanceRecord(type: Self.self, witnessTable: Int(bitPattern: witnessTable))
return withUnsafePointer(to: shortcut as! Self) { pointer in decomposeIntoActions(pointer, conformanceRecord) }
}
}
private protocol ShortcutVisitor: AssociatedTypeRequirementsVisitor {
associatedtype Visitor = ShortcutVisitor
associatedtype Input = Shortcut
associatedtype Output

@_silgen_name("_swift_shortcuts_decomposeIntoActions")
@available(*, unavailable)
public func decomposeIntoActions<S: Shortcut>(shortcut: S) -> [Action] {
_decomposeIntoActions(shortcut: shortcut)
func callAsFunction<S : Shortcut>(_ shortcut: S) -> Output
}

private func _decomposeIntoActions<S: Shortcut>(shortcut: S) -> [Action] {
if S.Body.self == Never.self {
return shortcut.decompose()
}
private struct ShortcutTypeEraser : ShortcutVisitor {

let body = shortcut.body
if let component = body as? Action {
return [component]
} else {
return _decomposeIntoActions(shortcut: body)
func callAsFunction<S : Shortcut>(_ shortcut: S) -> AnyShortcut {
return AnyShortcut(shortcut)
}
}

private typealias ShortcutToActionsFunction = @convention(thin) (UnsafeRawPointer, ProtocolConformanceRecord) -> [Action]
#if os(Linux)
private let decomposeIntoActions = unsafeBitCast(decomposeIntoActionsSymbol(), to: ShortcutToActionsFunction.self)
#else
private let decomposeIntoActions: ShortcutToActionsFunction = {
let symbolName = "_swift_shortcuts_decomposeIntoActions"
let handle = dlopen(nil, RTLD_GLOBAL)
let pointer = dlsym(handle, symbolName)
return unsafeBitCast(pointer, to: ShortcutToActionsFunction.self)
}()
#endif

func actionComponents(from value: Any) -> [Action]? {
// Synthesize a fake protocol conformance record to ActionStepsConvertible
let conformance = ProtocolConformanceRecord(type: type(of: value), witnessTable: 0)
let type = unsafeBitCast(conformance, to: ActionsConvertible.Type.self)
return type.actions(from: value)
}

// MARK: - Protocol Runtime Information

private struct ProtocolConformanceRecord {
let type: Any.Type
let witnessTable: Int
}

private struct ProtocolDescriptor {}

private struct ProtocolMetadata {
let kind: Int
let layoutFlags: UInt32
let numberOfProtocols: UInt32
let protocolDescriptorVector: UnsafeMutablePointer<ProtocolDescriptor>
private struct ShortcutDecomposer : ShortcutVisitor {

init(type: Any.Type) {
self = unsafeBitCast(type, to: UnsafeMutablePointer<Self>.self).pointee
func callAsFunction<S : Shortcut>(_ shortcut: S) -> [Action] {
return shortcut.decompose()
}
}

@_silgen_name("swift_conformsToProtocol")
private func _conformsToProtocol(
_ type: Any.Type,
_ protocolDescriptor: UnsafeMutablePointer<ProtocolDescriptor>
) -> UnsafeRawPointer?
}