Skip to content

Commit 50f8705

Browse files
committed
Handle conformance with multiple protocol requirements with the same selector.
When concurrency is enabled, the same Objective-C method on a protocol can be imported as two different protocol requirements, both of which have the same selector. When performing conformance checking, only treat a given @objc protocol requirement as "unsatisfied" by a conformance if none of the requirements with the same Objective-C selector (+ instance/class designation) are satisfied.
1 parent fe4a8bb commit 50f8705

File tree

4 files changed

+266
-18
lines changed

4 files changed

+266
-18
lines changed

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,12 +2372,22 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
23722372
if (conflicts.empty())
23732373
continue;
23742374

2375+
auto bestConflict = conflicts[0];
2376+
for (auto conflict : conflicts) {
2377+
if (conflict->getName().isCompoundName() &&
2378+
conflict->getName().getArgumentNames().size() ==
2379+
req->getName().getArgumentNames().size()) {
2380+
bestConflict = conflict;
2381+
break;
2382+
}
2383+
}
2384+
23752385
// Diagnose the conflict.
23762386
auto reqDiagInfo = getObjCMethodDiagInfo(unsatisfied.second);
2377-
auto conflictDiagInfo = getObjCMethodDiagInfo(conflicts[0]);
2387+
auto conflictDiagInfo = getObjCMethodDiagInfo(bestConflict);
23782388
auto protocolName
23792389
= cast<ProtocolDecl>(req->getDeclContext())->getName();
2380-
Ctx.Diags.diagnose(conflicts[0],
2390+
Ctx.Diags.diagnose(bestConflict,
23812391
diag::objc_optional_requirement_conflict,
23822392
conflictDiagInfo.first,
23832393
conflictDiagInfo.second,
@@ -2387,9 +2397,9 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
23872397
protocolName);
23882398

23892399
// Fix the name of the witness, if we can.
2390-
if (req->getName() != conflicts[0]->getName() &&
2391-
req->getKind() == conflicts[0]->getKind() &&
2392-
isa<AccessorDecl>(req) == isa<AccessorDecl>(conflicts[0])) {
2400+
if (req->getName() != bestConflict->getName() &&
2401+
req->getKind() == bestConflict->getKind() &&
2402+
isa<AccessorDecl>(req) == isa<AccessorDecl>(bestConflict)) {
23932403
// They're of the same kind: fix the name.
23942404
unsigned kind;
23952405
if (isa<ConstructorDecl>(req))
@@ -2402,29 +2412,29 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
24022412
llvm_unreachable("unhandled @objc declaration kind");
24032413
}
24042414

2405-
auto diag = Ctx.Diags.diagnose(conflicts[0],
2415+
auto diag = Ctx.Diags.diagnose(bestConflict,
24062416
diag::objc_optional_requirement_swift_rename,
24072417
kind, req->getName());
24082418

24092419
// Fix the Swift name.
2410-
fixDeclarationName(diag, conflicts[0], req->getName());
2420+
fixDeclarationName(diag, bestConflict, req->getName());
24112421

24122422
// Fix the '@objc' attribute, if needed.
2413-
if (!conflicts[0]->canInferObjCFromRequirement(req))
2414-
fixDeclarationObjCName(diag, conflicts[0],
2415-
conflicts[0]->getObjCRuntimeName(),
2423+
if (!bestConflict->canInferObjCFromRequirement(req))
2424+
fixDeclarationObjCName(diag, bestConflict,
2425+
bestConflict->getObjCRuntimeName(),
24162426
req->getObjCRuntimeName(),
24172427
/*ignoreImpliedName=*/true);
24182428
}
24192429

24202430
// @nonobjc will silence this warning.
24212431
bool hasExplicitObjCAttribute = false;
2422-
if (auto objcAttr = conflicts[0]->getAttrs().getAttribute<ObjCAttr>())
2432+
if (auto objcAttr = bestConflict->getAttrs().getAttribute<ObjCAttr>())
24232433
hasExplicitObjCAttribute = !objcAttr->isImplicit();
24242434
if (!hasExplicitObjCAttribute)
2425-
Ctx.Diags.diagnose(conflicts[0], diag::req_near_match_nonobjc, true)
2435+
Ctx.Diags.diagnose(bestConflict, diag::req_near_match_nonobjc, true)
24262436
.fixItInsert(
2427-
conflicts[0]->getAttributeInsertionLoc(/*forModifier=*/false),
2437+
bestConflict->getAttributeInsertionLoc(/*forModifier=*/false),
24282438
"@nonobjc ");
24292439

24302440
Ctx.Diags.diagnose(getDeclContextLoc(unsatisfied.first),

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 170 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,9 @@ class swift::MultiConformanceChecker {
15251525
NormalProtocolConformance *conformance, bool issueFixit);
15261526

15271527
/// Determine whether the given requirement was left unsatisfied.
1528-
bool isUnsatisfiedReq(NormalProtocolConformance *conformance, ValueDecl *req);
1528+
bool isUnsatisfiedReq(
1529+
ConformanceChecker &checker, NormalProtocolConformance *conformance,
1530+
ValueDecl *req);
15291531
public:
15301532
MultiConformanceChecker(ASTContext &ctx) : Context(ctx) {}
15311533

@@ -1551,17 +1553,34 @@ class swift::MultiConformanceChecker {
15511553
};
15521554

15531555
bool MultiConformanceChecker::
1554-
isUnsatisfiedReq(NormalProtocolConformance *conformance, ValueDecl *req) {
1556+
isUnsatisfiedReq(ConformanceChecker &checker,
1557+
NormalProtocolConformance *conformance, ValueDecl *req) {
15551558
if (conformance->isInvalid()) return false;
15561559
if (isa<TypeDecl>(req)) return false;
15571560

15581561
auto witness = conformance->hasWitness(req)
15591562
? conformance->getWitnessUncached(req).getDecl()
15601563
: nullptr;
15611564

1562-
// An optional requirement might not have a witness...
1563-
if (!witness)
1565+
if (!witness) {
1566+
// If another @objc requirement refers to the same Objective-C
1567+
// method, this requirement isn't unsatisfied.
1568+
if (conformance->getProtocol()->isObjC() &&
1569+
isa<AbstractFunctionDecl>(req)) {
1570+
auto funcReq = cast<AbstractFunctionDecl>(req);
1571+
auto key = checker.getObjCMethodKey(funcReq);
1572+
for (auto otherReq : checker.getObjCRequirements(key)) {
1573+
if (otherReq == req)
1574+
continue;
1575+
1576+
if (conformance->hasWitness(otherReq))
1577+
return false;
1578+
}
1579+
}
1580+
1581+
// An optional requirement might not have a witness.
15641582
return req->getAttrs().hasAttribute<OptionalAttr>();
1583+
}
15651584

15661585
// If the witness lands within the declaration context of the conformance,
15671586
// record it as a "covered" member.
@@ -1586,13 +1605,26 @@ void MultiConformanceChecker::checkAllConformances() {
15861605
continue;
15871606
// Check whether there are any unsatisfied requirements.
15881607
auto proto = conformance->getProtocol();
1608+
Optional<ConformanceChecker> checker;
1609+
auto getChecker = [&] () -> ConformanceChecker& {
1610+
if (checker)
1611+
return *checker;
1612+
1613+
if (!AllUsedCheckers.empty() &&
1614+
AllUsedCheckers.back().Conformance == conformance)
1615+
return AllUsedCheckers.back();
1616+
1617+
checker.emplace(getASTContext(), conformance, MissingWitnesses);
1618+
return *checker;
1619+
};
1620+
15891621
for (auto member : proto->getMembers()) {
15901622
auto req = dyn_cast<ValueDecl>(member);
15911623
if (!req || !req->isProtocolRequirement()) continue;
15921624

15931625
// If the requirement is unsatisfied, we might want to warn
15941626
// about near misses; record it.
1595-
if (isUnsatisfiedReq(conformance, req)) {
1627+
if (isUnsatisfiedReq(getChecker(), conformance, req)) {
15961628
UnsatisfiedReqs.push_back(req);
15971629
continue;
15981630
}
@@ -3100,10 +3132,108 @@ filterProtocolRequirements(
31003132
return Filtered;
31013133
}
31023134

3135+
/// Prune the set of missing witnesses for the given conformance, eliminating
3136+
/// any requirements that do not actually need to satisfied.
3137+
static ArrayRef<MissingWitness> pruneMissingWitnesses(
3138+
ConformanceChecker &checker,
3139+
ProtocolDecl *proto,
3140+
NormalProtocolConformance *conformance,
3141+
ArrayRef<MissingWitness> missingWitnesses,
3142+
SmallVectorImpl<MissingWitness> &scratch) {
3143+
if (missingWitnesses.empty())
3144+
return missingWitnesses;
3145+
3146+
// For an @objc protocol defined in Objective-C, the Clang importer might
3147+
// have imported the same underlying Objective-C declaration as more than
3148+
// one Swift declaration. If we aren't in an imported @objc protocol, there
3149+
// is nothing to do.
3150+
if (!proto->isObjC())
3151+
return missingWitnesses;
3152+
3153+
// Consider each of the missing witnesses to remove any that should not
3154+
// longer be considered "missing".
3155+
llvm::SmallDenseSet<ConformanceChecker::ObjCMethodKey>
3156+
alreadyReportedAsMissing;
3157+
bool removedAny = false;
3158+
for (unsigned missingWitnessIdx : indices(missingWitnesses)) {
3159+
const auto &missingWitness = missingWitnesses[missingWitnessIdx];
3160+
3161+
// Local function to skip this particular witness.
3162+
auto skipWitness = [&] {
3163+
if (removedAny)
3164+
return;
3165+
3166+
// This is the first witness we skipped. Copy all of the earlier
3167+
// missing witnesses over.
3168+
scratch.clear();
3169+
scratch.append(
3170+
missingWitnesses.begin(),
3171+
missingWitnesses.begin() + missingWitnessIdx);
3172+
removedAny = true;
3173+
};
3174+
3175+
// Local function to add this particular witness.
3176+
auto addWitness = [&] {
3177+
if (removedAny)
3178+
scratch.push_back(missingWitness);
3179+
};
3180+
3181+
// We only care about functions
3182+
auto funcRequirement = dyn_cast<AbstractFunctionDecl>(
3183+
missingWitness.requirement);
3184+
if (!funcRequirement) {
3185+
addWitness();
3186+
continue;
3187+
}
3188+
3189+
// ... whose selector is one that maps to multiple requirement declarations.
3190+
auto key = checker.getObjCMethodKey(funcRequirement);
3191+
auto matchingRequirements = checker.getObjCRequirements(key);
3192+
if (matchingRequirements.size() < 2) {
3193+
addWitness();
3194+
continue;
3195+
}
3196+
3197+
// If we have already reported a function with this selector as missing,
3198+
// don't do it again.
3199+
if (!alreadyReportedAsMissing.insert(key).second) {
3200+
skipWitness();
3201+
continue;
3202+
}
3203+
3204+
// If there is a witness for any of the *other* requirements with this
3205+
// same selector, don't report it.
3206+
bool foundOtherWitness = false;
3207+
for (auto otherReq : matchingRequirements) {
3208+
if (otherReq == funcRequirement)
3209+
continue;
3210+
3211+
if (conformance->getWitness(otherReq)) {
3212+
foundOtherWitness = true;
3213+
break;
3214+
}
3215+
}
3216+
3217+
if (foundOtherWitness)
3218+
skipWitness();
3219+
else
3220+
addWitness();
3221+
}
3222+
3223+
if (removedAny)
3224+
return scratch;
3225+
3226+
return missingWitnesses;
3227+
}
3228+
31033229
bool ConformanceChecker::
31043230
diagnoseMissingWitnesses(MissingWitnessDiagnosisKind Kind) {
31053231
auto LocalMissing = getLocalMissingWitness();
31063232

3233+
SmallVector<MissingWitness, 4> MissingWitnessScratch;
3234+
LocalMissing = pruneMissingWitnesses(
3235+
*this, Proto, Conformance, LocalMissing, MissingWitnessScratch);
3236+
31073237
// If this conformance has nothing to complain, return.
31083238
if (LocalMissing.empty())
31093239
return false;
@@ -4451,6 +4581,41 @@ void ConformanceChecker::checkConformance(MissingWitnessDiagnosisKind Kind) {
44514581
}
44524582
}
44534583

4584+
/// Retrieve the Objective-C method key from the given function.
4585+
auto ConformanceChecker::getObjCMethodKey(AbstractFunctionDecl *func)
4586+
-> ObjCMethodKey {
4587+
return ObjCMethodKey(func->getObjCSelector(), func->isInstanceMember());
4588+
}
4589+
4590+
/// Retrieve the Objective-C requirements in this protocol that have the
4591+
/// given Objective-C method key.
4592+
ArrayRef<AbstractFunctionDecl *>
4593+
ConformanceChecker::getObjCRequirements(ObjCMethodKey key) {
4594+
auto proto = Conformance->getProtocol();
4595+
if (!proto->isObjC())
4596+
return { };
4597+
4598+
// Fill in the data structure if we haven't done so yet.
4599+
if (!computedObjCMethodRequirements) {
4600+
for (auto requirement : proto->getSemanticMembers()) {
4601+
auto funcRequirement = dyn_cast<AbstractFunctionDecl>(requirement);
4602+
if (!funcRequirement)
4603+
continue;
4604+
4605+
objcMethodRequirements[getObjCMethodKey(funcRequirement)]
4606+
.push_back(funcRequirement);
4607+
}
4608+
4609+
computedObjCMethodRequirements = true;
4610+
}
4611+
4612+
auto known = objcMethodRequirements.find(key);
4613+
if (known == objcMethodRequirements.end())
4614+
return { };
4615+
4616+
return known->second;
4617+
}
4618+
44544619
void swift::diagnoseConformanceFailure(Type T,
44554620
ProtocolDecl *Proto,
44564621
DeclContext *DC,

lib/Sema/TypeCheckProtocol.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,12 @@ class DelayedMissingWitnesses : public MissingWitnessesBase {
678678
/// This helper class handles most of the details of checking whether a
679679
/// given type (\c Adoptee) conforms to a protocol (\c Proto).
680680
class ConformanceChecker : public WitnessChecker {
681+
public:
682+
/// Key that can be used to uniquely identify a particular Objective-C
683+
/// method.
684+
using ObjCMethodKey = std::pair<ObjCSelector, char>;
685+
686+
private:
681687
friend class MultiConformanceChecker;
682688
friend class AssociatedTypeInference;
683689

@@ -712,6 +718,14 @@ class ConformanceChecker : public WitnessChecker {
712718
/// Whether we checked the requirement signature already.
713719
bool CheckedRequirementSignature = false;
714720

721+
/// Mapping from Objective-C methods to the set of requirements within this
722+
/// protocol that have the same selector and instance/class designation.
723+
llvm::SmallDenseMap<ObjCMethodKey, TinyPtrVector<AbstractFunctionDecl *>, 4>
724+
objcMethodRequirements;
725+
726+
/// Whether objcMethodRequirements has been computed.
727+
bool computedObjCMethodRequirements = false;
728+
715729
/// Retrieve the associated types that are referenced by the given
716730
/// requirement with a base of 'Self'.
717731
ArrayRef<AssociatedTypeDecl *> getReferencedAssociatedTypes(ValueDecl *req);
@@ -827,6 +841,13 @@ class ConformanceChecker : public WitnessChecker {
827841
/// Check the entire protocol conformance, ensuring that all
828842
/// witnesses are resolved and emitting any diagnostics.
829843
void checkConformance(MissingWitnessDiagnosisKind Kind);
844+
845+
/// Retrieve the Objective-C method key from the given function.
846+
ObjCMethodKey getObjCMethodKey(AbstractFunctionDecl *func);
847+
848+
/// Retrieve the Objective-C requirements in this protocol that have the
849+
/// given Objective-C method key.
850+
ArrayRef<AbstractFunctionDecl *> getObjCRequirements(ObjCMethodKey key);
830851
};
831852

832853
/// Captures the state needed to infer associated types.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -enable-experimental-concurrency %s -verify -verify-ignore-unknown
2+
3+
// REQUIRES: objc_interop
4+
import Foundation
5+
import ObjCConcurrency
6+
7+
// Conform via async method
8+
class C1: ConcurrentProtocol {
9+
func askUser(toSolvePuzzle puzzle: String) async throws -> String? { nil }
10+
11+
func askUser(toJumpThroughHoop hoop: String) async -> String { "hello" }
12+
}
13+
14+
// Conform via completion-handler method
15+
class C2: ConcurrentProtocol {
16+
func askUser(toSolvePuzzle puzzle: String, completionHandler: ((String?, Error?) -> Void)?) {
17+
completionHandler?("hello", nil)
18+
}
19+
20+
func askUser(toJumpThroughHoop hoop: String, completionHandler: ((String) -> Void)?) {
21+
completionHandler?("hello")
22+
}
23+
}
24+
25+
// Conform via both; this is an error
26+
class C3: ConcurrentProtocol {
27+
// expected-note@+1{{method 'askUser(toSolvePuzzle:)' declared here}}
28+
func askUser(toSolvePuzzle puzzle: String) async throws -> String? { nil }
29+
30+
// expected-error@+1{{'askUser(toSolvePuzzle:completionHandler:)' with Objective-C selector 'askUserToSolvePuzzle:completionHandler:' conflicts with method 'askUser(toSolvePuzzle:)' with the same Objective-C selector}}
31+
func askUser(toSolvePuzzle puzzle: String, completionHandler: ((String?, Error?) -> Void)?) {
32+
completionHandler?("hello", nil)
33+
}
34+
}
35+
36+
// Conform but forget to supply either. Also an error.
37+
// FIXME: Suppress one of the notes?
38+
class C4: ConcurrentProtocol { // expected-error{{type 'C4' does not conform to protocol 'ConcurrentProtocol'}}
39+
}
40+
41+
class C5 {
42+
}
43+
44+
extension C5: ConcurrentProtocol {
45+
func askUser(toSolvePuzzle puzzle: String, completionHandler: ((String?, Error?) -> Void)?) {
46+
completionHandler?("hello", nil)
47+
}
48+
49+
func askUser(toJumpThroughHoop hoop: String, completionHandler: ((String) -> Void)?) {
50+
completionHandler?("hello")
51+
}
52+
}

0 commit comments

Comments
 (0)