Skip to content

Commit 8022266

Browse files
authored
Merge pull request #9933 from jckarter/partial-key-path-application
Support application of AnyKeyPath/PartialKeyPath.
2 parents 3b628d3 + cdc7a5c commit 8022266

File tree

9 files changed

+201
-46
lines changed

9 files changed

+201
-46
lines changed

include/swift/AST/KnownDecls.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ FUNC_DECL(GetErrorEmbeddedNSError, "_stdlib_getErrorEmbeddedNSError")
7070

7171
FUNC_DECL(UnsafeBitCast, "unsafeBitCast")
7272

73+
FUNC_DECL(ProjectKeyPathAny, "_projectKeyPathAny")
74+
FUNC_DECL(ProjectKeyPathPartial, "_projectKeyPathPartial")
7375
FUNC_DECL(ProjectKeyPathReadOnly, "_projectKeyPathReadOnly")
7476
FUNC_DECL(ProjectKeyPathWritable, "_projectKeyPathWritable")
7577
FUNC_DECL(ProjectKeyPathReferenceWritable, "_projectKeyPathReferenceWritable")

include/swift/AST/KnownStdlibTypes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ KNOWN_STDLIB_TYPE_DECL(Dictionary, NominalTypeDecl, 2)
5151
KNOWN_STDLIB_TYPE_DECL(AnyHashable, NominalTypeDecl, 0)
5252
KNOWN_STDLIB_TYPE_DECL(MutableCollection, ProtocolDecl, 1)
5353

54+
KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0)
5455
KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1)
5556
KNOWN_STDLIB_TYPE_DECL(KeyPath, NominalTypeDecl, 2)
5657
KNOWN_STDLIB_TYPE_DECL(WritableKeyPath, NominalTypeDecl, 2)

