Skip to content

Commit bbe2e1c

Browse files
committed
[NameLookup] SendableCompletionHandlers: Inject @Sendable into Swift decls that could shadow ObjC ones
When `SendableCompletionHandlers` feature is enabled, ClangImporter is going to inject `@Sendable` attribute into the completion handler parameter's function type. We need to match this behavior in Swift declarations that could shadow ObjC declarations otherwise it would result in ambiguities.
1 parent 78825d9 commit bbe2e1c

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

lib/AST/NameLookup.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,77 @@ static CanType removeThrownError(Type type) {
800800
})->getCanonicalType();
801801
}
802802

803+
/// When `SendableCompletionHandlers` feature is enabled, ClangImporter
804+
/// is going to inject `@Sendable` attribute into the completion handler
805+
/// parameter's function type. We need to match this behavior in Swift
806+
/// declarations that could shadow ObjC declarations otherwise it would
807+
/// result in ambiguities.
808+
static CanType injectSendableToCompletionHandler(ValueDecl *decl,
809+
CanType type) {
810+
auto &ctx = decl->getASTContext();
811+
812+
if (!ctx.LangOpts.hasFeature(Feature::SendableCompletionHandlers))
813+
return type;
814+
815+
if (!decl->isObjC() || decl->isObjCImplementation())
816+
return type;
817+
818+
auto *paramList = getParameterList(decl);
819+
if (!paramList || paramList->size() == 0)
820+
return type;
821+
822+
// Check if the last parameter expects "completionHandler" argument.
823+
auto *lastParam = paramList->back();
824+
if (!isCompletionHandlerParamName(lastParam->getArgumentName().str()))
825+
return type;
826+
827+
return type
828+
.transformRec([](TypeBase *type) -> std::optional<Type> {
829+
auto funcType = type->getAs<AnyFunctionType>();
830+
if (!funcType || funcType->isAsync() || funcType->getNumParams() == 0)
831+
return std::nullopt;
832+
833+
auto &lastParam = funcType->getParams().back();
834+
if (!(lastParam.hasLabel() &&
835+
isCompletionHandlerParamName(lastParam.getLabel().str())))
836+
return std::nullopt;
837+
838+
auto completionType = lastParam.getPlainType();
839+
// Completion handler could be wrapped in a number of optionals.
840+
auto newCompletionType = completionType.transformRec(
841+
[](TypeBase *type) -> std::optional<Type> {
842+
if (auto *funcType = type->getAs<AnyFunctionType>()) {
843+
auto extInfo = funcType->getExtInfo();
844+
if (!extInfo.isSendable())
845+
return funcType->withExtInfo(extInfo.withSendable(true));
846+
return type;
847+
}
848+
return std::nullopt;
849+
});
850+
851+
if (newCompletionType->isEqual(completionType))
852+
return type;
853+
854+
SmallVector<AnyFunctionType::Param, 2> newParams;
855+
newParams.append(funcType->getParams().begin(),
856+
funcType->getParams().end() - 1);
857+
newParams.push_back(lastParam.withType(newCompletionType));
858+
859+
if (auto *concreteFn = funcType->getAs<FunctionType>()) {
860+
return FunctionType::get(newParams, funcType->getResult(),
861+
funcType->getExtInfo())
862+
->getCanonicalType();
863+
}
864+
865+
return GenericFunctionType::get(funcType->castTo<GenericFunctionType>()
866+
->getGenericSignature(),
867+
newParams, funcType->getResult(),
868+
funcType->getExtInfo())
869+
->getCanonicalType();
870+
})
871+
->getCanonicalType();
872+
}
873+
803874
/// Given a set of declarations whose names and generic signatures have matched,
804875
/// figure out which of these declarations have been shadowed by others.
805876
static void recordShadowedDeclsAfterSignatureMatch(
@@ -826,6 +897,8 @@ static void recordShadowedDeclsAfterSignatureMatch(
826897
else
827898
type = removeThrownError(decl->getInterfaceType()->getCanonicalType());
828899

900+
type = injectSendableToCompletionHandler(decl, type);
901+
829902
// Record this declaration based on its signature.
830903
auto &known = collisions[type];
831904
if (known.size() == 1) {

test/Concurrency/sendable_objc_attr_in_type_context_swift5.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
6868
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
6969
@end
7070

71+
@interface Shadowing : NSObject
72+
-(void)computeWithCompletion: (void (^)(void)) completion;
73+
-(void)updateWithCompletionHandler: (void (^_Nullable)(void)) handler;
74+
@end
75+
7176
#pragma clang assume_nonnull end
7277

7378
//--- main.swift
@@ -154,3 +159,16 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
154159
@objc func compute(completionHandler: @escaping () -> Void) {}
155160
// 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}}
156161
}
162+
163+
// Methods deliberately has no `@Sendable` to make sure that
164+
// shadowing rules are preserved when SendableCompletionHandlers feature is enabled.
165+
@objc extension Shadowing {
166+
@objc func compute(completion: @escaping () -> Void) {}
167+
168+
@objc func update(completionHandler: (() -> Void)? = nil) {}
169+
170+
func testCompute() {
171+
self.compute { } // Ok - no ambiguity
172+
self.update { } // Ok - no ambiguity
173+
}
174+
}

test/Concurrency/sendable_objc_attr_in_type_context_swift6.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
6868
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
6969
@end
7070

71+
@interface Shadowing : NSObject
72+
-(void)computeWithCompletion: (void (^)(void)) completion;
73+
-(void)updateWithCompletionHandler: (void (^_Nullable)(void)) handler;
74+
@end
75+
7176
#pragma clang assume_nonnull end
7277

7378
//--- main.swift
@@ -161,3 +166,16 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
161166
@objc func compute(completionHandler: @escaping () -> Void) {}
162167
// 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}}
163168
}
169+
170+
// Methods deliberately has no `@Sendable` to make sure that
171+
// shadowing rules are preserved when SendableCompletionHandlers feature is enabled.
172+
@objc extension Shadowing {
173+
@objc func compute(completion: @escaping () -> Void) {}
174+
175+
@objc func update(completionHandler: (() -> Void)? = nil) {}
176+
177+
func testCompute() {
178+
self.compute { } // Ok - no ambiguity
179+
self.update { } // Ok - no ambiguity
180+
}
181+
}

0 commit comments

Comments
 (0)