Skip to content

Commit db3b0d9

Browse files
authored
Merge pull request #26054 from gregomni/kp_closures
[ConstraintSystem][SE-0249] Key Path Expressions as Functions
2 parents 3a2d284 + 9107626 commit db3b0d9

File tree

11 files changed

+290
-89
lines changed

11 files changed

+290
-89
lines changed

include/swift/AST/Expr.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3690,14 +3690,20 @@ class ClosureExpr : public AbstractClosureExpr {
36903690
}
36913691
};
36923692

3693-
3694-
/// This is a closure of the contained subexpression that is formed
3695-
/// when a scalar expression is converted to @autoclosure function type.
3696-
/// For example:
3693+
/// This is an implicit closure of the contained subexpression that is usually
3694+
/// formed when a scalar expression is converted to @autoclosure function type.
36973695
/// \code
36983696
/// func f(x : @autoclosure () -> Int)
36993697
/// f(42) // AutoclosureExpr convert from Int to ()->Int
37003698
/// \endcode
3699+
///
3700+
/// They are also created when key path expressions are converted to function
3701+
/// type, in which case, a pair of nested implicit closures are formed:
3702+
/// \code
3703+
/// { $kp$ in { $0[keyPath: $kp$] } }( \(E) )
3704+
/// \endcode
3705+
/// This is to ensure side effects of the key path expression (mainly indices in
3706+
/// subscripts) are only evaluated once.
37013707
class AutoClosureExpr : public AbstractClosureExpr {
37023708
BraceStmt *Body;
37033709

lib/Sema/CSApply.cpp

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4198,9 +4198,18 @@ namespace {
41984198

41994199
// Resolve each of the components.
42004200
bool didOptionalChain = false;
4201-
auto keyPathTy = cs.getType(E)->castTo<BoundGenericType>();
4202-
Type baseTy = keyPathTy->getGenericArgs()[0];
4203-
Type leafTy = keyPathTy->getGenericArgs()[1];
4201+
bool isFunctionType = false;
4202+
Type baseTy, leafTy;
4203+
Type exprType = cs.getType(E);
4204+
if (auto fnTy = exprType->getAs<FunctionType>()) {
4205+
baseTy = fnTy->getParams()[0].getPlainType();
4206+
leafTy = fnTy->getResult();
4207+
isFunctionType = true;
4208+
} else {
4209+
auto keyPathTy = exprType->castTo<BoundGenericType>();
4210+
baseTy = keyPathTy->getGenericArgs()[0];
4211+
leafTy = keyPathTy->getGenericArgs()[1];
4212+
}
42044213

42054214
// Updates the constraint system with the type of the last resolved
42064215
// component. We do it this way because we sometimes insert new
@@ -4405,7 +4414,101 @@ namespace {
44054414
// key path.
44064415
assert(!baseTy || baseTy->hasUnresolvedType()
44074416
|| baseTy->getWithoutSpecifierType()->isEqual(leafTy));
4408-
return E;
4417+
4418+
if (!isFunctionType)
4419+
return E;
4420+
4421+
// If we've gotten here, the user has used key path literal syntax to form
4422+
// a closure. The type checker has given E a function type to indicate
4423+
// this; we're going to change E's type to KeyPath<baseTy, leafTy> and
4424+
// then wrap it in a larger closure expression with the appropriate type.
4425+
4426+
// baseTy has been overwritten by the loop above; restore it.
4427+
baseTy = exprType->getAs<FunctionType>()->getParams()[0].getPlainType();
4428+
4429+
// Compute KeyPath<baseTy, leafTy> and set E's type back to it.
4430+
auto kpDecl = cs.getASTContext().getKeyPathDecl();
4431+
auto keyPathTy =
4432+
BoundGenericType::get(kpDecl, nullptr, { baseTy, leafTy });
4433+
E->setType(keyPathTy);
4434+
cs.cacheType(E);
4435+
4436+
// To ensure side effects of the key path expression (mainly indices in
4437+
// subscripts) are only evaluated once, we construct an outer closure,
4438+
// which is immediately evaluated, and an inner closure, which it returns.
4439+
// The result looks like this:
4440+
//
4441+
// return "{ $kp$ in { $0[keyPath: $kp$] } }( \(E) )"
4442+
4443+
auto &ctx = cs.getASTContext();
4444+
auto discriminator = AutoClosureExpr::InvalidDiscriminator;
4445+
4446+
// The inner closure.
4447+
//
4448+
// let closure = "{ $0[keyPath: $kp$] }"
4449+
auto closureTy =
4450+
FunctionType::get({ FunctionType::Param(baseTy) }, leafTy);
4451+
auto closure = new (ctx)
4452+
AutoClosureExpr(E, leafTy, discriminator, cs.DC);
4453+
auto param = new (ctx) ParamDecl(
4454+
ParamDecl::Specifier::Default, SourceLoc(),
4455+
/*argument label*/ SourceLoc(), Identifier(),
4456+
/*parameter name*/ SourceLoc(), ctx.getIdentifier("$0"), closure);
4457+
param->setType(baseTy);
4458+
param->setInterfaceType(baseTy->mapTypeOutOfContext());
4459+
4460+
// The outer closure.
4461+
//
4462+
// let outerClosure = "{ $kp$ in \(closure) }"
4463+
auto outerClosureTy =
4464+
FunctionType::get({ FunctionType::Param(keyPathTy) }, closureTy);
4465+
auto outerClosure = new (ctx)
4466+
AutoClosureExpr(closure, closureTy, discriminator, cs.DC);
4467+
auto outerParam =
4468+
new (ctx) ParamDecl(ParamDecl::Specifier::Default, SourceLoc(),
4469+
/*argument label*/ SourceLoc(), Identifier(),
4470+
/*parameter name*/ SourceLoc(),
4471+
ctx.getIdentifier("$kp$"), outerClosure);
4472+
outerParam->setType(keyPathTy);
4473+
outerParam->setInterfaceType(keyPathTy->mapTypeOutOfContext());
4474+
4475+
// let paramRef = "$0"
4476+
auto *paramRef = new (ctx)
4477+
DeclRefExpr(param, DeclNameLoc(E->getLoc()), /*Implicit=*/true);
4478+
paramRef->setType(baseTy);
4479+
cs.cacheType(paramRef);
4480+
4481+
// let outerParamRef = "$kp$"
4482+
auto outerParamRef = new (ctx)
4483+
DeclRefExpr(outerParam, DeclNameLoc(E->getLoc()), /*Implicit=*/true);
4484+
outerParamRef->setType(keyPathTy);
4485+
cs.cacheType(outerParamRef);
4486+
4487+
// let application = "\(paramRef)[keyPath: \(outerParamRef)]"
4488+
auto *application = new (ctx)
4489+
KeyPathApplicationExpr(paramRef,
4490+
E->getStartLoc(), outerParamRef, E->getEndLoc(),
4491+
leafTy, /*implicit=*/true);
4492+
cs.cacheType(application);
4493+
4494+
// Finish up the inner closure.
4495+
closure->setParameterList(ParameterList::create(ctx, {param}));
4496+
closure->setBody(application);
4497+
closure->setType(closureTy);
4498+
cs.cacheType(closure);
4499+
4500+
// Finish up the outer closure.
4501+
outerClosure->setParameterList(ParameterList::create(ctx, {outerParam}));
4502+
outerClosure->setBody(closure);
4503+
outerClosure->setType(outerClosureTy);
4504+
cs.cacheType(outerClosure);
4505+
4506+
// let outerApply = "\( outerClosure )( \(E) )"
4507+
auto outerApply = CallExpr::createImplicit(ctx, outerClosure, {E}, {});
4508+
outerApply->setType(closureTy);
4509+
cs.cacheExprTypes(outerApply);
4510+
4511+
return coerceToType(outerApply, exprType, cs.getConstraintLocator(E));
44094512
}
44104513

44114514
KeyPathExpr::Component

lib/Sema/CSGen.cpp

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ namespace {
10601060

10611061
// Add the member constraint for a subscript declaration.
10621062
// FIXME: weak name!
1063-
auto memberTy = CS.createTypeVariable(resultLocator, TVO_CanBindToNoEscape);
1063+
auto memberTy = CS.createTypeVariable(memberLocator, TVO_CanBindToNoEscape);
10641064

10651065
// FIXME: synthesizeMaterializeForSet() wants to statically dispatch to
10661066
// a known subscript here. This might be cleaner if we split off a new
@@ -3013,7 +3013,7 @@ namespace {
30133013

30143014
case KeyPathExpr::Component::Kind::OptionalChain: {
30153015
didOptionalChain = true;
3016-
3016+
30173017
// We can't assign an optional back through an optional chain
30183018
// today. Force the base to an rvalue.
30193019
auto rvalueTy = CS.createTypeVariable(resultLocator,
@@ -3027,10 +3027,9 @@ namespace {
30273027
auto optionalObjTy = CS.createTypeVariable(resultLocator,
30283028
TVO_CanBindToLValue |
30293029
TVO_CanBindToNoEscape);
3030-
3030+
30313031
CS.addConstraint(ConstraintKind::OptionalObject, base, optionalObjTy,
30323032
resultLocator);
3033-
30343033
base = optionalObjTy;
30353034
break;
30363035
}
@@ -3065,20 +3064,14 @@ namespace {
30653064
auto rvalueBase = CS.createTypeVariable(baseLocator,
30663065
TVO_CanBindToNoEscape);
30673066
CS.addConstraint(ConstraintKind::Equal, base, rvalueBase, locator);
3068-
3067+
30693068
// The result is a KeyPath from the root to the end component.
3070-
Type kpTy;
3071-
if (didOptionalChain) {
3072-
// Optional-chaining key paths are always read-only.
3073-
kpTy = BoundGenericType::get(kpDecl, Type(), {root, rvalueBase});
3074-
} else {
3075-
// The type of key path depends on the overloads chosen for the key
3076-
// path components.
3077-
auto typeLoc =
3078-
CS.getConstraintLocator(locator, ConstraintLocator::KeyPathType);
3079-
kpTy = CS.createTypeVariable(typeLoc, TVO_CanBindToNoEscape);
3080-
CS.addKeyPathConstraint(kpTy, root, rvalueBase, locator);
3081-
}
3069+
// The type of key path depends on the overloads chosen for the key
3070+
// path components.
3071+
auto typeLoc =
3072+
CS.getConstraintLocator(locator, ConstraintLocator::KeyPathType);
3073+
Type kpTy = CS.createTypeVariable(typeLoc, TVO_CanBindToNoEscape);
3074+
CS.addKeyPathConstraint(kpTy, root, rvalueBase, locator);
30823075
return kpTy;
30833076
}
30843077

lib/Sema/CSRanking.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11041104
++score1;
11051105
continue;
11061106
}
1107-
1107+
11081108
// FIXME:
11091109
// This terrible hack is in place to support equality comparisons of non-
11101110
// equatable option types to 'nil'. Until we have a way to constrain a type

0 commit comments

Comments
 (0)