Advanced Swift Syntax Enum Properties

Episode #54 • Apr 15, 2019 • Subscriber-Only

This week we’ll put the finishing touches on our enum property code generation tool. We’ll add support for enum cases with multiple associated values and enum cases with no associated values, and we’ll add a feature that will make enums even more ergonomic to work with!

Advanced Swift Syntax Enum Properties
Previously
00:05
Supporting multiple associated values
03:09
Supporting no associated values
12:24
Adding boolean properties
23:09
What’s the point?
27:28

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Previously

Last time we set out to automate all the boilerplate involved in creating what we call “enum properties”, which are computed properties on enums that give us easy access to the data held inside the enum’s cases. We’re doing this because Swift makes it very easy to access data in structs, but does not give us many tools for doing the same with enums.

To achieve this we started creating a package with the Swift Package Manager. It has a dependency on SwiftSyntax, which is Apple’s library for parsing Swift source code into a tree of tokens that we can traverse and inspect. We ended up with a playground that is capable of parsing the code in a Swift file, finding all the enums in it, and creating computed properties for each of the cases in those enums.

That’s awesome, but there are still a lot of subtle edge cases that we are not accounting for, and so we can’t really point this tool at a real world code base and expect it to work.

So today we are going handle more of those edge cases so that we will be in a good position to extract everything out of the playground and make a real command line tool out of it!

Let’s take a look at what we’ve written so far.

import SwiftSyntax
import Foundation

let url = Bundle.main.url(forResource: "Enums", withExtension: "swift")!
let tree = try SyntaxTreeParser.parse(url)

class Visitor: SyntaxVisitor {
  override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorKind {
    print("extension \(node.identifier) {")
    return .visitChildren
  }

  override func visitPost(_ node: Syntax) {
    if node is EnumDeclSyntax {
      print("}")
    }
  }

  override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorKind {
    print("  var \(node.identifier): (\(node.associatedValue!.parameterList))? {")
    print("    guard case let .\(node.identifier)(value) = self else { return nil }")
    print("    return value")
    print("  }")
  }
}

let visitor = Visitor()
tree.walk(visitor)
// extension Validated {
//   var valid: Valid? {
//     guard case let .valid(value) = self else { return nil }
//     return value
//   }
//   var invalid: [Invalid]? {
//     guard case let .invalid(value) = self else { return nil }
//     return value
//   }
// }

Now this tool is very useful but currently only works for enums where every case has a single associated value. In the case of multiple associated values or no associated values, we would be generating invalid code. So today we’re going to address a bunch of these shortcomings to end up with a tool that we can run on code bases with many different kinds of enums in various shapes that should always generate correct, compiling code.

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in