Files
swift-mirror/lib/AST/RequirementMachine/ConcreteContraction.cpp

744 lines
26 KiB
C++

//===--- ConcreteContraction.cpp - Preprocessing concrete conformances ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2022 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
//
//===----------------------------------------------------------------------===//
//
// The concrete contraction pass runs after requirement desugaring and before
// rewrite rules are built from desugared requirements when constructing a
// rewrite system for a user-written generic signature.
//
// This is an imperfect hack to deal with two issues:
//
// a) When a generic parameter is subject to both a conformance requirement and
// a concrete type requirement (or a superclass requirement where the
// superclass also conforms to the protocol), the conformance requirement
// becomes redundant during property map construction.
//
// However, unlike other kinds of requirements, dropping a conformance
// requirement can change canonical types in the rewrite system, and in
// particular, it can change the canonical types of other minimal
// requirements, if the protocol in the conformance requirement has
// associated types.
//
// Consider this example:
//
// protocol P {
// associatedtype T
// }
//
// protocol Q {}
//
// struct S<T> : P {}
//
// struct Holder<A : P, B : P> where A.T : Q {}
//
// extension Holder where A == S<B.T> {}
//
// The signature of the extension is built from these requirements:
//
// - A : P
// - B : P
// - A.T : Q
// - A == S<B.T>
//
// In this rewrite system, the canonical type of 'B.T' is 'A.T', so the
// requirements canonicalize as follows:
//
// - A : P
// - B : P
// - A.T : Q
// - A == S<A.T>
//
// Also, the first requirement 'A : P' is redundant in this rewrite system,
// because 'A == S<B.T>' and S conforms to P.
//
// However, simply dropping 'A : P' is not enough. If the user instead
// directly wrote a signature with the original requirements omitting
// 'A : P', we would have:
//
// - B : P
// - A == S<B.T>
// - B.T : Q
//
// Indeed, computing canonical types from the first rewrite system produces
// different results, because 'B.T' canonicalizes to 'A.T' and not 'B.T'.
//
// b) The GenericSignatureBuilder permitted references to "fully concrete"
// member types of a dependent type that were not associated types from
// conformance requirements.
//
// That is, the following was permitted:
//
// class C {
// typealias A = Int
// }
//
// <T, U where T : C, U == T.A>
//
// The requirement 'U == T.A' was resolved to 'U == Int', despite 'T' not
// declaring any protocol conformance requirements with an associated type
// named 'A' (or any conformance requirements at all).
//
// The GenericSignatureBuilder dealt with a) using a "rebuilding" pass that
// build a new generic signature after dropping redundant conformance
// requirements, feeding the original requirements back in. The behavior b)
// was a consequence of how requirement resolution was implemented, by calling
// into name lookup.
//
// The Requirement Machine's approach to both a) and b) doesn't handle as many
// cases, but is much simpler and hopefully more robust. Before building the
// rewrite system, we pre-process the requirements and identify generic
// parameters subject to a superclass or concrete type requirement. Then, we
// substitute this generic parameter for the superclass or concrete type,
// respectively, in all other requirements.
//
// In the above example, it produces the following list of requirements:
//
// - S<B.T> : P
// - B : P
// - S<B.T>.T : Q
// - A == S<B.T>
//
// The requirements are fed back into desugarRequirements(), and we get:
//
// - B : P
// - B.T : Q
// - A == S<B.T>
//
// This also handles b), where the original requirements are:
//
// - T : C
// - U == T.A
//
// and after concrete contraction, we get
//
// - T : C
// - U == Int
//
// Since this is all a heuristic that is applied before the rewrite system is
// built, it doesn't handle cases where a nested type of a generic parameter is
// subject to both a concrete type and conformance requirement, nor does it
// handle more complex cases where the redundant conformance is only discovered
// via an intermediate same-type requirement, such as the following:
//
// <T, U, V where T == S<V>, T == U, U : P>
//
// If concrete contraction fails, the minimized generic signature will fail
// verification if it still contains incorrectly-canonicalized types.
//
// We might need a more general solution eventually, but for now this is good
// enough to handle the cases where this arises in practice.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Module.h"
#include "swift/AST/Requirement.h"
#include "swift/AST/Type.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "NameLookup.h"
#include "RequirementLowering.h"
using namespace swift;
using namespace rewriting;
/// Strip associated types from types used as keys to erase differences between
/// resolved types coming from the parent generic signature and unresolved types
/// coming from user-written requirements.
static Type stripBoundDependentMemberTypes(Type t) {
if (auto *depMemTy = t->getAs<DependentMemberType>()) {
return DependentMemberType::get(
stripBoundDependentMemberTypes(depMemTy->getBase()),
depMemTy->getName());
}
return t;
}
/// Returns true if \p lhs appears as the base of a member type in \p rhs.
static bool typeOccursIn(Type lhs, Type rhs) {
return rhs.findIf([lhs](Type t) -> bool {
if (auto *memberType = t->getAs<DependentMemberType>())
return memberType->getBase()->isEqual(lhs);
return false;
});
}
namespace {
/// Utility class to store some shared state.
class ConcreteContraction {
bool Debug;
llvm::SmallDenseMap<CanType, llvm::SmallDenseSet<Type, 1>> ConcreteTypes;
llvm::SmallDenseMap<CanType, llvm::SmallDenseSet<Type, 1>> Superclasses;
llvm::SmallDenseMap<CanType, llvm::SmallVector<ProtocolDecl *, 1>> Conformances;
enum Position {
/// Base type of some other type appearing in a requirement.
BaseType,
/// Subject type of a conformance requirement.
ConformanceRequirement,
/// Subject type of a superclass requirement.
SuperclassRequirement,
/// Subject type of a same-type requirement.
SameTypeRequirement,
/// Some other position.
Other
};
std::optional<Type> substTypeParameterRec(Type type, Position position) const;
Type substTypeParameter(Type type, Position position) const;
Type substType(Type type) const;
Requirement substRequirement(const Requirement &req) const;
bool preserveSameTypeRequirement(const Requirement &req) const;
bool hasResolvedMemberTypeOfInterestingParameter(Type t) const;
public:
ConcreteContraction(bool debug) : Debug(debug) {}
bool performConcreteContraction(
ArrayRef<StructuralRequirement> requirements,
SmallVectorImpl<StructuralRequirement> &result,
SmallVectorImpl<RequirementError> &errors);
};
} // end namespace
/// A re-implementation of Type::subst() that also handles unresolved
/// DependentMemberTypes by performing name lookup into the base type.
///
/// When substituting a superclass requirement, we have to handle the
/// case where the superclass might not conform to the protocol in
/// question. For example, you can have a generic signature like this
///
/// <T where T : Sequence, T : SomeClass, T.Element == Int>
///
/// If SomeClass does not conform to Sequence, the type T is understood
/// to be some subclass of SomeClass which does conform to Sequence;
/// this is perfectly valid, and we cannot substitute the 'T.Element'
/// requirement. In this case, this method returns None.
std::optional<Type>
ConcreteContraction::substTypeParameterRec(Type type, Position position) const {
// If we have a superclass (T : C) or same-type requirement (T == C),
// don't substitute T, since then we end up with 'C == C' or 'C : C',
// losing the requirement.
if (position == Position::BaseType ||
position == Position::ConformanceRequirement) {
auto key = stripBoundDependentMemberTypes(type)->getCanonicalType();
Type concreteType;
{
auto found = ConcreteTypes.find(key);
if (found != ConcreteTypes.end() && found->second.size() == 1)
concreteType = *found->second.begin();
}
Type superclass;
{
auto found = Superclasses.find(key);
if (found != Superclasses.end() && found->second.size() == 1)
superclass = *found->second.begin();
}
// If we have both, prefer the concrete type requirement since it is more
// specific.
if (!concreteType && superclass)
concreteType = superclass;
if (concreteType) {
return concreteType;
}
}
if (auto *memberType = type->getAs<DependentMemberType>()) {
auto baseType = memberType->getBase();
auto substBaseType = substTypeParameterRec(baseType, Position::BaseType);
if (!substBaseType)
return std::nullopt;
// A resolved DependentMemberType stores an associated type declaration.
//
// Handle this by looking up the corresponding type witness in the base
// type's conformance to the associated type's protocol.
if (auto *assocType = memberType->getAssocType()) {
auto *proto = assocType->getProtocol();
// The 'Sendable' protocol does not declare any associated types, so the
// 'allowMissing' value here is actually irrelevant.
auto conformance = lookupConformance(*substBaseType, proto,
/*allowMissing=*/false);
if (proto->isSpecificProtocol(KnownProtocolKind::Sendable) &&
conformance.hasUnavailableConformance()) {
conformance = ProtocolConformanceRef::forInvalid();
}
// The base type doesn't conform, in which case the requirement remains
// unsubstituted.
if (!conformance) {
if (Debug) {
llvm::dbgs() << "@@@ " << substBaseType << " does not conform to "
<< proto->getName() << "\n";
}
return std::nullopt;
}
return conformance.getTypeWitness(assocType);
}
// An unresolved DependentMemberType stores an identifier. Handle this
// by performing a name lookup into the base type.
SmallVector<TypeDecl *> concreteDecls;
lookupConcreteNestedType(*substBaseType, memberType->getName(), concreteDecls);
auto *typeDecl = findBestConcreteNestedType(concreteDecls);
if (typeDecl == nullptr) {
// The base type doesn't contain a member type with this name, in which
// case the requirement remains unsubstituted.
if (Debug) {
llvm::dbgs() << "@@@ Lookup of " << memberType->getName() << " failed on "
<< *substBaseType << "\n";
}
return std::nullopt;
}
// Substitute the base type into the member type.
auto *dc = typeDecl->getDeclContext();
auto subMap = (*substBaseType)->getContextSubstitutionMap(dc);
return typeDecl->getDeclaredInterfaceType().subst(subMap);
}
return std::nullopt;
}
/// Replace the generic parameter at the root of \p type, which must be a
/// type parameter, with the superclass or concrete type requirement that
/// the generic parameter is subject to.
///
/// Note that if the generic parameter has a superclass conformance, we
/// only substitute if it's the root of a member type; the generic parameter
/// itself does not become concrete when it's superclass-constrained, unless
/// it is the subject of a conformance requirement.
Type ConcreteContraction::substTypeParameter(
Type type, Position position) const {
ASSERT(type->isTypeParameter());
auto result = substTypeParameterRec(type, position);
if (!result)
return type;
return *result;
}
/// Substitute all type parameters occurring in structural positions of \p type.
Type ConcreteContraction::substType(Type type) const {
return type.transformRec([&](Type type) -> std::optional<Type> {
if (!type->isTypeParameter())
return std::nullopt;
return substTypeParameter(type, Position::Other);
});
}
/// Substitute all type parameters occurring in the given requirement.
Requirement
ConcreteContraction::substRequirement(const Requirement &req) const {
auto firstType = req.getFirstType();
switch (req.getKind()) {
case RequirementKind::SameShape: {
auto substFirstType = substType(firstType);
auto substSecondType = substType(req.getSecondType());
return Requirement(req.getKind(), substFirstType, substSecondType);
}
case RequirementKind::Superclass:
case RequirementKind::SameType: {
auto position = (req.getKind() == RequirementKind::Superclass
? Position::SuperclassRequirement
: Position::SameTypeRequirement);
auto substFirstType = substTypeParameter(firstType, position);
auto secondType = req.getSecondType();
auto substSecondType = substType(secondType);
return Requirement(req.getKind(),
substFirstType,
substSecondType);
}
case RequirementKind::Conformance: {
auto substFirstType = substTypeParameter(
firstType, Position::ConformanceRequirement);
auto *proto = req.getProtocolDecl();
// For conformance to 'Sendable', allow synthesis of a missing conformance
// if the generic parameter is concrete, that is, if we're looking at a
// signature of the form 'T == Foo, T : Sendable'.
//
// Otherwise, we have a superclass requirement, like 'T : C, T : Sendable';
// don't synthesize the conformance in this case since dropping
// 'T : Sendable' would be incorrect; we want to ensure that we only admit
// subclasses of 'C' which are 'Sendable'.
bool allowMissing = false;
auto key = stripBoundDependentMemberTypes(firstType)->getCanonicalType();
if (ConcreteTypes.count(key) > 0)
allowMissing = true;
if (!substFirstType->isTypeParameter()) {
auto conformance = lookupConformance(substFirstType, proto,
allowMissing);
if (!allowMissing &&
proto->isSpecificProtocol(KnownProtocolKind::Sendable) &&
conformance.hasUnavailableConformance()) {
conformance = ProtocolConformanceRef::forInvalid();
}
if (!conformance) {
// Handle the case of <T where T : P, T : C> where C is a class and
// C does not conform to P and only substitute the parent type of T
// by pretending we have a same-type requirement here.
substFirstType = substTypeParameter(
firstType, Position::SameTypeRequirement);
}
}
// Otherwise, replace the generic parameter in the conformance
// requirement with the concrete type. It will desugar to nothing
// (if the conformance is conditional) or to zero or more
// conditional requirements needed to satisfy the conformance.
return Requirement(req.getKind(),
substFirstType,
req.getSecondType());
}
case RequirementKind::Layout: {
auto substFirstType = substTypeParameter(firstType, Position::Other);
if (!substFirstType->isTypeParameter() &&
!substFirstType->satisfiesClassConstraint() &&
req.getLayoutConstraint()->isClass()) {
// If the concrete type doesn't satisfy the layout constraint, produce
// a better diagnostic and only substitute the parent type by pretending
// we have a same-type requirement here.
substFirstType = substTypeParameter(
firstType, Position::SameTypeRequirement);
}
return Requirement(req.getKind(),
substFirstType,
req.getLayoutConstraint());
}
}
}
bool ConcreteContraction::
hasResolvedMemberTypeOfInterestingParameter(Type type) const {
return type.findIf([&](Type t) -> bool {
if (auto *memberTy = t->getAs<DependentMemberType>()) {
if (memberTy->getAssocType() == nullptr)
return false;
auto key = stripBoundDependentMemberTypes(memberTy->getBase())
->getCanonicalType();
Type concreteType;
{
auto found = ConcreteTypes.find(key);
if (found != ConcreteTypes.end() && found->second.size() == 1)
return true;
}
Type superclass;
{
auto found = Superclasses.find(key);
if (found != Superclasses.end() && found->second.size() == 1)
return true;
}
}
return false;
});
}
/// Another silly GenericSignatureBuilder compatibility hack.
///
/// Consider this code:
///
/// class C<T> {
/// typealias A = T
/// }
///
/// protocol P {
/// associatedtype A
/// }
///
/// func f<X, T>(_: X, _: T) where X : P, X : C<T>, X.A == T {}
///
/// The GenericSignatureBuilder would introduce an equivalence between
/// typealias A in class C and associatedtype A in protocol P, so the
/// requirement 'X.A == T' would effectively constrain _both_.
///
/// Simulate this by keeping both the original and substituted same-type
/// requirement in a narrow case.
bool ConcreteContraction::preserveSameTypeRequirement(
const Requirement &req) const {
if (req.getKind() != RequirementKind::SameType)
return false;
// One of the parent types of this type parameter should be subject
// to a superclass requirement.
auto type = stripBoundDependentMemberTypes(req.getFirstType())
->getCanonicalType();
while (true) {
if (Superclasses.find(type) != Superclasses.end())
break;
if (auto memberType = dyn_cast<DependentMemberType>(type)) {
type = memberType.getBase();
continue;
}
return false;
}
if (hasResolvedMemberTypeOfInterestingParameter(req.getFirstType()) ||
hasResolvedMemberTypeOfInterestingParameter(req.getSecondType()))
return false;
return true;
}
/// Substitute all occurrences of generic parameters subject to superclass
/// or concrete type requirements with their corresponding superclass or
/// concrete type.
///
/// If this returns false, \p result should be ignored and the requirements
/// remain unchanged. If this returns true, \p result should replace the
/// original \p requirements.
bool ConcreteContraction::performConcreteContraction(
ArrayRef<StructuralRequirement> requirements,
SmallVectorImpl<StructuralRequirement> &result,
SmallVectorImpl<RequirementError> &errors) {
// Phase 1 - collect concrete type and superclass requirements where the
// subject type is a generic parameter.
for (auto req : requirements) {
auto subjectType = req.req.getFirstType();
ASSERT(subjectType->isTypeParameter() &&
"Forgot to call desugarRequirement()");
auto kind = req.req.getKind();
switch (kind) {
case RequirementKind::SameShape:
ASSERT(req.req.getSecondType()->isTypeParameter());
continue;
case RequirementKind::SameType: {
auto constraintType = req.req.getSecondType();
// Same-type requirements between type parameters are not interesting
// to this pass.
if (constraintType->isTypeParameter())
break;
subjectType = stripBoundDependentMemberTypes(subjectType);
if (typeOccursIn(subjectType,
stripBoundDependentMemberTypes(constraintType))) {
if (Debug) {
llvm::dbgs() << "@ Subject type of same-type requirement "
<< subjectType << " == " << constraintType << " "
<< "occurs in the constraint type, skipping\n";
}
break;
}
ConcreteTypes[subjectType->getCanonicalType()].insert(constraintType);
break;
}
case RequirementKind::Superclass: {
auto constraintType = req.req.getSecondType();
ASSERT(!constraintType->isTypeParameter() &&
"Forgot to call desugarRequirement()");
subjectType = stripBoundDependentMemberTypes(subjectType);
if (typeOccursIn(subjectType,
stripBoundDependentMemberTypes(constraintType))) {
if (Debug) {
llvm::dbgs() << "@ Subject type of superclass requirement "
<< subjectType << " : " << constraintType << " "
<< "occurs in the constraint type, skipping\n";
}
break;
}
Superclasses[subjectType->getCanonicalType()].insert(constraintType);
break;
}
case RequirementKind::Conformance: {
auto *protoDecl = req.req.getProtocolDecl();
subjectType = stripBoundDependentMemberTypes(subjectType);
Conformances[subjectType->getCanonicalType()].push_back(protoDecl);
break;
}
case RequirementKind::Layout:
break;
}
}
// Block concrete contraction if a generic parameter conforms to a protocol P
// which has a superclass bound C which again conforms to P. This is a really
// silly edge case, but we go to great pains to produce the same minimized
// signature as the GenericSignatureBuilder in this case, <T : P>, and not the
// more logical <T : C>.
for (const auto &pair : Conformances) {
auto subjectType = pair.first;
auto found = Superclasses.find(subjectType);
if (found == Superclasses.end() || found->second.size() != 1)
continue;
auto superclassTy = *found->second.begin();
for (auto *proto : pair.second) {
if (lookupConformance(superclassTy, proto)) {
auto genericSig = proto->getGenericSignature();
// FIXME: If we end up here while building the requirement
// signature of `proto`, we will hit a request cycle.
if (auto otherSuperclassTy = genericSig->getSuperclassBound(
proto->getSelfInterfaceType())) {
if (Debug) {
llvm::dbgs() << "@ Subject type of superclass requirement "
<< subjectType << " : " << superclassTy
<< " conforms to "<< proto->getName()
<< " which has a superclass bound "
<< otherSuperclassTy << "\n";
}
if (superclassTy->isEqual(otherSuperclassTy)) {
Superclasses.erase(subjectType);
break;
}
}
}
}
}
// If there's nothing to do just return.
if (ConcreteTypes.empty() && Superclasses.empty())
return false;
if (Debug) {
llvm::dbgs() << "@ Concrete types: @\n";
for (auto pair : ConcreteTypes) {
llvm::dbgs() << pair.first;
if (pair.second.size() == 1) {
llvm::dbgs() << " == " << *pair.second.begin() << "\n";
} else {
llvm::dbgs() << " has duplicate concrete type requirements\n";
}
}
llvm::dbgs() << "@ Superclasses: @\n";
for (auto pair : Superclasses) {
llvm::dbgs() << pair.first;
if (pair.second.size() == 1) {
llvm::dbgs() << " : " << *pair.second.begin() << "\n";
} else {
llvm::dbgs() << " has duplicate superclass requirements\n";
}
}
}
// Phase 2: Replace each concretely-conforming generic parameter with its
// concrete type.
for (auto req : requirements) {
if (Debug) {
llvm::dbgs() << "@ Original requirement: ";
req.req.dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
// Substitute the requirement.
auto substReq = substRequirement(req.req);
if (Debug) {
llvm::dbgs() << "@ Substituted requirement: ";
substReq.dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
// Otherwise, desugar the requirement again, since we might now have a
// requirement where the left hand side is not a type parameter.
SmallVector<Requirement, 4> reqs;
SmallVector<InverseRequirement, 4> ignoreInverses;
desugarRequirement(substReq, req.loc, reqs, ignoreInverses, errors);
for (auto desugaredReq : reqs) {
if (Debug) {
llvm::dbgs() << "@@ Desugared requirement: ";
desugaredReq.dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
result.push_back({desugaredReq, req.loc});
}
if (preserveSameTypeRequirement(req.req) &&
(!req.req.getFirstType()->isEqual(substReq.getFirstType()) ||
!req.req.getSecondType()->isEqual(substReq.getSecondType()))) {
if (Debug) {
llvm::dbgs() << "@ Preserving original requirement: ";
req.req.dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
// Make the duplicated requirement 'inferred' so that we don't diagnose
// it as redundant.
result.push_back({req.req, SourceLoc()});
}
}
if (Debug) {
llvm::dbgs() << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
llvm::dbgs() << "@ Concrete contraction succeeded @\n";
llvm::dbgs() << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
}
return true;
}
/// Substitute all occurrences of generic parameters subject to superclass
/// or concrete type requirements with their corresponding superclass or
/// concrete type.
///
/// If this returns false, \p result should be ignored and the requirements
/// remain unchanged. If this returns true, \p result should replace the
/// original \p requirements.
bool swift::rewriting::performConcreteContraction(
ArrayRef<StructuralRequirement> requirements,
SmallVectorImpl<StructuralRequirement> &result,
SmallVectorImpl<RequirementError> &errors,
bool debug) {
ConcreteContraction concreteContraction(debug);
return concreteContraction.performConcreteContraction(
requirements, result, errors);
}