KeyPaths, Tuples and Swift 5

Hello everyone, this is my first time writing on the Swift Forums albeit I've been around here sneakily reading since the mailing list was a thing - but I think it's now time to be less shy and (try to) contribute to the language!

Lately I've started to take a look at the Swift compiler, in particular after finding out that \SomeType.aTuple.0 does not work:

[Swift 4.2.1] error: type has no member '0'
[Swift 5 preview] error: key path support for tuples is not implemented

Inspired by this recent thread and "encouraged" by this post indicating this feature as a potential Starter Bug, I finally decided to fork the project to start looking around. Now I'd like to have some advice from anyone that could point me in the right direction - up until now, I've taken a look in particular at the CSSimplify.cpp, CSApply.cpp and Expr.h files among the others.

It would be great to have this feature implemented as soon as possible, but I'd also like to ask first if this can really be considered a Starter Bug (the post that I've found is actually a bit old), and second if being in a hurry would really be worth it given that Swift 5's final branching has already been done: could this be a change that can be integrated back into Swift 5 - or it is too late?
It would be a shame if this feature becomes harder to implement or breaks something once the ABI becomes a thing!


Last but not least, I would like to thank everyone involved in the Swift Project - you have created, and continue to evolve, such a great and beautiful and fun language to learn and use! Thank you!! :blush::orange_heart:

7 Likes

I think this would be a great starter project. The runtime ABI for keypaths should not need any modification to handle tuple components, so there's no rush to implement this. There's an ABI document that describes the layout of key path objects; the details are somewhat out of date, but the high level structure is the same:

A tuple component ought to be compiled to the same runtime representation as a "struct stored property" component, with the in-memory offset of the tuple element from the start of the tuple stored instead of a struct field. The main thing missing is a representation for the tuple component in the AST (KeyPathExpr::Component) and SIL (KeyPathPatternComponent) data structures. If you add "tuple element" cases to both of those enums, teach the type checker to recognize references to tuple elements and create a tuple component in the AST, teach SIL generation to map that to the SIL component representation, and finally teach LLVM IR generation to emit a struct key path component into the binary, then you should be good to go.

7 Likes

That's great to hear!
I'm now a bit relieved about the time I may need to implement this feature, and beside the document (how could I miss it?) you gave me a lot of information too. Thank you!

1 Like

Okay, after some days of (not-so-deep, due to the schedule of my work lately) code analysis I think I have a rough idea of what I need to do in order to make this work, but I have another question and I'm not sure about the answer.

When creating KeyPathComponentHeaders (in ABI/KeyPath.h) with static methods ::for...ComponentWith..., I see that a boolean is used to toggle the isLet bit. Now, given that a tuple element is only mutable if the tuple itself is, how should I set this bit?
Should I track the choice made by the tuple containing the element? Or should I indiscriminately set it to 1 or 0? Or there is some other reasoning that I'm missing?

My idea is that I should leave this flag alone (i.e. set it to 0), but because I don't know how it is actually used at runtime I'm not really sure. I also think that a valuable answer could be pointing me to the code that actually uses this flag, so that I can try to understand it myself instead!

The field should be left false for tuple elements. Tuple elements always behave like var struct fields in that, like you said, they're mutable when the outer tuple is mutable. The flag is used at runtime to decide whether a key path can be a WritableKeyPath or not.

1 Like

I finally have a very basic-proof-of-concept-minimal implementation of this, but I have two issues...

The first one is a very slow compilation time, sometimes I just change a line and the build takes several minutes. I use Xcode if that makes a difference. Maybe this is normal behavior but I wanted to be sure.

The second one is that I get this fatal error

<unknown>:0: error: fatal error encountered during compilation; please file a bug report with your project and the crash log
<unknown>:0: note: unsupported relocation with subtraction expression, symbol '_symbolic 6REPL_24TestV' can not be undefined in a subtraction expression

every time I execute any code about key paths for the SECOND time, but not the first time, even if I repeat the first command (that actually succeeded). Test is a struct that I defined, with some KP stuff inside. What can be wrong here?

Would you be able to push a Github PR with your changes? It'd be easier to help if we can see the code as you're working on it.

Yeah, everything is online @ my repo.

[EDIT] Sorry I misread your post, I'm submitting the PR.
[EDIT 2] Here it is => pull request 21355.

5 Likes

Update: For anyone interested in toying around with this, the PR is well advanced and all the major things are (should be) implemented!

5 Likes

:orange_heart: https://github.com/apple/swift/pull/21355#issuecomment-467179476

Big thanks especially to @Joe_Groff for guiding and helping me!

11 Likes

This is really cool Andrea, thanks!