Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
lorentey opened this issue Jun 29, 2022 · 6 comments · Fixed by #58
Closed

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

lorentey opened this issue Jun 29, 2022 · 6 comments · Fixed by #58
Labels
bug Something isn't working
Milestone

Comments

@lorentey
Copy link
Member

lorentey commented Jun 29, 2022

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.

rdar://100626316

@lorentey lorentey added the bug Something isn't working label Jun 29, 2022
@itaiferber
Copy link

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
Copy link

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
Copy link

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
Copy link

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

@lorentey lorentey linked a pull request Sep 30, 2022 that will close this issue
6 tasks
@lorentey
Copy link
Member Author

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 lorentey added this to the 1.1.0 milestone Mar 18, 2023
@itaiferber
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants