GSoC is almost over () and I wanted to share a little overview of the work I did, the experience in general, and what's next.
In summary:
@propertyWrapper
struct Clamped<Value: Comparable> {
// a plain struct, shared across wrapper instances
@shared let storage: RangeStorage<Value>
var wrappedValue: Value { ... }
}
-
Designed and prototyped the
@expanded
attribute for function/subscripts parameters.
class Pastry {
init(emoji: String, ingredients: [String]) { ... }
init(name: String, origin: String) { ... }
}
func bake(pastry: @expanded Pastry, qty: Int) { }
// new valid ways to call `bake(pastry:qty:)`
bake(emoji: "π₯", ingredients: [π§, π], qty: 100)
bake(name: "Pastel", origin: "π§π·", qty: 42)
Next steps
Share the @expanded
feature with the community, gather feedback and iterate on the design and implementation.
Behind the scenes of shared storage for property wrappers
The project started as a feature to allow property wrappers to reference their enclosing instance, but it changed a lot throughout the summer. Even though I knew property wrappers from a user perspective, proposing a change to it required me to deepen my understanding. So I invested hours reading previous proposals, discussions on the forum, and asked Holly, my mentor, a lot of questions. At this stage, I also had to start thinking about Swift as something I can actually change rather than just understand how it works.
While the core of the feature was already discussed by other members of the community, writing a pitch meant considering every single detail about it that I could think of. Defining how it works together with other features, what level of customization it should have, and how are people going to use it. The latter was perhaps what I was most curious about.
The shared storage pitch was my first post that received quite a lot of engagement, and discussing these ideas with the community was one of the highlights of this summer. I remember wanting to thank every person that commented β either for suggesting changes, saying they didn't like it or that it was interesting.
I learned from the feedbacks that this feature had some aspects that would be useful outside of property wrappers. I guess this was one of the reasons why it got broken down into a trilogy of proposals. Another branch of feedback questioned whether using subscripts would be better. And in fact, comparing previously suggested alternatives to the one I was proposing should've received more attention in the pitch.
Behind the scenes of @expanded
For the second part of GSoC, I focused on one of the three features that originated from the last pitch. @expanded
is considerably simpler from a design perspective β it's all about adding special rules for argument to parameter matching.
My first idea was to look up the constructors available for the expanded type and use them to collect the appropriate arguments from the call. But since there may be multiple initializers and the arguments might need diagnostics, I ended up going with the approach of generating an implicit init call and gathering arguments based on them not matching the parameter next to @expanded
. This implicit init call would then go through constraint generation and solution independently.
Working on the implementation was quite interesting. Often I would add a new test using @expanded
differently and uncover cases that didn't work. These were the most fun because they weren't always supposed to be fixed, but rather an opportunity to decide what should be valid swifty code and what shouldn't. This is the type of question I remember discussing more often with Holly, and a lesson I take from it is that it's better to add support for something later than to remove it. But even the cases that weren't allowed might deserve special emoji diagnostics, so I still had to treat some of them in the compiler.
I went through this loop of adding a new test case > find bug > debug > solve it (or not) multiple times, and my workflow for debugging changed a lot. It is quite easy for me to go from one method definition to the other, come up with a bunch of ideas of what might be wrong, experiment, and forget how I got there (). Especially when methods have hundred of lines. So I started writing my own stack trace, taking notes of my assumptions, where exactly I was investigating and why, and where to look next. This technique will be useful in whatever project I work on next.
Closing thoughts
It can be daunting to approach compiler code for the first time while learning a new language. The types of problems that you run into are really different, but cool too. I remember spending quite some time figuring out how to keep argument expressions from being evaluated multiple times by the type checker. The strategy suggested by Holly was to wrap them somehow, which led me to explore a bunch of ways that could be done, and out of curiosity look for other Swift features that had to use similar techniques in its implementation. Fun trivia.
An interesting takeaway is that working on the code can serve as a design tool as well. Previously, for shared storage, I would try to anticipate corner cases just by thinking about how would people use them. But this time, ideas for the several ways it could be used (and broken) appeared as I worked on the implementation.
I'm grateful I had such an incredible mentor. @hborla would give me clear starting points and discuss alternatives as I progressed. I had no compiler background and no idea of what even is an AST, and her patience to explain these concepts (or anything else I asked!) was reassuring. Also, shoutout to @augusto2112 for the ninja and lldb tips and to @John_McCall for the language design master class. I learned so much every step of the way, and I hope to see other GSoC projects focused on language features in the future.