Skip to content

Commit e30c8a5

Browse files
committed
Allow opening existentials for inout parameters.
1 parent 17ce779 commit e30c8a5

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

lib/Sema/CSApply.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,16 @@ namespace {
10851085
// Dig out the base type.
10861086
Type baseTy = cs.getType(base);
10871087

1088-
// Look through lvalues.
1088+
// Look through inout.
10891089
bool isLValue = false;
1090+
InOutExpr *origInOutBase = dyn_cast<InOutExpr>(base);
1091+
if (origInOutBase) {
1092+
base = origInOutBase->getSubExpr();
1093+
baseTy = baseTy->getInOutObjectType();
1094+
isLValue = true;
1095+
}
1096+
1097+
// Look through lvalues.
10901098
if (auto lvalueTy = baseTy->getAs<LValueType>()) {
10911099
isLValue = true;
10921100
baseTy = lvalueTy->getObjectType();
@@ -1104,7 +1112,7 @@ namespace {
11041112
// If the base was an lvalue but it will only be treated as an
11051113
// rvalue, turn the base into an rvalue now. This results in
11061114
// better SILGen.
1107-
if (isLValue &&
1115+
if (isLValue && !origInOutBase &&
11081116
(isNonMutatingMember(member) ||
11091117
member->getDeclContext()->getDeclaredInterfaceType()
11101118
->hasReferenceSemantics())) {
@@ -1138,7 +1146,15 @@ namespace {
11381146
// Record the opened existential.
11391147
OpenedExistentials.push_back({archetype, base, archetypeVal, depth});
11401148

1141-
return archetypeVal;
1149+
// Re-apply inout if needed.
1150+
Expr *resultExpr = archetypeVal;
1151+
if (origInOutBase) {
1152+
resultExpr = new (ctx) InOutExpr(
1153+
origInOutBase->getLoc(), resultExpr, opaqueType->getRValueType());
1154+
cs.cacheType(resultExpr);
1155+
}
1156+
1157+
return resultExpr;
11421158
}
11431159

11441160
/// Try to close the innermost active existential, if there is one.
@@ -6045,7 +6061,8 @@ ArgumentList *ExprRewriter::coerceCallArguments(
60456061

60466062
// If the argument is an existential type that has been opened, perform
60476063
// the open operation.
6048-
if (argType->isAnyExistentialType() && paramType->hasOpenedExistential()) {
6064+
if (argType->getInOutObjectType()->isAnyExistentialType() &&
6065+
paramType->hasOpenedExistential()) {
60496066
// FIXME: Look for an opened existential and use it. We need to
60506067
// know how far out we need to go to close the existentials. Huh.
60516068
auto knownOpened = solution.OpenedExistentialTypes.find(

lib/Sema/CSSimplify.cpp

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,18 @@ class OpenTypeSequenceElements {
13181318
};
13191319
}
13201320

1321+
namespace {
1322+
/// Flags that should be applied to the existential argument type after
1323+
/// opening.
1324+
enum class OpenedExistentialAdjustmentFlags {
1325+
/// The argument should be made inout after opening.
1326+
InOut = 0x01,
1327+
};
1328+
1329+
using OpenedExistentialAdjustments =
1330+
OptionSet<OpenedExistentialAdjustmentFlags>;
1331+
}
1332+
13211333
/// Determine whether we should open up the existential argument to the
13221334
/// given parameters.
13231335
///
@@ -1330,9 +1342,11 @@ class OpenTypeSequenceElements {
13301342
///
13311343
/// \returns If the argument type is existential and opening it can bind a
13321344
/// generic parameter in the callee, returns the type variable (from the
1333-
/// opened parameter type) and the existential type that needs to be opened
1334-
/// (from the argument type).
1335-
static Optional<std::pair<TypeVariableType *, Type>>
1345+
/// opened parameter type) the existential type that needs to be opened
1346+
/// (from the argument type), and the adjustements that need to be applied to
1347+
/// the existential type after it is opened.
1348+
static Optional<
1349+
std::tuple<TypeVariableType *, Type, OpenedExistentialAdjustments>>
13361350
shouldOpenExistentialCallArgument(
13371351
ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy) {
13381352
if (!callee)
@@ -1368,6 +1382,14 @@ shouldOpenExistentialCallArgument(
13681382
if (!paramTy->hasTypeVariable())
13691383
return None;
13701384

1385+
OpenedExistentialAdjustments adjustments;
1386+
1387+
// If the argument is inout, strip it off and we can add it back.
1388+
if (auto inOutArg = argTy->getAs<InOutType>()) {
1389+
argTy = inOutArg->getObjectType();
1390+
adjustments |= OpenedExistentialAdjustmentFlags::InOut;
1391+
}
1392+
13711393
// The argument type needs to be an existential type or metatype thereof.
13721394
if (!argTy->isAnyExistentialType())
13731395
return None;
@@ -1380,14 +1402,19 @@ shouldOpenExistentialCallArgument(
13801402
if (param->isVariadic() && !param->getVarargBaseTy()->hasTypeSequence())
13811403
return None;
13821404

1405+
// Look through an inout type on the formal type of the parameter.
1406+
auto formalParamTy = param->getInterfaceType()->getInOutObjectType();
1407+
13831408
// If the argument is of an existential metatype, look through the
13841409
// metatype on the parameter.
1385-
auto formalParamTy = param->getInterfaceType();
13861410
if (argTy->is<AnyMetatypeType>()) {
13871411
formalParamTy = formalParamTy->getMetatypeInstanceType();
13881412
paramTy = paramTy->getMetatypeInstanceType();
13891413
}
13901414

1415+
// Look through an inout type on the parameter.
1416+
paramTy = paramTy->getInOutObjectType();
1417+
13911418
// The parameter type must be a type variable.
13921419
auto paramTypeVar = paramTy->getAs<TypeVariableType>();
13931420
if (!paramTypeVar)
@@ -1418,7 +1445,7 @@ shouldOpenExistentialCallArgument(
14181445
referenceInfo.assocTypeRef > TypePosition::Covariant)
14191446
return None;
14201447

1421-
return std::make_pair(paramTypeVar, argTy);
1448+
return std::make_tuple(paramTypeVar, argTy, adjustments);
14221449
}
14231450

14241451
// Match the argument of a call to the parameter.
@@ -1657,12 +1684,20 @@ static ConstraintSystem::TypeMatchResult matchCallArguments(
16571684
// consider opening the existential type.
16581685
if (auto existentialArg = shouldOpenExistentialCallArgument(
16591686
callee, paramIdx, paramTy, argTy)) {
1660-
assert(existentialArg->second->isEqual(argTy));
1687+
// My kingdom for a decent "if let" in C++.
1688+
TypeVariableType *openedTypeVar;
1689+
Type existentialType;
1690+
OpenedExistentialAdjustments adjustments;
1691+
std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg;
1692+
16611693
OpenedArchetypeType *opened;
16621694
std::tie(argTy, opened) = cs.openExistentialType(
1663-
argTy, cs.getConstraintLocator(loc));
1695+
existentialType, cs.getConstraintLocator(loc));
1696+
1697+
if (adjustments.contains(OpenedExistentialAdjustmentFlags::InOut))
1698+
argTy = InOutType::get(argTy);
16641699

1665-
openedExistentials.push_back({existentialArg->first, opened});
1700+
openedExistentials.push_back({openedTypeVar, opened});
16661701
}
16671702

16681703
auto argLabel = argument.getLabel();

test/Constraints/opened_existentials.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ func callVariadic(p1: any P, p2: any P) {
152152
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}
153153
}
154154

155+
func takesInOut<T: P>(_ value: inout T) { }
156+
157+
func passesInOut(i: Int) {
158+
var p: any P = i
159+
takesInOut(&p)
160+
}
161+
155162
@available(SwiftStdlib 5.1, *)
156163
func testReturningOpaqueTypes(p: any P) {
157164
let q = p.getQ()

0 commit comments

Comments
 (0)