Skip to content

Commit 78825d9

Browse files
committed
[TypeChecker] Allow @objc @implementation to mismatch on @Sendable in parameter positions
Just like for protocol matching, let's allow `@Sendable` mismatches in parameter positions to make sure that it's possible for header authors to add concurrency annotations without breaking clients. This is especially important for `SendableCompletionHandlers` feature that makes imported sync completion handler parameters `@Sendable`.
1 parent 5c28f1d commit 78825d9

File tree

4 files changed

+110
-47
lines changed

4 files changed

+110
-47
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,6 +1898,11 @@ ERROR(objc_implementation_type_mismatch,none,
18981898
"%kind0 of type %1 does not match type %2 declared by the header",
18991899
(ValueDecl *, Type, Type))
19001900

1901+
ERROR(objc_implementation_sendability_mismatch,none,
1902+
"sendability of function types in %kind0 of type %1 does not match "
1903+
"type %2 declared by the header",
1904+
(ValueDecl *, Type, Type))
1905+
19011906
ERROR(objc_implementation_required_attr_mismatch,none,
19021907
"%kind0 %select{should not|should}2 be 'required' to match %1 declared "
19031908
"by the header",

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3436,6 +3436,8 @@ class ObjCImplementationChecker {
34363436
WrongRequiredAttr,
34373437
WrongForeignErrorConvention,
34383438

3439+
WrongSendability,
3440+
34393441
Match,
34403442
MatchWithExplicitObjCName,
34413443

@@ -3621,70 +3623,99 @@ class ObjCImplementationChecker {
36213623
return lhs == rhs;
36223624
}
36233625

3624-
static bool matchParamTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
3625-
TypeMatchOptions matchOpts = {};
3626-
3626+
static MatchOutcome matchParamTypes(Type reqTy, Type implTy,
3627+
ValueDecl *implDecl,
3628+
TypeMatchOptions matchOpts = {}) {
36273629
// Try a plain type match.
36283630
if (implTy->matchesParameter(reqTy, matchOpts))
3629-
return true;
3631+
return MatchOutcome::Match;
36303632

3631-
// If the implementation type is IUO, try unwrapping it.
3632-
if (auto unwrappedImplTy = implTy->getOptionalObjectType())
3633-
return implDecl->isImplicitlyUnwrappedOptional()
3634-
&& unwrappedImplTy->matchesParameter(reqTy, matchOpts);
3633+
// Try to drop `@Sendable`.
3634+
{
3635+
auto ignoreSendable =
3636+
matchOpts | TypeMatchFlags::IgnoreFunctionSendability;
36353637

3636-
return false;
3638+
if (implTy->matchesParameter(reqTy, ignoreSendable))
3639+
return MatchOutcome::WrongSendability;
3640+
}
3641+
3642+
if (matchOpts.contains(TypeMatchFlags::AllowNonOptionalForIUOParam)) {
3643+
ASSERT(implDecl);
3644+
3645+
// If the implementation type is IUO, try unwrapping it.
3646+
if (auto unwrappedImplTy = implTy->getOptionalObjectType())
3647+
return implDecl->isImplicitlyUnwrappedOptional() &&
3648+
unwrappedImplTy->matchesParameter(reqTy, matchOpts)
3649+
? MatchOutcome::Match
3650+
: MatchOutcome::WrongType;
3651+
}
3652+
3653+
return MatchOutcome::WrongType;
36373654
}
36383655

3639-
static bool matchTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
3656+
static MatchOutcome matchTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
36403657
TypeMatchOptions matchOpts = {};
36413658

36423659
// Try a plain type match.
36433660
if (reqTy->matches(implTy, matchOpts))
3644-
return true;
3661+
return MatchOutcome::Match;
36453662

36463663
// If the implementation type is optional, try unwrapping it.
36473664
if (auto unwrappedImplTy = implTy->getOptionalObjectType())
3648-
return implDecl->isImplicitlyUnwrappedOptional()
3649-
&& reqTy->matches(unwrappedImplTy, matchOpts);
3665+
return implDecl->isImplicitlyUnwrappedOptional() &&
3666+
reqTy->matches(unwrappedImplTy, matchOpts)
3667+
? MatchOutcome::Match
3668+
: MatchOutcome::WrongType;
36503669

36513670
// Apply these rules to the result type and parameters if it's a function
36523671
// type.
3653-
if (auto funcReqTy = reqTy->getAs<AnyFunctionType>())
3654-
if (auto funcImplTy = implTy->getAs<AnyFunctionType>())
3655-
return funcReqTy->matchesFunctionType(funcImplTy, matchOpts,
3656-
[=]() -> bool {
3657-
auto reqParams = funcReqTy->getParams();
3658-
auto implParams = funcImplTy->getParams();
3659-
if (reqParams.size() != implParams.size())
3660-
return false;
3661-
3662-
ParameterList *implParamList = nullptr;
3663-
if (auto afd = dyn_cast<AbstractFunctionDecl>(implDecl))
3664-
implParamList = afd->getParameters();
3665-
3666-
for (auto i : indices(reqParams)) {
3667-
const auto &reqParam = reqParams[i];
3668-
const auto &implParam = implParams[i];
3669-
if (implParamList) {
3670-
// Some of the parameters may be IUOs; apply special logic.
3671-
if (!matchParamTypes(reqParam.getOldType(),
3672-
implParam.getOldType(),
3673-
implParamList->get(i)))
3672+
if (auto funcReqTy = reqTy->getAs<AnyFunctionType>()) {
3673+
if (auto funcImplTy = implTy->getAs<AnyFunctionType>()) {
3674+
bool hasSendabilityMismatches = false;
3675+
bool isMatch = funcReqTy->matchesFunctionType(
3676+
funcImplTy, matchOpts, [=, &hasSendabilityMismatches]() -> bool {
3677+
auto reqParams = funcReqTy->getParams();
3678+
auto implParams = funcImplTy->getParams();
3679+
if (reqParams.size() != implParams.size())
36743680
return false;
3675-
} else {
3676-
// IUOs not allowed here; apply ordinary logic.
3677-
if (!reqParam.getOldType()->matchesParameter(
3678-
implParam.getOldType(), matchOpts))
3679-
return false;
3680-
}
3681-
}
36823681

3683-
return matchTypes(funcReqTy->getResult(), funcImplTy->getResult(),
3684-
implDecl);
3685-
});
3682+
ParameterList *implParamList = nullptr;
3683+
if (auto afd = dyn_cast<AbstractFunctionDecl>(implDecl))
3684+
implParamList = afd->getParameters();
36863685

3687-
return false;
3686+
for (auto i : indices(reqParams)) {
3687+
const auto &reqParam = reqParams[i];
3688+
const auto &implParam = implParams[i];
3689+
3690+
TypeMatchOptions options = {};
3691+
if (implParamList)
3692+
options |= TypeMatchFlags::AllowNonOptionalForIUOParam;
3693+
3694+
auto outcome = matchParamTypes(
3695+
reqParam.getOldType(), implParam.getOldType(),
3696+
implParamList ? implParamList->get(i) : nullptr, options);
3697+
3698+
if (outcome == MatchOutcome::WrongSendability)
3699+
hasSendabilityMismatches = true;
3700+
3701+
if (outcome < MatchOutcome::WrongSendability)
3702+
return false;
3703+
}
3704+
3705+
return matchTypes(funcReqTy->getResult(), funcImplTy->getResult(),
3706+
implDecl) == MatchOutcome::Match;
3707+
});
3708+
3709+
if (isMatch) {
3710+
return hasSendabilityMismatches ? MatchOutcome::WrongSendability
3711+
: MatchOutcome::Match;
3712+
}
3713+
3714+
return MatchOutcome::WrongType;
3715+
}
3716+
}
3717+
3718+
return MatchOutcome::WrongType;
36883719
}
36893720

36903721
static Type getMemberType(ValueDecl *decl) {
@@ -3729,8 +3760,10 @@ class ObjCImplementationChecker {
37293760
if (cand->getKind() != req->getKind())
37303761
return MatchOutcome::WrongDeclKind;
37313762

3732-
if (!matchTypes(getMemberType(req), getMemberType(cand), cand))
3733-
return MatchOutcome::WrongType;
3763+
auto matchTypesOutcome =
3764+
matchTypes(getMemberType(req), getMemberType(cand), cand);
3765+
if (matchTypesOutcome < MatchOutcome::Match)
3766+
return matchTypesOutcome;
37343767

37353768
if (auto reqVar = dyn_cast<AbstractStorageDecl>(req))
37363769
if (reqVar->isSettable(nullptr) &&
@@ -3787,6 +3820,13 @@ class ObjCImplementationChecker {
37873820
diagnoseVTableUse(cand);
37883821
return;
37893822

3823+
case MatchOutcome::WrongSendability:
3824+
diagnose(cand, diag::objc_implementation_sendability_mismatch, cand,
3825+
getMemberType(cand), getMemberType(req))
3826+
.limitBehaviorWithPreconcurrency(DiagnosticBehavior::Warning,
3827+
/*preconcurrency*/ true);
3828+
return;
3829+
37903830
case MatchOutcome::WrongImplicitObjCName:
37913831
case MatchOutcome::WrongExplicitObjCName: {
37923832
auto diag = diagnose(cand, diag::objc_implementation_wrong_objc_name,

test/Concurrency/sendable_objc_attr_in_type_context_swift5.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
6464
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
6565
@end
6666

67+
@interface SwiftImpl : NSObject
68+
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
69+
@end
70+
6771
#pragma clang assume_nonnull end
6872

6973
//--- main.swift
@@ -145,3 +149,8 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
145149
func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
146150
}
147151
}
152+
153+
@objc @implementation extension SwiftImpl {
154+
@objc func compute(completionHandler: @escaping () -> Void) {}
155+
// expected-warning@-1 {{sendability of function types in instance method 'compute(completionHandler:)' of type '(@escaping () -> Void) -> ()' does not match type '(@escaping @Sendable () -> Void) -> Void' declared by the header}}
156+
}

test/Concurrency/sendable_objc_attr_in_type_context_swift6.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
6464
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
6565
@end
6666

67+
@interface SwiftImpl : NSObject
68+
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
69+
@end
70+
6771
#pragma clang assume_nonnull end
6872

6973
//--- main.swift
@@ -152,3 +156,8 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
152156
func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
153157
}
154158
}
159+
160+
@objc @implementation extension SwiftImpl {
161+
@objc func compute(completionHandler: @escaping () -> Void) {}
162+
// expected-warning@-1 {{sendability of function types in instance method 'compute(completionHandler:)' of type '(@escaping () -> Void) -> ()' does not match type '(@escaping @Sendable () -> Void) -> Void' declared by the header}}
163+
}

0 commit comments

Comments
 (0)