Skip to content

Commit 456ea14

Browse files
committed
[ClangImporter] SE-0463: Implement @Sendable inference exception for global actor isolated functions
Functions that are isolated to a global actor don't have completion handlers imported as `@Sendable`. Main actor isolated functions with completion handlers that are always called on the main actor is a very common Objective-C pattern, and this carve out will eliminate false positive warnings in the cases where the main actor annotation is missing on the completion handler parameter. Resolves: rdar://149811049
1 parent 74471e8 commit 456ea14

File tree

3 files changed

+139
-12
lines changed

3 files changed

+139
-12
lines changed

lib/ClangImporter/ImportType.cpp

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,11 +2443,30 @@ ImportedType ClangImporter::Implementation::importFunctionParamsAndReturnType(
24432443
return {swiftResultTy, importedType.isImplicitlyUnwrapped()};
24442444
}
24452445

2446+
static bool isParameterContextGlobalActorIsolated(DeclContext *dc,
2447+
const clang::Decl *parent) {
2448+
if (getActorIsolationOfContext(dc).isGlobalActor())
2449+
return true;
2450+
2451+
if (!parent->hasAttrs())
2452+
return false;
2453+
2454+
for (const auto *attr : parent->getAttrs()) {
2455+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) {
2456+
if (isMainActorAttr(swiftAttr))
2457+
return true;
2458+
}
2459+
}
2460+
2461+
return false;
2462+
}
2463+
24462464
std::optional<ClangImporter::Implementation::ImportParameterTypeResult>
24472465
ClangImporter::Implementation::importParameterType(
2448-
const clang::ParmVarDecl *param, OptionalTypeKind optionalityOfParam,
2449-
bool allowNSUIntegerAsInt, bool isNSDictionarySubscriptGetter,
2450-
bool paramIsError, bool paramIsCompletionHandler,
2466+
DeclContext *dc, const clang::Decl *parent, const clang::ParmVarDecl *param,
2467+
OptionalTypeKind optionalityOfParam, bool allowNSUIntegerAsInt,
2468+
bool isNSDictionarySubscriptGetter, bool paramIsError,
2469+
bool paramIsCompletionHandler,
24512470
std::optional<unsigned> completionHandlerErrorParamIndex,
24522471
ArrayRef<GenericTypeParamDecl *> genericParams,
24532472
llvm::function_ref<void(Diagnostic &&)> addImportDiagnosticFn) {
@@ -2466,7 +2485,8 @@ ClangImporter::Implementation::importParameterType(
24662485

24672486
if (SwiftContext.LangOpts.hasFeature(Feature::SendableCompletionHandlers) &&
24682487
paramIsCompletionHandler) {
2469-
attrs |= ImportTypeAttr::DefaultsToSendable;
2488+
if (!isParameterContextGlobalActorIsolated(dc, parent))
2489+
attrs |= ImportTypeAttr::DefaultsToSendable;
24702490
}
24712491

24722492
if (auto optionSetEnum = importer::findOptionSetEnum(paramTy, *this)) {
@@ -2743,13 +2763,13 @@ ParameterList *ClangImporter::Implementation::importFunctionParameterList(
27432763

27442764
ImportDiagnosticAdder paramAddDiag(*this, clangDecl, param->getLocation());
27452765

2746-
auto swiftParamTyOpt =
2747-
importParameterType(param, optionalityOfParam, allowNSUIntegerAsInt,
2748-
/*isNSDictionarySubscriptGetter=*/false,
2749-
/*paramIsError=*/false,
2750-
/*paramIsCompletionHandler=*/false,
2751-
/*completionHandlerErrorParamIndex=*/std::nullopt,
2752-
genericParams, paramAddDiag);
2766+
auto swiftParamTyOpt = importParameterType(
2767+
dc, clangDecl, param, optionalityOfParam, allowNSUIntegerAsInt,
2768+
/*isNSDictionarySubscriptGetter=*/false,
2769+
/*paramIsError=*/false,
2770+
/*paramIsCompletionHandler=*/false,
2771+
/*completionHandlerErrorParamIndex=*/std::nullopt, genericParams,
2772+
paramAddDiag);
27532773
if (!swiftParamTyOpt) {
27542774
addImportDiagnostic(param,
27552775
Diagnostic(diag::parameter_type_not_imported, param),
@@ -3301,7 +3321,8 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
33013321
ImportDiagnosticAdder paramAddDiag(*this, clangDecl, param->getLocation());
33023322

33033323
auto swiftParamTyOpt = importParameterType(
3304-
param, optionalityOfParam, allowNSUIntegerAsIntInParam,
3324+
origDC, clangDecl, param, optionalityOfParam,
3325+
allowNSUIntegerAsIntInParam,
33053326
kind == SpecialMethodKind::NSDictionarySubscriptGetter, paramIsError,
33063327
paramIsCompletionHandler, completionHandlerErrorParamIndex,
33073328
ArrayRef<GenericTypeParamDecl *>(), paramAddDiag);

lib/ClangImporter/ImporterImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
14661466

14671467
/// Import a parameter type
14681468
///
1469+
/// \param dc The declaration context in which this parameter appears.
1470+
/// \param parent The declaration with which this parameter is associated.
14691471
/// \param param The underlaying parameter declaraction.
14701472
/// \param optionalityOfParam The kind of optionality for the parameter
14711473
/// being imported.
@@ -1493,6 +1495,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
14931495
///
14941496
/// \returns The imported parameter result on success, or None on failure.
14951497
std::optional<ImportParameterTypeResult> importParameterType(
1498+
DeclContext *dc, const clang::Decl *parent,
14961499
const clang::ParmVarDecl *param, OptionalTypeKind optionalityOfParam,
14971500
bool allowNSUIntegerAsInt, bool isNSDictionarySubscriptGetter,
14981501
bool paramIsError, bool paramIsCompletionHandler,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// RUN: %empty-directory(%t/src)
2+
// RUN: %empty-directory(%t/sdk)
3+
// RUN: split-file %s %t/src
4+
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
6+
// RUN: -import-objc-header %t/src/Test.h \
7+
// RUN: -swift-version 6 \
8+
// RUN: -module-name main -I %t -verify
9+
10+
// REQUIRES: objc_interop
11+
// REQUIRES: swift_feature_SendableCompletionHandlers
12+
13+
//--- Test.h
14+
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
15+
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
16+
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))
17+
18+
#pragma clang assume_nonnull begin
19+
20+
@import Foundation;
21+
22+
MAIN_ACTOR
23+
@protocol P <NSObject>
24+
@end
25+
26+
@interface TestFromProtocol <P>
27+
-(void) compute: (void (^)(void)) completion;
28+
@end
29+
30+
MAIN_ACTOR
31+
@interface TestFromType <NSObject>
32+
-(void) compute: (void (^)(void)) completion;
33+
@end
34+
35+
@interface TestSubclass : TestFromType
36+
-(void) subCompute: (void (^)(void)) completion;
37+
@end
38+
39+
@interface TestFromMethod <NSObject>
40+
-(void) MAIN_ACTOR compute: (void (^)(void)) completion;
41+
+(void) MAIN_ACTOR computeStatic: (void (^)(void)) completion;
42+
@end
43+
44+
#pragma clang assume_nonnull end
45+
46+
//--- main.swift
47+
48+
func testFromProtocol(v: TestFromProtocol) {
49+
let _: Int = v.compute
50+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
51+
}
52+
53+
func testFromType(v: TestFromType) {
54+
let _: Int = v.compute
55+
// expected-error@-1 {{annot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
56+
}
57+
58+
func testFromSuperclass(v: TestSubclass) {
59+
let _: Int = v.subCompute
60+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
61+
}
62+
63+
func testFromMethod(v: TestFromMethod, t: TestFromMethod.Type) {
64+
let _: Int = v.compute
65+
// expected-error@-1 {{cannot convert value of type '@MainActor (@escaping () -> Void) -> Void' to specified type 'Int'}}
66+
67+
let _: Int = t.computeStatic
68+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
69+
}
70+
71+
nonisolated func testUse(v1: TestFromProtocol, v2: TestFromType, v3: TestSubclass, v4: TestFromMethod, v5: TestFromMethod.Type) async {
72+
var val: Int = 0
73+
74+
await v1.compute { val += 1 } // No execution warning because parameter type isn't Sendable
75+
// expected-warning@-1 {{consider using asynchronous alternative function}}
76+
77+
await v1.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
78+
// expected-warning@-1 {{consider using asynchronous alternative function}}
79+
80+
await v2.compute { val += 1 } // No execution warning because parameter type isn't Sendable
81+
// expected-warning@-1 {{consider using asynchronous alternative function}}
82+
83+
await v2.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
84+
// expected-warning@-1 {{consider using asynchronous alternative function}}
85+
86+
await v3.subCompute { val += 1 } // No execution warning because parameter type isn't Sendable
87+
// expected-warning@-1 {{consider using asynchronous alternative function}}
88+
89+
await v3.subCompute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
90+
// expected-warning@-1 {{consider using asynchronous alternative function}}
91+
92+
await v4.compute { val += 1 } // No execution warning because parameter type isn't Sendable
93+
// expected-warning@-1 {{consider using asynchronous alternative function}}
94+
95+
await v4.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
96+
// expected-warning@-1 {{consider using asynchronous alternative function}}
97+
98+
await v5.computeStatic { val += 1 } // No execution warning because parameter type isn't Sendable
99+
// expected-warning@-1 {{consider using asynchronous alternative function}}
100+
101+
await v5.computeStatic { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
102+
// expected-warning@-1 {{consider using asynchronous alternative function}}
103+
}

0 commit comments

Comments
 (0)