Cross module `Sendable` inference

While wrestling with eliminating some concurrency warnings, I noticed that if I mark a type as frozen, the warnings disappear. This is a bit confusing as the docs state:

When the compiler isn’t in library evolution mode, all structures and enumerations are implicitly frozen, and this attribute is ignored.

The affected project is an iOS app which organises its code into modules using a local swift package, and, afaict, library evolution mode isn't on. (BUILD_LIBRARY_FOR_DISTRIBUTION = No)

This can be reproduced with the following types and module hierarchy:

Module 1

import Module2

@MainActor final class Container {
  
  let a1 = A1()
  let a2 = A2()
  
  deinit {
    a1.cleanup() // This is fine.
    a2.cleanup() // WARNING: Cannot access property 'a2' with a non-sendable type 'A2' from non-isolated deinit; this is an error in Swift 6
  }
}


internal struct A1 {
  internal init() {}
  internal func cleanup() {}
}

Module 2

public struct A2 {
  public init() {}
  public func cleanup() {}
}

When A2 isn't annotated with @frozen, a warning is raised in the deinit. However, when A2 is annotated with the @frozen attribute, the warning disappears.

So are we safe to assume that the @frozen attribute does have utility outside of library evolution? Or is it that the implicitly frozen status of non-evolution mode types isn't being picked up by the compiler?

1 Like

Looking further into this, it seems that marking a type as @frozen allows Sendable to be inferred and hence the elimination of the warning. So better just to mark the type as Sendable.

But if frozen is implicit when evolution mode is disabled, shouldn't cross module Sendable inference also be enabled?

3 Likes