Skip to content

Commit d14635b

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 7c258f2 commit d14635b

File tree

4 files changed

+118
-45
lines changed

4 files changed

+118
-45
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 & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3435,6 +3435,7 @@ class ObjCImplementationChecker {
34353435
WrongWritability,
34363436
WrongRequiredAttr,
34373437
WrongForeignErrorConvention,
3438+
WrongSendability,
34383439

34393440
Match,
34403441
MatchWithExplicitObjCName,
@@ -3621,70 +3622,93 @@ class ObjCImplementationChecker {
36213622
return lhs == rhs;
36223623
}
36233624

3624-
static bool matchParamTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
3625+
static MatchOutcome matchParamTypes(Type reqTy, Type implTy,
3626+
ValueDecl *implDecl) {
36253627
TypeMatchOptions matchOpts = {};
3626-
36273628
// Try a plain type match.
36283629
if (implTy->matchesParameter(reqTy, matchOpts))
3629-
return true;
3630+
return MatchOutcome::Match;
36303631

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);
3632+
// Try to drop `@Sendable`.
3633+
{
3634+
auto ignoreSendable =
3635+
matchOpts | TypeMatchFlags::IgnoreFunctionSendability;
36353636

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

3639-
static bool matchTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
3653+
static MatchOutcome matchTypes(Type reqTy, Type implTy, ValueDecl *implDecl) {
36403654
TypeMatchOptions matchOpts = {};
36413655

36423656
// Try a plain type match.
36433657
if (reqTy->matches(implTy, matchOpts))
3644-
return true;
3658+
return MatchOutcome::Match;
36453659

36463660
// If the implementation type is optional, try unwrapping it.
36473661
if (auto unwrappedImplTy = implTy->getOptionalObjectType())
3648-
return implDecl->isImplicitlyUnwrappedOptional()
3649-
&& reqTy->matches(unwrappedImplTy, matchOpts);
3662+
return implDecl->isImplicitlyUnwrappedOptional() &&
3663+
reqTy->matches(unwrappedImplTy, matchOpts)
3664+
? MatchOutcome::Match
3665+
: MatchOutcome::WrongType;
36503666

36513667
// Apply these rules to the result type and parameters if it's a function
36523668
// 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)))
3669+
if (auto funcReqTy = reqTy->getAs<AnyFunctionType>()) {
3670+
if (auto funcImplTy = implTy->getAs<AnyFunctionType>()) {
3671+
bool hasSendabilityMismatches = false;
3672+
bool isMatch = funcReqTy->matchesFunctionType(
3673+
funcImplTy, matchOpts, [=, &hasSendabilityMismatches]() -> bool {
3674+
auto reqParams = funcReqTy->getParams();
3675+
auto implParams = funcImplTy->getParams();
3676+
if (reqParams.size() != implParams.size())
36743677
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-
}
36823678

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

3687-
return false;
3683+
for (auto i : indices(reqParams)) {
3684+
const auto &reqParam = reqParams[i];
3685+
const auto &implParam = implParams[i];
3686+
3687+
auto outcome = matchParamTypes(
3688+
reqParam.getOldType(), implParam.getOldType(),
3689+
implParamList ? implParamList->get(i) : nullptr);
3690+
3691+
if (outcome == MatchOutcome::WrongSendability)
3692+
hasSendabilityMismatches = true;
3693+
3694+
if (outcome < MatchOutcome::WrongSendability)
3695+
return false;
3696+
}
3697+
3698+
return matchTypes(funcReqTy->getResult(), funcImplTy->getResult(),
3699+
implDecl) == MatchOutcome::Match;
3700+
});
3701+
3702+
if (isMatch) {
3703+
return hasSendabilityMismatches ? MatchOutcome::WrongSendability
3704+
: MatchOutcome::Match;
3705+
}
3706+
3707+
return MatchOutcome::WrongType;
3708+
}
3709+
}
3710+
3711+
return MatchOutcome::WrongType;
36883712
}
36893713

36903714
static Type getMemberType(ValueDecl *decl) {
@@ -3729,7 +3753,9 @@ class ObjCImplementationChecker {
37293753
if (cand->getKind() != req->getKind())
37303754
return MatchOutcome::WrongDeclKind;
37313755

3732-
if (!matchTypes(getMemberType(req), getMemberType(cand), cand))
3756+
auto matchTypesOutcome =
3757+
matchTypes(getMemberType(req), getMemberType(cand), cand);
3758+
if (matchTypesOutcome == MatchOutcome::WrongType)
37333759
return MatchOutcome::WrongType;
37343760

37353761
if (auto reqVar = dyn_cast<AbstractStorageDecl>(req))
@@ -3750,6 +3776,15 @@ class ObjCImplementationChecker {
37503776
if (explicitObjCName)
37513777
return MatchOutcome::MatchWithExplicitObjCName;
37523778

3779+
// Sendable mismatches are downgraded to warnings because ObjC
3780+
// declarations are `@preconcurrency`, we need to make sure
3781+
// that there are no other problems before returning `WrongSendability`.
3782+
if (matchTypesOutcome == MatchOutcome::WrongSendability)
3783+
return MatchOutcome::WrongSendability;
3784+
3785+
ASSERT(matchTypesOutcome == MatchOutcome::Match &&
3786+
"unexpected matchTypes() return");
3787+
37533788
return MatchOutcome::Match;
37543789
}
37553790

@@ -3787,6 +3822,13 @@ class ObjCImplementationChecker {
37873822
diagnoseVTableUse(cand);
37883823
return;
37893824

3825+
case MatchOutcome::WrongSendability:
3826+
diagnose(cand, diag::objc_implementation_sendability_mismatch, cand,
3827+
getMemberType(cand), getMemberType(req))
3828+
.limitBehaviorWithPreconcurrency(DiagnosticBehavior::Warning,
3829+
/*preconcurrency*/ true);
3830+
return;
3831+
37903832
case MatchOutcome::WrongImplicitObjCName:
37913833
case MatchOutcome::WrongExplicitObjCName: {
37923834
auto diag = diagnose(cand, diag::objc_implementation_wrong_objc_name,

test/Concurrency/sendable_objc_attr_in_type_context_swift5.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ 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+
-(id)initWithCallback: (void (^ SWIFT_SENDABLE)(void)) callback;
69+
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
70+
@end
71+
6772
#pragma clang assume_nonnull end
6873

6974
//--- main.swift
@@ -145,3 +150,11 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
145150
func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
146151
}
147152
}
153+
154+
@objc @implementation extension SwiftImpl {
155+
@objc public required init(callback: @escaping () -> Void) {}
156+
// expected-error@-1 {{initializer 'init(callback:)' should not be 'required' to match initializer declared by the header}}
157+
158+
@objc func compute(completionHandler: @escaping () -> Void) {}
159+
// 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}}
160+
}

test/Concurrency/sendable_objc_attr_in_type_context_swift6.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ 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+
-(id)initWithCallback: (void (^ SWIFT_SENDABLE)(void)) callback;
69+
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
70+
@end
71+
6772
#pragma clang assume_nonnull end
6873

6974
//--- main.swift
@@ -152,3 +157,11 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
152157
func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
153158
}
154159
}
160+
161+
@objc @implementation extension SwiftImpl {
162+
@objc public required init(callback: @escaping () -> Void) {}
163+
// expected-error@-1 {{initializer 'init(callback:)' should not be 'required' to match initializer declared by the header}}
164+
165+
@objc func compute(completionHandler: @escaping () -> Void) {}
166+
// 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}}
167+
}

0 commit comments

Comments
 (0)