KeyPaths and Access Control

Just bringing up this topic since I think that it's a bit of a curious use of KeyPaths - and I wonder if anyone else has comments about it - or perhaps good ideas for ways to use this:

If a type has private properties, the type can still return keypaths to these properties to the outside world.
This basically means that you can allow code outside of the type to access a private property.

I guess this is completely intentional, but I can't find any mention of it in the keypaths proposal: swift-evolution/0161-key-paths.md at master · apple/swift-evolution · GitHub

Could it be used for a kind of 'pseudo friend access' implementation, where the original type injects keypaths to private properties into it's 'friend' types? I know that the friends could easily leak this information to other code too, so as soon as the keypath to the private property is passed to another type, basically anyone can access the property.

One thought:
Have keypaths to reference functions ever been considered? I can't seem to find discussions about them in the forums. (I guess they could be represented using variadic generics?) If so, I guess keypaths to private functions could mess with the compiler's ability to inline or optimize them?

Another thought:
If it were a goal to prevent access to a private property even though the keypath was known outside of a type, could this have been modelled with other sub types of the keypath type, where only part of the hierarchy could be used when accessing a private property from outside of the type?

Yet another thought:
Does the compiler never optimize away properties? Even though a private property is never referenced?

2 Likes

Is there really a substantive difference between declaring a public function which modifies a private property, and declaring a public function which returns a keypath to that same private property?

No, you’re right - that could be considered the same. It just feels a bit different to be ‘given’ the access through the KeyPath rather than having it due to access control levels or through a public function.

In general, access control only prevents people from directly referencing entities. It doesn't stop someone with permission to reference something from passing it out to other clients. You can pass private functions as function values too, for instance.

It would make sense for keypaths to allow references to functions. That's not really any different from a read-only subscript operation.

The compiler reserves the right to delete properties that are never used. Private global or static properties that are never used should get removed by the optimizer. We don't yet do the same for instance properties (AFAIK) but we could in the future.

2 Likes

Thank you for the explanation @Joe_Groff
If instance properties were to be optimized away,
I guess that the compiler could consider an instance property to be in use in case a referencing KeyPath is used locally or if it escapes local scope.

It would be really interesting to see what kind of things could be done with KeyPaths to functions.

But I guess that these would be similar to the situation where you have a keypath to a property that is a function today?

What happens if powerful reflection comes into place though?

You would most likely have to opt in some way if you want to guarantee that your type is fully reflectable at runtime.

1 Like

Wait, so will reflection kind of work on types that don't declare this attribute (i.e. on instance properties that weren't optimized out), or will it not work at all?

I suppose it will depend on how you opt in to the reflection. If you do through conforming to a protocol, then you can't use any of this hypothesised reflection API at all since it will require a type conforming to it.
If it's through some kind of annotation, maybe per-property, then it might simply be that at runtime you might or might not find that depending on internal implementation details (which are bound to change).

IMO we never ought to intentionally prevent reflection from working with whatever information is available for a type. Opting in to reflection would serve the purpose of inhibiting optimizations that would otherwise assume the type or parts thereof are unused, and also alert users reading your code that there's runtime behavior dependent on the type.