Skip to content

AST: Use availability to disambiguate multiple overlapping conformances #37812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,10 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
/// is also included.
unsigned getSemanticDepth() const;

/// Returns if this extension is always available on the current deployment
/// target. Used for conformance lookup disambiguation.
bool isAlwaysAvailableConformanceContext() const;

/// \returns true if traversal was aborted, false otherwise.
bool walkContext(ASTWalker &Walker);

Expand Down
12 changes: 12 additions & 0 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,18 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
ConformanceEntry *lhs,
ConformanceEntry *rhs,
bool &diagnoseSuperseded) {
// If only one of the conformances is unconditionally available on the
// current deployment target, pick that one.
//
// FIXME: Conformance lookup should really depend on source location for
// this to be 100% correct.
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
? Ordering::Before
: Ordering::After);
}

// If one entry is fixed and the other is not, we have our answer.
if (lhs->isFixed() != rhs->isFixed()) {
// If the non-fixed conformance is not replaceable, we have a failure to
Expand Down
19 changes: 19 additions & 0 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1305,3 +1305,22 @@ static bool isSpecializeExtensionContext(const DeclContext *dc) {
bool DeclContext::isInSpecializeExtensionContext() const {
return isSpecializeExtensionContext(this);
}

bool DeclContext::isAlwaysAvailableConformanceContext() const {
auto *ext = dyn_cast<ExtensionDecl>(this);
if (ext == nullptr)
return true;

if (AvailableAttr::isUnavailable(ext))
return false;

auto &ctx = getASTContext();

AvailabilityContext conformanceAvailability{
AvailabilityInference::availableRange(ext, ctx)};

auto deploymentTarget =
AvailabilityContext::forDeploymentTarget(ctx);

return deploymentTarget.isContainedIn(conformanceAvailability);
}
22 changes: 21 additions & 1 deletion lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,27 @@ LookupConformanceInModuleRequest::evaluate(
}
}

// FIXME: Ambiguity resolution.
assert(!conformances.empty());

// If we have multiple conformances, first try to filter out any that are
// unavailable on the current deployment target.
//
// FIXME: Conformance lookup should really depend on source location for
// this to be 100% correct.
if (conformances.size() > 1) {
SmallVector<ProtocolConformance *, 2> availableConformances;

for (auto *conformance : conformances) {
if (conformance->getDeclContext()->isAlwaysAvailableConformanceContext())
availableConformances.push_back(conformance);
}

// Don't filter anything out if all conformances are unavailable.
if (!availableConformances.empty())
std::swap(availableConformances, conformances);
}

// If we still have multiple conformances, just pick the first one.
auto conformance = conformances.front();

// Rebuild inherited conformances based on the root normal conformance.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public protocol P {}

public struct HasUnavailableConformance {}

@available(*, unavailable)
extension HasUnavailableConformance : P {}

public struct HasConditionallyAvailableConformance {}

@available(macOS 100, *)
extension HasConditionallyAvailableConformance : P {}

public struct HasAlwaysAvailableConformance {}

extension HasAlwaysAvailableConformance : P {}
21 changes: 21 additions & 0 deletions test/Sema/conformance_availability_overlapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module %S/Inputs/conformance_availability_overlapping_other.swift -emit-module-path %t/conformance_availability_overlapping_other.swiftmodule
// RUN: %target-typecheck-verify-swift -I %t

// REQUIRES: OS=macosx

import conformance_availability_overlapping_other

extension HasUnavailableConformance : P {}

extension HasConditionallyAvailableConformance : P {}

extension HasAlwaysAvailableConformance : P {}
// expected-warning@-1 {{conformance of 'HasAlwaysAvailableConformance' to protocol 'P' was already stated in the type's module 'conformance_availability_overlapping_other'}}

struct G<T : P> {}

// None of these should produce a warning about an unavailable conformance.
func usesConformance(_: G<HasUnavailableConformance>) {}
func usesConformance(_: G<HasConditionallyAvailableConformance>) {}
func usesConformance(_: G<HasAlwaysAvailableConformance>) {}