//===--- GenericSignatureQueries.cpp --------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Implements the various operations on interface types in GenericSignature. // Use those methods instead of calling into the RequirementMachine directly. // //===----------------------------------------------------------------------===// #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/GenericSignature.h" #include "swift/AST/Module.h" #include "llvm/ADT/TinyPtrVector.h" #include #include "RequirementMachine.h" using namespace swift; using namespace rewriting; /// Collects all requirements on a type parameter that are used to construct /// its ArchetypeType in a GenericEnvironment. GenericSignature::LocalRequirements RequirementMachine::getLocalRequirements( Type depType, TypeArrayView genericParams) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); GenericSignature::LocalRequirements result; result.anchor = Context.getTypeForTerm(term, genericParams); auto *props = Map.lookUpProperties(term); if (!props) return result; if (props->isConcreteType()) { result.concreteType = props->getConcreteType({}, term, Context); return result; } if (props->hasSuperclassBound()) { result.superclass = props->getSuperclassBound({}, term, Context); } for (const auto *proto : props->getConformsToExcludingSuperclassConformances()) result.protos.push_back(const_cast(proto)); result.layout = props->getLayoutConstraint(); return result; } bool RequirementMachine::requiresClass(Type depType) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return false; if (props->isConcreteType()) return false; auto layout = props->getLayoutConstraint(); return (layout && layout->isClass()); } LayoutConstraint RequirementMachine::getLayoutConstraint(Type depType) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return LayoutConstraint(); return props->getLayoutConstraint(); } bool RequirementMachine::requiresProtocol(Type depType, const ProtocolDecl *proto) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return false; if (props->isConcreteType()) return false; for (auto *otherProto : props->getConformsTo()) { if (otherProto == proto) return true; } return false; } GenericSignature::RequiredProtocols RequirementMachine::getRequiredProtocols(Type depType) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return { }; if (props->isConcreteType()) return { }; GenericSignature::RequiredProtocols result; for (auto *otherProto : props->getConformsTo()) { result.push_back(const_cast(otherProto)); } ProtocolType::canonicalizeProtocols(result); return result; } Type RequirementMachine:: getSuperclassBound(Type depType, TypeArrayView genericParams) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return Type(); if (!props->hasSuperclassBound()) return Type(); return props->getSuperclassBound(genericParams, term, Context); } bool RequirementMachine::isConcreteType(Type depType) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return false; return props->isConcreteType(); } Type RequirementMachine:: getConcreteType(Type depType, TypeArrayView genericParams) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return Type(); if (!props->isConcreteType()) return Type(); return props->getConcreteType(genericParams, term, Context); } bool RequirementMachine::areSameTypeParameterInContext(Type depType1, Type depType2) const { auto term1 = Context.getMutableTermForType(depType1->getCanonicalType(), /*proto=*/nullptr); System.simplify(term1); verify(term1); auto term2 = Context.getMutableTermForType(depType2->getCanonicalType(), /*proto=*/nullptr); System.simplify(term2); verify(term2); return (term1 == term2); } MutableTerm RequirementMachine::getLongestValidPrefix(const MutableTerm &term) const { MutableTerm prefix; for (auto symbol : term) { switch (symbol.getKind()) { case Symbol::Kind::Name: return prefix; case Symbol::Kind::Protocol: assert(prefix.empty() && "Protocol symbol can only appear at the start of a type term"); break; case Symbol::Kind::GenericParam: assert(prefix.empty() && "Generic parameter symbol can only appear at the start of a type term"); break; case Symbol::Kind::AssociatedType: { const auto *props = Map.lookUpProperties(prefix); if (!props) return prefix; auto conformsTo = props->getConformsTo(); for (const auto *proto : symbol.getProtocols()) { // T.[P:A] is valid iff T conforms to P. if (std::find(conformsTo.begin(), conformsTo.end(), proto) == conformsTo.end()) return prefix; } break; } case Symbol::Kind::Layout: case Symbol::Kind::Superclass: case Symbol::Kind::ConcreteType: case Symbol::Kind::ConcreteConformance: llvm_unreachable("Property symbol cannot appear in a type term"); } // This symbol is valid, add it to the longest prefix. prefix.add(symbol); } return prefix; } /// Unlike most other queries, the input type can be any type, not just a /// type parameter. /// /// Returns true if all structural components that are type parameters are /// in their canonical form, and are not concrete (in which case they're /// not considered canonical, since they can be replaced with their /// concrete type). bool RequirementMachine::isCanonicalTypeInContext(Type type) const { // Look for non-canonical type parameters. return !type.findIf([&](Type component) -> bool { if (!component->isTypeParameter()) return false; auto term = Context.getMutableTermForType(component->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return false; if (props->isConcreteType()) return true; auto anchor = Context.getTypeForTerm(term, {}); return CanType(anchor) != CanType(component); }); } /// Unlike most other queries, the input type can be any type, not just a /// type parameter. /// /// Replaces all structural components that are type parameters with their /// most canonical form, which is either a (possibly different) /// type parameter, or a concrete type, in which case we recursively /// simplify any type parameters appearing in structural positions of /// that concrete type as well, and so on. Type RequirementMachine::getCanonicalTypeInContext( Type type, TypeArrayView genericParams) const { return type.transformRec([&](Type t) -> Optional { if (!t->isTypeParameter()) return None; // Get a simplified term T. auto term = Context.getMutableTermForType(t->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); // We need to handle "purely concrete" member types, eg if I have a // signature , and we're asked to canonicalize the // type T.[P:A] where Foo : A. // // This comes up because we can derive the signature // from a generic signature like ; adding the // concrete requirement 'T == Foo' renders 'T : P' redundant. We then // want to take interface types written against the original signature // and canonicalize them with respect to the derived signature. // // The problem is that T.[P:A] is not a valid term in the rewrite system // for , since we do not have the requirement T : P. // // A more principled solution would build a substitution map when // building a derived generic signature that adds new requirements; // interface types would first be substituted before being canonicalized // in the new signature. // // For now, we handle this with a two-step process; we split a term up // into a longest valid prefix, which must resolve to a concrete type, // and the remaining suffix, which we use to perform a concrete // substitution using subst(). // In the below, let T be a type term, with T == UV, where U is the // longest valid prefix. // // Note that V can be empty if T is fully valid; we expect this to be // true most of the time. auto prefix = getLongestValidPrefix(term); // Get a type (concrete or dependent) for U. auto prefixType = [&]() -> Type { verify(prefix); auto *props = Map.lookUpProperties(prefix); if (props) { if (props->isConcreteType()) { auto concreteType = props->getConcreteType(genericParams, prefix, Context); if (!concreteType->hasTypeParameter()) return concreteType; // FIXME: Recursion guard is needed here return getCanonicalTypeInContext(concreteType, genericParams); } // Skip this part if the entire input term is valid, because in that // case we don't want to replace the term with its superclass bound; // unlike a fixed concrete type, the superclass bound only comes into // play when looking up a member type. if (props->hasSuperclassBound() && prefix.size() != term.size()) { auto superclass = props->getSuperclassBound(genericParams, prefix, Context); if (!superclass->hasTypeParameter()) return superclass; // FIXME: Recursion guard is needed here return getCanonicalTypeInContext(superclass, genericParams); } } return Context.getTypeForTerm(prefix, genericParams); }(); // If T is already valid, the longest valid prefix U of T is T itself, and // V is empty. Just return the type we computed above. // // This is the only case where U is allowed to be dependent. if (prefix.size() == term.size()) return prefixType; // If U is not concrete, we have an invalid member type of a dependent // type, which is not valid in this generic signature. Give up. if (prefixType->isTypeParameter()) { llvm::errs() << "Invalid type parameter in getCanonicalTypeInContext()\n"; llvm::errs() << "Original type: " << type << "\n"; llvm::errs() << "Simplified term: " << term << "\n"; llvm::errs() << "Longest valid prefix: " << prefix << "\n"; llvm::errs() << "Prefix type: " << prefixType << "\n"; llvm::errs() << "\n"; dump(llvm::errs()); abort(); } // Compute the type of the unresolved suffix term V, rooted in the // generic parameter τ_0_0. auto origType = Context.getRelativeTypeForTerm(term, prefix); // Substitute τ_0_0 in the above relative type with the concrete type // for U. // // Example: if T == A.B.C and the longest valid prefix is A.B which // maps to a concrete type Foo, then we have: // // U == A.B // V == C // // prefixType == Foo // origType == τ_0_0.C // substType == Foo.C // auto substType = origType.subst( [&](SubstitutableType *type) -> Type { assert(cast(type)->getDepth() == 0); assert(cast(type)->getIndex() == 0); return prefixType; }, LookUpConformanceInSignature(Sig.getPointer())); // FIXME: Recursion guard is needed here return getCanonicalTypeInContext(substType, genericParams); }); } /// Retrieve the conformance access path used to extract the conformance of /// interface \c type to the given \c protocol. /// /// \param type The interface type whose conformance access path is to be /// queried. /// \param protocol A protocol to which \c type conforms. /// /// \returns the conformance access path that starts at a requirement of /// this generic signature and ends at the conformance that makes \c type /// conform to \c protocol. /// /// \seealso ConformanceAccessPath ConformanceAccessPath RequirementMachine::getConformanceAccessPath(Type type, ProtocolDecl *protocol) { assert(type->isTypeParameter()); auto mutTerm = Context.getMutableTermForType(type->getCanonicalType(), /*proto=*/nullptr); System.simplify(mutTerm); verify(mutTerm); #ifndef NDEBUG auto *props = Map.lookUpProperties(mutTerm); assert(props && "Subject type of conformance access path should be known"); assert(!props->isConcreteType() && "Concrete types do not have conformance access paths"); auto conformsTo = props->getConformsTo(); assert(std::find(conformsTo.begin(), conformsTo.end(), protocol) && "Subject type of conformance access path must conform to protocol"); #endif auto term = Term::get(mutTerm, Context); // Check if we've already cached the result before doing anything else. auto found = ConformanceAccessPaths.find( std::make_pair(term, protocol)); if (found != ConformanceAccessPaths.end()) { return found->second; } auto &ctx = Context.getASTContext(); FrontendStatsTracer tracer(Stats, "get-conformance-access-path"); auto recordPath = [&](Term term, ProtocolDecl *proto, ConformanceAccessPath path) { // Add the path to the buffer. CurrentConformanceAccessPaths.emplace_back(term, path); // Add the path to the map. auto key = std::make_pair(term, proto); auto inserted = ConformanceAccessPaths.insert( std::make_pair(key, path)); assert(inserted.second); (void) inserted; if (Stats) ++Stats->getFrontendCounters().NumConformanceAccessPathsRecorded; }; // If this is the first time we're asked to look up a conformance access path, // visit all of the root conformance requirements in our generic signature and // add them to the buffer. if (ConformanceAccessPaths.empty()) { for (const auto &req : Sig.getRequirements()) { // We only care about conformance requirements. if (req.getKind() != RequirementKind::Conformance) continue; auto rootType = CanType(req.getFirstType()); auto *rootProto = req.getProtocolDecl(); ConformanceAccessPath::Entry root(rootType, rootProto); ArrayRef path(root); ConformanceAccessPath result(ctx.AllocateCopy(path)); auto mutTerm = Context.getMutableTermForType(rootType, nullptr); System.simplify(mutTerm); auto rootTerm = Term::get(mutTerm, Context); recordPath(rootTerm, rootProto, result); } } // We enumerate conformance access paths in shortlex order until we find the // path whose corresponding type canonicalizes to the one we are looking for. while (true) { auto found = ConformanceAccessPaths.find( std::make_pair(term, protocol)); if (found != ConformanceAccessPaths.end()) { return found->second; } if (CurrentConformanceAccessPaths.empty()) { llvm::errs() << "Failed to find conformance access path for "; llvm::errs() << type << " (" << term << ")" << " : "; llvm::errs() << protocol->getName() << ":\n"; type.dump(llvm::errs()); llvm::errs() << "\n"; dump(llvm::errs()); abort(); } // The buffer consists of all conformance access paths of length N. // Swap it out with an empty buffer, and fill it with all paths of // length N+1. std::vector> oldPaths; std::swap(CurrentConformanceAccessPaths, oldPaths); for (const auto &pair : oldPaths) { const auto &lastElt = pair.second.back(); auto *lastProto = lastElt.second; // A copy of the current path, populated as needed. SmallVector entries; for (const auto &req : lastProto->getRequirementSignature()) { // We only care about conformance requirements. if (req.getKind() != RequirementKind::Conformance) continue; auto nextSubjectType = req.getFirstType()->getCanonicalType(); auto *nextProto = req.getProtocolDecl(); MutableTerm mutTerm(pair.first); mutTerm.append(Context.getMutableTermForType(nextSubjectType, /*proto=*/lastProto)); System.simplify(mutTerm); auto nextTerm = Term::get(mutTerm, Context); // If we've already seen a path for this conformance, skip it and // don't add it to the buffer. Note that because we iterate over // conformance access paths in shortlex order, the existing // conformance access path is shorter than the one we found just now. if (ConformanceAccessPaths.count( std::make_pair(nextTerm, nextProto))) continue; if (entries.empty()) { // Fill our temporary vector. entries.insert(entries.begin(), pair.second.begin(), pair.second.end()); } // Add the next entry. entries.emplace_back(nextSubjectType, nextProto); ConformanceAccessPath result = ctx.AllocateCopy(entries); entries.pop_back(); recordPath(nextTerm, nextProto, result); } } } } static void lookupConcreteNestedType(NominalTypeDecl *decl, Identifier name, SmallVectorImpl &concreteDecls) { SmallVector foundMembers; decl->getParentModule()->lookupQualified( decl, DeclNameRef(name), NL_QualifiedDefault | NL_OnlyTypes | NL_ProtocolMembers, foundMembers); for (auto member : foundMembers) concreteDecls.push_back(cast(member)); } static TypeDecl * findBestConcreteNestedType(SmallVectorImpl &concreteDecls) { return *std::min_element(concreteDecls.begin(), concreteDecls.end(), [](TypeDecl *type1, TypeDecl *type2) { return TypeDecl::compare(type1, type2) < 0; }); } TypeDecl * RequirementMachine::lookupNestedType(Type depType, Identifier name) const { auto term = Context.getMutableTermForType(depType->getCanonicalType(), /*proto=*/nullptr); System.simplify(term); verify(term); auto *props = Map.lookUpProperties(term); if (!props) return nullptr; // Look for types with the given name in protocols that we know about. AssociatedTypeDecl *bestAssocType = nullptr; SmallVector concreteDecls; for (const auto *proto : props->getConformsTo()) { // Look for an associated type and/or concrete type with this name. for (auto member : const_cast(proto)->lookupDirect(name)) { // If this is an associated type, record whether it is the best // associated type we've seen thus far. if (auto assocType = dyn_cast(member)) { // Retrieve the associated type anchor. assocType = assocType->getAssociatedTypeAnchor(); if (!bestAssocType || compareAssociatedTypes(assocType, bestAssocType) < 0) bestAssocType = assocType; continue; } // If this is another type declaration, record it. if (auto type = dyn_cast(member)) { concreteDecls.push_back(type); continue; } } } // If we haven't found anything yet but have a concrete type or a superclass, // look for a type in that. // FIXME: Shouldn't we always look here? if (!bestAssocType && concreteDecls.empty()) { Type typeToSearch; if (props->isConcreteType()) typeToSearch = props->getConcreteType(); else if (props->hasSuperclassBound()) typeToSearch = props->getSuperclassBound(); if (typeToSearch) if (auto *decl = typeToSearch->getAnyNominal()) lookupConcreteNestedType(decl, name, concreteDecls); } if (bestAssocType) { assert(bestAssocType->getOverriddenDecls().empty() && "Lookup should never keep a non-anchor associated type"); return bestAssocType; } else if (!concreteDecls.empty()) { // Find the best concrete type. return findBestConcreteNestedType(concreteDecls); } return nullptr; } void RequirementMachine::verify(const MutableTerm &term) const { #ifndef NDEBUG // If the term is in the generic parameter domain, ensure we have a valid // generic parameter. if (term.begin()->getKind() == Symbol::Kind::GenericParam) { auto *genericParam = term.begin()->getGenericParam(); TypeArrayView genericParams = getGenericParams(); auto found = std::find(genericParams.begin(), genericParams.end(), genericParam); if (found == genericParams.end()) { llvm::errs() << "Bad generic parameter in " << term << "\n"; dump(llvm::errs()); abort(); } } MutableTerm erased; // First, "erase" resolved associated types from the term, and try // to simplify it again. for (auto symbol : term) { if (erased.empty()) { switch (symbol.getKind()) { case Symbol::Kind::Protocol: case Symbol::Kind::GenericParam: erased.add(symbol); continue; case Symbol::Kind::AssociatedType: erased.add(Symbol::forProtocol(symbol.getProtocols()[0], Context)); break; case Symbol::Kind::Name: case Symbol::Kind::Layout: case Symbol::Kind::Superclass: case Symbol::Kind::ConcreteType: case Symbol::Kind::ConcreteConformance: llvm::errs() << "Bad initial symbol in " << term << "\n"; abort(); break; } } switch (symbol.getKind()) { case Symbol::Kind::Name: assert(!erased.empty()); erased.add(symbol); break; case Symbol::Kind::AssociatedType: erased.add(Symbol::forName(symbol.getName(), Context)); break; case Symbol::Kind::Protocol: case Symbol::Kind::GenericParam: case Symbol::Kind::Layout: case Symbol::Kind::Superclass: case Symbol::Kind::ConcreteType: case Symbol::Kind::ConcreteConformance: llvm::errs() << "Bad interior symbol " << symbol << " in " << term << "\n"; abort(); break; } } MutableTerm simplified = erased; System.simplify(simplified); // We should end up with the same term. if (simplified != term) { llvm::errs() << "Term verification failed\n"; llvm::errs() << "Initial term: " << term << "\n"; llvm::errs() << "Erased term: " << erased << "\n"; llvm::errs() << "Simplified term: " << simplified << "\n"; llvm::errs() << "\n"; dump(llvm::errs()); abort(); } #endif }