Skip to content

Commit 1c48a46

Browse files
authored
Merge pull request #37812 from slavapestov/conformance-lookup-with-availability
AST: Use availability to disambiguate multiple overlapping conformances
2 parents fccadb4 + a250688 commit 1c48a46

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)