Sema: Tighten up existential vs generic type parameter distinction

Rename existentialConformsToSelf() to existentialTypeSupported(). This
predicate is the "protocol has no Self or associated type requirements"
check, which is a looser condition than self-conformance. This was being
tested to see if the user could refer to the protocol via an existential
type.

The new existentialConformsToSelf() now checks for protocol being @objc,
and for the absence of static methods. This is used as part of the
argument type matching logic in matchType() to determine if the
existential can be bound to a generic type parameter.

The latter condition is stricter, for two reasons:

1) We allow binding existentials to multiple type parameters all sharing
   the same generic type parameter T, so we don't want the user to be
   able to see any static methods on T.
2) There is an IRGen limitation whereby only existentials without witness
   tables can be passed in this manner.

Using the above, the representsNonTrivialGenericParameter() function
has been renamed to canBindGenericParamToExistential(). It now allows
an existential type to be bound to a generic type parameter only under
the following circumstances:

A) If the generic type parameter has no conformances, the match is allowed.

B) If the generic type parameter has at least one conformance, then all
   of the conformances on the generic type parameter must be
   existentialConformsToSelf() (condition 1 above), and all conformances
   on the existential must be @objc (condition 2 above).

Fixes <rdar://problem/18378390> and <rdar://problem/18683843>, and lays
the groundwork for fixing a few other related issues.

Swift SVN r29337
This commit is contained in:
Slava Pestov
2015-06-07 10:16:21 +00:00
parent c1e3876190
commit 322f58d8b1
16 changed files with 328 additions and 119 deletions

View File

@@ -1139,7 +1139,7 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
type2->getAnyExistentialTypeProtocols(protocols);
for (auto proto : protocols) {
switch (simplifyConformsToConstraint(type1, proto, locator, flags, false)) {
switch (simplifyConformsToConstraint(type1, proto, locator, flags, true)) {
case SolutionKind::Solved:
break;
@@ -1222,37 +1222,58 @@ static bool allowsBridgingFromObjC(TypeChecker &tc, DeclContext *dc,
return true;
}
/// Determine whether this type variable represents a "non-trivial"
/// generic parameter, meaning that the generic parameter has a
/// non-Objective-C protocol requirement.
/// Determine whether this type variable represents a generic parameter
/// that may be bound to an existential conforming to the given protocols.
///
/// FIXME: This matches a limitation of our generics system, where we
/// cannot pass the witness tables from
///
/// \returns the archetype for the generic parameter.
static Type representsNonTrivialGenericParameter(TypeVariableType *typeVar) {
/// This is only possible if the metatype is "trivial" in the sense that
/// there is nothing interesting the code can do with it, like calling
/// static methods. It also dovetails with an IRGen limitation, where
/// witness tables cannot be passed from an existential to an archetype
/// parameter, so for now we also restrict this to @objc protocols.
static bool canBindGenericParamToExistential(ConstraintSystem &cs,
TypeVariableType *typeVar,
const SmallVector<ProtocolDecl *, 2> &protocols) {
auto locator = typeVar->getImpl().getLocator();
if (!locator || locator->getPath().empty() ||
locator->getPath().back().getKind() != ConstraintLocator::Archetype)
return nullptr;
return true;
auto archetype = locator->getPath().back().getArchetype();
// If the archetype has no protocol requirements, there is nothing to
// check. In particular, we don't care if the existential has any
// problematic conformances.
if (archetype->getConformsTo().empty())
return true;
// Check archetype conformances.
for (auto proto : archetype->getConformsTo()) {
// AnyObject is always trivially representable as a generic parameter;
// there is no runtime witness.
if (proto->isSpecificProtocol(KnownProtocolKind::AnyObject))
continue;
// ObjC protocols are trivially representable as a generic parameter;
// they are witnessed by the ObjC runtime.
if (proto->isObjC())
continue;
// Other protocols would need Swift runtime support to self-conform.
return archetype;
if (!proto->existentialConformsToSelf()) {
if (cs.shouldRecordFailures()) {
cs.recordFailure(cs.getConstraintLocator(locator),
Failure::IsNotSelfConforming,
archetype, proto->getDeclaredType(),
// value is for diag::protocol_not_self_conforming
proto->isObjC() ? 1 : 0);
}
return false;
}
}
return nullptr;
// We are going to shoehorn this existential into an archetype. It better
// not have any witness tables.
for (auto proto : protocols) {
if (!proto->isObjC()) {
if (cs.shouldRecordFailures()) {
cs.recordFailure(cs.getConstraintLocator(locator),
Failure::ExistentialIsNotObjC,
archetype, proto->getDeclaredType());
}
return false;
}
}
return true;
}
/// Check whether the given value type is one of a few specific
@@ -1360,14 +1381,10 @@ ConstraintSystem::matchTypes(Type type1, Type type2, TypeMatchKind kind,
// type if the existential type is compatible with
// Objective-C.
Type archetype;
if (type2->isExistentialType() &&
(archetype = representsNonTrivialGenericParameter(typeVar1))) {
if (shouldRecordFailures()) {
recordFailure(getConstraintLocator(locator),
Failure::ExistentialGenericParameter,
archetype, type2);
}
SmallVector<ProtocolDecl *, 2> protocols;
if (type2->isExistentialType(protocols) &&
!canBindGenericParamToExistential(*this, typeVar1, protocols)) {
return SolutionKind::Error;
}
@@ -1834,8 +1851,8 @@ ConstraintSystem::matchTypes(Type type1, Type type2, TypeMatchKind kind,
// Bridging from an ErrorType to an Objective-C NSError.
auto errorType = TC.Context.getProtocol(KnownProtocolKind::ErrorType);
if (TC.conformsToProtocol(type1, errorType, DC,
ConformanceCheckFlags::InExpression))
if (TC.containsProtocol(type1, errorType, DC,
ConformanceCheckFlags::InExpression))
if (auto NSErrorTy = TC.getNSErrorType(DC))
if (type2->isEqual(NSErrorTy))
conversionsOrFixes.push_back(
@@ -2305,17 +2322,13 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
if (typeVar)
return SolutionKind::Unsolved;
// If existential types don't need to conform (i.e., they only need to
// contain the protocol), check that separately.
if (allowNonConformingExistential && type->isExistentialType()) {
SmallVector<ProtocolDecl *, 4> protocols;
type->getAnyExistentialTypeProtocols(protocols);
for (auto ap : protocols) {
// If this isn't the protocol we're looking for, continue looking.
if (ap == protocol || ap->inheritsFrom(protocol))
return SolutionKind::Solved;
}
// For purposes of argument type matching, existential types don't need to
// conform -- they only need to contain the protocol, so check that
// separately.
if (allowNonConformingExistential) {
if (TC.containsProtocol(type, protocol, DC,
ConformanceCheckFlags::InExpression))
return SolutionKind::Solved;
} else {
// Check whether this type conforms to the protocol.
if (TC.conformsToProtocol(type, protocol, DC,
@@ -2334,9 +2347,11 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
}
// There's nothing more we can do; fail.
recordFailure(getConstraintLocator(locator),
Failure::DoesNotConformToProtocol, type,
protocol->getDeclaredType());
if (shouldRecordFailures()) {
recordFailure(getConstraintLocator(locator),
Failure::DoesNotConformToProtocol, type,
protocol->getDeclaredType());
}
return SolutionKind::Error;
}