swift-atomics
swift-atomics copied to clipboard
Swift 5.7/6 and Non-Final Class Opt-In to AtomicReference
From Itai on the forum: (https://forums.swift.org/t/swift-5-7-6-and-non-final-class-opt-in-to-atomicreference/58509)
Consider a class Cls, to which we'd like to be able to make an atomic reference via ManagedAtomic. This class is intentionally the root of a class hierarchy, and we'd like to be able to make references to it, and its subclasses:
import Atomics
// Swift 5.6
class Cls: AtomicReference {} // ✅
This works without complaint in Swift 5.5/5.6, but in Swift 5.7, new warnings are introduced by the conformance above:
// Swift 5.7
// warning: Non-final class 'Cls' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
// warning: Non-final class 'Cls' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
// warning: Non-final class 'Cls' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
class Cls: AtomicReference {} // ⚠️
These warnings make perfect sense: as written, the constraints on the Atomic* types all require type equality, which will clearly be violated by subclasses. (In practice, I don't believe this matters, because as stored, Cls and its subclasses all have identical layouts—just a pointer to actual storage—but happy to be corrected if this is a nontrivial mistake.)
Is there guidance going forward on what to do here?
- Will an exception somehow be made for these types, or will clients of
swift-atomicsno longer be able to do this?- If this is safe to do, will there be a way to silence these warnings in Swift 5.7? (Apologies if this is already possible, and I've missed that capability)
- And if not, what's the right way to handle this? Should we be writing a generic
final class AtomicWrapper<T: AnyObject>: AtomicReferenceto handle this type of storage, and make references to that? Drop down toAtomicReferenceStorage/AtomicOptionalReferenceStoragedirectly? Something else?
Apologies if there is already official guidance somewhere on how to handle this — in my searching, I haven't found anything definitive.
I'm looking into this a little in some spare time to see what I might be able to do to help. Karoy's suggestion on the forums was to explore a direction like
public protocol AtomicReference: AnyObject, AtomicOptionalWrappable
{
associatedtype AtomicBaseClass: AtomicReference = Self
where
Self: AtomicBaseClass,
AtomicRepresentation == AtomicReferenceStorage<AtomicBaseClass>,
AtomicOptionalRepresentation == AtomicOptionalReferenceStorage<AtomicBaseClass>
}
I think this semantically represents our intentions, but the compiler can't accept Self constraints in this way:
where
Self: AtomicBaseClass
// 🛑 Constraint with subject type of 'Self' is not supported; consider adding requirement to protocol inheritance clause instead
// 🛑 Type 'Self' constrained to non-protocol, non-class type 'Self.AtomicBaseClass'
(Moving the Self constraint to a protocol inheritance clause doesn't help because the underlying issue is that we can't constrain Self to inherit from the unknown type Self.AtomicBaseClass; we still have the "non-protocol, non-class type" error.)
If we drop this self requirement but leave the other requirements as-is, we still get legit warnings when building, because the compiler isn't satisfied:
class LockFreeQueue<Element> {
class Node: AtomicReference {
// ⚠️ Non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicBaseClass' is exactly equal to 'Self'; this is an error in Swift 6
// ⚠️ Non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
// ⚠️ Non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
The first warning is due to the = Self default value assignment being treated as strict equality; the others are deeper issues down in AtomicOptionalWrappable and AtomicValue, which also have strict equality requirements. I'm a bit worried about those latter ones, because we can't introduce inheritance relationships there even if we wanted to.
In general, I'm not sure there are any constraints we can put on Self in these cases that the compiler will accept, unless there are features that I'm not aware of which would allow that (and there very well may be!).
I'll keep thinking on this; we may need to inject some more associatedtype hackery, or additional protocols that help us express some of these rules. There may also be something stupidly obvious I'm missing.
I will also add that these warnings are also present in swift-atomics itself, in the tests. Building with Xcode 14β3 yields:
$ swift test
Building for debugging...
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceShuffle.swift:34:9: warning: non-final class 'List<Value>.Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/autogenerated/Basics.swift:46:15: warning: non-final class 'Baz' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/StrongReferenceRace.swift:26:15: warning: non-final class 'Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
private class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicReference', which requires that 'Self.AtomicOptionalRepresentation' is exactly equal to 'AtomicOptionalReferenceStorage<Self>'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicOptionalWrappable', which requires that 'Self.AtomicOptionalRepresentation.Value' is exactly equal to 'Self?'; this is an error in Swift 6
class Node: AtomicReference {
^
/Users/itai/Development/swift-atomics/Tests/AtomicsTests/LockFreeQueue.swift:43:9: warning: non-final class 'LockFreeQueue<Element>.Node' cannot safely conform to protocol 'AtomicValue', which requires that 'Self.AtomicRepresentation.Value' is exactly equal to 'Self'; this is an error in Swift 6
class Node: AtomicReference {
^
It seems that theoretically, we might be able to make some progress with something like
public protocol AtomicReference: AnyObject, AtomicOptionalWrappable
{
associatedtype AtomicBaseClass: AtomicReference = Self
where
AtomicRepresentation: AtomicBaseClass,
AtomicOptionalRepresentation: AtomicBaseClass?
}
and drop the hard requirements on equality, except the compiler won't accept inheritance requirements using AtomicBaseClass with similar errors:
$ swift build
Building for debugging...
/Users/itai/Development/swift-atomics/Sources/Atomics/AtomicStrongReference.swift:24:27: error: type 'Self.AtomicRepresentation' constrained to non-protocol, non-class type 'Self.AtomicBaseClass'
AtomicRepresentation: AtomicBaseClass,
^
/Users/itai/Development/swift-atomics/Sources/Atomics/AtomicStrongReference.swift:25:35: error: type 'Self.AtomicOptionalRepresentation' constrained to non-protocol, non-class type 'Self.AtomicBaseClass?'
AtomicOptionalRepresentation: AtomicBaseClass?
^
/Users/itai/Development/swift-atomics/Sources/Atomics/AtomicStrongReference.swift:24:27: error: type 'Self.AtomicRepresentation' constrained to non-protocol, non-class type 'Self.AtomicBaseClass'
AtomicRepresentation: AtomicBaseClass,
^
/Users/itai/Development/swift-atomics/Sources/Atomics/AtomicStrongReference.swift:25:35: error: type 'Self.AtomicOptionalRepresentation' constrained to non-protocol, non-class type 'Self.AtomicBaseClass?'
AtomicOptionalRepresentation: AtomicBaseClass?
A minimized representation of the problem for anyone who might want to play along at home:
struct S<T>: X {}
protocol X {
associatedtype T
}
protocol Y {
associatedtype U: X
where U.T == Self
}
protocol Z: AnyObject, Y {
associatedtype V: Z = Self
where U == S<V>
}
class C: Z {}
I think the best way forward is to push the AtomicValue requirement that AtomicStorage.Value == Self down into the UnmanagedAtomic/ManagedAtomic generics:
public protocol AtomicValue {
/// The atomic storage representation for this value.
associatedtype AtomicRepresentation: AtomicStorage
/* where Self is a subtype of AtomicRepresentation.Value */
}
public protocol AtomicReference: AnyObject, AtomicOptionalWrappable
where
AtomicRepresentation == AtomicReferenceStorage<_AtomicBase>,
AtomicOptionalRepresentation == AtomicOptionalReferenceStorage<_AtomicBase>
{
associatedtype _AtomicBase: AnyObject = Self
}
@frozen
public struct UnsafeAtomic<Value: AtomicValue>
where Value.AtomicRepresentation.Value == Value { ... }
@_fixed_layout
public class ManagedAtomic<Value: AtomicValue>
where Value.AtomicRepresentation.Value == Value { ... }
This will allow class hierarchies to conform to AtomicReference, but attempts to use a subclass as the generic parameter to ManagedAtomic/UnsafeAtomic will be rejected:
class Base: AtomicReference {}
class Child: Base {}
let ref: ManagedAtomic<Child>
// error: 'ManagedAtomic' requires the types 'Derived' and 'Child' be equivalent
This aligns this package with the newly enforced type system restrictions in Swift 5.7+.
Thanks for taking care of this, Karoy! I haven't gotten to test in production, but the fix here seems entirely reasonable.