Skip to content

Canonicalize conformance access paths for sources pre-requirement-signature #7987

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
12 changes: 8 additions & 4 deletions include/swift/AST/GenericSignatureBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ class GenericSignatureBuilder::RequirementSource final
WrittenRequirementLoc> {

friend class FloatingRequirementSource;
friend class GenericSignature;

public:
enum Kind : uint8_t {
Expand Down Expand Up @@ -584,6 +585,9 @@ class GenericSignatureBuilder::RequirementSource final
/// Whether there is a trailing written requirement location.
const bool hasTrailingWrittenRequirementLoc;

/// Whether a protocol requirement came from the requirement signature.
const bool usesRequirementSignature;

/// The actual storage, described by \c storageKind.
union {
/// The root archetype.
Expand Down Expand Up @@ -673,7 +677,7 @@ class GenericSignatureBuilder::RequirementSource final
WrittenRequirementLoc writtenReqLoc)
: kind(kind), storageKind(StorageKind::RootArchetype),
hasTrailingWrittenRequirementLoc(!writtenReqLoc.isNull()),
parent(nullptr) {
usesRequirementSignature(false), parent(nullptr) {
assert(isAcceptableStorageKind(kind, storageKind) &&
"RequirementSource kind/storageKind mismatch");

Expand All @@ -689,7 +693,7 @@ class GenericSignatureBuilder::RequirementSource final
WrittenRequirementLoc writtenReqLoc)
: kind(kind), storageKind(StorageKind::StoredType),
hasTrailingWrittenRequirementLoc(!writtenReqLoc.isNull()),

usesRequirementSignature(protocol->isRequirementSignatureComputed()),
parent(parent) {
assert((static_cast<bool>(parent) != isRootKind(kind)) &&
"Root RequirementSource should not have parent (or vice versa)");
Expand All @@ -707,7 +711,7 @@ class GenericSignatureBuilder::RequirementSource final
ProtocolConformance *conformance)
: kind(kind), storageKind(StorageKind::ProtocolConformance),
hasTrailingWrittenRequirementLoc(false),
parent(parent) {
usesRequirementSignature(false), parent(parent) {
assert((static_cast<bool>(parent) != isRootKind(kind)) &&
"Root RequirementSource should not have parent (or vice versa)");
assert(isAcceptableStorageKind(kind, storageKind) &&
Expand All @@ -720,7 +724,7 @@ class GenericSignatureBuilder::RequirementSource final
AssociatedTypeDecl *assocType)
: kind(kind), storageKind(StorageKind::AssociatedTypeDecl),
hasTrailingWrittenRequirementLoc(false),
parent(parent) {
usesRequirementSignature(false), parent(parent) {
assert((static_cast<bool>(parent) != isRootKind(kind)) &&
"Root RequirementSource should not have parent (or vice versa)");
assert(isAcceptableStorageKind(kind, storageKind) &&
Expand Down
105 changes: 81 additions & 24 deletions lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,16 @@ GenericEnvironment *CanGenericSignature::getGenericEnvironment(
module);
}

/// Remove all of the associated type declarations from the given type
/// parameter, producing \c DependentMemberTypes with names alone.
static Type eraseAssociatedTypes(Type type) {
if (auto depMemTy = type->getAs<DependentMemberType>())
return DependentMemberType::get(eraseAssociatedTypes(depMemTy->getBase()),
depMemTy->getName());

return type;
}

ConformanceAccessPath GenericSignature::getConformanceAccessPath(
Type type,
ProtocolDecl *protocol,
Expand Down Expand Up @@ -770,42 +780,82 @@ ConformanceAccessPath GenericSignature::getConformanceAccessPath(
#endif

// Local function to construct the conformance access path from the
// requirement
std::function<void(const RequirementSource *, ProtocolDecl *)> buildPath;
buildPath = [&](const RequirementSource *source,
// requirement.
std::function<void(GenericSignature *, const RequirementSource *,
ProtocolDecl *)> buildPath;
buildPath = [&](GenericSignature *sig, const RequirementSource *source,
ProtocolDecl *conformingProto) {
// Each protocol requirement is a step along the path.
if (source->kind == RequirementSource::ProtocolRequirement) {
// Follow the rest of the path to derive the conformance into which
// this particular protocol requirement step would look.
auto inProtocol = source->getProtocolDecl();
buildPath(source->parent, inProtocol);
buildPath(sig, source->parent, inProtocol);
assert(path.path.back().second == inProtocol &&
"path produces incorrect conformance");

// If this step was computed via the requirement signature, add it
// directly.
if (source->usesRequirementSignature) {
// Add this step along the path, which involes looking for the
// conformance we want (\c conformingProto) within the protocol
// described by this source.

// Canonicalize the subject type within the protocol's requirement
// signature.
// FIXME: We might be able to guarantee this when building from a
// requirement signature.
Type subjectType = source->getStoredType();
subjectType = inProtocol->getRequirementSignature()
->getCanonicalTypeInContext(subjectType,
*inProtocol->getParentModule());

assert(hasConformanceInSignature(inProtocol->getRequirementSignature(),
subjectType, conformingProto) &&
"missing explicit conformance in requirement signature");

// Record this step.
path.path.push_back({subjectType, conformingProto});
return;
}

// Add this step along the path, which involes looking for the
// conformance we want (\c conformingProto) within the protocol
// described by this source.
// Canonicalize this step with respect to the requirement signature.
if (!inProtocol->isRequirementSignatureComputed()) {
inProtocol->computeRequirementSignature();
assert(inProtocol->isRequirementSignatureComputed() &&
"missing signature");
}

// Get a generic signature builder for the requirement signature. This has
// the requirement we need.
auto reqSig = inProtocol->getRequirementSignature();
auto &reqSigBuilder = *reqSig->getGenericSignatureBuilder(
*inProtocol->getModuleContext());

// Canonicalize the subject type within the protocol's requirement
// signature.
Type subjectType = source->getStoredType();
subjectType = inProtocol->getRequirementSignature()
->getCanonicalTypeInContext(subjectType,
*inProtocol->getParentModule());
// Retrieve the stored type, but erase all of the specific associated
// type declarations; we don't want any details of the enclosing context
// to sneak in here.
Type storedType = eraseAssociatedTypes(source->getStoredType());

assert(hasConformanceInSignature(inProtocol->getRequirementSignature(),
subjectType, conformingProto) &&
"missing explicit conformance in requirement signature");
// Dig out the potential archetype for this stored type.
auto pa = reqSigBuilder.resolveArchetype(storedType);
auto rep = pa->getRepresentative();

// Record this step.
path.path.push_back({subjectType, conformingProto});
// Find the conformance of this potential archetype to the protocol in
// question.
auto knownConforms = rep->getConformsTo().find(conformingProto);
assert(knownConforms != rep->getConformsTo().end());

// Build the path according to the requirement signature.
buildPath(reqSig, knownConforms->second, conformingProto);

// We're done.
return;
}

// If we still have a parent, keep going.
if (source->parent) {
buildPath(source->parent, conformingProto);
buildPath(sig, source->parent, conformingProto);
return;
}

Expand All @@ -815,17 +865,24 @@ ConformanceAccessPath GenericSignature::getConformanceAccessPath(

// Retrieve the subject type, which is the archetype anchor for the root
// of this requirement.
auto anchor =
source->getRootPotentialArchetype()->getArchetypeAnchor(builder);
Type subjectType = anchor->getDependentType(getGenericParams(), false);
auto subjectPA = source->getRootPotentialArchetype();
subjectPA = subjectPA->getArchetypeAnchor(*subjectPA->getBuilder());
Type subjectType = subjectPA->getDependentType(sig->getGenericParams(),
false);

// Skip trivial path elements. These occur when querying a requirement
// signature.
if (!path.path.empty() && conformingProto == path.path.back().second &&
subjectType->isEqual(conformingProto->getSelfInterfaceType()))
return;

assert(hasConformanceInSignature(this, subjectType, conformingProto) &&
assert(hasConformanceInSignature(sig, subjectType, conformingProto) &&
"missing explicit conformance in signature");

// Add the root of the path, which starts at this explicit requirement.
path.path.push_back({subjectType, conformingProto});
};
buildPath(conforms->second, protocol);
buildPath(this, conforms->second, protocol);

// Return the path; we're done!
return path;
Expand Down
19 changes: 17 additions & 2 deletions test/Generics/conformance_access_path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,24 @@ func testPaths2<U: P2 & P4>(_ t: U) where U.AssocP3 == U.AssocP2.AssocP1 {
}

func testPaths3<V: P5>(_ v: V) {
// CHECK: Conformance access path for V.AssocP3: P0 is V: P5 -> τ_0_0.AssocP3: Q0 -> τ_0_0: P0
// CHECK: Conformance access path for V.AssocP3: P0 is V: P5 -> Self.AssocP3: Q0 -> τ_0_0: P0
acceptP0(v.getAssocP3())

// CHECK: Conformance access path for V.AssocP3: Q0 is V: P5 -> τ_0_0.AssocP3: Q0
// CHECK: Conformance access path for V.AssocP3: Q0 is V: P5 -> Self.AssocP3: Q0
acceptQ0(v.getAssocP3())
}

protocol P6Unordered: P5Unordered {
associatedtype A: P0
}

protocol P5Unordered {
associatedtype A: P0

func getA() -> A
}

func testUnorderedP5_P6<W: P6Unordered>(_ w: W) {
// CHECK: Conformance access path for W.A: P0 is W: P6Unordered -> Self: P5Unordered -> τ_0_0.A: P0
acceptP0(w.getA())
}