Permalink
Browse files

[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.
  • Loading branch information...
Huon Wilson
Huon Wilson committed Sep 14, 2017
1 parent fd07426 commit 945f723d598bc94610065ba4a44ccd6efe64fe68
@@ -296,6 +296,12 @@ class alignas(1 << TypeAlignInBits) GenericSignature final
/// 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 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance {
/// 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 @@ class NormalProtocolConformance : public ProtocolConformance,
}
}
/// 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 @@ class SpecializedProtocolConformance : public ProtocolConformance,
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 @@ class InheritedProtocolConformance : public ProtocolConformance,
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 @@ class ProtocolConformanceRef {
/// 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 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
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 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
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);

0 comments on commit 945f723

Please sign in to comment.