swift-atomics icon indicating copy to clipboard operation
swift-atomics copied to clipboard

Swift 5.7/6 and Non-Final Class Opt-In to AtomicReference

Open lorentey opened this issue 3 years ago • 4 comments

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-atomics no 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>: AtomicReference to handle this type of storage, and make references to that? Drop down to AtomicReferenceStorage/AtomicOptionalReferenceStorage directly? Something else?

Apologies if there is already official guidance somewhere on how to handle this — in my searching, I haven't found anything definitive.

lorentey avatar Jun 29 '22 23:06 lorentey

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.

itaiferber avatar Jul 08 '22 13:07 itaiferber

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 {
        ^

itaiferber avatar Jul 08 '22 13:07 itaiferber

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?

itaiferber avatar Jul 08 '22 14:07 itaiferber

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 {}

itaiferber avatar Jul 08 '22 14:07 itaiferber

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+.

lorentey avatar Mar 18 '23 04:03 lorentey

Thanks for taking care of this, Karoy! I haven't gotten to test in production, but the fix here seems entirely reasonable.

itaiferber avatar Mar 22 '23 00:03 itaiferber