Skip to content

Commit 6a74b6e

Browse files
authored
Merge pull request #132 from unsignedapps/public-extensions
Added correct detection of conformance scopes when inside a public extension
2 parents e117134 + 2c183af commit 6a74b6e

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

Sources/VexilMacros/FlagContainerMacro.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ extension FlagContainerMacro: MemberMacro {
2525
providingMembersOf declaration: some DeclGroupSyntax,
2626
in context: some MacroExpansionContext
2727
) throws -> [DeclSyntax] {
28-
try [
28+
// If the declaration doesn't have any scopes attached we might be inheriting scopes from a public extension
29+
var scopes = declaration.modifiers.scopeSyntax
30+
if scopes.isEmpty, let parent = context.lexicalContext.first?.as(ExtensionDeclSyntax.self) {
31+
scopes = parent.modifiers.scopeSyntax
32+
}
33+
34+
return try [
2935

3036
// Properties
3137

@@ -43,7 +49,7 @@ extension FlagContainerMacro: MemberMacro {
4349
ExprSyntax("self._flagKeyPath = _flagKeyPath")
4450
ExprSyntax("self._flagLookup = _flagLookup")
4551
}
46-
.with(\.modifiers, declaration.modifiers.scopeSyntax)
52+
.with(\.modifiers, scopes)
4753
),
4854

4955
]
@@ -79,10 +85,16 @@ extension FlagContainerMacro: ExtensionMacro {
7985

8086
// Check that conformance doesn't already exist, or that we are inside a unit test.
8187
// The latter is a workaround for https://github.com/apple/swift-syntax/issues/2031
82-
guard shouldGenerateConformance.flagContainer else {
88+
guard shouldGenerateConformance.flagContainer else {
8389
return []
8490
}
8591

92+
// If the declaration doesn't have any scopes attached we might be inheriting scopes from a public extension
93+
var scopes = declaration.modifiers.scopeSyntax
94+
if scopes.isEmpty, let parent = context.lexicalContext.first?.as(ExtensionDeclSyntax.self) {
95+
scopes = parent.modifiers.scopeSyntax
96+
}
97+
8698
var decls = try [
8799
ExtensionDeclSyntax(
88100
extendedType: type,
@@ -102,7 +114,7 @@ extension FlagContainerMacro: ExtensionMacro {
102114
}
103115
"visitor.endContainer(keyPath: _flagKeyPath)"
104116
}
105-
.with(\.modifiers, declaration.modifiers.scopeSyntax)
117+
.with(\.modifiers, scopes)
106118

107119
// Flag Key Paths
108120

@@ -135,7 +147,7 @@ extension FlagContainerMacro: ExtensionMacro {
135147
"[:]"
136148
}
137149
}
138-
.with(\.modifiers, declaration.modifiers.scopeSyntax)
150+
.with(\.modifiers, scopes)
139151

140152
},
141153
]
@@ -161,7 +173,7 @@ extension FlagContainerMacro: ExtensionMacro {
161173
ExprSyntax("lhs.\(lastBinding) == rhs.\(lastBinding)")
162174
}
163175
}
164-
.with(\.modifiers, Array(declaration.modifiers.scopeSyntax) + [ DeclModifierSyntax(name: .keyword(.static)) ])
176+
.with(\.modifiers, Array(scopes) + [ DeclModifierSyntax(name: .keyword(.static)) ])
165177
}
166178
},
167179
]

Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,84 @@ final class EquatableFlagContainerMacroTests: XCTestCase {
233233
)
234234
}
235235

236+
func testExpandsPublicExtension() throws {
237+
assertMacroExpansion(
238+
"""
239+
public extension SomeContainer {
240+
@FlagContainer
241+
struct TestFlags {
242+
@Flag(default: false, description: "Some Flag")
243+
var someFlag: Bool
244+
}
245+
}
246+
""",
247+
expandedSource:
248+
"""
249+
public extension SomeContainer {
250+
struct TestFlags {
251+
var someFlag: Bool {
252+
get {
253+
_flagLookup.value(for: _flagKeyPath.append(.automatic("some-flag"))) ?? false
254+
}
255+
}
256+
257+
var $someFlag: FlagWigwag<Bool> {
258+
FlagWigwag(
259+
keyPath: _flagKeyPath.append(.automatic("some-flag")),
260+
name: nil,
261+
defaultValue: false,
262+
description: "Some Flag",
263+
displayOption: .default,
264+
lookup: _flagLookup
265+
)
266+
}
267+
268+
fileprivate let _flagKeyPath: FlagKeyPath
269+
270+
fileprivate let _flagLookup: any FlagLookup
271+
272+
public init(_flagKeyPath: FlagKeyPath, _flagLookup: any FlagLookup) {
273+
self._flagKeyPath = _flagKeyPath
274+
self._flagLookup = _flagLookup
275+
}
276+
}
277+
}
278+
279+
extension SomeContainer.TestFlags: FlagContainer {
280+
public func walk(visitor: any FlagVisitor) {
281+
visitor.beginContainer(keyPath: _flagKeyPath, containerType: SomeContainer.TestFlags.self)
282+
visitor.visitFlag(
283+
keyPath: _flagKeyPath.append(.automatic("some-flag")),
284+
value: { [self] in
285+
_flagLookup.value(for: _flagKeyPath.append(.automatic("some-flag")))
286+
},
287+
defaultValue: false,
288+
wigwag: { [self] in
289+
$someFlag
290+
}
291+
)
292+
visitor.endContainer(keyPath: _flagKeyPath)
293+
}
294+
public var _allFlagKeyPaths: [PartialKeyPath<SomeContainer.TestFlags>: FlagKeyPath] {
295+
[
296+
\\SomeContainer.TestFlags.someFlag: _flagKeyPath.append(.automatic("some-flag")),
297+
]
298+
}
299+
}
300+
301+
extension SomeContainer.TestFlags: Equatable {
302+
public static func ==(lhs: SomeContainer.TestFlags, rhs: SomeContainer.TestFlags) -> Bool {
303+
lhs.someFlag == rhs.someFlag
304+
}
305+
}
306+
""",
307+
macros: [
308+
"FlagContainer": FlagContainerMacro.self,
309+
"Flag": FlagMacro.self,
310+
]
311+
)
312+
}
313+
236314
func testExpandsButAlreadyConforming() throws {
237315
assertMacroExpansion(
238316
"""

0 commit comments

Comments
 (0)