Skip to content

Commit ab227b4

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 ab227b4

File tree

3 files changed

+138
-12
lines changed

3 files changed

+138
-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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
12+
//--- Test.h
13+
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
14+
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
15+
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))
16+
17+
#pragma clang assume_nonnull begin
18+
19+
@import Foundation;
20+
21+
MAIN_ACTOR
22+
@protocol P <NSObject>
23+
@end
24+
25+
@interface TestFromProtocol <P>
26+
-(void) compute: (void (^)(void)) completion;
27+
@end
28+
29+
MAIN_ACTOR
30+
@interface TestFromType <NSObject>
31+
-(void) compute: (void (^)(void)) completion;
32+
@end
33+
34+
@interface TestSubclass : TestFromType
35+
-(void) subCompute: (void (^)(void)) completion;
36+
@end
37+
38+
@interface TestFromMethod <NSObject>
39+
-(void) MAIN_ACTOR compute: (void (^)(void)) completion;
40+
+(void) MAIN_ACTOR computeStatic: (void (^)(void)) completion;
41+
@end
42+
43+
#pragma clang assume_nonnull end
44+
45+
//--- main.swift
46+
47+
func testFromProtocol(v: TestFromProtocol) {
48+
let _: Int = v.compute
49+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
50+
}
51+
52+
func testFromType(v: TestFromType) {
53+
let _: Int = v.compute
54+
// expected-error@-1 {{annot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
55+
}
56+
57+
func testFromSuperclass(v: TestSubclass) {
58+
let _: Int = v.subCompute
59+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
60+
}
61+
62+
func testFromMethod(v: TestFromMethod, t: TestFromMethod.Type) {
63+
let _: Int = v.compute
64+
// expected-error@-1 {{cannot convert value of type '@MainActor (@escaping () -> Void) -> Void' to specified type 'Int'}}
65+
66+
let _: Int = t.computeStatic
67+
// expected-error@-1 {{cannot convert value of type '@MainActor @Sendable (@escaping () -> Void) -> Void' to specified type 'Int'}}
68+
}
69+
70+
nonisolated func testUse(v1: TestFromProtocol, v2: TestFromType, v3: TestSubclass, v4: TestFromMethod, v5: TestFromMethod.Type) async {
71+
var val: Int = 0
72+
73+
await v1.compute { val += 1 } // No execution warning because parameter type isn't Sendable
74+
// expected-warning@-1 {{consider using asynchronous alternative function}}
75+
76+
await v1.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
77+
// expected-warning@-1 {{consider using asynchronous alternative function}}
78+
79+
await v2.compute { val += 1 } // No execution warning because parameter type isn't Sendable
80+
// expected-warning@-1 {{consider using asynchronous alternative function}}
81+
82+
await v2.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
83+
// expected-warning@-1 {{consider using asynchronous alternative function}}
84+
85+
await v3.subCompute { val += 1 } // No execution warning because parameter type isn't Sendable
86+
// expected-warning@-1 {{consider using asynchronous alternative function}}
87+
88+
await v3.subCompute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
89+
// expected-warning@-1 {{consider using asynchronous alternative function}}
90+
91+
await v4.compute { val += 1 } // No execution warning because parameter type isn't Sendable
92+
// expected-warning@-1 {{consider using asynchronous alternative function}}
93+
94+
await v4.compute { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
95+
// expected-warning@-1 {{consider using asynchronous alternative function}}
96+
97+
await v5.computeStatic { val += 1 } // No execution warning because parameter type isn't Sendable
98+
// expected-warning@-1 {{consider using asynchronous alternative function}}
99+
100+
await v5.computeStatic { @Sendable in val += 1 } // expected-warning {{mutation of captured var 'val' in concurrently-executing code}}
101+
// expected-warning@-1 {{consider using asynchronous alternative function}}
102+
}

0 commit comments

Comments
 (0)