Skip to content

Commit a250688

Browse files
committed
AST: Use availability to disambiguate multiple overlapping conformances
If a conformance is found in an imported module as well as the current module, and one of the two conformances is conditionally unavailable on the current deployment target, pick the one that is always available. Fixes <rdar://problem/78633800>.
1 parent c139bb4 commit a250688

File tree

6 files changed

+92
-1
lines changed

6 files changed

+92
-1
lines changed

include/swift/AST/DeclContext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,10 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
618618
/// is also included.
619619
unsigned getSemanticDepth() const;
620620

621+
/// Returns if this extension is always available on the current deployment
622+
/// target. Used for conformance lookup disambiguation.
623+
bool isAlwaysAvailableConformanceContext() const;
624+
621625
/// \returns true if traversal was aborted, false otherwise.
622626
bool walkContext(ASTWalker &Walker);
623627

lib/AST/ConformanceLookupTable.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,18 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
529529
ConformanceEntry *lhs,
530530
ConformanceEntry *rhs,
531531
bool &diagnoseSuperseded) {
532+
// If only one of the conformances is unconditionally available on the
533+
// current deployment target, pick that one.
534+
//
535+
// FIXME: Conformance lookup should really depend on source location for
536+
// this to be 100% correct.
537+
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
538+
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
539+
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
540+
? Ordering::Before
541+
: Ordering::After);
542+
}
543+
532544
// If one entry is fixed and the other is not, we have our answer.
533545
if (lhs->isFixed() != rhs->isFixed()) {
534546
// If the non-fixed conformance is not replaceable, we have a failure to

lib/AST/DeclContext.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,3 +1305,22 @@ static bool isSpecializeExtensionContext(const DeclContext *dc) {
13051305
bool DeclContext::isInSpecializeExtensionContext() const {
13061306
return isSpecializeExtensionContext(this);
13071307
}
1308+
1309+
bool DeclContext::isAlwaysAvailableConformanceContext() const {
1310+
auto *ext = dyn_cast<ExtensionDecl>(this);
1311+
if (ext == nullptr)
1312+
return true;
1313+
1314+
if (AvailableAttr::isUnavailable(ext))
1315+
return false;
1316+
1317+
auto &ctx = getASTContext();
1318+
1319+
AvailabilityContext conformanceAvailability{
1320+
AvailabilityInference::availableRange(ext, ctx)};
1321+
1322+
auto deploymentTarget =
1323+
AvailabilityContext::forDeploymentTarget(ctx);
1324+
1325+
return deploymentTarget.isContainedIn(conformanceAvailability);
1326+
}

lib/AST/Module.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,27 @@ LookupConformanceInModuleRequest::evaluate(
10661066
}
10671067
}
10681068

1069-
// FIXME: Ambiguity resolution.
1069+
assert(!conformances.empty());
1070+
1071+
// If we have multiple conformances, first try to filter out any that are
1072+
// unavailable on the current deployment target.
1073+
//
1074+
// FIXME: Conformance lookup should really depend on source location for
1075+
// this to be 100% correct.
1076+
if (conformances.size() > 1) {
1077+
SmallVector<ProtocolConformance *, 2> availableConformances;
1078+
1079+
for (auto *conformance : conformances) {
1080+
if (conformance->getDeclContext()->isAlwaysAvailableConformanceContext())
1081+
availableConformances.push_back(conformance);
1082+
}
1083+
1084+
// Don't filter anything out if all conformances are unavailable.
1085+
if (!availableConformances.empty())
1086+
std::swap(availableConformances, conformances);
1087+
}
1088+
1089+
// If we still have multiple conformances, just pick the first one.
10701090
auto conformance = conformances.front();
10711091

10721092
// Rebuild inherited conformances based on the root normal conformance.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public protocol P {}
2+
3+
public struct HasUnavailableConformance {}
4+
5+
@available(*, unavailable)
6+
extension HasUnavailableConformance : P {}
7+
8+
public struct HasConditionallyAvailableConformance {}
9+
10+
@available(macOS 100, *)
11+
extension HasConditionallyAvailableConformance : P {}
12+
13+
public struct HasAlwaysAvailableConformance {}
14+
15+
extension HasAlwaysAvailableConformance : P {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module %S/Inputs/conformance_availability_overlapping_other.swift -emit-module-path %t/conformance_availability_overlapping_other.swiftmodule
3+
// RUN: %target-typecheck-verify-swift -I %t
4+
5+
// REQUIRES: OS=macosx
6+
7+
import conformance_availability_overlapping_other
8+
9+
extension HasUnavailableConformance : P {}
10+
11+
extension HasConditionallyAvailableConformance : P {}
12+
13+
extension HasAlwaysAvailableConformance : P {}
14+
// expected-warning@-1 {{conformance of 'HasAlwaysAvailableConformance' to protocol 'P' was already stated in the type's module 'conformance_availability_overlapping_other'}}
15+
16+
struct G<T : P> {}
17+
18+
// None of these should produce a warning about an unavailable conformance.
19+
func usesConformance(_: G<HasUnavailableConformance>) {}
20+
func usesConformance(_: G<HasConditionallyAvailableConformance>) {}
21+
func usesConformance(_: G<HasAlwaysAvailableConformance>) {}

0 commit comments

Comments
 (0)