mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[AST] Compute conditional requirements in a conformance.
This allows determining which requirements make a conformance conditional; as in, which requirements aren't known as part of the type itself. Additionally, use this to assert that a few builtin protocols aren't conditionally-conformed-to, something we won't support for now.
This commit is contained in:
@@ -296,6 +296,12 @@ public:
|
||||
/// The type parameters must be known to not be concrete within the context.
|
||||
bool areSameTypeParameterInContext(Type type1, Type type2);
|
||||
|
||||
/// Determine if \c sig can prove \c requirement, meaning that it can deduce
|
||||
/// T: Foo or T == U (etc.) with the information it knows. This includes
|
||||
/// checking against global state, if any/all of the types in the requirement
|
||||
/// are concrete, not type parameters.
|
||||
bool isRequirementSatisfied(Requirement requirement);
|
||||
|
||||
/// Return the canonical version of the given type under this generic
|
||||
/// signature.
|
||||
CanType getCanonicalTypeInContext(Type type);
|
||||
|
||||
@@ -294,6 +294,10 @@ public:
|
||||
/// Get the property declaration for a behavior conformance, if this is one.
|
||||
AbstractStorageDecl *getBehaviorDecl() const;
|
||||
|
||||
/// Get any additional requirements that are required for this conformance to
|
||||
/// be satisfied.
|
||||
ArrayRef<Requirement> getConditionalRequirements() const;
|
||||
|
||||
/// Substitute the conforming type and produce a ProtocolConformance that
|
||||
/// applies to the substituted type.
|
||||
ProtocolConformance *subst(Type substType,
|
||||
@@ -349,6 +353,10 @@ class NormalProtocolConformance : public ProtocolConformance,
|
||||
/// requirement signature of the protocol.
|
||||
ArrayRef<ProtocolConformanceRef> SignatureConformances;
|
||||
|
||||
/// Any additional requirements that are required for this conformance to
|
||||
/// apply, e.g. 'Something: Baz' in 'extension Foo: Bar where Something: Baz'.
|
||||
ArrayRef<Requirement> ConditionalRequirements;
|
||||
|
||||
/// The lazy member loader provides callbacks for populating imported and
|
||||
/// deserialized conformances.
|
||||
///
|
||||
@@ -365,6 +373,7 @@ class NormalProtocolConformance : public ProtocolConformance,
|
||||
: ProtocolConformance(ProtocolConformanceKind::Normal, conformingType),
|
||||
ProtocolAndState(protocol, state), Loc(loc), ContextAndInvalid(dc, false)
|
||||
{
|
||||
differenceAndStoreConditionalRequirements();
|
||||
}
|
||||
|
||||
NormalProtocolConformance(Type conformingType,
|
||||
@@ -375,10 +384,13 @@ class NormalProtocolConformance : public ProtocolConformance,
|
||||
ProtocolAndState(protocol, state), Loc(loc),
|
||||
ContextAndInvalid(behaviorStorage, false)
|
||||
{
|
||||
differenceAndStoreConditionalRequirements();
|
||||
}
|
||||
|
||||
void resolveLazyInfo() const;
|
||||
|
||||
void differenceAndStoreConditionalRequirements();
|
||||
|
||||
public:
|
||||
/// Get the protocol being conformed to.
|
||||
ProtocolDecl *getProtocol() const { return ProtocolAndState.getPointer(); }
|
||||
@@ -397,6 +409,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Get any additional requirements that are required for this conformance to
|
||||
/// be satisfied, e.g. for Array<T>: Equatable, T: Equatable also needs
|
||||
/// to be satisfied.
|
||||
ArrayRef<Requirement> getConditionalRequirements() const {
|
||||
return ConditionalRequirements;
|
||||
}
|
||||
|
||||
/// Retrieve the state of this conformance.
|
||||
ProtocolConformanceState getState() const {
|
||||
return ProtocolAndState.getInt();
|
||||
@@ -580,6 +599,10 @@ class SpecializedProtocolConformance : public ProtocolConformance,
|
||||
/// generic conformance.
|
||||
mutable TypeWitnessMap TypeWitnesses;
|
||||
|
||||
/// Any conditional requirements, in substituted form. (E.g. given Foo<T>: Bar
|
||||
/// where T: Bar, Foo<Baz<U>> will include Baz<U>: Bar.)
|
||||
ArrayRef<Requirement> ConditionalRequirements;
|
||||
|
||||
friend class ASTContext;
|
||||
|
||||
SpecializedProtocolConformance(Type conformingType,
|
||||
@@ -599,6 +622,15 @@ public:
|
||||
return GenericSubstitutions;
|
||||
}
|
||||
|
||||
/// Get the substitution map representing the substitutions used to produce
|
||||
/// this specialized conformance.
|
||||
SubstitutionMap getSubstitutionMap() const;
|
||||
|
||||
/// Get any requirements that must be satisfied for this conformance to apply.
|
||||
ArrayRef<Requirement> getConditionalRequirements() const {
|
||||
return ConditionalRequirements;
|
||||
}
|
||||
|
||||
/// Get the protocol being conformed to.
|
||||
ProtocolDecl *getProtocol() const {
|
||||
return GenericConformance->getProtocol();
|
||||
@@ -697,6 +729,11 @@ public:
|
||||
return InheritedConformance->getProtocol();
|
||||
}
|
||||
|
||||
/// Get any requirements that must be satisfied for this conformance to apply.
|
||||
ArrayRef<Requirement> getConditionalRequirements() const {
|
||||
return InheritedConformance->getConditionalRequirements();
|
||||
}
|
||||
|
||||
/// Get the declaration context that contains the conforming extension or
|
||||
/// nominal type declaration.
|
||||
DeclContext *getDeclContext() const {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "llvm/ADT/Hashing.h"
|
||||
#include "llvm/ADT/PointerUnion.h"
|
||||
#include "swift/AST/Requirement.h"
|
||||
#include "swift/AST/TypeAlignments.h"
|
||||
#include "swift/AST/Type.h"
|
||||
|
||||
@@ -128,6 +129,10 @@ public:
|
||||
|
||||
/// Create a canonical conformance from the current one.
|
||||
ProtocolConformanceRef getCanonicalConformanceRef() const;
|
||||
|
||||
/// Get any additional requirements that are required for this conformance to
|
||||
/// be satisfied.
|
||||
ArrayRef<Requirement> getConditionalRequirements() const;
|
||||
};
|
||||
|
||||
} // end namespace swift
|
||||
|
||||
@@ -633,6 +633,63 @@ bool GenericSignature::areSameTypeParameterInContext(Type type1, Type type2) {
|
||||
return equivClass1 == equivClass2;
|
||||
}
|
||||
|
||||
bool GenericSignature::isRequirementSatisfied(Requirement requirement) {
|
||||
auto GSB = getGenericSignatureBuilder();
|
||||
|
||||
auto firstType = requirement.getFirstType();
|
||||
auto canFirstType = getCanonicalTypeInContext(firstType);
|
||||
|
||||
switch (requirement.getKind()) {
|
||||
case RequirementKind::Conformance: {
|
||||
auto protocolType = requirement.getSecondType()->castTo<ProtocolType>();
|
||||
auto protocol = protocolType->getDecl();
|
||||
|
||||
if (canFirstType->isTypeParameter())
|
||||
return conformsToProtocol(canFirstType, protocol);
|
||||
else
|
||||
return (bool)GSB->lookupConformance(/*dependentType=*/CanType(),
|
||||
canFirstType, protocolType);
|
||||
}
|
||||
|
||||
case RequirementKind::SameType: {
|
||||
auto canSecondType = getCanonicalTypeInContext(requirement.getSecondType());
|
||||
return canFirstType->isEqual(canSecondType);
|
||||
}
|
||||
|
||||
case RequirementKind::Superclass: {
|
||||
auto requiredSuperclass =
|
||||
getCanonicalTypeInContext(requirement.getSecondType());
|
||||
|
||||
// The requirement could be in terms of type parameters like a user-written
|
||||
// requirement, but it could also be in terms of concrete types if it has
|
||||
// been substituted/otherwise 'resolved', so we need to handle both.
|
||||
auto baseType = canFirstType;
|
||||
if (canFirstType->isTypeParameter()) {
|
||||
auto directSuperclass = getSuperclassBound(baseType);
|
||||
if (!directSuperclass)
|
||||
return false;
|
||||
|
||||
baseType = getCanonicalTypeInContext(directSuperclass);
|
||||
}
|
||||
|
||||
return requiredSuperclass->isExactSuperclassOf(baseType);
|
||||
}
|
||||
|
||||
case RequirementKind::Layout: {
|
||||
auto requiredLayout = requirement.getLayoutConstraint();
|
||||
|
||||
if (canFirstType->isTypeParameter())
|
||||
return getLayoutConstraint(canFirstType) == requiredLayout;
|
||||
else {
|
||||
// The requirement is on a concrete type, so it's either globally correct
|
||||
// or globally incorrect, independent of this generic context. The latter
|
||||
// case should be diagnosed elsewhere, so let's assume it's correct.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GenericSignature::isCanonicalTypeInContext(Type type) {
|
||||
// If the type isn't independently canonical, it's certainly not canonical
|
||||
// in this context.
|
||||
|
||||
@@ -285,6 +285,63 @@ AbstractStorageDecl *ProtocolConformance::getBehaviorDecl() const {
|
||||
return getRootNormalConformance()->getBehaviorDecl();
|
||||
}
|
||||
|
||||
ArrayRef<Requirement> ProtocolConformance::getConditionalRequirements() const {
|
||||
CONFORMANCE_SUBCLASS_DISPATCH(getConditionalRequirements, ());
|
||||
}
|
||||
|
||||
ArrayRef<Requirement>
|
||||
ProtocolConformanceRef::getConditionalRequirements() const {
|
||||
if (isConcrete())
|
||||
return getConcrete()->getConditionalRequirements();
|
||||
else
|
||||
// An abstract conformance is never conditional: any conditionality in the
|
||||
// concrete types that will eventually pass through this at runtime is
|
||||
// completely pre-checked and packaged up.
|
||||
return {};
|
||||
}
|
||||
|
||||
void NormalProtocolConformance::differenceAndStoreConditionalRequirements() {
|
||||
assert(ConditionalRequirements.size() == 0 &&
|
||||
"should not recompute conditional requirements");
|
||||
auto &ctxt = getProtocol()->getASTContext();
|
||||
auto DC = getDeclContext();
|
||||
// Only conformances in extensions can be conditional
|
||||
if (!isa<ExtensionDecl>(DC))
|
||||
return;
|
||||
|
||||
auto typeSig = DC->getAsNominalTypeOrNominalTypeExtensionContext()
|
||||
->getGenericSignature();
|
||||
auto extensionSig = DC->getGenericSignatureOfContext();
|
||||
|
||||
// If the type is generic, the extension should be too, and vice versa.
|
||||
assert((bool)typeSig == (bool)extensionSig &&
|
||||
"unexpected generic-ness mismatch on conformance");
|
||||
if (!typeSig)
|
||||
return;
|
||||
|
||||
auto canExtensionSig = extensionSig->getCanonicalSignature();
|
||||
auto canTypeSig = typeSig->getCanonicalSignature();
|
||||
if (canTypeSig == canExtensionSig)
|
||||
return;
|
||||
|
||||
// The extension signature should be a superset of the type signature, meaning
|
||||
// every thing in the type signature either is included too or is implied by
|
||||
// something else. The most important bit is having the same type
|
||||
// parameters. (NB. if/when Swift gets parameterized extensions, this needs to
|
||||
// change.)
|
||||
assert(canTypeSig.getGenericParams() == canExtensionSig.getGenericParams());
|
||||
|
||||
auto mod = DC->getParentModule();
|
||||
// Find the requirements in the extension that aren't proved by the original
|
||||
// type, these are the ones that make the conformance conditional.
|
||||
SmallVector<Requirement, 4> reqs;
|
||||
for (auto requirement : canExtensionSig->getRequirements()) {
|
||||
if (!canTypeSig->isRequirementSatisfied(requirement))
|
||||
reqs.push_back(requirement);
|
||||
}
|
||||
ConditionalRequirements = ctxt.AllocateCopy(reqs);
|
||||
}
|
||||
|
||||
void NormalProtocolConformance::setSignatureConformances(
|
||||
ArrayRef<ProtocolConformanceRef> conformances) {
|
||||
auto &ctx = getProtocol()->getASTContext();
|
||||
@@ -635,11 +692,24 @@ SpecializedProtocolConformance::SpecializedProtocolConformance(
|
||||
GenericSubstitutions(substitutions)
|
||||
{
|
||||
assert(genericConformance->getKind() != ProtocolConformanceKind::Specialized);
|
||||
|
||||
// Substitute the conditional requirements so that they're phrased in terms of
|
||||
// the specialized types, not the conformance-declaring decl's types.
|
||||
auto subMap = getSubstitutionMap();
|
||||
SmallVector<Requirement, 4> newReqs;
|
||||
for (auto oldReq : GenericConformance->getConditionalRequirements()) {
|
||||
newReqs.push_back(*oldReq.subst(subMap));
|
||||
}
|
||||
auto &ctxt = getProtocol()->getASTContext();
|
||||
ConditionalRequirements = ctxt.AllocateCopy(newReqs);
|
||||
}
|
||||
|
||||
SubstitutionMap SpecializedProtocolConformance::getSubstitutionMap() const {
|
||||
auto *genericSig = GenericConformance->getGenericSignature();
|
||||
return genericSig->getSubstitutionMap(GenericSubstitutions);
|
||||
if (genericSig)
|
||||
return genericSig->getSubstitutionMap(GenericSubstitutions);
|
||||
|
||||
return SubstitutionMap();
|
||||
}
|
||||
|
||||
bool SpecializedProtocolConformance::hasTypeWitness(
|
||||
|
||||
@@ -1593,6 +1593,9 @@ namespace {
|
||||
FuncDecl *fn = nullptr;
|
||||
|
||||
if (bridgedToObjectiveCConformance) {
|
||||
assert(bridgedToObjectiveCConformance->getConditionalRequirements()
|
||||
.empty() &&
|
||||
"cannot conditionally conform to _BridgedToObjectiveC");
|
||||
// The conformance to _BridgedToObjectiveC is statically known.
|
||||
// Retrieve the bridging operation to be used if a static conformance
|
||||
// to _BridgedToObjectiveC can be proven.
|
||||
|
||||
@@ -2982,7 +2982,13 @@ static void checkEnumRawValues(TypeChecker &TC, EnumDecl *ED) {
|
||||
// primitive literal protocols.
|
||||
auto conformsToProtocol = [&](KnownProtocolKind protoKind) {
|
||||
ProtocolDecl *proto = TC.getProtocol(ED->getLoc(), protoKind);
|
||||
return TC.conformsToProtocol(rawTy, proto, ED->getDeclContext(), None);
|
||||
auto conformance =
|
||||
TC.conformsToProtocol(rawTy, proto, ED->getDeclContext(), None);
|
||||
if (conformance)
|
||||
assert(conformance->getConditionalRequirements().empty() &&
|
||||
"conditionally conforming to literal protocol not currently "
|
||||
"supported");
|
||||
return conformance;
|
||||
};
|
||||
|
||||
static auto otherLiteralProtocolKinds = {
|
||||
|
||||
@@ -5968,6 +5968,9 @@ bool TypeChecker::useObjectiveCBridgeableConformances(DeclContext *dc,
|
||||
WasUnsatisfied |= result.hasUnsatisfiedDependency();
|
||||
if (WasUnsatisfied)
|
||||
return Action::Stop;
|
||||
if (result.getStatus() == RequirementCheckResult::Success)
|
||||
assert(result.getConformance().getConditionalRequirements().empty() &&
|
||||
"cannot conform conditionally to _ObjectiveCBridgeable");
|
||||
|
||||
// Set and Dictionary bridging also requires the conformance
|
||||
// of the key type to Hashable.
|
||||
@@ -6062,6 +6065,9 @@ void TypeChecker::useBridgedNSErrorConformances(DeclContext *dc, Type type) {
|
||||
auto conformance = conformsToProtocol(type, bridgedStoredNSError, dc,
|
||||
ConformanceCheckFlags::Used);
|
||||
if (conformance && conformance->isConcrete()) {
|
||||
assert(conformance->getConditionalRequirements().empty() &&
|
||||
"cannot conform condtionally to _BridgedStoredNSError");
|
||||
|
||||
// Hack: If we've used a conformance to the _BridgedStoredNSError
|
||||
// protocol, also use the RawRepresentable and _ErrorCodeProtocol
|
||||
// conformances on the Code associated type witness.
|
||||
@@ -6080,6 +6086,9 @@ void TypeChecker::useBridgedNSErrorConformances(DeclContext *dc, Type type) {
|
||||
(ConformanceCheckFlags::SuppressDependencyTracking|
|
||||
ConformanceCheckFlags::Used));
|
||||
if (conformance && conformance->isConcrete()) {
|
||||
assert(conformance->getConditionalRequirements().empty() &&
|
||||
"cannot conform condtionally to _ErrorCodeProtocol");
|
||||
|
||||
if (Type errorType = ProtocolConformanceRef::getTypeWitnessByName(
|
||||
type, *conformance, Context.Id_ErrorType, this)) {
|
||||
(void)conformsToProtocol(errorType, bridgedStoredNSError, dc,
|
||||
|
||||
@@ -613,7 +613,10 @@ public:
|
||||
sequence->getLoc());
|
||||
if (!conformance)
|
||||
return nullptr;
|
||||
|
||||
|
||||
assert(conformance->getConditionalRequirements().empty() &&
|
||||
"conditionally conforming to Sequence is not currently supported");
|
||||
|
||||
generatorTy = TC.getWitnessType(sequenceType, sequenceProto,
|
||||
*conformance,
|
||||
TC.Context.Id_Iterator,
|
||||
@@ -664,7 +667,10 @@ public:
|
||||
sequence->getLoc());
|
||||
if (!genConformance)
|
||||
return nullptr;
|
||||
|
||||
assert(
|
||||
genConformance->getConditionalRequirements().empty() &&
|
||||
"conditionally conforming to IteratorProtocol not currently supported");
|
||||
|
||||
Type elementTy = TC.getWitnessType(generatorTy, generatorProto,
|
||||
*genConformance, TC.Context.Id_Element,
|
||||
diag::iterator_protocol_broken);
|
||||
|
||||
Reference in New Issue
Block a user