lib/SILGen/SILGenExpr.cpp

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,34 +2789,49 @@ visitKeyPathApplicationExpr(KeyPathApplicationExpr *E, SGFContext C) {
27892789
auto root = SGF.emitMaterializedRValueAsOrig(E->getBase(),
27902790
AbstractionPattern::getOpaque());
27912791
auto keyPath = SGF.emitRValueAsSingleValue(E->getKeyPath());
2792-
2793-
// Get the root and leaf type from the key path type.
2794-
auto keyPathTy = E->getKeyPath()->getType()->castTo<BoundGenericType>();
27952792

2796-
// Upcast the keypath to KeyPath<T, U> if it isn't already.
2797-
if (keyPathTy->getDecl() != SGF.getASTContext().getKeyPathDecl()) {
2798-
auto castToTy = BoundGenericType::get(SGF.getASTContext().getKeyPathDecl(),
2793+
auto keyPathDecl = E->getKeyPath()->getType()->getAnyNominal();
2794+
FuncDecl *projectFn;
2795+
SmallVector<Substitution, 4> subs;
2796+
2797+
if (keyPathDecl == SGF.getASTContext().getAnyKeyPathDecl()) {
2798+
// Invoke projectKeyPathAny with the type of the base value.
2799+
// The result is always `Any?`.
2800+
projectFn = SGF.getASTContext().getProjectKeyPathAny(nullptr);
2801+
subs.push_back(Substitution(E->getBase()->getType(), {}));
2802+
} else {
2803+
auto keyPathTy = E->getKeyPath()->getType()->castTo<BoundGenericType>();
2804+
if (keyPathDecl == SGF.getASTContext().getPartialKeyPathDecl()) {
2805+
// Invoke projectKeyPathPartial with the type of the base value.
2806+
// The result is always `Any`.
2807+
projectFn = SGF.getASTContext().getProjectKeyPathPartial(nullptr);
2808+
subs.push_back(Substitution(keyPathTy->getGenericArgs()[0], {}));
2809+
} else {
2810+
projectFn = SGF.getASTContext().getProjectKeyPathReadOnly(nullptr);
2811+
// Get the root and leaf type from the key path type.
2812+
subs.push_back(Substitution(keyPathTy->getGenericArgs()[0], {}));
2813+
subs.push_back(Substitution(keyPathTy->getGenericArgs()[1], {}));
2814+
2815+
// Upcast the keypath to KeyPath<T, U> if it isn't already.
2816+
if (keyPathTy->getDecl() != SGF.getASTContext().getKeyPathDecl()) {
2817+
auto castToTy = BoundGenericType::get(
2818+
SGF.getASTContext().getKeyPathDecl(),
27992819
nullptr,
28002820
keyPathTy->getGenericArgs())
2801-
->getCanonicalType();
2802-
auto upcast = SGF.B.createUpcast(SILLocation(E),
2821+
->getCanonicalType();
2822+
auto upcast = SGF.B.createUpcast(SILLocation(E),
28032823
keyPath.forward(SGF),
28042824
SILType::getPrimitiveObjectType(castToTy));
2805-
keyPath = SGF.emitManagedRValueWithCleanup(upcast);
2825+
keyPath = SGF.emitManagedRValueWithCleanup(upcast);
2826+
}
2827+
}
28062828
}
28072829

2808-
auto projectFn = SGF.getASTContext().getProjectKeyPathReadOnly(nullptr);
2809-
Substitution genericArgs[] = {
2810-
Substitution(keyPathTy->getGenericArgs()[0], {}),
2811-
Substitution(keyPathTy->getGenericArgs()[1], {}),
2812-
};
2813-
28142830
auto genericArgsMap =
2815-
projectFn->getGenericSignature()->getSubstitutionMap(genericArgs);
2831+
projectFn->getGenericSignature()->getSubstitutionMap(subs);
28162832

28172833
return SGF.emitApplyOfLibraryIntrinsic(SILLocation(E),
2818-
SGF.getASTContext().getProjectKeyPathReadOnly(nullptr),
2819-
genericArgsMap, {root, keyPath}, C);
2834+
projectFn, genericArgsMap, {root, keyPath}, C);
28202835
}
28212836

28222837
RValue RValueEmitter::

lib/Sema/CSApply.cpp

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,24 +1303,53 @@ namespace {
13031303
// Apply a key path if we have one.
13041304
if (choice.getKind() == OverloadChoiceKind::KeyPathApplication) {
13051305
// The index argument should be (keyPath: KeyPath<Root, Value>).
1306-
auto keyPathTy = index->getType()->castTo<TupleType>()
1307-
->getElementType(0)->castTo<BoundGenericType>();
1308-
auto valueTy = keyPathTy->getGenericArgs()[1];
1306+
auto keyPathTTy = index->getType()->castTo<TupleType>()
1307+
->getElementType(0);
13091308

1310-
// The result may be an lvalue based on the base and key path kind.
1309+
Type valueTy;
13111310
bool resultIsLValue;
1312-
if (keyPathTy->getDecl() == cs.getASTContext().getKeyPathDecl()) {
1313-
resultIsLValue = false;
1314-
base = cs.coerceToRValue(base);
1315-
} else if (keyPathTy->getDecl() ==
1316-
cs.getASTContext().getWritableKeyPathDecl()) {
1317-
resultIsLValue = base->getType()->isLValueType();
1318-
} else if (keyPathTy->getDecl() ==
1319-
cs.getASTContext().getReferenceWritableKeyPathDecl()) {
1320-
resultIsLValue = true;
1321-
base = cs.coerceToRValue(base);
1311+
1312+
if (auto nom = keyPathTTy->getAs<NominalType>()) {
1313+
// AnyKeyPath is <T> rvalue T -> rvalue Any?
1314+
if (nom->getDecl() == cs.getASTContext().getAnyKeyPathDecl()) {
1315+
valueTy = ProtocolCompositionType::get(cs.getASTContext(), {},
1316+
/*explicit anyobject*/ false);
1317+
valueTy = OptionalType::get(valueTy);
1318+
resultIsLValue = false;
1319+
base = cs.coerceToRValue(base);
1320+
} else {
1321+
llvm_unreachable("unknown key path class!");
1322+
}
13221323
} else {
1323-
llvm_unreachable("unknown key path class!");
1324+
auto keyPathBGT = keyPathTTy->castTo<BoundGenericType>();
1325+
1326+
if (keyPathBGT->getDecl()
1327+
== cs.getASTContext().getPartialKeyPathDecl()) {
1328+
// PartialKeyPath<T> is rvalue T -> rvalue Any
1329+
valueTy = ProtocolCompositionType::get(cs.getASTContext(), {},
1330+
/*explicit anyobject*/ false);
1331+
resultIsLValue = false;
1332+
base = cs.coerceToRValue(base);
1333+
} else {
1334+
// *KeyPath<T, U> is T -> U, with rvalueness based on mutability
1335+
// of base and keypath
1336+
valueTy = keyPathBGT->getGenericArgs()[1];
1337+
1338+
// The result may be an lvalue based on the base and key path kind.
1339+
if (keyPathBGT->getDecl() == cs.getASTContext().getKeyPathDecl()) {
1340+
resultIsLValue = false;
1341+
base = cs.coerceToRValue(base);
1342+
} else if (keyPathBGT->getDecl() ==
1343+
cs.getASTContext().getWritableKeyPathDecl()) {
1344+
resultIsLValue = base->getType()->isLValueType();
1345+
} else if (keyPathBGT->getDecl() ==
1346+
cs.getASTContext().getReferenceWritableKeyPathDecl()) {
1347+
resultIsLValue = true;
1348+
base = cs.coerceToRValue(base);
1349+
} else {
1350+
llvm_unreachable("unknown key path class!");
1351+
}
1352+
}
13241353
}
13251354
if (resultIsLValue)
13261355
valueTy = LValueType::get(valueTy);

lib/Sema/CSSimplify.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3933,13 +3933,21 @@ ConstraintSystem::simplifyKeyPathApplicationConstraint(
39333933
return SolutionKind::Unsolved;
39343934
};
39353935

3936+
if (auto clas = keyPathTy->getAs<NominalType>()) {
3937+
if (clas->getDecl() == getASTContext().getAnyKeyPathDecl()) {
3938+
// Read-only keypath, whose projected value is upcast to `Any?`.
3939+
// The root type can be anything.
3940+
Type resultTy = ProtocolCompositionType::get(getASTContext(), {},
3941+
/*explicit AnyObject*/ false);
3942+
resultTy = OptionalType::get(resultTy);
3943+
return matchTypes(resultTy, valueTy, ConstraintKind::Bind,
3944+
subflags, locator);
3945+
}
3946+
}
3947+
39363948
if (auto bgt = keyPathTy->getAs<BoundGenericType>()) {
3937-
if (bgt->getGenericArgs().size() < 2)
3938-
return SolutionKind::Error;
3939-
39403949
// We have the key path type. Match it to the other ends of the constraint.
39413950
auto kpRootTy = bgt->getGenericArgs()[0];
3942-
auto kpValueTy = bgt->getGenericArgs()[1];
39433951

39443952
// Try to match the root type.
39453953
rootTy = getFixedTypeRecursive(rootTy, flags, /*wantRValue=*/false);
@@ -3954,7 +3962,19 @@ ConstraintSystem::simplifyKeyPathApplicationConstraint(
39543962
case SolutionKind::Unsolved:
39553963
llvm_unreachable("should have generated constraints");
39563964
}
3957-
3965+
3966+
if (bgt->getDecl() == getASTContext().getPartialKeyPathDecl()) {
3967+
// Read-only keypath, whose projected value is upcast to `Any`.
3968+
auto resultTy = ProtocolCompositionType::get(getASTContext(), {},
3969+
/*explicit AnyObject*/ false);
3970+
return matchTypes(resultTy, valueTy,
3971+
ConstraintKind::Bind, subflags, locator);
3972+
}
3973+
3974+
if (bgt->getGenericArgs().size() < 2)
3975+
return SolutionKind::Error;
3976+
auto kpValueTy = bgt->getGenericArgs()[1];
3977+
39583978
/// Solve for an rvalue base.
39593979
auto solveRValue = [&]() -> ConstraintSystem::SolutionKind {
39603980
return matchTypes(kpValueTy, valueTy,
@@ -3973,6 +3993,7 @@ ConstraintSystem::simplifyKeyPathApplicationConstraint(
39733993
return matchTypes(LValueType::get(kpValueTy), valueTy,
39743994
ConstraintKind::Bind, subflags, locator);
39753995
};
3996+
39763997

39773998
if (bgt->getDecl() == getASTContext().getKeyPathDecl()) {
39783999
// Read-only keypath.

stdlib/public/core/KeyPath.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,39 @@ internal struct KeyPathBuffer {
11321132

11331133
// MARK: Library intrinsics for projecting key paths.
11341134

1135+
@_inlineable
1136+
public // COMPILER_INTRINSIC
1137+
func _projectKeyPathPartial<Root>(
1138+
root: Root,
1139+
keyPath: PartialKeyPath<Root>
1140+
) -> Any {
1141+
func open<Value>(_: Value.Type) -> Any {
1142+
return _projectKeyPathReadOnly(root: root,
1143+
keyPath: unsafeDowncast(keyPath, to: KeyPath<Root, Value>.self))
1144+
}
1145+
return _openExistential(type(of: keyPath).valueType, do: open)
1146+
}
1147+
1148+
@_inlineable
1149+
public // COMPILER_INTRINSIC
1150+
func _projectKeyPathAny<RootValue>(
1151+
root: RootValue,
1152+
keyPath: AnyKeyPath
1153+
) -> Any? {
1154+
let (keyPathRoot, keyPathValue) = type(of: keyPath)._rootAndValueType
1155+
func openRoot<KeyPathRoot>(_: KeyPathRoot.Type) -> Any? {
1156+
guard let rootForKeyPath = root as? KeyPathRoot else {
1157+
return nil
1158+
}
1159+
func openValue<Value>(_: Value.Type) -> Any {
1160+
return _projectKeyPathReadOnly(root: rootForKeyPath,
1161+
keyPath: unsafeDowncast(keyPath, to: KeyPath<KeyPathRoot, Value>.self))
1162+
}
1163+
return _openExistential(keyPathValue, do: openValue)
1164+
}
1165+
return _openExistential(keyPathRoot, do: openRoot)
1166+
}
1167+
11351168
public // COMPILER_INTRINSIC
11361169
func _projectKeyPathReadOnly<Root, Value>(
11371170
root: Root,

test/SILGen/keypath_application.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,24 @@ func reabstracted(readonly: @escaping () -> (),
220220
readonly[keyPath: rkp] = value
221221
writable[keyPath: rkp] = value
222222
}
223+
224+
// CHECK-LABEL: sil hidden @{{.*}}partial
225+
func partial<A>(valueA: A,
226+
valueB: Int,
227+
pkpA: PartialKeyPath<A>,
228+
pkpB: PartialKeyPath<Int>,
229+
akp: AnyKeyPath) {
230+
// CHECK: [[PROJECT:%.*]] = function_ref @{{.*}}projectKeyPathAny
231+
// CHECK: apply [[PROJECT]]<A>
232+
_ = valueA[keyPath: akp]
233+
// CHECK: [[PROJECT:%.*]] = function_ref @{{.*}}projectKeyPathPartial
234+
// CHECK: apply [[PROJECT]]<A>
235+
_ = valueA[keyPath: pkpA]
236+
237+
// CHECK: [[PROJECT:%.*]] = function_ref @{{.*}}projectKeyPathAny
238+
// CHECK: apply [[PROJECT]]<Int>
239+
_ = valueB[keyPath: akp]
240+
// CHECK: [[PROJECT:%.*]] = function_ref @{{.*}}projectKeyPathPartial
241+
// CHECK: apply [[PROJECT]]<Int>
242+
_ = valueB[keyPath: pkpB]
243+
}

test/expr/unary/keypath/keypath.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,15 @@ func testKeyPathSubscript(readonly: Z, writable: inout Z,
209209
readonly[keyPath: rkp] = sink
210210
writable[keyPath: rkp] = sink
211211

212-
// TODO: PartialKeyPath and AnyKeyPath application
213-
214-
/*
215212
let pkp: PartialKeyPath = rkp
216213

217214
var anySink1 = readonly[keyPath: pkp]
218215
expect(&anySink1, toHaveType: Exactly<Any>.self)
219216
var anySink2 = writable[keyPath: pkp]
220217
expect(&anySink2, toHaveType: Exactly<Any>.self)
221218

222-
readonly[keyPath: pkp] = anySink1 // e/xpected-error{{cannot assign to immutable}}
223-
writable[keyPath: pkp] = anySink2 // e/xpected-error{{cannot assign to immutable}}
219+
readonly[keyPath: pkp] = anySink1 // expected-error{{cannot assign to immutable}}
220+
writable[keyPath: pkp] = anySink2 // expected-error{{cannot assign to immutable}}
224221

225222
let akp: AnyKeyPath = pkp
226223

@@ -229,9 +226,8 @@ func testKeyPathSubscript(readonly: Z, writable: inout Z,
229226
var anyqSink2 = writable[keyPath: akp]
230227
expect(&anyqSink2, toHaveType: Exactly<Any?>.self)
231228

232-
readonly[keyPath: akp] = anyqSink1 // e/xpected-error{{cannot assign to immutable}}
233-
writable[keyPath: akp] = anyqSink2 // e/xpected-error{{cannot assign to immutable}}
234-
*/
229+
readonly[keyPath: akp] = anyqSink1 // expected-error{{cannot assign to immutable}}
230+
writable[keyPath: akp] = anyqSink2 // expected-error{{cannot assign to immutable}}
235231
}
236232

237233
func testKeyPathSubscriptMetatype(readonly: Z.Type, writable: inout Z.Type,

test/stdlib/KeyPath.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,41 @@ keyPath.test("computed properties") {
277277
}
278278
}
279279

280+
class AB {
281+
}
282+
class ABC: AB {
283+
var a = LifetimeTracked(1)
284+
var b = LifetimeTracked(2)
285+
var c = LifetimeTracked(3)
286+
}
287+
288+
keyPath.test("dynamically-typed application") {
289+
let cPaths = [\ABC.a, \ABC.b, \ABC.c]
290+
291+
let subject = ABC()
292+
293+
do {
294+
let fields = cPaths.map { subject[keyPath: $0] }
295+
expectTrue(fields[0] as! AnyObject === subject.a)
296+
expectTrue(fields[1] as! AnyObject === subject.b)
297+
expectTrue(fields[2] as! AnyObject === subject.c)
298+
}
299+
300+
let erasedSubject: AB = subject
301+
let erasedPaths: [AnyKeyPath] = cPaths
302+
let wrongSubject = AB()
303+
304+
do {
305+
let fields = erasedPaths.map { erasedSubject[keyPath: $0] }
306+
expectTrue(fields[0]! as! AnyObject === subject.a)
307+
expectTrue(fields[1]! as! AnyObject === subject.b)
308+
expectTrue(fields[2]! as! AnyObject === subject.c)
309+
310+
let wrongFields = erasedPaths.map { wrongSubject[keyPath: $0] }
311+
expectTrue(wrongFields[0] == nil)
312+
expectTrue(wrongFields[1] == nil)
313+
expectTrue(wrongFields[2] == nil)
314+
}
315+
}
316+
280317
runAllTests()

0 commit comments

Comments
 (0)