Skip to content

Commit 0a60a82

Browse files
authored
Merge pull request #37813 from slavapestov/conformance-lookup-with-availability-5.5
AST: Use availability to disambiguate multiple overlapping conformances [5.5]
2 parents e16e013 + 7fef887 commit 0a60a82

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
@@ -1294,3 +1294,22 @@ static bool isSpecializeExtensionContext(const DeclContext *dc) {
12941294
bool DeclContext::isInSpecializeExtensionContext() const {
12951295
return isSpecializeExtensionContext(this);
12961296
}
1297+
1298+
bool DeclContext::isAlwaysAvailableConformanceContext() const {
1299+
auto *ext = dyn_cast<ExtensionDecl>(this);
1300+
if (ext == nullptr)
1301+
return true;
1302+
1303+
if (AvailableAttr::isUnavailable(ext))
1304+
return false;
1305+
1306+
auto &ctx = getASTContext();
1307+
1308+
AvailabilityContext conformanceAvailability{
1309+
AvailabilityInference::availableRange(ext, ctx)};
1310+
1311+
auto deploymentTarget =
1312+
AvailabilityContext::forDeploymentTarget(ctx);
1313+
1314+
return deploymentTarget.isContainedIn(conformanceAvailability);
1315+
}

lib/AST/Module.cpp

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

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

10711091
// 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)