Skip to content

Commit 950c5e9

Browse files
committed
AST: Improve handling of missing @objc async protocol witnesses.
Serialization now enumerates the value witnesses of conformances with a resolver in order to facilitate lazy type checking (#68262). This change in behavior introduced an assertion failure when compiling modules from swiftinterface when the interface contains a conformance to an `@objc` protocol that has a requirement that is imported with an `async` variant. The `forEachValueWitness()` invocation during serialization was causing the missing witness for an objc async protocol requirement to be resolved after conformance check was already marked "complete". This witness had not been previously resolved because `ConformanceChecker::resolveValueWitnesses()` had special logic to skip objc protocol requirements with witnessed siblings, whereas `forEachValueWitness()` did not. There are multiple potential solutions to this problem, but the one that seemed least disruptive to me was to stop skipping resolution of these sibling witnesses during `ConformanceChecker::resolveValueWitnesses()`. When I looked into why they were being skipped, I discovered that this seemed to be a concession to bugs in the logic for pruning missing witnesses. When I adjusted the pruning logic it no longer became necessary to skip witnesses in `ConformanceChecker::resolveValueWitnesses()` in order to avoid incorrect diagnostics. Resolves rdar://119435253
1 parent f08f86c commit 950c5e9

File tree

2 files changed

+59
-39
lines changed

2 files changed

+59
-39
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3804,7 +3804,7 @@ filterProtocolRequirements(
38043804

38053805
/// Prune the set of missing witnesses for the given conformance, eliminating
38063806
/// any requirements that do not actually need to satisfied.
3807-
static ArrayRef<MissingWitness> pruneMissingWitnesses( // FIXME: this does not remove the missing witness note here!!!
3807+
static ArrayRef<MissingWitness> pruneMissingWitnesses(
38083808
ConformanceChecker &checker,
38093809
ProtocolDecl *proto,
38103810
NormalProtocolConformance *conformance,
@@ -3814,14 +3814,6 @@ static ArrayRef<MissingWitness> pruneMissingWitnesses( // FIXME: this does not r
38143814
return missingWitnesses;
38153815
}
38163816

3817-
// For an @objc protocol defined in Objective-C, the Clang importer might
3818-
// have imported the same underlying Objective-C declaration as more than
3819-
// one Swift declaration. If we aren't in an imported @objc protocol, there
3820-
// is nothing to do.
3821-
if (!proto->isObjC()) {
3822-
return missingWitnesses;
3823-
}
3824-
38253817
// Consider each of the missing witnesses to remove any that should not
38263818
// longer be considered "missing".
38273819
llvm::SmallDenseSet<ConformanceChecker::ObjCMethodKey>
@@ -3857,30 +3849,46 @@ static ArrayRef<MissingWitness> pruneMissingWitnesses( // FIXME: this does not r
38573849
continue;
38583850
}
38593851

3852+
// For an @objc protocol defined in Objective-C, the Clang importer might
3853+
// have imported the same underlying Objective-C declaration as more than
3854+
// one Swift declaration. Only one of those requirements should be witnessed
3855+
// so if the requirement sibling is witnessed then this requirement is not
3856+
// missing.
3857+
3858+
// Prune requirements that come from other protocols - we don't want to
3859+
// attempt to diagnose those here since we won't be able to find their
3860+
// siblings.
3861+
if (proto != missingWitness.requirement->getDeclContext()->getAsDecl()) {
3862+
skipWitness();
3863+
continue;
3864+
}
3865+
3866+
if (!proto->isObjC()) {
3867+
addWitness();
3868+
continue;
3869+
}
3870+
38603871
auto fnRequirement = cast<AbstractFunctionDecl>(missingWitness.requirement);
38613872
auto key = checker.getObjCMethodKey(fnRequirement);
38623873

3863-
// If we have already reported a function with this selector as missing,
3864-
// don't do it again.
3865-
if (alreadyReportedAsMissing.count(key)) {
3874+
if (checker.getObjCRequirementSibling(
3875+
fnRequirement, [conformance](AbstractFunctionDecl *candidate) {
3876+
return static_cast<bool>(conformance->getWitness(candidate));
3877+
})) {
38663878
skipWitness();
38673879
continue;
38683880
}
38693881

3870-
auto sibling = checker.getObjCRequirementSibling(
3871-
fnRequirement, [conformance](AbstractFunctionDecl *candidate) {
3872-
return static_cast<bool>(conformance->getWitness(candidate));
3873-
});
3874-
3875-
if (!sibling) {
3876-
alreadyReportedAsMissing.insert(key);
3877-
addWitness();
3882+
// If we have already reported a function with this selector as missing,
3883+
// don't do it again.
3884+
if (alreadyReportedAsMissing.count(key)) {
3885+
skipWitness();
38783886
continue;
38793887
}
38803888

3881-
// Otherwise, there is a witness for any of the *other* requirements with
3882-
// this same selector, so prune it out.
3883-
skipWitness();
3889+
// There is really a missing requirement for this witness.
3890+
alreadyReportedAsMissing.insert(key);
3891+
addWitness();
38843892
}
38853893

38863894
if (removedAny) {
@@ -5402,22 +5410,6 @@ void ConformanceChecker::resolveValueWitnesses() {
54025410
continue;
54035411
}
54045412

5405-
// If this requirement is part of a pair of imported async requirements,
5406-
// where one has already been witnessed, we can skip it.
5407-
//
5408-
// This situation primarily arises when the ClangImporter translates an
5409-
// async-looking ObjC protocol method requirement into two Swift protocol
5410-
// requirements: an async version and a sync version. Exactly one of the two
5411-
// must be witnessed by the conformer.
5412-
if (!requirement->isImplicit() && getObjCRequirementSibling(
5413-
requirement, [this](AbstractFunctionDecl *cand) {
5414-
return !cand->getAttrs().hasAttribute<OptionalAttr>() &&
5415-
!cand->isImplicit() &&
5416-
this->Conformance->hasWitness(cand);
5417-
})) {
5418-
continue;
5419-
}
5420-
54215413
// Try substituting into the requirement's interface type. If we fail,
54225414
// either a generic requirement was not satisfied or we tripped on an
54235415
// invalid type witness, and there's no point in resolving a witness.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
// RUN: %target-swift-emit-module-interface(%t/Conformance.swiftinterface) -module-name Conformance -I %t %t/Conformance.swift
4+
// RUN: %target-swift-frontend -compile-module-from-interface %t/Conformance.swiftinterface -module-name Conformance -o /dev/null -I %t
5+
// REQUIRES: objc_interop
6+
7+
//--- module.modulemap
8+
module ObjCProto {
9+
header "objc_proto.h"
10+
export *
11+
}
12+
13+
//--- objc_proto.h
14+
@protocol Doable
15+
- (void)doItWithCompletion:(void (^)())completion;
16+
@end
17+
18+
//--- Conformance.swift
19+
import ObjCProto
20+
21+
public final class ConformsToDoableWithCompletionHandler: Doable {
22+
public func doIt(completion: @escaping () -> Void) {}
23+
}
24+
25+
@available(SwiftStdlib 5.5, *)
26+
public final class ConformsToDoableWithAsync:Doable {
27+
public func doIt() async {}
28+
}

0 commit comments

Comments
 (0)