swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.58k stars 10.36k forks source link

Spurious error about sending a value when it’s not actually being sent #75600

Open ahoppen opened 3 months ago

ahoppen commented 3 months ago

Compiling the following in Swift 6 mode produces an error. As far as I can tell, we don’t actually send anything since nothing is asynchronous.

class DocumentManager {
  func fileHasInMemoryModifications() -> Bool { true }
}

class CheckedIndex {
  func fileHasInMemoryModifications() -> Bool { true }
  func unitTests() {}
}

actor Foo {
  let documentManager: DocumentManager = DocumentManager()

  private func test(index: CheckedIndex?) async {
    _ = [1, 2].filter { _ in
      if let index {
        return index.fileHasInMemoryModifications()
      } else {
        return documentManager.fileHasInMemoryModifications()
      }
    }

    index?.unitTests()
  }
}

Error:

/tmp/a.swift:15:14: error: sending 'index' risks causing data races
13 |   private func tests2(index: CheckedIndex?) async {
14 |     _ = [1, 2].filter { _ in
15 |       if let index {
   |              |- error: sending 'index' risks causing data races
   |              `- note: 'self'-isolated 'index' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
16 |         return index.fileHasInMemoryModifications()
17 |       } else {
   :
20 |     }
21 | 
22 |     index?.unitTests()
   |          `- note: access can happen concurrently
23 |   }
24 | }

A workaround appears to be to put the access to documentManager inside a local function

    func documentManagerHasInMemoryModifications() -> Bool {
      return documentManager.fileHasInMemoryModifications()
    }

    _ = [1, 2].filter { _ in
      if let index {
        return index.fileHasInMemoryModifications()
      } else {
        return documentManagerHasInMemoryModifications()
      }
    }

    index?.unitTests()

rdar://130112205

gottesmm commented 2 months ago

Looking at this a little bit. One thing that is interesting is that in the erroring example, we consider the closure to be actor isolated and the one that doesn't error is not considered actor isolated.