Skip to content

Compiler shouldn't allow a closure capturing isolated parameter to be marked @concurrent or @MainActor #87694

@rayx

Description

@rayx

Description

Compiler currently doesn't allow a function taking isolated parameter to be marked @concurrent or @MainActor, but it allows a closure capturing isolated parameter to do that. While the implementation appears to work well (see SIL below), it's quite misleading, especially when the closure is defined within an actor and captures self by calling methods or accessing properties.

Also, the behaviors of the two examples appears inconsistent. See table below.

isolation specified by modifier isolation specified by isolation parameter where does fn1 runs?
example 1 global executor MainActor MainActor
example 2 MyGlobalActor MainActor MyGlobalActor

Example 1) @concurrent + isolated parameter

SIL: https://swift.godbolt.org/z/qhKTWvWv3

func foo(_ isolation: isolated MainActor) async {
    let c: @concurrent () async -> Void = { 
        fn1()
        _ = isolation; 
        await fn2() 
    }

    await bar(isolation, c)
}

func bar(_ isolation: isolated MainActor, _ fp: @concurrent () async -> Void) async {
    await fp()
}

func fn1() {}

@MainActor 
func fn2() {}

Below are what happen in closure c, based on its SIL:

  • hop to MainActor
  • call fn1
  • hop to MainActor
  • call fn2
  • hop (back) to MainActor and return

Example 2) @MainActor + isolated parameter

SIL: https://swift.godbolt.org/z/c7MGfEzqn

@globalActor
actor MyGlobalActor {
    static let shared = MyGlobalActor()
}

func foo(_ isolation: isolated MainActor) async {
    let c: @MyGlobalActor () async -> Void = { 
        fn1()
        _ = isolation; 
        await fn2() 
    }

    await bar(isolation, c)
}

func bar(_ isolation: isolated MainActor, _ fp: @MyGlobalActor () async -> Void) async {
    await fp()
}

func fn1() {}

@MainActor 
func fn2() {}

Below are what happen in closure c, based on its SIL:

  • hop to MyGlobalActor
  • call fn1
  • hop to MainActor
  • call fn2
  • hop back to MyGlobalActor and return

Reproduction

See above examples

Expected behavior

They shouldn't compile

Environment

Swift 6.2 and beyond (nightly, etc.)

Additional information

  1. I suspect this may also cause weird RBI issues.
  2. I also suspect Converting nonisolated(nonsending) closure to @concurrent closure may cause data race #87674 might be related to this issue, but I don't have more details yet.

Metadata

Metadata

Assignees

No one assigned

    Labels

    @concurrentFeature → attributes: The "concurrent" attributeSILGenArea → compiler: The SIL generation stageactor isolationFeature → concurrency: Actor isolationcompilerThe Swift compiler itselfconcurrencyFeature: umbrella label for concurrency language features

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions