From df8689128de591abe68156104faf00edb3142cf0 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Fri, 5 Jul 2019 14:09:22 -0400 Subject: [PATCH] AST: Move ClangImporter's finishSignatureConformances() to a method on NormalProtocolConformance Also generalize the algorithm slightly to handle the full generality of non-imported conformances. --- include/swift/AST/ProtocolConformance.h | 4 + lib/AST/ProtocolConformance.cpp | 98 ++++++++++++++++++++++++- lib/ClangImporter/ImportDecl.cpp | 54 +------------- 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 427723a2497fc..507f681738c4e 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -654,6 +654,10 @@ class NormalProtocolConformance : public RootProtocolConformance, /// with the conformance requirements in the requirement signature (in order). std::function populateSignatureConformances(); + /// Populate the signature conformances without checking if they satisfy + /// requirements. Can only be used with parsed or imported conformances. + void finishSignatureConformances(); + /// Determine whether the witness for the given type requirement /// is the default definition. bool usesDefaultDefinition(AssociatedTypeDecl *requirement) const { diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 3828743a4a085..1176a235ed1a6 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -631,7 +631,8 @@ void NormalProtocolConformance::setSignatureConformances( !conformances[idx].getConcrete()->getType()->hasArchetype() && "Should have interface types here"); assert(idx < conformances.size()); - assert(conformances[idx].getRequirement() == + assert(conformances[idx].isInvalid() || + conformances[idx].getRequirement() == req.getSecondType()->castTo()->getDecl()); ++idx; } @@ -942,6 +943,98 @@ NormalProtocolConformance::getAssociatedConformance(Type assocType, "requested conformance was not a direct requirement of the protocol"); } + +/// A stripped-down version of Type::subst that only works on the protocol +/// Self type wrapped in zero or more DependentMemberTypes. +static Type +recursivelySubstituteBaseType(ModuleDecl *module, + NormalProtocolConformance *conformance, + DependentMemberType *depMemTy) { + Type origBase = depMemTy->getBase(); + + // Recursive case. + if (auto *depBase = origBase->getAs()) { + Type substBase = recursivelySubstituteBaseType( + module, conformance, depBase); + auto result = depMemTy->substBaseType(module, substBase); + if (!result) + return ErrorType::get(substBase); + return result; + } + + // Base case. The associated type's protocol should be either the + // conformance protocol or an inherited protocol. + auto *reqProto = depMemTy->getAssocType()->getProtocol(); + assert(origBase->isEqual(reqProto->getSelfInterfaceType())); + + ProtocolConformance *reqConformance = conformance; + + // If we have an inherited protocol just look up the conformance. + if (reqProto != conformance->getProtocol()) { + reqConformance = module->lookupConformance( + conformance->getType(), reqProto)->getConcrete(); + } + + return reqConformance->getTypeWitness(depMemTy->getAssocType(), + /*resolver=*/nullptr); +} + +/// Collect conformances for the requirement signature. +void NormalProtocolConformance::finishSignatureConformances() { + if (!SignatureConformances.empty()) + return; + + auto *proto = getProtocol(); + auto reqSig = proto->getRequirementSignature(); + if (reqSig.empty()) + return; + + SmallVector reqConformances; + for (const auto &req : reqSig) { + if (req.getKind() != RequirementKind::Conformance) + continue; + + ModuleDecl *module = getDeclContext()->getParentModule(); + + Type substTy; + auto origTy = req.getFirstType(); + if (origTy->isEqual(proto->getSelfInterfaceType())) { + substTy = getType(); + } else { + auto *depMemTy = origTy->castTo(); + substTy = recursivelySubstituteBaseType(module, this, depMemTy); + } + auto reqProto = req.getSecondType()->castTo()->getDecl(); + + // Looking up a conformance for a contextual type and mapping the + // conformance context produces a more accurate result than looking + // up a conformance from an interface type. + // + // This can happen if the conformance has an associated conformance + // depending on an associated type that is made concrete in a + // refining protocol. + // + // That is, the conformance of an interface type G : P really + // depends on the generic signature of the current context, because + // performing the lookup in a "more" constrained extension than the + // one where the conformance was defined must produce concrete + // conformances. + // + // FIXME: Eliminate this, perhaps by adding a variant of + // lookupConformance() taking a generic signature. + if (substTy->hasTypeParameter()) + substTy = getDeclContext()->mapTypeIntoContext(substTy); + + auto reqConformance = module->lookupConformance(substTy, reqProto); + if (!reqConformance) + reqConformance = ProtocolConformanceRef::forInvalid(); + + reqConformance = reqConformance->mapConformanceOutOfContext(); + reqConformances.push_back(*reqConformance); + } + setSignatureConformances(reqConformances); +} + Witness RootProtocolConformance::getWitness(ValueDecl *requirement, LazyResolver *resolver) const { ROOT_CONFORMANCE_SUBCLASS_DISPATCH(getWitness, (requirement, resolver)) @@ -1068,6 +1161,9 @@ SpecializedProtocolConformance::getTypeWitnessAndDecl( AssociatedTypeDecl *assocType, LazyResolver *resolver, SubstOptions options) const { + assert(getProtocol() == cast(assocType->getDeclContext()) && + "associated type in wrong protocol"); + // If we've already created this type witness, return it. auto known = TypeWitnesses.find(assocType); if (known != TypeWitnesses.end()) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index b0e80bd85dd62..4b1cdcea07170 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -7723,58 +7723,6 @@ static void finishTypeWitnesses( } } -/// A stripped-down version of Type::subst that only works on non-generic -/// associated types. -/// -/// This is used to finish a conformance for a concrete imported type that may -/// rely on default associated types defined in protocol extensions...without -/// having to do all the work of gathering conformances from scratch. -static Type -recursivelySubstituteBaseType(const NormalProtocolConformance *conformance, - DependentMemberType *depMemTy) { - Type origBase = depMemTy->getBase(); - if (auto *depBase = origBase->getAs()) { - Type substBase = recursivelySubstituteBaseType(conformance, depBase); - ModuleDecl *module = conformance->getDeclContext()->getParentModule(); - return depMemTy->substBaseType(module, substBase); - } - - const ProtocolDecl *proto = conformance->getProtocol(); - assert(origBase->isEqual(proto->getSelfInterfaceType())); - (void)proto; - return conformance->getTypeWitness(depMemTy->getAssocType(), - /*resolver=*/nullptr); -} - -/// Collect conformances for the requirement signature. -static void finishSignatureConformances( - NormalProtocolConformance *conformance) { - auto *proto = conformance->getProtocol(); - - SmallVector reqConformances; - for (const auto &req : proto->getRequirementSignature()) { - if (req.getKind() != RequirementKind::Conformance) - continue; - - Type substTy; - auto origTy = req.getFirstType(); - if (origTy->isEqual(proto->getSelfInterfaceType())) { - substTy = conformance->getType(); - } else { - auto *depMemTy = origTy->castTo(); - substTy = recursivelySubstituteBaseType(conformance, depMemTy); - } - auto reqProto = req.getSecondType()->castTo()->getDecl(); - - ModuleDecl *M = conformance->getDeclContext()->getParentModule(); - auto reqConformance = M->lookupConformance(substTy, reqProto); - assert(reqConformance && reqConformance->isConcrete() && - "required conformance not found"); - reqConformances.push_back(*reqConformance); - } - conformance->setSignatureConformances(reqConformances); -} - /// Create witnesses for requirements not already met. static void finishMissingOptionalWitnesses( NormalProtocolConformance *conformance) { @@ -7820,7 +7768,7 @@ void ClangImporter::Implementation::finishNormalConformance( conformance); finishTypeWitnesses(conformance); - finishSignatureConformances(conformance); + conformance->finishSignatureConformances(); // Imported conformances to @objc protocols also require additional // initialization to complete the requirement to witness mapping.