Canonicalize conformance access paths for sources pre-requirement-signature.

When a requirement source involving a ProtocolRequirement element is
built prior to the requirement signature of the protocol it
references, we can end up with a requirement source whose steps don't
reflect was is actually available via the requirement signatures. When
building a conformance access path from such requirement sources,
canonicalize on-the-fly using the requirement signatures (which have
been/can be computed by this point) to produce a correct access path.
This commit is contained in:
Doug Gregor
2017-03-08 15:51:23 -08:00
parent 4cd76d9df4
commit 1f8b0f9b85
3 changed files with 106 additions and 30 deletions

View File

@@ -504,6 +504,7 @@ class GenericSignatureBuilder::RequirementSource final
WrittenRequirementLoc> {
friend class FloatingRequirementSource;
friend class GenericSignature;
public:
enum Kind : uint8_t {
@@ -584,6 +585,9 @@ private:
/// 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.
@@ -673,7 +677,7 @@ public:
WrittenRequirementLoc writtenReqLoc)
: kind(kind), storageKind(StorageKind::RootArchetype),
hasTrailingWrittenRequirementLoc(!writtenReqLoc.isNull()),
parent(nullptr) {
usesRequirementSignature(false), parent(nullptr) {
assert(isAcceptableStorageKind(kind, storageKind) &&
"RequirementSource kind/storageKind mismatch");
@@ -689,7 +693,7 @@ public:
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)");
@@ -707,7 +711,7 @@ public:
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) &&
@@ -720,7 +724,7 @@ public:
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) &&

View File

@@ -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,
@@ -770,34 +780,74 @@ 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");
// Add this step along the path, which involes looking for the
// conformance we want (\c conformingProto) within the protocol
// described by this source.
// 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.
Type subjectType = source->getStoredType();
subjectType = inProtocol->getRequirementSignature()
->getCanonicalTypeInContext(subjectType,
*inProtocol->getParentModule());
// 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");
assert(hasConformanceInSignature(inProtocol->getRequirementSignature(),
subjectType, conformingProto) &&
"missing explicit conformance in requirement signature");
// Record this step.
path.path.push_back({subjectType, conformingProto});
// Record this step.
path.path.push_back({subjectType, conformingProto});
return;
}
// 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());
// 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());
// Dig out the potential archetype for this stored type.
auto pa = reqSigBuilder.resolveArchetype(storedType);
auto rep = pa->getRepresentative();
// 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;
@@ -805,7 +855,7 @@ ConformanceAccessPath GenericSignature::getConformanceAccessPath(
// If we still have a parent, keep going.
if (source->parent) {
buildPath(source->parent, conformingProto);
buildPath(sig, source->parent, conformingProto);
return;
}
@@ -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);
assert(hasConformanceInSignature(this, subjectType, conformingProto) &&
// 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(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;

View File

@@ -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())
}