Skip to content

Commit e256e12

Browse files
authored
fix: fixed failure in structs with static members (by @Midbin) (#37)
1 parent 4eb999c commit e256e12

File tree

9 files changed

+237
-0
lines changed

9 files changed

+237
-0
lines changed

Sources/CodableMacroPlugin/Attributes/CodedBy.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ struct CodedBy: PropertyAttribute {
3838
/// The following conditions are checked by the
3939
/// built diagnoser:
4040
/// * Attached declaration is a variable declaration.
41+
/// * Attached declaration is not a static variable
42+
/// declaration
4143
/// * Macro usage is not duplicated for the same
4244
/// declaration.
4345
/// * This attribute isn't used combined with
@@ -47,6 +49,7 @@ struct CodedBy: PropertyAttribute {
4749
func diagnoser() -> DiagnosticProducer {
4850
return AggregatedDiagnosticProducer {
4951
expect(syntax: VariableDeclSyntax.self)
52+
attachedToNonStaticVariable()
5053
cantDuplicate()
5154
cantBeCombined(with: IgnoreCoding.self)
5255
}

Sources/CodableMacroPlugin/Attributes/Default.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ struct Default: PropertyAttribute {
3737
/// The following conditions are checked by the
3838
/// built diagnoser:
3939
/// * Attached declaration is a variable declaration.
40+
/// * Attached declaration is not a static variable
41+
/// declaration
4042
/// * Macro usage is not duplicated for the same
4143
/// declaration.
4244
/// * This attribute isn't used combined with
@@ -46,6 +48,7 @@ struct Default: PropertyAttribute {
4648
func diagnoser() -> DiagnosticProducer {
4749
return AggregatedDiagnosticProducer {
4850
expect(syntax: VariableDeclSyntax.self)
51+
attachedToNonStaticVariable()
4952
cantDuplicate()
5053
cantBeCombined(with: IgnoreCoding.self)
5154
}

Sources/CodableMacroPlugin/Attributes/KeyPath/CodedAt.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ struct CodedAt: PropertyAttribute {
3737
/// declaration.
3838
/// * Attached declaration is not a grouped variable
3939
/// declaration.
40+
/// * Attached declaration is not a static variable
41+
/// declaration
4042
/// * This attribute isn't used combined with `CodedIn`
4143
/// and `IgnoreCoding` attribute.
4244
///
4345
/// - Returns: The built diagnoser instance.
4446
func diagnoser() -> DiagnosticProducer {
4547
return AggregatedDiagnosticProducer {
4648
attachedToUngroupedVariable()
49+
attachedToNonStaticVariable()
4750
cantDuplicate()
4851
cantBeCombined(with: CodedIn.self)
4952
cantBeCombined(with: IgnoreCoding.self)

Sources/CodableMacroPlugin/Attributes/KeyPath/CodedIn.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ struct CodedIn: PropertyAttribute {
4848
/// * Attached declaration is a variable declaration.
4949
/// * Macro usage is not duplicated for the same
5050
/// declaration.
51+
/// * Attached declaration is not a static variable
52+
/// declaration
5153
/// * This attribute isn't used combined with `CodedAt`
5254
/// and `IgnoreCoding` attribute.
5355
///
@@ -56,6 +58,7 @@ struct CodedIn: PropertyAttribute {
5658
return AggregatedDiagnosticProducer {
5759
expect(syntax: VariableDeclSyntax.self)
5860
cantDuplicate()
61+
attachedToNonStaticVariable()
5962
cantBeCombined(with: CodedAt.self)
6063
cantBeCombined(with: IgnoreCoding.self)
6164
}

Sources/CodableMacroPlugin/Attributes/RegistrationAttribute.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ extension RegistrationAttribute {
4242
guard let decl = member.decl.as(VariableDeclSyntax.self)
4343
else { return }
4444

45+
// The Macro fails to compile if the decl.modifiers.contains
46+
// is directly used in the guard statement. Otherwise it should
47+
// be used as a second condition in guard block above.
48+
let isStatic = decl.modifiers.contains {
49+
$0.name.tokenKind == .keyword(.static)
50+
}
51+
guard !isStatic else { return }
52+
4553
// builder
4654
let builder =
4755
CodingKeys(from: declaration)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@_implementationOnly import SwiftDiagnostics
2+
@_implementationOnly import SwiftSyntax
3+
@_implementationOnly import SwiftSyntaxMacros
4+
5+
/// A diagnostic producer type that can validate passed syntax is not a static
6+
/// variable declaration.
7+
///
8+
/// This producer can be used for macro-attributes that must be attached to
9+
/// non static variable declarations.
10+
struct StaticVariableDeclaration<Attr: PropertyAttribute>: DiagnosticProducer {
11+
/// The attribute for which
12+
/// validation performed.
13+
///
14+
/// Uses this attribute name
15+
/// in generated diagnostic
16+
/// messages.
17+
let attr: Attr
18+
19+
/// Creates a static variable declaration validation instance
20+
/// with provided attribute.
21+
///
22+
/// - Parameter attr: The attribute for which
23+
/// validation performed.
24+
/// - Returns: Newly created diagnostic producer.
25+
init(_ attr: Attr) {
26+
self.attr = attr
27+
}
28+
29+
/// Validates and produces diagnostics for the passed syntax
30+
/// in the macro expansion context provided.
31+
///
32+
/// Checks whether provided syntax is a non static variable declaration,
33+
/// for static variable declarations error diagnostics
34+
/// are generated.
35+
///
36+
/// - Parameters:
37+
/// - syntax: The syntax to validate and produce diagnostics for.
38+
/// - context: The macro expansion context diagnostics produced in.
39+
///
40+
/// - Returns: True if syntax fails validation, false otherwise.
41+
@discardableResult
42+
func produce(
43+
for syntax: some SyntaxProtocol,
44+
in context: some MacroExpansionContext
45+
) -> Bool {
46+
// The Macro fails to compile if the .modifiers.contains
47+
// is directly used in the guard statement.
48+
let isStatic = syntax.as(VariableDeclSyntax.self)?
49+
.modifiers.contains { $0.name.tokenKind == .keyword(.static) }
50+
guard isStatic ?? false else { return false }
51+
let message = attr.node.diagnostic(
52+
message:
53+
"@\(attr.name) can't be used with static variables declarations",
54+
id: attr.misuseMessageID,
55+
severity: .error
56+
)
57+
context.diagnose(attr: attr, message: message)
58+
return true
59+
}
60+
}
61+
62+
extension PropertyAttribute {
63+
/// Indicates attribute must be attached to non static variable declaration.
64+
///
65+
/// The created diagnostic producer produces error diagnostic,
66+
/// if attribute is attached to static variable declarations.
67+
///
68+
/// - Returns: Static variable declaration validation diagnostic producer.
69+
func attachedToNonStaticVariable() -> StaticVariableDeclaration<Self> {
70+
return .init(self)
71+
}
72+
}

Tests/MetaCodableTests/CodableTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,47 @@ final class CodableTests: XCTestCase {
7272
)
7373
}
7474

75+
func testWithoutAnyCustomizationWithStaticVar() throws {
76+
assertMacroExpansion(
77+
"""
78+
@Codable
79+
struct SomeCodable {
80+
let value: String
81+
static let otherValue: String
82+
public private(set) static var valueWithModifiers: String
83+
}
84+
""",
85+
expandedSource:
86+
"""
87+
struct SomeCodable {
88+
let value: String
89+
static let otherValue: String
90+
public private(set) static var valueWithModifiers: String
91+
}
92+
93+
extension SomeCodable: Decodable {
94+
init(from decoder: any Decoder) throws {
95+
let container = try decoder.container(keyedBy: CodingKeys.self)
96+
self.value = try container.decode(String.self, forKey: CodingKeys.value)
97+
}
98+
}
99+
100+
extension SomeCodable: Encodable {
101+
func encode(to encoder: any Encoder) throws {
102+
var container = encoder.container(keyedBy: CodingKeys.self)
103+
try container.encode(self.value, forKey: CodingKeys.value)
104+
}
105+
}
106+
107+
extension SomeCodable {
108+
enum CodingKeys: String, CodingKey {
109+
case value = "value"
110+
}
111+
}
112+
"""
113+
)
114+
}
115+
75116
func testOnlyDecodeConformance() throws {
76117
assertMacroExpansion(
77118
"""

Tests/MetaCodableTests/CodedAtTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,82 @@ final class CodedAtTests: XCTestCase {
5656
)
5757
}
5858

59+
func testMisuseOnStaticVariableDeclaration() throws {
60+
assertMacroExpansion(
61+
"""
62+
struct SomeCodable {
63+
@CodedAt
64+
static let value: String
65+
}
66+
""",
67+
expandedSource:
68+
"""
69+
struct SomeCodable {
70+
static let value: String
71+
}
72+
""",
73+
diagnostics: [
74+
.init(
75+
id: CodedAt.misuseID,
76+
message:
77+
"@CodedAt can't be used with static variables declarations",
78+
line: 2, column: 5,
79+
fixIts: [
80+
.init(message: "Remove @CodedAt attribute")
81+
]
82+
)
83+
]
84+
)
85+
}
86+
87+
func testMisuseOnStaticWithCodedByDefaultVariableDeclaration() throws {
88+
assertMacroExpansion(
89+
"""
90+
struct SomeCodable {
91+
@Default("some")
92+
@CodedBy(Since1970DateCoder())
93+
@CodedAt
94+
static let value: String
95+
}
96+
""",
97+
expandedSource:
98+
"""
99+
struct SomeCodable {
100+
static let value: String
101+
}
102+
""",
103+
diagnostics: [
104+
.init(
105+
id: Default.misuseID,
106+
message:
107+
"@Default can't be used with static variables declarations",
108+
line: 2, column: 5,
109+
fixIts: [
110+
.init(message: "Remove @Default attribute")
111+
]
112+
),
113+
.init(
114+
id: CodedBy.misuseID,
115+
message:
116+
"@CodedBy can't be used with static variables declarations",
117+
line: 3, column: 5,
118+
fixIts: [
119+
.init(message: "Remove @CodedBy attribute")
120+
]
121+
),
122+
.init(
123+
id: CodedAt.misuseID,
124+
message:
125+
"@CodedAt can't be used with static variables declarations",
126+
line: 4, column: 5,
127+
fixIts: [
128+
.init(message: "Remove @CodedAt attribute")
129+
]
130+
),
131+
]
132+
)
133+
}
134+
59135
func testMisuseInCombinationWithCodedInMacro() throws {
60136
assertMacroExpansion(
61137
"""

Tests/MetaCodableTests/CodedInTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,34 @@ final class CodedInTests: XCTestCase {
3535
)
3636
}
3737

38+
func testMisuseOnStaticVariableDeclaration() throws {
39+
assertMacroExpansion(
40+
"""
41+
struct SomeCodable {
42+
@CodedIn
43+
static let value: String
44+
}
45+
""",
46+
expandedSource:
47+
"""
48+
struct SomeCodable {
49+
static let value: String
50+
}
51+
""",
52+
diagnostics: [
53+
.init(
54+
id: CodedIn.misuseID,
55+
message:
56+
"@CodedIn can't be used with static variables declarations",
57+
line: 2, column: 5,
58+
fixIts: [
59+
.init(message: "Remove @CodedIn attribute")
60+
]
61+
)
62+
]
63+
)
64+
}
65+
3866
func testDuplicatedMisuse() throws {
3967
assertMacroExpansion(
4068
"""

0 commit comments

Comments
 (0)