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!

Previous episode
Advanced Swift Syntax Enum Properties
Next episode
Locked

Unlock This Episode

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

Sign in with GitHub

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.

Supporting multiple associated values


Get started with our free plan

Our free plan includes 1 subscriber-only episode of your choice, access to 62 free episodes with transcripts and code samples, and weekly updates from our newsletter.

View plans and pricing