Skip to content

Commit 8bd85bb

Browse files
authored
Add ConditionalMetadata (#207)
* Update ProtocolDescriptor * Update TupleTypeDescription * Add ConditionalMetadata * Update Metadata+OpenSwiftUI * Update TypeConformance * Add ConditionalMetadataTests * Fix test case issue
1 parent a1cd25e commit 8bd85bb

12 files changed

+582
-92
lines changed

Sources/OpenSwiftUICore/OpenGraph/Runtime/Metadata+OpenSwiftUI.swift

+16-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Audited for iOS 18.0
66
// Status: Complete
77

8-
public import OpenGraphShims
8+
package import OpenGraphShims
99

1010
extension Metadata {
1111
package var isValueType: Bool {
@@ -15,24 +15,36 @@ extension Metadata {
1515
}
1616
}
1717

18-
public func genericType(at index: Int) -> any Any.Type {
18+
package func genericType(at index: Int) -> any Any.Type {
1919
UnsafeRawPointer(rawValue)
2020
.advanced(by: index &* 8)
2121
.advanced(by: 16)
2222
.assumingMemoryBound(to: Any.Type.self)
2323
.pointee
2424
}
2525

26-
#if OPENSWIFTUI_SUPPORT_2024_API
2726
@inline(__always)
2827
package func projectEnum(
2928
at ptr: UnsafeRawPointer,
3029
tag: Int,
3130
_ body: (UnsafeRawPointer) -> Void
3231
) {
32+
#if OPENSWIFTUI_SUPPORT_2024_API
3333
projectEnumData(UnsafeMutableRawPointer(mutating: ptr))
3434
body(ptr)
3535
injectEnumTag(tag: UInt32(tag), UnsafeMutableRawPointer(mutating: ptr))
36+
#endif
3637
}
37-
#endif
38+
}
39+
40+
@inline(__always)
41+
package func compareEnumTags<T>(_ v1: T, _ v2: T) -> Bool {
42+
func tag(of value: T) -> Int {
43+
withUnsafePointer(to: value) {
44+
Int(Metadata(T.self).enumTag($0))
45+
}
46+
}
47+
let tag1 = tag(of: v1)
48+
let tag2 = tag(of: v2)
49+
return tag1 == tag2
3850
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//
2+
// ConditionalMetadata.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
// ID: 2319071E64CA2FA820BFB26F46C6ECC6 (SwiftUICore)
8+
9+
import OpenGraphShims
10+
11+
// MARK: - ConditionalProtocolDescriptor
12+
13+
package protocol ConditionalProtocolDescriptor: ProtocolDescriptor {
14+
static func fetchConditionalType(key: ObjectIdentifier) -> ConditionalTypeDescriptor<Self>?
15+
16+
static func insertConditionalType(key: ObjectIdentifier, value: ConditionalTypeDescriptor<Self>)
17+
}
18+
19+
// MARK: - ConditionalMetadata
20+
21+
package struct ConditionalMetadata<P> where P: ConditionalProtocolDescriptor {
22+
var desc: ConditionalTypeDescriptor<P>
23+
var ids: [UniqueID]
24+
25+
init(_ desc: ConditionalTypeDescriptor<P>) {
26+
self.desc = desc
27+
self.ids = (0..<desc.count).map { _ in UniqueID() }
28+
}
29+
30+
func childInfo<V>(ptr: UnsafePointer<V>, emptyType: any Any.Type) -> (any Any.Type, UniqueID?) {
31+
var targetType: (any Any.Type)?
32+
var targetIndex = 0
33+
desc.project(at: UnsafeRawPointer(ptr), baseIndex: 0) { index, conformance, ptr in
34+
targetIndex = index
35+
targetType = conformance?.type
36+
}
37+
return (targetType ?? emptyType, ids[targetIndex])
38+
}
39+
}
40+
41+
// MARK: - ConditionalTypeDescriptor
42+
43+
package struct ConditionalTypeDescriptor<P> where P: ConditionalProtocolDescriptor {
44+
fileprivate enum Storage {
45+
case atom(TypeConformance<P>)
46+
indirect case optional(any Any.Type, ConditionalTypeDescriptor<P>)
47+
indirect case either(any Any.Type, f: ConditionalTypeDescriptor<P>, t: ConditionalTypeDescriptor<P>)
48+
}
49+
50+
private var storage: Storage
51+
52+
var count: Int
53+
54+
fileprivate static func descriptor(type: any Any.Type) -> Self {
55+
if let descriptor = P.fetchConditionalType(key: ObjectIdentifier(type)) {
56+
return descriptor
57+
} else {
58+
let descriptor = ConditionalTypeDescriptor(type)
59+
P.insertConditionalType(key: ObjectIdentifier(type), value: descriptor)
60+
return descriptor
61+
}
62+
}
63+
64+
fileprivate init(storage: Storage, count: Int) {
65+
self.storage = storage
66+
self.count = count
67+
}
68+
69+
init(_ type: any Any.Type) {
70+
let storage: Storage
71+
let count: Int
72+
73+
let metadata = Metadata(type)
74+
let descriptor = metadata.nominalDescriptor
75+
76+
if descriptor == conditionalTypeDescriptor {
77+
let falseDescriptor = Self.descriptor(type: metadata.genericType(at: 1))
78+
let trueDescriptor = Self.descriptor(type: metadata.genericType(at: 0))
79+
storage = .either(type, f: falseDescriptor, t: trueDescriptor)
80+
count = falseDescriptor.count + trueDescriptor.count
81+
} else if descriptor == optionalTypeDescriptor {
82+
let wrappedDescriptor = Self.descriptor(type: metadata.genericType(at: 0))
83+
storage = .optional(type, wrappedDescriptor)
84+
count = wrappedDescriptor.count + 1
85+
} else {
86+
storage = .atom(P.conformance(of: type)!)
87+
count = 1
88+
}
89+
90+
self.init(storage: storage, count: count)
91+
}
92+
93+
fileprivate func project(at base: UnsafeRawPointer, baseIndex: Int, _ body: (Int, TypeConformance<P>?, UnsafeRawPointer?) -> ()) {
94+
#if OPENSWIFTUI_SUPPORT_2024_API
95+
switch storage {
96+
case let .atom(conformance):
97+
body(baseIndex, conformance, base)
98+
case let .optional(type, descriptor):
99+
let metadata = Metadata(type)
100+
let tag = Int(metadata.enumTag(base))
101+
if tag == 1 {
102+
body(baseIndex, nil, nil)
103+
} else {
104+
metadata.projectEnum(
105+
at: base,
106+
tag: tag
107+
) { ptr in
108+
descriptor.project(at: ptr, baseIndex: baseIndex &+ 1, body)
109+
}
110+
}
111+
case let .either(type, falseDescriptor, trueDescriptor):
112+
let metadata = Metadata(type)
113+
let tag = Int(metadata.enumTag(base))
114+
metadata.projectEnum(
115+
at: base,
116+
tag: tag
117+
) { ptr in
118+
if tag == 1 {
119+
falseDescriptor.project(at: ptr, baseIndex: baseIndex, body)
120+
} else {
121+
trueDescriptor.project(at: ptr, baseIndex: baseIndex + falseDescriptor.count, body)
122+
}
123+
}
124+
}
125+
#endif
126+
}
127+
}
128+
129+
private let optionalTypeDescriptor: UnsafeRawPointer = Metadata(Void?.self).nominalDescriptor!
130+
private let conditionalTypeDescriptor: UnsafeRawPointer = Metadata(_ConditionalContent<Void, Void>.self).nominalDescriptor!
131+
132+
// MARK: - Optional + ConditionalMetadata
133+
134+
extension Optional {
135+
package static func makeConditionalMetadata<P>(_ protocolDescriptor: P.Type) -> ConditionalMetadata<P> where P: ConditionalProtocolDescriptor {
136+
let descriptor: ConditionalTypeDescriptor<P>
137+
if let result = P.fetchConditionalType(key: ObjectIdentifier(Self.self)) {
138+
descriptor = result
139+
} else {
140+
descriptor = {
141+
let wrappedDescriptor = ConditionalTypeDescriptor<P>.descriptor(type: Wrapped.self)
142+
return ConditionalTypeDescriptor(
143+
storage: .optional(Self.self, wrappedDescriptor),
144+
count: wrappedDescriptor.count + 1
145+
)
146+
}()
147+
P.insertConditionalType(key: ObjectIdentifier(Self.self), value: descriptor)
148+
}
149+
return ConditionalMetadata(descriptor)
150+
}
151+
}
152+
153+
// MARK: ConditionalMetadata + ViewDescriptor
154+
155+
extension ConditionalMetadata where P == ViewDescriptor {
156+
func makeView<V>(ptr: UnsafePointer<V>, view: Attribute<V>, inputs: _ViewInputs) -> _ViewOutputs {
157+
var visitor = MakeView(desc: desc, view: view, inputs: inputs)
158+
desc.project(at: ptr, baseIndex: 0) { index, conformance, ptr in
159+
guard let conformance, let ptr else { return }
160+
visitor.index = index
161+
visitor.ptr = ptr
162+
conformance.visitType(visitor: &visitor)
163+
}
164+
return visitor.outputs ?? .init()
165+
}
166+
167+
func makeViewList<V>(ptr: UnsafePointer<V>, view: Attribute<V>, inputs: _ViewListInputs) -> _ViewListOutputs {
168+
var visitor = MakeList(desc: desc, view: view, inputs: inputs)
169+
desc.project(at: ptr, baseIndex: 0) { index, conformance, ptr in
170+
guard let conformance, let ptr else { return }
171+
visitor.index = index
172+
visitor.ptr = ptr
173+
conformance.visitType(visitor: &visitor)
174+
}
175+
return visitor.outputs ?? .emptyViewList(inputs: inputs)
176+
}
177+
178+
private struct MakeView<Source>: ViewTypeVisitor {
179+
var desc: ConditionalTypeDescriptor<ViewDescriptor>
180+
var view: Attribute<Source>
181+
var index: Int = 0
182+
var ptr: UnsafeRawPointer?
183+
var inputs: _ViewInputs
184+
var outputs: _ViewOutputs?
185+
186+
mutating func visit<V>(type: V.Type) where V: View {
187+
inputs.base.pushStableID(index)
188+
let unwrapConditional = UnwrapConditional<P, Source, V>(source: view, desc: desc, index: index)
189+
let attribute = Attribute(unwrapConditional)
190+
attribute.value = ptr!.assumingMemoryBound(to: V.self).pointee
191+
outputs = V.makeDebuggableView(view: _GraphValue(attribute), inputs: inputs)
192+
}
193+
}
194+
195+
196+
private struct MakeList<Source>: ViewTypeVisitor {
197+
var desc: ConditionalTypeDescriptor<ViewDescriptor>
198+
var view: Attribute<Source>
199+
var index: Int = 0
200+
var ptr: UnsafeRawPointer?
201+
var inputs: _ViewListInputs
202+
var outputs: _ViewListOutputs?
203+
204+
mutating func visit<V>(type: V.Type) where V: View {
205+
inputs.base.pushStableID(index)
206+
let unwrapConditional = UnwrapConditional<P, Source, V>(source: view, desc: desc, index: index)
207+
let attribute = Attribute(unwrapConditional)
208+
attribute.value = ptr!.assumingMemoryBound(to: V.self).pointee
209+
outputs = V.makeDebuggableViewList(view: _GraphValue(attribute), inputs: inputs)
210+
}
211+
}
212+
}
213+
214+
// MARK: - UnwrapConditional
215+
216+
private struct UnwrapConditional<P, Source, Value>: StatefulRule, AsyncAttribute where P: ConditionalProtocolDescriptor {
217+
@Attribute var source: Source
218+
let desc: ConditionalTypeDescriptor<P>
219+
let index: Int
220+
221+
func updateValue() {
222+
withUnsafePointer(to: source) { ptr in
223+
desc.project(at: ptr, baseIndex: 0) { index, conformance, ptr in
224+
guard self.index == index else { return }
225+
value = ptr!.assumingMemoryBound(to: Value.self).pointee
226+
}
227+
}
228+
}
229+
}
230+

Sources/OpenSwiftUICore/Runtime/ProtocolDescriptor.swift

-60
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// TupleTypeDescription.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
package import OpenGraphShims
9+
10+
// MARK: - TupleDescriptor
11+
12+
package protocol TupleDescriptor: ProtocolDescriptor {
13+
static var typeCache: [ObjectIdentifier: TupleTypeDescription<Self>] { get set }
14+
}
15+
16+
extension TupleDescriptor {
17+
package static func tupleDescription(_ type: TupleType) -> TupleTypeDescription<Self> {
18+
let id = ObjectIdentifier(type.type)
19+
if let cache = typeCache[id] {
20+
return cache
21+
} else {
22+
let description = TupleTypeDescription<Self>(type)
23+
typeCache[id] = description
24+
return description
25+
}
26+
}
27+
}
28+
29+
// MARK: - TupleTypeDescription
30+
31+
package struct TupleTypeDescription<P> where P: ProtocolDescriptor {
32+
package let contentTypes: [(Int, TypeConformance<P>)]
33+
34+
package init(_ type: TupleType) {
35+
var contentTypes: [(Int, TypeConformance<P>)] = []
36+
for index in type.indices {
37+
let type = type.type(at: index)
38+
guard let comformance = P.conformance(of: type) else {
39+
let message = "Ignoring invalid type at index \(index), type \(type)"
40+
#if OPENSWIFTUI_SWIFT_LOG
41+
Log.unlocatedIssuesLog.error("\(message)")
42+
#else
43+
Log.unlocatedIssuesLog.fault("\(message, privacy: .public)")
44+
#endif
45+
continue
46+
}
47+
contentTypes.append((index, comformance))
48+
}
49+
self.contentTypes = contentTypes
50+
}
51+
}

0 commit comments

Comments
 (0)