From 93b5de61e754a6934a415f41a50a1d3d676d2cf5 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Tue, 18 Sep 2018 19:05:41 -0700 Subject: [PATCH] Implement the final approved syntax for SE-227 identity key paths. `\.self` is the final chosen syntax. Implement support for this syntax, and remove the stopgap builtin and `WritableKeyPath._identity` property that were in place before. --- CHANGELOG.md | 14 ++++++++ include/swift/AST/Builtins.def | 5 --- include/swift/AST/Expr.h | 33 ++++++++++++----- lib/AST/ASTDumper.cpp | 4 +++ lib/AST/ASTWalker.cpp | 1 + lib/AST/Builtins.cpp | 14 +------- lib/AST/Expr.cpp | 22 +++++++----- lib/IDE/SourceEntityWalker.cpp | 1 + lib/SILGen/SILGenBuiltin.cpp | 52 --------------------------- lib/SILGen/SILGenExpr.cpp | 3 ++ lib/Sema/CSApply.cpp | 18 ++++++++-- lib/Sema/CSDiag.cpp | 1 + lib/Sema/CSGen.cpp | 2 ++ lib/Sema/CSSimplify.cpp | 2 ++ lib/Sema/TypeCheckAvailability.cpp | 1 + lib/Sema/TypeCheckConstraints.cpp | 10 ++++-- lib/Sema/TypeCheckExprObjC.cpp | 1 + stdlib/public/core/KeyPath.swift | 10 ------ test/SILGen/keypaths.swift | 8 ++--- test/expr/unary/keypath/keypath.swift | 14 ++++++++ test/stdlib/KeyPath.swift | 9 +++-- 21 files changed, 115 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6eebec442d1..5b560ff679d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,20 @@ CHANGELOG Swift 5.0 --------- +* [SE-0227][]: + + Key paths now support the `\.self` keypath, which is a `WritableKeyPath` + that refers to its entire input value: + + ```swift + let id = \Int.self + + var x = 2 + print(x[keyPath: id]) // prints 2 + x[keyPath: id] = 3 + print(x[keyPath: id]) // prints 3 + ``` + * [SE-0214][]: Renamed the `DictionaryLiteral` type to `KeyValuePairs`. diff --git a/include/swift/AST/Builtins.def b/include/swift/AST/Builtins.def index fc0268654e55a..5a6b51eb1c197 100644 --- a/include/swift/AST/Builtins.def +++ b/include/swift/AST/Builtins.def @@ -373,11 +373,6 @@ BUILTIN_SIL_OPERATION(AllocWithTailElems, "allocWithTailElems", Special) /// Projects the first tail-allocated element of type E from a class C. BUILTIN_SIL_OPERATION(ProjectTailElems, "projectTailElems", Special) -/// identityKeyPath : () -> WritableKeyPath -/// -/// Creates an identity key path object. (TODO: replace with proper syntax) -BUILTIN_SIL_OPERATION(IdentityKeyPath, "identityKeyPath", Special) - #undef BUILTIN_SIL_OPERATION // BUILTIN_RUNTIME_CALL - A call into a runtime function. diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 65206c92ac0ee..d953afb1b4336 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -4773,7 +4773,8 @@ class KeyPathExpr : public Expr { Subscript, OptionalForce, OptionalChain, - OptionalWrap + OptionalWrap, + Identity, }; private: @@ -4787,9 +4788,11 @@ class KeyPathExpr : public Expr { } Decl; - llvm::PointerIntPair SubscriptIndexExprAndKind; - ArrayRef SubscriptLabels; - ArrayRef SubscriptHashableConformances; + Expr *SubscriptIndexExpr; + const Identifier *SubscriptLabelsData; + const ProtocolConformanceRef *SubscriptHashableConformancesData; + unsigned SubscriptSize; + Kind KindValue; Type ComponentType; SourceLoc Loc; @@ -4914,12 +4917,18 @@ class KeyPathExpr : public Expr { SourceLoc()); } + static Component forIdentity(SourceLoc selfLoc) { + return Component(nullptr, {}, nullptr, {}, {}, + Kind::Identity, Type(), + selfLoc); + } + SourceLoc getLoc() const { return Loc; } Kind getKind() const { - return SubscriptIndexExprAndKind.getInt(); + return KindValue; } bool isValid() const { @@ -4936,6 +4945,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalWrap: case Kind::OptionalForce: case Kind::Property: + case Kind::Identity: return true; case Kind::UnresolvedSubscript: @@ -4950,7 +4960,7 @@ class KeyPathExpr : public Expr { switch (getKind()) { case Kind::Subscript: case Kind::UnresolvedSubscript: - return SubscriptIndexExprAndKind.getPointer(); + return SubscriptIndexExpr; case Kind::Invalid: case Kind::OptionalChain: @@ -4958,6 +4968,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalForce: case Kind::UnresolvedProperty: case Kind::Property: + case Kind::Identity: return nullptr; } llvm_unreachable("unhandled kind"); @@ -4967,7 +4978,7 @@ class KeyPathExpr : public Expr { switch (getKind()) { case Kind::Subscript: case Kind::UnresolvedSubscript: - return SubscriptLabels; + return {SubscriptLabelsData, (size_t)SubscriptSize}; case Kind::Invalid: case Kind::OptionalChain: @@ -4975,6 +4986,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalForce: case Kind::UnresolvedProperty: case Kind::Property: + case Kind::Identity: llvm_unreachable("no subscript labels for this kind"); } llvm_unreachable("unhandled kind"); @@ -4984,7 +4996,9 @@ class KeyPathExpr : public Expr { getSubscriptIndexHashableConformances() const { switch (getKind()) { case Kind::Subscript: - return SubscriptHashableConformances; + if (!SubscriptHashableConformancesData) + return {}; + return {SubscriptHashableConformancesData, (size_t)SubscriptSize}; case Kind::UnresolvedSubscript: case Kind::Invalid: @@ -4993,6 +5007,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalForce: case Kind::UnresolvedProperty: case Kind::Property: + case Kind::Identity: return {}; } llvm_unreachable("unhandled kind"); @@ -5013,6 +5028,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalWrap: case Kind::OptionalForce: case Kind::Property: + case Kind::Identity: llvm_unreachable("no unresolved name for this kind"); } llvm_unreachable("unhandled kind"); @@ -5030,6 +5046,7 @@ class KeyPathExpr : public Expr { case Kind::OptionalChain: case Kind::OptionalWrap: case Kind::OptionalForce: + case Kind::Identity: llvm_unreachable("no decl ref for this kind"); } llvm_unreachable("unhandled kind"); diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 3220748741b02..3dfb7a403ac9e 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2597,6 +2597,10 @@ class PrintExpr : public ExprVisitor { component.getIndexExpr()->print(OS, Indent + 4); OS.indent(Indent + 4); break; + case KeyPathExpr::Component::Kind::Identity: + OS << "identity"; + OS << '\n'; + break; } OS << "type="; component.getComponentType().print(OS); diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 062eef59e0b6a..53108bed8ea8c 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -1034,6 +1034,7 @@ class Traversal : public ASTVisitor T.Type -> Builtin.Int8 @@ -1879,10 +1870,7 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) { return getTypeJoinInoutOperation(Context, Id); case BuiltinValueKind::TypeJoinMeta: - return getTypeJoinMetaOperation(Context, Id); - - case BuiltinValueKind::IdentityKeyPath: - return getIdentityKeyPathOperation(Context, Id); + return getTypeJoinMetaOperation(Context, Id); } llvm_unreachable("bad builtin value!"); diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 0cac25e29cdb5..14160a133ec52 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -2135,13 +2135,16 @@ KeyPathExpr::Component::Component(ASTContext *ctxForCopyingLabels, Kind kind, Type type, SourceLoc loc) - : Decl(decl), SubscriptIndexExprAndKind(indexExpr, kind), - SubscriptLabels(subscriptLabels.empty() - ? subscriptLabels - : ctxForCopyingLabels->AllocateCopy(subscriptLabels)), - SubscriptHashableConformances(indexHashables), + : Decl(decl), SubscriptIndexExpr(indexExpr), KindValue(kind), ComponentType(type), Loc(loc) - {} +{ + assert(subscriptLabels.size() == indexHashables.size() + || indexHashables.empty()); + SubscriptLabelsData = subscriptLabels.data(); + SubscriptHashableConformancesData = indexHashables.empty() + ? nullptr : indexHashables.data(); + SubscriptSize = subscriptLabels.size(); +} KeyPathExpr::Component KeyPathExpr::Component::forSubscriptWithPrebuiltIndexExpr( @@ -2157,8 +2160,10 @@ void KeyPathExpr::Component::setSubscriptIndexHashableConformances( ArrayRef hashables) { switch (getKind()) { case Kind::Subscript: - SubscriptHashableConformances = getComponentType()->getASTContext() - .AllocateCopy(hashables); + assert(hashables.size() == SubscriptSize); + SubscriptHashableConformancesData = getComponentType()->getASTContext() + .AllocateCopy(hashables) + .data(); return; case Kind::UnresolvedSubscript: @@ -2168,6 +2173,7 @@ void KeyPathExpr::Component::setSubscriptIndexHashableConformances( case Kind::OptionalForce: case Kind::UnresolvedProperty: case Kind::Property: + case Kind::Identity: llvm_unreachable("no hashable conformances for this kind"); } } diff --git a/lib/IDE/SourceEntityWalker.cpp b/lib/IDE/SourceEntityWalker.cpp index d7addc94f438c..1145260dd4c52 100644 --- a/lib/IDE/SourceEntityWalker.cpp +++ b/lib/IDE/SourceEntityWalker.cpp @@ -367,6 +367,7 @@ std::pair SemaAnnotator::walkToExprPre(Expr *E) { case KeyPathExpr::Component::Kind::OptionalChain: case KeyPathExpr::Component::Kind::OptionalWrap: case KeyPathExpr::Component::Kind::OptionalForce: + case KeyPathExpr::Component::Kind::Identity: break; } } diff --git a/lib/SILGen/SILGenBuiltin.cpp b/lib/SILGen/SILGenBuiltin.cpp index db5a220dc0a6b..2e85503a55f76 100644 --- a/lib/SILGen/SILGenBuiltin.cpp +++ b/lib/SILGen/SILGenBuiltin.cpp @@ -970,58 +970,6 @@ static ManagedValue emitBuiltinProjectTailElems(SILGenFunction &SGF, return ManagedValue::forUnmanaged(result); } -static ManagedValue emitBuiltinIdentityKeyPath(SILGenFunction &SGF, - SILLocation loc, - SubstitutionMap subs, - ArrayRef args, - SGFContext C) { - assert(subs.getReplacementTypes().size() == 1 && - "identityKeyPath should have one substitution"); - assert(args.size() == 0 && - "identityKeyPath should have no args"); - - auto identityTy = subs.getReplacementTypes()[0]->getCanonicalType(); - - // The `self` key can be used for identity in Cocoa KVC as well. - StringRef objcString = SGF.getASTContext().LangOpts.EnableObjCInterop - ? "self" : ""; - - // The key path pattern has to capture some generic context if the type is - // dependent on this generic context. We only need the specific type, though, - // not the entire generic environment. - bool isDependent = identityTy->hasArchetype(); - CanType identityPatternTy = identityTy; - CanGenericSignature patternSig = nullptr; - SubstitutionMap patternSubs; - if (isDependent) { - auto param = GenericTypeParamType::get(0, 0, SGF.getASTContext()); - identityPatternTy = param->getCanonicalType(); - patternSig = GenericSignature::get(param, {})->getCanonicalSignature(); - patternSubs = SubstitutionMap::get(patternSig, - llvm::makeArrayRef((Type)identityTy), - {}); - } - - auto identityPattern = KeyPathPattern::get(SGF.SGM.M, - patternSig, - identityPatternTy, - identityPatternTy, - {}, - objcString); - - auto kpTy = BoundGenericType::get(SGF.getASTContext().getWritableKeyPathDecl(), - Type(), - {identityTy, identityTy}) - ->getCanonicalType(); - - auto keyPath = SGF.B.createKeyPath(loc, identityPattern, - patternSubs, - {}, - SILType::getPrimitiveObjectType(kpTy)); - return SGF.emitManagedRValueWithCleanup(keyPath); -} - - /// Specialized emitter for type traits. template diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 5ee1f150c8b3c..d5635f845c122 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3849,6 +3849,9 @@ RValue RValueEmitter::visitKeyPathExpr(KeyPathExpr *E, SGFContext C) { KeyPathPatternComponent::forOptional(loweredKind, baseTy)); break; } + + case KeyPathExpr::Component::Kind::Identity: + continue; case KeyPathExpr::Component::Kind::Invalid: case KeyPathExpr::Component::Kind::UnresolvedProperty: diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index c6a7e6eb443d6..0344c0bba229c 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -276,7 +276,11 @@ static bool buildObjCKeyPathString(KeyPathExpr *E, case KeyPathExpr::Component::Kind::OptionalWrap: // KVC propagates nulls, so these don't affect the key path string. continue; - + case KeyPathExpr::Component::Kind::Identity: + // The identity component can be elided from the KVC string (unless it's + // the only component, in which case we use @"self"). + continue; + case KeyPathExpr::Component::Kind::Property: { // Property references must be to @objc properties. // TODO: If we added special properties matching KVC operators like '@sum', @@ -307,6 +311,12 @@ static bool buildObjCKeyPathString(KeyPathExpr *E, } } + // If there are no non-identity components, this is the "self" key. + if (buf.empty()) { + auto self = StringRef("self"); + buf.append(self.begin(), self.end()); + } + return true; } @@ -4514,7 +4524,11 @@ namespace { baseTy = component.getComponentType(); resolvedComponents.push_back(component); break; - + case KeyPathExpr::Component::Kind::Identity: + component = origComponent; + component.setComponentType(baseTy); + resolvedComponents.push_back(component); + break; case KeyPathExpr::Component::Kind::Property: case KeyPathExpr::Component::Kind::Subscript: case KeyPathExpr::Component::Kind::OptionalWrap: diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index 82fcbedaaa479..16318333653e3 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -6635,6 +6635,7 @@ static bool diagnoseKeyPathComponents(ConstraintSystem &CS, KeyPathExpr *KPE, break; case KeyPathExpr::Component::Kind::Invalid: + case KeyPathExpr::Component::Kind::Identity: case KeyPathExpr::Component::Kind::OptionalChain: case KeyPathExpr::Component::Kind::OptionalForce: // FIXME: Diagnose optional chaining and forcing properly. diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 8ca4734d61f79..fd3b93a2cb7cb 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -3067,6 +3067,8 @@ namespace { base = OptionalType::get(base); break; } + case KeyPathExpr::Component::Kind::Identity: + continue; } } diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 5915a4b241e1a..dc5e61a8907db 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -638,6 +638,7 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, case KeyPathExpr::Component::Kind::OptionalForce: case KeyPathExpr::Component::Kind::OptionalChain: case KeyPathExpr::Component::Kind::OptionalWrap: + case KeyPathExpr::Component::Kind::Identity: return std::make_tuple(nullptr, 0, argLabels, hasTrailingClosure); } @@ -4137,6 +4138,7 @@ ConstraintSystem::simplifyKeyPathConstraint(Type keyPathTy, switch (component.getKind()) { case KeyPathExpr::Component::Kind::Invalid: + case KeyPathExpr::Component::Kind::Identity: break; case KeyPathExpr::Component::Kind::Property: diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index a16977027a452..5d9bea0321414 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -2447,6 +2447,7 @@ class AvailabilityWalker : public ASTWalker { case KeyPathExpr::Component::Kind::OptionalChain: case KeyPathExpr::Component::Kind::OptionalWrap: case KeyPathExpr::Component::Kind::OptionalForce: + case KeyPathExpr::Component::Kind::Identity: break; } } diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index a08a6e341fbc0..625f6cb5d7a66 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -1722,7 +1722,6 @@ void PreCheckExpression::resolveKeyPathExpr(KeyPathExpr *KPE) { // Pre-order visit of a sequence foo.bar[0]?.baz, which means that the // components are pushed in reverse order. - auto traversePath = [&](Expr *expr, bool isInParsedPath, bool emitErrors = true) { Expr *outermostExpr = expr; @@ -1739,7 +1738,12 @@ void PreCheckExpression::resolveKeyPathExpr(KeyPathExpr *KPE) { } // Recurring cases: - if (auto UDE = dyn_cast(expr)) { + if (auto SE = dyn_cast(expr)) { + // .self, the identity component. + components.push_back(KeyPathExpr::Component::forIdentity( + SE->getSelfLoc())); + expr = SE->getSubExpr(); + } else if (auto UDE = dyn_cast(expr)) { // .foo components.push_back(KeyPathExpr::Component::forUnresolvedProperty( UDE->getName(), UDE->getLoc())); @@ -1816,7 +1820,7 @@ void PreCheckExpression::resolveKeyPathExpr(KeyPathExpr *KPE) { traversePath(root, /*isInParsedPath=*/false); } - // Key paths must have at least one component. + // Key paths must be spelled with at least one component. if (components.empty()) { TC.diagnose(KPE->getLoc(), diag::expr_swift_keypath_empty); // Passes further down the pipeline expect keypaths to always have at least diff --git a/lib/Sema/TypeCheckExprObjC.cpp b/lib/Sema/TypeCheckExprObjC.cpp index 1c383dfd90f3d..1931bf57cf80c 100644 --- a/lib/Sema/TypeCheckExprObjC.cpp +++ b/lib/Sema/TypeCheckExprObjC.cpp @@ -203,6 +203,7 @@ Optional TypeChecker::checkObjCKeyPathExpr(DeclContext *dc, // TODO: Perhaps we can map subscript components to dictionary keys. switch (auto kind = component.getKind()) { case KeyPathExpr::Component::Kind::Invalid: + case KeyPathExpr::Component::Kind::Identity: continue; case KeyPathExpr::Component::Kind::UnresolvedProperty: diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 3b77da60845b9..66827a5c077ea 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -329,16 +329,6 @@ public class WritableKeyPath: KeyPath { } } -extension WritableKeyPath where Root == Value { - // FIXME: Replace with proper surface syntax - - /// Returns an identity key path that references the entire input value. - @inlinable - public static var _identity: WritableKeyPath { - return Builtin.identityKeyPath() - } -} - /// A key path that supports reading from and writing to the resulting value /// with reference semantics. public class ReferenceWritableKeyPath< diff --git a/test/SILGen/keypaths.swift b/test/SILGen/keypaths.swift index 3b129a000fffb..74f040f9a9da5 100644 --- a/test/SILGen/keypaths.swift +++ b/test/SILGen/keypaths.swift @@ -389,10 +389,10 @@ func subclass_generics, U: C, V/*: PoC*/>(_: T, _: U, _: V) { // CHECK-LABEL: sil hidden @{{.*}}identity func identity(_: T) { // CHECK: keypath $WritableKeyPath, <τ_0_0> ({{.*}}root $τ_0_0) - let _: WritableKeyPath = Builtin.identityKeyPath() - // CHECK: keypath $WritableKeyPath, Array>, <τ_0_0> ({{.*}}root $τ_0_0) > - let _: WritableKeyPath<[T], [T]> = Builtin.identityKeyPath() + let _: WritableKeyPath = \T.self + // CHECK: keypath $WritableKeyPath, Array>, <τ_0_0> ({{.*}}root $Array<τ_0_0>) + let _: WritableKeyPath<[T], [T]> = \[T].self // CHECK: keypath $WritableKeyPath, ({{.*}}root $String) - let _: WritableKeyPath = Builtin.identityKeyPath() + let _: WritableKeyPath = \String.self } diff --git a/test/expr/unary/keypath/keypath.swift b/test/expr/unary/keypath/keypath.swift index 0aa2416c0c0fc..3aad504ec3963 100644 --- a/test/expr/unary/keypath/keypath.swift +++ b/test/expr/unary/keypath/keypath.swift @@ -658,6 +658,20 @@ struct Container { var rdar32057712 = \Container.base?.i +var identity1 = \Container.self +var identity2: WritableKeyPath = \Container.self +var identity3: WritableKeyPath = \Container.self +var identity4: WritableKeyPath = \.self +var identity5: KeyPath = \Container.self +var identity6: KeyPath = \Container.self +var identity7: KeyPath = \.self +var identity8: PartialKeyPath = \Container.self +var identity9: PartialKeyPath = \Container.self +var identity10: PartialKeyPath = \.self +var identity11: AnyKeyPath = \Container.self + +var interleavedIdentityComponents = \Container.self.base.self?.self.i.self + func testSyntaxErrors() { // expected-note{{}} _ = \. ; // expected-error{{expected member name following '.'}} _ = \.a ; diff --git a/test/stdlib/KeyPath.swift b/test/stdlib/KeyPath.swift index f8bb9fe92e93b..582368e500302 100644 --- a/test/stdlib/KeyPath.swift +++ b/test/stdlib/KeyPath.swift @@ -685,7 +685,7 @@ struct NonOffsetableProperties { } func getIdentityKeyPathOfType(_: T.Type) -> KeyPath { - return WritableKeyPath._identity + return \.self } keyPath.test("offsets") { @@ -704,15 +704,14 @@ keyPath.test("offsets") { expectNil(NOPLayout.offset(of: \NonOffsetableProperties.y)) expectNil(NOPLayout.offset(of: \NonOffsetableProperties.z)) - expectEqual(SLayout.offset(of: WritableKeyPath, S>._identity), - 0) + expectEqual(SLayout.offset(of: \.self), 0) expectEqual(SLayout.offset(of: getIdentityKeyPathOfType(S.self)), 0) } keyPath.test("identity key path") { var x = LifetimeTracked(1738) - let id = WritableKeyPath._identity + let id = \LifetimeTracked.self expectTrue(x === x[keyPath: id]) let newX = LifetimeTracked(679) @@ -731,7 +730,7 @@ keyPath.test("identity key path") { let valueKey = \LifetimeTracked.value let valueKey2 = id.appending(path: valueKey) - let valueKey3 = (valueKey as KeyPath).appending(path: WritableKeyPath._identity) + let valueKey3 = (valueKey as KeyPath).appending(path: \Int.self) expectEqual(valueKey, valueKey2) expectEqual(valueKey.hashValue, valueKey2.hashValue)