|
1 | 1 | # Destructuring |
2 | 2 |
|
3 | 3 | Destructuring is a game changer for developing robust macros without drowning yourself in verbose input validation code. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Destructuring is a common concept in programming available in many languages from Javascript to Rust. It is the act of |
| 8 | +taking data and pulling it apart into its component parts; often with some pattern matching involved. |
| 9 | + |
| 10 | +## A first look |
| 11 | + |
| 12 | +The quickest way to demonstrate why you'll love destructuring is to compare a snippet that uses MacroKit and destructuring, |
| 13 | +to one that doesn't. |
| 14 | + |
| 15 | +Notice how when we're using MacroToolkit, we simply describe what we expect the `returnType` to look like, and MacroToolkit |
| 16 | +performs all of the pattern matching and validation for us! |
| 17 | + |
| 18 | +### Without MacroToolkit |
| 19 | + |
| 20 | +```swift |
| 21 | +// We're expecting the return type to look like `Result<A, B>` |
| 22 | +guard |
| 23 | + let simpleReturnType = returnType.as(SimpleTypeIdentifierSyntax.self), |
| 24 | + simpleReturnType.name.description == "Result", |
| 25 | + let genericArguments = (simpleReturnType.genericArgumentClause?.arguments).map(Array.init), |
| 26 | + genericArguments.count == 2 |
| 27 | +else { |
| 28 | + throw MacroError("Invalid return type") |
| 29 | +} |
| 30 | +let successType = genericArguments[0] |
| 31 | +let failureType = genericArguments[1] |
| 32 | +``` |
| 33 | + |
| 34 | +### With MacroToolkit |
| 35 | + |
| 36 | +```swift |
| 37 | +// We're expecting the return type to look like `Result<A, B>` |
| 38 | +guard case let .simple("Result", (successType, failureType))? = destructure(returnType) else { |
| 39 | + throw MacroError("Invalid return type") |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +## General destructuring |
| 44 | + |
| 45 | +MacroToolkit provides a set of functions that can be used to destructure any array-like data (e.g. argument lists, |
| 46 | +attribute lists, etc.). Lets use destructuring to parse an argument list. |
| 47 | + |
| 48 | +```swift |
| 49 | +// We're expecting exactly two arguments; a name and an age |
| 50 | +guard |
| 51 | + let (nameExpr, ageExpr) = attribute.arguments, |
| 52 | + let name = nameExpr.asStringLiteral?.value, |
| 53 | + let age = ageExpr.asIntegerLiteral?.value |
| 54 | +else { |
| 55 | + throw MacroError("Usage: @MyMacro(\"stackotter\", 105)") |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +- 0 elements: ``destructure(_:)-12e8l`` |
| 60 | +- 1 element: ``destructureSingle(_:)-1dg2k`` |
| 61 | +- 2 elements: ``destructure(_:)-2c4y9`` |
| 62 | +- 3 elements: ``destructure(_:)-65tob`` |
| 63 | +- 4 elements: ``destructure(_:)-2vkcn`` |
| 64 | +- 5 elements: ``destructure(_:)-2ooj8`` |
| 65 | +- 6 elements: ``destructure(_:)-ztug`` |
| 66 | + |
| 67 | +At the moment a separate implementation is required for each number of arguments because variadic generics aren't |
| 68 | +yet ready for this use-case. In the future the 6 element limit will be lifted (and if you need it lifted now you |
| 69 | +can just ask and I can bump up the maximum with a bit of copy and pasting). |
| 70 | + |
| 71 | +## Type destructuring |
| 72 | + |
| 73 | +As you saw in [A first look](#A-first-look), type destructuring is a highly expressive way to parse and validate |
| 74 | +types. |
| 75 | + |
| 76 | +### Destructuring arbitrary types |
| 77 | + |
| 78 | +So far only the ``Type/simple(_:)`` and ``Type/function(_:)`` variants of ``Type`` can be destructured. But with minimal |
| 79 | +effort this can be expanded to variants such as ``Type/tuple(_:)``. |
| 80 | + |
| 81 | +```swift |
| 82 | +// We're expecting the function to have a signature of the form `(parameterType1, parameterType2) -> returnType`. |
| 83 | +guard |
| 84 | + case .function((parameterType1, parameterType2), returnType) = destructure(functionType) |
| 85 | +else { |
| 86 | + throw MacroError("Invalid return type") |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +- 0 unknowns: ``destructure(_:)-6saio`` |
| 91 | +- 1 unknown: ``destructureSingle(_:)-9tjyg`` |
| 92 | +- 2 unknowns: ``destructure(_:)-1vwqf`` |
| 93 | +- 3 unknowns: ``destructure(_:)-365tz`` |
| 94 | +- 4 unknowns: ``destructure(_:)-867ko`` |
| 95 | +- 5 unknowns: ``destructure(_:)-5joxh`` |
| 96 | +- 6 unknowns: ``destructure(_:)-nbe1`` |
| 97 | + |
| 98 | +### Destructuring simple types (aka nominal types) |
| 99 | + |
| 100 | +Simple types can be destructured into a name and a collection of generic type parameters. |
| 101 | + |
| 102 | +```swift |
| 103 | +// We're expecting the return type to look like `Result<A, B>`. |
| 104 | +guard case let ("Result", (successType, failureType))? = destructure(simpleReturnType) else { |
| 105 | + throw MacroError("Invalid return type") |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +- 0 generic type parameters: ``destructure(_:)-3e860`` |
| 110 | +- 1 generic type parameter: ``destructureSingle(_:)-chrb`` |
| 111 | +- 2 generic type parameters: ``destructure(_:)-3xse5`` |
| 112 | +- 3 generic type parameters: ``destructure(_:)-8aio2`` |
| 113 | +- 4 generic type parameters: ``destructure(_:)-560mv`` |
| 114 | +- 5 generic type parameters: ``destructure(_:)-1h0o8`` |
| 115 | +- 6 generic type parameters: ``destructure(_:)-45hpd`` |
| 116 | + |
| 117 | +### Destructuring function types |
| 118 | + |
| 119 | +Function types can be destructured into a collection of parameter types and a return type. |
| 120 | + |
| 121 | +```swift |
| 122 | +// We're expecting the function to have a signature of the form `(first, _, third) -> Int`. |
| 123 | +guard |
| 124 | + // Destructure a function type |
| 125 | + case let ((first, _, third), returnType)? = destructure(functionType), |
| 126 | + // Destructure an arbitrary type |
| 127 | + case .simple("Int", ()) = destructure(returnType) |
| 128 | +else { |
| 129 | + throw MacroError("Invalid return type") |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +- 0 parameter types: ``destructure(_:)-180j9`` |
| 134 | +- 1 parameter type: ``destructureSingle(_:)-5r8sv`` |
| 135 | +- 2 parameter types: ``destructure(_:)-2idom`` |
| 136 | +- 3 parameter types: ``destructure(_:)-8g4v5`` |
| 137 | +- 4 parameter types: ``destructure(_:)-7o2hx`` |
| 138 | +- 5 parameter types: ``destructure(_:)-8m59b`` |
| 139 | +- 6 parameter types: ``destructure(_:)-9wc1b`` |
0 commit comments