Skip to content

Commit 01ed3f5

Browse files
authored
Merge pull request #133 from unsignedapps/public-extensions
Added support for omitting the default parameter/initialiser for optional flag types.
2 parents 6a74b6e + 74d4d30 commit 01ed3f5

File tree

2 files changed

+80
-19
lines changed

2 files changed

+80
-19
lines changed

Sources/VexilMacros/FlagMacro.swift

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14+
import SwiftDiagnostics
1415
import SwiftSyntax
1516
import SwiftSyntaxBuilder
1617
import SwiftSyntaxMacros
@@ -33,15 +34,15 @@ public struct FlagMacro {
3334
/// Create a FlagMacro from the given attribute/declaration
3435
init(node: AttributeSyntax, declaration: some DeclSyntaxProtocol, context: some MacroExpansionContext) throws {
3536
guard node.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Flag" else {
36-
throw Diagnostic.notFlagMacro
37+
throw DiagnosticsError(diagnostics: [ .init(node: node, message: Diagnostic.notFlagMacro) ])
3738
}
3839
guard let arguments = node.arguments else {
39-
throw Diagnostic.missingArguments
40+
throw DiagnosticsError(diagnostics: [ .init(node: node, message: Diagnostic.missingArguments) ])
4041
}
4142

4243
// Description can have an explicit or omitted label
4344
guard let description = arguments.descriptionArgument else {
44-
throw Diagnostic.missingDescription
45+
throw DiagnosticsError(diagnostics: [ .init(node: node, message: Diagnostic.missingDescription) ])
4546
}
4647

4748
guard
@@ -51,11 +52,16 @@ public struct FlagMacro {
5152
let type = binding.typeAnnotation?.type ?? binding.inferredType,
5253
binding.accessorBlock == nil
5354
else {
54-
throw Diagnostic.onlySimpleVariableSupported
55+
throw DiagnosticsError(diagnostics: [ .init(node: node, message: Diagnostic.onlySimpleVariableSupported) ])
5556
}
5657

57-
guard let defaultExprSyntax = arguments[label: "default"]?.expression ?? binding.initializer?.value else {
58-
throw Diagnostic.missingDefaultValue
58+
var defaultExprSyntax: ExprSyntax
59+
if let defaultExpr = arguments[label: "default"]?.expression ?? binding.initializer?.value {
60+
defaultExprSyntax = defaultExpr
61+
} else if binding.typeAnnotation?.type.is(OptionalTypeSyntax.self) == true {
62+
defaultExprSyntax = ExprSyntax(NilLiteralExprSyntax())
63+
} else {
64+
throw DiagnosticsError(diagnostics: [ .init(node: node, message: Diagnostic.missingDefaultValue) ])
5965
}
6066

6167
let strategy = KeyStrategy(exprSyntax: arguments[label: "keyStrategy"]?.expression) ?? .default
@@ -123,18 +129,14 @@ extension FlagMacro: AccessorMacro {
123129
providingAccessorsOf declaration: some DeclSyntaxProtocol,
124130
in context: some MacroExpansionContext
125131
) throws -> [AccessorDeclSyntax] {
126-
do {
127-
let macro = try FlagMacro(node: node, declaration: declaration, context: context)
128-
return [
129-
"""
130-
get {
131-
\(macro.makeLookupExpression())
132-
}
133-
""",
134-
]
135-
} catch {
136-
return []
137-
}
132+
let macro = try FlagMacro(node: node, declaration: declaration, context: context)
133+
return [
134+
"""
135+
get {
136+
\(macro.makeLookupExpression())
137+
}
138+
""",
139+
]
138140
}
139141

140142
}
@@ -177,12 +179,36 @@ extension FlagMacro: PeerMacro {
177179

178180
extension FlagMacro {
179181

180-
enum Diagnostic: Error {
182+
enum Diagnostic: DiagnosticMessage {
181183
case notFlagMacro
182184
case missingArguments
183185
case missingDefaultValue
184186
case missingDescription
185187
case onlySimpleVariableSupported
188+
189+
var message: String {
190+
switch self {
191+
case .notFlagMacro: "This is not a @Flag macro..?"
192+
case .missingArguments: "Required arguments have not been provided."
193+
case .missingDefaultValue: "Could not infer the default value. Initialise the property or set the default: parameter."
194+
case .missingDescription: "Description parameter missing."
195+
case .onlySimpleVariableSupported: "Only simple single-binding properties supported."
196+
}
197+
}
198+
199+
var diagnosticID: MessageID {
200+
switch self {
201+
case .notFlagMacro: MessageID(domain: "com.unsignedapps.vexil.flagMacro", id: "notFlagMacro")
202+
case .missingArguments: MessageID(domain: "com.unsignedapps.vexil.flagMacro", id: "missingArguments")
203+
case .missingDefaultValue: MessageID(domain: "com.unsignedapps.vexil.flagMacro", id: "missingDefaultValue")
204+
case .missingDescription: MessageID(domain: "com.unsignedapps.vexil.flagMacro", id: "missingDescription")
205+
case .onlySimpleVariableSupported: MessageID(domain: "com.unsignedapps.vexil.flagMacro", id: "onlySimpleVariableSupported")
206+
}
207+
}
208+
209+
var severity: DiagnosticSeverity {
210+
.error
211+
}
186212
}
187213

188214
}

Tests/VexilMacroTests/FlagMacroTests.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,41 @@ final class FlagMacroTests: XCTestCase {
162162
)
163163
}
164164

165+
func testExpandsOptional() throws {
166+
assertMacroExpansion(
167+
"""
168+
struct TestFlags {
169+
@Flag(description: "meow")
170+
var testProperty: Bool?
171+
}
172+
""",
173+
expandedSource:
174+
"""
175+
struct TestFlags {
176+
var testProperty: Bool? {
177+
get {
178+
_flagLookup.value(for: _flagKeyPath.append(.automatic("test-property"))) ?? nil
179+
}
180+
}
181+
182+
var $testProperty: FlagWigwag<Bool?> {
183+
FlagWigwag(
184+
keyPath: _flagKeyPath.append(.automatic("test-property")),
185+
name: nil,
186+
defaultValue: nil,
187+
description: "meow",
188+
displayOption: .default,
189+
lookup: _flagLookup
190+
)
191+
}
192+
}
193+
""",
194+
macros: [
195+
"Flag": FlagMacro.self,
196+
]
197+
)
198+
}
199+
165200

166201
// MARK: - Property Initialisation Tests
167202

0 commit comments

Comments
 (0)