Skip to content

Commit b78de8c

Browse files
authored
added _SendableAnyHTMLBox (#60)
* added _SendableAnyHTMLBox * ifed out embedded and swift 6 only test case * gosh darn complier crashes... * added swift github issue link
1 parent ba82021 commit b78de8c

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#if swift(>=6.0) && !hasFeature(Embedded)
2+
import Synchronization
3+
4+
@available(macOS 15.0, *)
5+
final class SendOnceBox: Sendable, SendOnceBoxing {
6+
// final class SendOnceBox<Value>: Sendable, SendOnceBoxing {
7+
typealias Value = any HTML
8+
// NOTE: generics+Synchronization crashes the compiler ATM
9+
// https://github.com/swiftlang/swift/issues/78048
10+
11+
let mutex: Mutex<Value?>
12+
13+
init(_ value: sending Value) {
14+
mutex = Mutex(value)
15+
}
16+
17+
func tryTake() -> sending Value? {
18+
mutex.withLock { value -> sending Value? in
19+
let result = value
20+
value = nil
21+
return result
22+
}
23+
}
24+
}
25+
26+
// NOTE: this is for macOS runtime availability of SendOnceBox and can be removed when macOS 15 is the minimum
27+
protocol SendOnceBoxing<Value>: AnyObject, Sendable {
28+
associatedtype Value
29+
func tryTake() -> sending Value?
30+
}
31+
#endif
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#if !hasFeature(Embedded)
2+
/// A wrapper around an `any HTML` value that can be safely sent once.
3+
///
4+
/// Note: For non-sendable values, this will only allow the value to be taken only once.
5+
/// Sendable values can safely be taken multiple times.
6+
public struct _SendableAnyHTMLBox: Sendable {
7+
var storage: Storage
8+
9+
enum Storage {
10+
case sendable(any HTML & Sendable)
11+
#if swift(>=6.0)
12+
// NOTE: protocol can be removed when macOS 15 is the minimum
13+
case sendOnceBox(any SendOnceBoxing<any HTML>)
14+
#endif
15+
}
16+
17+
public init(_ html: any HTML & Sendable) {
18+
storage = .sendable(html)
19+
}
20+
21+
#if swift(>=6.0)
22+
@available(macOS 15, *)
23+
public init(_ html: sending any HTML) {
24+
storage = .sendOnceBox(SendOnceBox(html))
25+
}
26+
#endif
27+
28+
#if swift(>=6.0)
29+
public consuming func tryTake() -> sending (any HTML)? {
30+
switch storage {
31+
case let .sendable(html):
32+
return html
33+
case let .sendOnceBox(box):
34+
return box.tryTake()
35+
}
36+
}
37+
#else
38+
public consuming func tryTake() -> (any HTML & Sendable)? {
39+
switch storage {
40+
case let .sendable(html):
41+
return html
42+
}
43+
}
44+
#endif
45+
}
46+
#endif
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Elementary
2+
import XCTest
3+
4+
@available(macOS 15.0, *)
5+
final class SendOnceHTMLValueTests: XCTestCase {
6+
func testHoldsSendableValue() {
7+
let html = div { "Hello, World!" }
8+
let box = _SendableAnyHTMLBox(html)
9+
XCTAssertNotNil(box.tryTake())
10+
XCTAssertNotNil(box.tryTake())
11+
}
12+
13+
#if swift(>=6.0)
14+
func testHoldsNonSendable() {
15+
let html = MyComponent()
16+
let box = _SendableAnyHTMLBox(html)
17+
XCTAssertNotNil(box.tryTake())
18+
XCTAssertNil(box.tryTake())
19+
}
20+
#endif
21+
}
22+
23+
class NonSendable {
24+
var x: Int = 0
25+
}
26+
27+
struct MyComponent: HTML {
28+
let ns = NonSendable()
29+
var content: some HTML {
30+
div { "\(ns.x)" }
31+
}
32+
}

0 commit comments

Comments
 (0)