Type-erase contravariant uses of opened existentials in subsequent parameters.

When we open an existential argument in a call to a generic function,
type-erase contravariant uses of that opened existential in subsequent
parameters. This primarily impacts closure parameters, where we want
the closure to be provided with an existential parameter type rather
than permit the parameter to have opened existential type. This
prevents the opened existential type from being directly exposed in
the type system.

Note that we do not need to perform this erasure when the argument is
a reference to a generic function, because there it is suitable to
infer that the generic arguments are the opened archetypes. This
subsumes the use case for `_openExistential`.
This commit is contained in:
Doug Gregor
2022-03-23 15:22:06 -07:00
parent 1f39f271d5
commit 50451d2583
4 changed files with 106 additions and 16 deletions

View File

@@ -1432,12 +1432,13 @@ namespace {
/// \param argTy The type of the argument.
///
/// \returns If the argument type is existential and opening it can bind a
/// generic parameter in the callee, returns the type variable (from the
/// opened parameter type) the existential type that needs to be opened
/// (from the argument type), and the adjustements that need to be applied to
/// the existential type after it is opened.
/// generic parameter in the callee, returns the generic parameter, type
/// variable (from the opened parameter type) the existential type that needs
/// to be opened (from the argument type), and the adjustements that need to be
/// applied to the existential type after it is opened.
static Optional<
std::tuple<TypeVariableType *, Type, OpenedExistentialAdjustments>>
std::tuple<GenericTypeParamType *, TypeVariableType *, Type,
OpenedExistentialAdjustments>>
shouldOpenExistentialCallArgument(
ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy,
Expr *argExpr, ConstraintSystem &cs) {
@@ -1551,7 +1552,7 @@ shouldOpenExistentialCallArgument(
referenceInfo.assocTypeRef > TypePosition::Covariant)
return None;
return std::make_tuple(paramTypeVar, argTy, adjustments);
return std::make_tuple(genericParam, paramTypeVar, argTy, adjustments);
}
// Match the argument of a call to the parameter.
@@ -1831,15 +1832,29 @@ static ConstraintSystem::TypeMatchResult matchCallArguments(
cs, cs.getConstraintLocator(loc)));
}
// Type-erase any opened existentials from subsequent parameter types
// unless the argument itself is a generic function, which could handle
// the opened existentials.
if (!openedExistentials.empty() && paramTy->hasTypeVariable() &&
!cs.isArgumentGenericFunction(argTy, argExpr)) {
for (const auto &opened : openedExistentials) {
paramTy = typeEraseOpenedExistentialReference(
paramTy, opened.second->getExistentialType(), opened.first,
/*FIXME*/cs.DC, TypePosition::Contravariant);
}
}
// If the argument is an existential type and the parameter is generic,
// consider opening the existential type.
if (auto existentialArg = shouldOpenExistentialCallArgument(
callee, paramIdx, paramTy, argTy, argExpr, cs)) {
// My kingdom for a decent "if let" in C++.
GenericTypeParamType *openedGenericParam;
TypeVariableType *openedTypeVar;
Type existentialType;
OpenedExistentialAdjustments adjustments;
std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg;
std::tie(openedGenericParam, openedTypeVar, existentialType,
adjustments) = *existentialArg;
OpenedArchetypeType *opened;
std::tie(argTy, opened) = cs.openExistentialType(
@@ -11314,7 +11329,8 @@ ConstraintSystem::simplifyApplicableFnConstraint(
if (result2->hasTypeVariable() && !openedExistentials.empty()) {
for (const auto &opened : openedExistentials) {
result2 = typeEraseOpenedExistentialReference(
result2, opened.second->getExistentialType(), opened.first, DC);
result2, opened.second->getExistentialType(), opened.first, DC,
TypePosition::Covariant);
}
}