mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Runtime support for identity key paths.
Make sure the implementation can handle a key path with zero components by removing inappropriate assumptions that the number of components is always non-empty. Identity key paths also need some special behavior: - Appending an identity key path should be an identity operation for the other operand - Identity key paths have a `MemoryLayout.offset(of:)` zero - Identity key paths interop with KVC as key paths to `@"self"` To be able to exercise and test this behavior, add a `Builtin.identityKeyPath()` function and `WritableKeyPath._identity` accessor in lieu of finalized syntax.
This commit is contained in:
@@ -390,6 +390,11 @@ 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 : <T> () -> WritableKeyPath<T, T>
|
||||
///
|
||||
/// 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.
|
||||
|
||||
@@ -583,8 +583,6 @@ ERROR(sil_keypath_computed_property_missing_part,none,
|
||||
ERROR(sil_keypath_external_missing_part,none,
|
||||
"keypath external component with indices needs an indices_equals and "
|
||||
"indices_hash function", ())
|
||||
ERROR(sil_keypath_no_components,none,
|
||||
"keypath must have at least one component", ())
|
||||
ERROR(sil_keypath_no_root,none,
|
||||
"keypath must have a root component declared",())
|
||||
ERROR(sil_keypath_index_not_hashable,none,
|
||||
|
||||
@@ -556,6 +556,20 @@ makeTuple(const Gs & ...elementGenerators) {
|
||||
};
|
||||
}
|
||||
|
||||
template <class... Gs>
|
||||
static BuiltinGenericSignatureBuilder::LambdaGenerator
|
||||
makeBoundGenericType(NominalTypeDecl *decl,
|
||||
const Gs & ...argumentGenerators) {
|
||||
return {
|
||||
[=](BuiltinGenericSignatureBuilder &builder) -> Type {
|
||||
Type args[] = {
|
||||
argumentGenerators.build(builder)...
|
||||
};
|
||||
return BoundGenericType::get(decl, Type(), args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static BuiltinGenericSignatureBuilder::MetatypeGenerator<T>
|
||||
makeMetatype(const T &object, Optional<MetatypeRepresentation> repr = None) {
|
||||
@@ -972,6 +986,15 @@ static ValueDecl *getTypeJoinMetaOperation(ASTContext &Context, Identifier Id) {
|
||||
return builder.build(Id);
|
||||
}
|
||||
|
||||
static ValueDecl *getIdentityKeyPathOperation(ASTContext &Context,
|
||||
Identifier Id) {
|
||||
BuiltinGenericSignatureBuilder builder(Context, 1);
|
||||
auto arg = makeGenericParam();
|
||||
builder.setResult(makeBoundGenericType(Context.getWritableKeyPathDecl(),
|
||||
arg, arg));
|
||||
return builder.build(Id);
|
||||
}
|
||||
|
||||
static ValueDecl *getCanBeObjCClassOperation(ASTContext &Context,
|
||||
Identifier Id) {
|
||||
// <T> T.Type -> Builtin.Int8
|
||||
@@ -1865,6 +1888,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
|
||||
|
||||
case BuiltinValueKind::TypeJoinMeta:
|
||||
return getTypeJoinMetaOperation(Context, Id);
|
||||
|
||||
case BuiltinValueKind::IdentityKeyPath:
|
||||
return getIdentityKeyPathOperation(Context, Id);
|
||||
}
|
||||
|
||||
llvm_unreachable("bad builtin value!");
|
||||
|
||||
@@ -2884,8 +2884,6 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (components.empty())
|
||||
P.diagnose(InstLoc.getSourceLoc(), diag::sil_keypath_no_components);
|
||||
if (rootType.isNull())
|
||||
P.diagnose(InstLoc.getSourceLoc(), diag::sil_keypath_no_root);
|
||||
|
||||
@@ -2943,8 +2941,13 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
|
||||
if (patternEnv && patternEnv->getGenericSignature()) {
|
||||
canSig = patternEnv->getGenericSignature()->getCanonicalSignature();
|
||||
}
|
||||
CanType leafType;
|
||||
if (!components.empty())
|
||||
leafType = components.back().getComponentType();
|
||||
else
|
||||
leafType = rootType;
|
||||
auto pattern = KeyPathPattern::get(B.getModule(), canSig,
|
||||
rootType, components.back().getComponentType(),
|
||||
rootType, leafType,
|
||||
components, objcString);
|
||||
|
||||
ResultVal = B.createKeyPath(InstLoc, pattern, subMap, operands, Ty);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "swift/AST/ASTContext.h"
|
||||
#include "swift/AST/Builtins.h"
|
||||
#include "swift/AST/DiagnosticsSIL.h"
|
||||
#include "swift/AST/GenericEnvironment.h"
|
||||
#include "swift/AST/Module.h"
|
||||
#include "swift/AST/ReferenceCounting.h"
|
||||
#include "swift/SIL/SILArgument.h"
|
||||
@@ -1055,6 +1056,58 @@ static ManagedValue emitBuiltinProjectTailElems(SILGenFunction &SGF,
|
||||
return ManagedValue::forUnmanaged(result);
|
||||
}
|
||||
|
||||
static ManagedValue emitBuiltinIdentityKeyPath(SILGenFunction &SGF,
|
||||
SILLocation loc,
|
||||
SubstitutionMap subs,
|
||||
ArrayRef<ManagedValue> 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<TypeTraitResult (TypeBase::*Trait)(),
|
||||
BuiltinValueKind Kind>
|
||||
|
||||
@@ -57,6 +57,7 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
|
||||
ObjectIdentifier(type(of: self)).hash(into: &hasher)
|
||||
return withBuffer {
|
||||
var buffer = $0
|
||||
if buffer.data.isEmpty { return }
|
||||
while true {
|
||||
let (component, type) = buffer.next()
|
||||
hasher.combine(component.value)
|
||||
@@ -88,6 +89,11 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
|
||||
return false
|
||||
}
|
||||
|
||||
// Identity is equal to identity
|
||||
if aBuffer.data.isEmpty {
|
||||
return bBuffer.data.isEmpty
|
||||
}
|
||||
|
||||
while true {
|
||||
let (aComponent, aType) = aBuffer.next()
|
||||
let (bComponent, bType) = bBuffer.next()
|
||||
@@ -153,6 +159,11 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
|
||||
internal var _storedInlineOffset: Int? {
|
||||
return withBuffer {
|
||||
var buffer = $0
|
||||
|
||||
// The identity key path is effectively a stored keypath of type Self
|
||||
// at offset zero
|
||||
if buffer.data.isEmpty { return 0 }
|
||||
|
||||
var offset = 0
|
||||
while true {
|
||||
let (rawComponent, optNextType) = buffer.next()
|
||||
@@ -220,6 +231,9 @@ public class KeyPath<Root, Value>: PartialKeyPath<Root> {
|
||||
var curBase: Any = root
|
||||
return withBuffer {
|
||||
var buffer = $0
|
||||
if buffer.data.isEmpty {
|
||||
return unsafeBitCast(root, to: Value.self)
|
||||
}
|
||||
while true {
|
||||
let (rawComponent, optNextType) = buffer.next()
|
||||
let valueType = optNextType ?? Value.self
|
||||
@@ -279,6 +293,13 @@ public class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
|
||||
_sanityCheck(!buffer.hasReferencePrefix,
|
||||
"WritableKeyPath should not have a reference prefix")
|
||||
|
||||
if buffer.data.isEmpty {
|
||||
return (
|
||||
UnsafeMutablePointer<Value>(
|
||||
mutating: p.assumingMemoryBound(to: Value.self)),
|
||||
nil)
|
||||
}
|
||||
|
||||
while true {
|
||||
let (rawComponent, optNextType) = buffer.next()
|
||||
let nextType = optNextType ?? Value.self
|
||||
@@ -306,7 +327,16 @@ public class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
|
||||
owner: keepAlive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<Root, Root> {
|
||||
return Builtin.identityKeyPath()
|
||||
}
|
||||
}
|
||||
|
||||
/// A key path that supports reading from and writing to the resulting value
|
||||
@@ -1941,6 +1971,16 @@ internal func _appendingKeyPaths<
|
||||
var rootBuffer = $0
|
||||
return leaf.withBuffer {
|
||||
var leafBuffer = $0
|
||||
|
||||
// If either operand is the identity key path, then we should return
|
||||
// the other operand back untouched.
|
||||
if leafBuffer.data.isEmpty {
|
||||
return unsafeDowncast(root, to: Result.self)
|
||||
}
|
||||
if rootBuffer.data.isEmpty {
|
||||
return unsafeDowncast(leaf, to: Result.self)
|
||||
}
|
||||
|
||||
// Reserve room for the appended KVC string, if both key paths are
|
||||
// KVC-compatible.
|
||||
let appendedKVCLength: Int, rootKVCLength: Int, leafKVCLength: Int
|
||||
@@ -2275,7 +2315,8 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern(
|
||||
var buffer = KeyPathBuffer(base: bufferPtr)
|
||||
var size = buffer.data.count + MemoryLayout<Int>.size
|
||||
|
||||
while true {
|
||||
if !buffer.data.isEmpty {
|
||||
while true {
|
||||
let header = buffer.pop(RawKeyPathComponent.Header.self)
|
||||
|
||||
// Ensure that we pop an amount of data consistent with what
|
||||
@@ -2552,6 +2593,7 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern(
|
||||
// Pop the type accessor reference.
|
||||
_ = buffer.popRaw(size: MemoryLayout<Int>.size,
|
||||
alignment: Int.self)
|
||||
}
|
||||
}
|
||||
|
||||
_sanityCheck(buffer.data.isEmpty, "didn't read entire pattern")
|
||||
@@ -2628,7 +2670,8 @@ internal func _instantiateKeyPathBuffer(
|
||||
var base: Any.Type = rootType
|
||||
// Some pattern forms are pessimistically larger than what we need in the
|
||||
// instantiated key path. Keep track of this.
|
||||
while true {
|
||||
if !patternBuffer.data.isEmpty {
|
||||
while true {
|
||||
let componentAddr = destData.baseAddress.unsafelyUnwrapped
|
||||
let header = patternBuffer.pop(RawKeyPathComponent.Header.self)
|
||||
|
||||
@@ -2996,6 +3039,7 @@ internal func _instantiateKeyPathBuffer(
|
||||
base = unsafeBitCast(componentTyAccessor(arguments), to: Any.Type.self)
|
||||
pushDest(base)
|
||||
previousComponentAddr = componentAddr
|
||||
}
|
||||
}
|
||||
|
||||
// We should have traversed both buffers.
|
||||
|
||||
@@ -448,6 +448,14 @@ entry(%0 : @trivial $*A, %1 : @trivial $*B, %2 : @trivial $*A, %3 : @trivial $*B
|
||||
return undef : $()
|
||||
}
|
||||
|
||||
sil @identity : $@convention(thin) <T> () -> () {
|
||||
entry:
|
||||
%v = keypath $WritableKeyPath<T, T>, <A> (root $A; objc "self") <T>
|
||||
%w = keypath $WritableKeyPath<Int, Int>, (root $Int; objc "self")
|
||||
|
||||
return undef : $()
|
||||
}
|
||||
|
||||
sil @s_get : $@convention(thin) <A: Hashable, B: Hashable> (@in_guaranteed A, UnsafeRawPointer) -> @out B
|
||||
sil @s_set : $@convention(thin) <A: Hashable, B: Hashable> (@in_guaranteed B, @in_guaranteed A, UnsafeRawPointer) -> ()
|
||||
sil @s_equals : $@convention(thin) <A: Hashable, B: Hashable> (UnsafeRawPointer, UnsafeRawPointer) -> Bool
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// RUN: %target-swift-emit-silgen -module-name keypaths %s | %FileCheck %s
|
||||
// RUN: %target-swift-emit-silgen -parse-stdlib -module-name keypaths %s | %FileCheck %s
|
||||
|
||||
import Swift
|
||||
|
||||
struct S<T> {
|
||||
var x: T
|
||||
@@ -383,3 +385,14 @@ func subclass_generics<T: C<Int>, U: C<V>, V/*: PoC*/>(_: T, _: U, _: V) {
|
||||
_ = \PoC.extension
|
||||
*/
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil hidden @{{.*}}identity
|
||||
func identity<T>(_: T) {
|
||||
// CHECK: keypath $WritableKeyPath<T, T>, <τ_0_0> ({{.*}}root $τ_0_0) <T>
|
||||
let _: WritableKeyPath<T, T> = Builtin.identityKeyPath()
|
||||
// CHECK: keypath $WritableKeyPath<Array<T>, Array<T>>, <τ_0_0> ({{.*}}root $τ_0_0) <Array<T>>
|
||||
let _: WritableKeyPath<[T], [T]> = Builtin.identityKeyPath()
|
||||
// CHECK: keypath $WritableKeyPath<String, String>, ({{.*}}root $String)
|
||||
let _: WritableKeyPath<String, String> = Builtin.identityKeyPath()
|
||||
}
|
||||
|
||||
|
||||
@@ -684,6 +684,10 @@ struct NonOffsetableProperties {
|
||||
var z: Int { return 0 }
|
||||
}
|
||||
|
||||
func getIdentityKeyPathOfType<T>(_: T.Type) -> KeyPath<T, T> {
|
||||
return WritableKeyPath<T, T>._identity
|
||||
}
|
||||
|
||||
keyPath.test("offsets") {
|
||||
let SLayout = MemoryLayout<S<Int>>.self
|
||||
expectNotNil(SLayout.offset(of: \S<Int>.x))
|
||||
@@ -699,6 +703,43 @@ keyPath.test("offsets") {
|
||||
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.x))
|
||||
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.y))
|
||||
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.z))
|
||||
|
||||
expectEqual(SLayout.offset(of: WritableKeyPath<S<Int>, S<Int>>._identity),
|
||||
0)
|
||||
expectEqual(SLayout.offset(of: getIdentityKeyPathOfType(S<Int>.self)), 0)
|
||||
}
|
||||
|
||||
keyPath.test("identity key path") {
|
||||
var x = LifetimeTracked(1738)
|
||||
|
||||
let id = WritableKeyPath<LifetimeTracked, LifetimeTracked>._identity
|
||||
expectTrue(x === x[keyPath: id])
|
||||
|
||||
let newX = LifetimeTracked(679)
|
||||
x[keyPath: id] = newX
|
||||
expectTrue(x === newX)
|
||||
|
||||
let id2 = getIdentityKeyPathOfType(LifetimeTracked.self)
|
||||
expectEqual(id, id2)
|
||||
expectEqual(id.hashValue, id2.hashValue)
|
||||
expectNotNil(id2 as? WritableKeyPath)
|
||||
|
||||
let id3 = id.appending(path: id2)
|
||||
expectEqual(id, id3)
|
||||
expectEqual(id.hashValue, id3.hashValue)
|
||||
expectNotNil(id3 as? WritableKeyPath)
|
||||
|
||||
let valueKey = \LifetimeTracked.value
|
||||
let valueKey2 = id.appending(path: valueKey)
|
||||
let valueKey3 = (valueKey as KeyPath).appending(path: WritableKeyPath<Int, Int>._identity)
|
||||
|
||||
expectEqual(valueKey, valueKey2)
|
||||
expectEqual(valueKey.hashValue, valueKey2.hashValue)
|
||||
expectEqual(valueKey, valueKey3)
|
||||
expectEqual(valueKey.hashValue, valueKey3.hashValue)
|
||||
|
||||
expectEqual(x[keyPath: valueKey2], 679)
|
||||
expectEqual(x[keyPath: valueKey3], 679)
|
||||
}
|
||||
|
||||
keyPath.test("let-ness") {
|
||||
|
||||
Reference in New Issue
Block a user