Skip to content

[sema] Work around a double curry thunk actor isolation inference bug that is a knock on effect of ced96aa5cd653f834d2a8293ead8cf46649202cb. #82375

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

Merged

Conversation

gottesmm
Copy link
Contributor

Specifically, there is currently a bug in TypeCheckConcurrency.cpp where we do not visit autoclosures. This causes us to never set the autoclosure's ActorIsolation field like all other closures. For a long time we were able to get away with this just by relying on the isolation of the decl context of the autoclosure... but with the introduction of nonisolated(nonsending), we found cases where the generated single curry autoclosure would necessarily be different than its decl context (e.x.: a synchronous outer curry thunk that is nonisolated that returns an inner curry thunk that is nonisolated(nonsending)). This problem caused us to hit asserts later in the compiler since the inner closure was actually nonisolated(nonsending), but we were thinking that it should have been concurrent.

To work around this problem, I changed the type checker in ced96aa to explicitly set the isolation of single curry thunk autoclosures when it generates them. The reason why we did this is that it made it so that we did not have to have a potential large source break in 6.2 by changing TypeCheckConcurrency.cpp to visit all autoclosures it has not been visiting.

This caused a follow on issue where since we were now inferring the inner autoclosure to have the correct isolation, in cases where we were creating a double curry thunk for an access to a global actor isolated field of a non-Sendable non-global actor isolated nominal type, we would have the outer curry thunk have unspecified isolation instead of main actor isolation. An example of this is the following:

class A {
  var block:  @MainActor () -> Void = {}
}

class B {
  let a = A()

  func d() {
    a.block = c // Error! Passing task isolated 'self' to @MainActor closure.
  }

  @MainActor
  func c() {}
}

This was unintentional. To work around this, this commit changes the type checker to explicitly set the double curry thunk isolation to the correct value when the type checker generates the double curry thunk in the same manner as it does for single curry thunks and validates that if we do set the value to something explicitly that it has the same value as the single curry thunk.

rdar://152522631

@gottesmm
Copy link
Contributor Author

@swift-ci smoke test

Copy link
Contributor

@xedin xedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few comments inline but they are minor. It would be good to add tests I suggested before merging though.

auto outerActorIsolation = [&]() -> std::optional<ActorIsolation> {
auto resultType = outerThunkTy->getResult();
// Look through an optional if we have one.
if (auto opt = resultType->getOptionalObjectType())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to look through all optional types if the member type has more than one optional on its result...

@@ -1536,6 +1540,29 @@ namespace {
cs.cacheType(selfOpenedRef);
}

auto outerActorIsolation = [&]() -> std::optional<ActorIsolation> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a comment explaining why we are doing this here and what the alternatives are.

@@ -1313,6 +1313,21 @@ namespace {
return callExpr;
}

std::optional<ActorIsolation>
getIsolationForFunctionType(FunctionType *thunkTy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be From not For?

let a = A()

func d() {
a.block = c // expected-warning {{converting non-Sendable function value to '@MainActor @Sendable () -> Void' may introduce data races}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the new checks are handling optionals explicitly, let's add a few test cases that have different levels optionality to make sure that works correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the optional check b/c I was hitting it in the test suite. But I can add an actual test.

… that is a knock on effect of ced96aa.

Specifically, there is currently a bug in TypeCheckConcurrency.cpp where we do
not visit autoclosures. This causes us to never set the autoclosure's
ActorIsolation field like all other closures. For a long time we were able to
get away with this just by relying on the isolation of the decl context of the
autoclosure... but with the introduction of nonisolated(nonsending), we found
cases where the generated single curry autoclosure would necessarily be
different than its decl context (e.x.: a synchronous outer curry thunk that is
nonisolated that returns an inner curry thunk that is
nonisolated(nonsending)). This problem caused us to hit asserts later in the
compiler since the inner closure was actually nonisolated(nonsending), but we
were thinking that it should have been concurrent.

To work around this problem, I changed the type checker in
ced96aa to explicitly set the isolation of
single curry thunk autoclosures when it generates them. The reason why we did
this is that it made it so that we did not have to have a potential large source
break in 6.2 by changing TypeCheckConcurrency.cpp to visit all autoclosures it
has not been visiting.

This caused a follow on issue where since we were now inferring the inner
autoclosure to have the correct isolation, in cases where we were creating a
double curry thunk for an access to a global actor isolated field of a
non-Sendable non-global actor isolated nominal type, we would have the outer
curry thunk have unspecified isolation instead of main actor isolation. An
example of this is the following:

```swift
class A {
  var block:  @mainactor () -> Void = {}
}

class B {
  let a = A()

  func d() {
    a.block = c // Error! Passing task isolated 'self' to @mainactor closure.
  }

  @mainactor
  func c() {}
}
```

This was unintentional. To work around this, this commit changes the type
checker to explicitly set the double curry thunk isolation to the correct value
when the type checker generates the double curry thunk in the same manner as it
does for single curry thunks and validates that if we do set the value to
something explicitly that it has the same value as the single curry thunk.

rdar://152522631
@gottesmm gottesmm force-pushed the pr-4e28ed16e5bf409f1bdd07e56b1074a13efabb7d branch from d3500c6 to c28490b Compare June 20, 2025 18:04
@gottesmm
Copy link
Contributor Author

@swift-ci smoke test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants