Skip to content

Commit

Permalink
Reintroduce conditional import (#108)
Browse files Browse the repository at this point in the history
* Reintroduce conditional import

Not all platforms have Observation enabled in their official releases at
this time, for example SwiftWasm.

* wip

* wip

* wip

* Fix

* wip

* wip

* wip

* wip

* wip
  • Loading branch information
stephencelis authored Nov 14, 2024
1 parent 81b311a commit dccdf5a
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 79 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,37 @@ jobs:
run: make test-compatibility
if: ${{ matrix.config == 'debug' }}

linux:
name: Linux
strategy:
matrix:
swift:
- '6.0'
runs-on: ubuntu-latest
container: swift:${{ matrix.swift }}
steps:
- uses: actions/checkout@v4
- name: Build
run: swift build

wasm:
name: Wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bytecodealliance/actions/wasmtime/setup@v1
- name: Install Swift and Swift SDK for WebAssembly
run: |
PREFIX=/opt/swift
set -ex
curl -f -o /tmp/swift.tar.gz "https://download.swift.org/swift-6.0.2-release/ubuntu2204/swift-6.0.2-RELEASE/swift-6.0.2-RELEASE-ubuntu22.04.tar.gz"
sudo mkdir -p $PREFIX; sudo tar -xzf /tmp/swift.tar.gz -C $PREFIX --strip-component 1
$PREFIX/usr/bin/swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.2-RELEASE/swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi.artifactbundle.zip --checksum 6ffedb055cb9956395d9f435d03d53ebe9f6a8d45106b979d1b7f53358e1dcb4
echo "$PREFIX/usr/bin" >> $GITHUB_PATH
- name: Build
run: swift build --swift-sdk wasm32-unknown-wasi -Xlinker -z -Xlinker stack-size=$((1024 * 1024))

check-macro-compatibility:
name: Check Macro Compatibility
runs-on: macos-latest
Expand Down
7 changes: 6 additions & 1 deletion Sources/PerceptionCore/Internal/Exports.swift
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
@_exported import Observation
#if canImport(Observation)
@_exported import Observation
#else
@available(macOS 14, iOS 17, watchOS 10, tvOS 17, *)
public protocol Observable {}
#endif
2 changes: 1 addition & 1 deletion Sources/PerceptionCore/Internal/ThreadLocal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Foundation

struct _ThreadLocal {
#if os(WASI)
static var value: UnsafeMutableRawPointer?
static nonisolated(unsafe) var value: UnsafeMutableRawPointer?
#else
static var value: UnsafeMutableRawPointer? {
get { Thread.current.threadDictionary[Key()] as! UnsafeMutableRawPointer? }
Expand Down
162 changes: 89 additions & 73 deletions Sources/PerceptionCore/PerceptionRegistrar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public struct PerceptionRegistrar: Sendable {
/// of a type.
public init(isPerceptionCheckingEnabled: Bool = PerceptionCore.isPerceptionCheckingEnabled) {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta {
self._rawValue = AnySendable(ObservationRegistrar())
#if canImport(Observation)
self._rawValue = AnySendable(ObservationRegistrar())
#else
self._rawValue = AnySendable(_PerceptionRegistrar())
#endif
} else {
self._rawValue = AnySendable(_PerceptionRegistrar())
}
Expand All @@ -33,47 +37,51 @@ public struct PerceptionRegistrar: Sendable {
#endif
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
private var registrar: ObservationRegistrar {
self._rawValue.base as! ObservationRegistrar
}
#if canImport(Observation)
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
private var registrar: ObservationRegistrar {
self._rawValue.base as! ObservationRegistrar
}
#endif

private var perceptionRegistrar: _PerceptionRegistrar {
self._rawValue.base as! _PerceptionRegistrar
}
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PerceptionRegistrar {
public func access<Subject: Observable, Member>(
_ subject: Subject,
keyPath: KeyPath<Subject, Member>,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) {
self.registrar.access(subject, keyPath: keyPath)
}
#if canImport(Observation)
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PerceptionRegistrar {
public func access<Subject: Observable, Member>(
_ subject: Subject,
keyPath: KeyPath<Subject, Member>,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) {
self.registrar.access(subject, keyPath: keyPath)
}

public func withMutation<Subject: Observable, Member, T>(
of subject: Subject, keyPath: KeyPath<Subject, Member>, _ mutation: () throws -> T
) rethrows -> T {
try self.registrar.withMutation(of: subject, keyPath: keyPath, mutation)
}
public func withMutation<Subject: Observable, Member, T>(
of subject: Subject, keyPath: KeyPath<Subject, Member>, _ mutation: () throws -> T
) rethrows -> T {
try self.registrar.withMutation(of: subject, keyPath: keyPath, mutation)
}

public func willSet<Subject: Observable, Member>(
_ subject: Subject, keyPath: KeyPath<Subject, Member>
) {
self.registrar.willSet(subject, keyPath: keyPath)
}
public func willSet<Subject: Observable, Member>(
_ subject: Subject, keyPath: KeyPath<Subject, Member>
) {
self.registrar.willSet(subject, keyPath: keyPath)
}

public func didSet<Subject: Observable, Member>(
_ subject: Subject, keyPath: KeyPath<Subject, Member>
) {
self.registrar.didSet(subject, keyPath: keyPath)
public func didSet<Subject: Observable, Member>(
_ subject: Subject, keyPath: KeyPath<Subject, Member>
) {
self.registrar.didSet(subject, keyPath: keyPath)
}
}
}
#endif

extension PerceptionRegistrar {
@_disfavoredOverload
Expand All @@ -93,17 +101,19 @@ extension PerceptionRegistrar {
column: column
)
#endif
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta {
func `open`<T: Observable>(_ subject: T) {
self.registrar.access(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<T, Member>.self)
)
}
if let subject = subject as? any Observable {
return open(subject)
#if canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta {
func `open`<T: Observable>(_ subject: T) {
self.registrar.access(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<T, Member>.self)
)
}
if let subject = subject as? any Observable {
return open(subject)
}
}
}
#endif
self.perceptionRegistrar.access(subject, keyPath: keyPath)
}

Expand All @@ -113,18 +123,20 @@ extension PerceptionRegistrar {
keyPath: KeyPath<Subject, Member>,
_ mutation: () throws -> T
) rethrows -> T {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) throws -> T {
return try self.registrar.withMutation(
of: subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self),
mutation
)
#if canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) throws -> T {
return try self.registrar.withMutation(
of: subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self),
mutation
)
}
return try open(subject)
}
return try open(subject)
}
#endif
return try self.perceptionRegistrar.withMutation(of: subject, keyPath: keyPath, mutation)
}

Expand All @@ -133,17 +145,19 @@ extension PerceptionRegistrar {
_ subject: Subject,
keyPath: KeyPath<Subject, Member>
) {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) {
return self.registrar.willSet(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self)
)
#if canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) {
return self.registrar.willSet(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self)
)
}
return open(subject)
}
return open(subject)
}
#endif
return self.perceptionRegistrar.willSet(subject, keyPath: keyPath)
}

Expand All @@ -152,17 +166,19 @@ extension PerceptionRegistrar {
_ subject: Subject,
keyPath: KeyPath<Subject, Member>
) {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) {
return self.registrar.didSet(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self)
)
#if canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta,
let subject = subject as? any Observable
{
func `open`<S: Observable>(_ subject: S) {
return self.registrar.didSet(
subject,
keyPath: unsafeDowncast(keyPath, to: KeyPath<S, Member>.self)
)
}
return open(subject)
}
return open(subject)
}
#endif
return self.perceptionRegistrar.didSet(subject, keyPath: keyPath)
}
}
Expand Down
8 changes: 5 additions & 3 deletions Sources/PerceptionCore/PerceptionTracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,11 @@ public func withPerceptionTracking<T>(
_ apply: () -> T,
onChange: @autoclosure () -> @Sendable () -> Void
) -> T {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta {
return withObservationTracking(apply, onChange: onChange())
}
#if canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *), !isObservationBeta {
return withObservationTracking(apply, onChange: onChange())
}
#endif
let (result, accessList) = generateAccessList(apply)
if let accessList {
PerceptionTracking._installTracking(accessList, onChange: onChange())
Expand Down
2 changes: 1 addition & 1 deletion Sources/PerceptionMacros/PerceptibleMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ extension PerceptibleMacro: ExtensionMacro {
}

let decl: DeclSyntax = """
extension \(raw: type.trimmedDescription): \(raw: qualifiedConformanceName), Observation.Observable {}
extension \(raw: type.trimmedDescription): \(raw: qualifiedConformanceName), Observable {}
"""
let ext = decl.cast(ExtensionDeclSyntax.self)

Expand Down

0 comments on commit dccdf5a

Please sign in to comment.