Skip to content

Commit 403afe9

Browse files
committed
add diagnosis for unavailable function usage, when possible
1 parent 8c79311 commit 403afe9

15 files changed

+177
-12
lines changed

Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ let package = Package(
2424
"510.0.0" ..< "610.0.0"
2525
),
2626
.package(
27-
url: "https://github.com/mahdibm/swift-mustache",
28-
branch: "mmbm-swift-6"
27+
url: "https://github.com/hummingbird-project/swift-mustache",
28+
from: "2.0.0-beta.2"
2929
),
3030
],
3131
targets: [

Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,25 @@ extension EnumeratorMacroType: MemberMacro {
4848
let rendered = templates.compactMap {
4949
(template, syntax) -> (rendered: String, syntax: StringLiteralExprSyntax)? in
5050
do {
51-
let rendered = try MustacheTemplate(
52-
string: "{{%CONTENT_TYPE:TEXT}}\n" + template
53-
).render([
54-
"cases": cases
55-
])
51+
let rendered: String? = try RenderingContext.$current.withValue(.init()) {
52+
let result = try MustacheTemplate(
53+
string: "{{%CONTENT_TYPE:TEXT}}\n" + template
54+
).render([
55+
"cases": cases
56+
])
57+
if let diagnostic = RenderingContext.current.diagnostic {
58+
context.addDiagnostics(
59+
from: diagnostic,
60+
node: syntax
61+
)
62+
return nil
63+
} else {
64+
return result
65+
}
66+
}
67+
guard let rendered else {
68+
return nil
69+
}
5670
return (rendered, syntax)
5771
} catch {
5872
let message: MacroError

Sources/EnumeratorMacroImpl/MacroError.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum MacroError: Error, CustomStringConvertible {
1111
case couldNotFindLocationOfNode(syntax: String)
1212
case mustacheTemplateError(message: String)
1313
case internalError(String)
14+
case invalidTransform(transform: String, normalizedTypeName: String)
1415

1516
var caseName: String {
1617
switch self {
@@ -32,6 +33,8 @@ enum MacroError: Error, CustomStringConvertible {
3233
"mustacheTemplateError"
3334
case .internalError:
3435
"internalError"
36+
case .invalidTransform:
37+
"invalidTransform"
3538
}
3639
}
3740

@@ -55,6 +58,11 @@ enum MacroError: Error, CustomStringConvertible {
5558
"Error while rendering the template: \(message)"
5659
case let .internalError(message):
5760
"An internal error occurred. Please file a bug report at https://github.com/mahdibm/enumerator-macro. Error:\n\(message)"
61+
case let .invalidTransform(transform, normalizedTypeName):
62+
"""
63+
Invalid function call detected.
64+
'\(normalizedTypeName)' doesn't have a function called '\(transform)'
65+
"""
5866
}
5967
}
6068
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// The macro works in a single thread so `@unchecked Sendable` is justified.
2+
final class RenderingContext: @unchecked Sendable {
3+
@TaskLocal static var current: RenderingContext!
4+
5+
var diagnostic: MacroError?
6+
7+
func cleanDiagnostic() {
8+
self.diagnostic = nil
9+
}
10+
11+
func addOrReplaceDiagnostic(_ error: MacroError) {
12+
self.diagnostic = error
13+
}
14+
}

Sources/EnumeratorMacroImpl/Types/EArray.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ extension EArray: CustomStringConvertible {
2626
}
2727
}
2828

29+
extension EArray: WithNormalizedTypeName {
30+
static var normalizedTypeName: String {
31+
"[\(bestEffortTypeName(Element.self))]"
32+
}
33+
}
34+
2935
extension EArray: CustomReflectable {
3036
var customMirror: Mirror {
3137
Mirror(reflecting: self.underlying)
@@ -37,6 +43,7 @@ extension EArray: MustacheTransformable {
3743
if let defaultTransformed = self.underlying.transform(name) {
3844
return convertToCustomTypesIfPossible(defaultTransformed)
3945
} else {
46+
RenderingContext.current.cleanDiagnostic()
4047
switch name {
4148
case "joined":
4249
let joined = self.underlying
@@ -68,6 +75,12 @@ extension EArray: MustacheTransformable {
6875
let value = keyValues.first(where: { $0.key == name })?.value
6976
return EOptional(value)
7077
}
78+
RenderingContext.current.addOrReplaceDiagnostic(
79+
.invalidTransform(
80+
transform: name,
81+
normalizedTypeName: Self.normalizedTypeName
82+
)
83+
)
7184
return nil
7285
}
7386
}

Sources/EnumeratorMacroImpl/Types/ECase.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ struct ECase {
2424
self.comments = .init(underlying: keyValueParts.map(EString.init))
2525
}
2626
}
27+
28+
extension ECase: WithNormalizedTypeName {
29+
static var normalizedTypeName: String {
30+
"Case"
31+
}
32+
}

Sources/EnumeratorMacroImpl/Types/ECases.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ extension ECases: CustomStringConvertible {
1818
self.underlying.description
1919
}
2020
}
21+
extension ECases: WithNormalizedTypeName {
22+
static var normalizedTypeName: String {
23+
"[Case]"
24+
}
25+
}
2126

2227
extension ECases: Sequence, MustacheSequence {
2328
func makeIterator() -> Array<ECase>.Iterator {
@@ -36,12 +41,19 @@ extension ECases: MustacheTransformable {
3641
if let defaultTransformed = self.underlying.transform(name) {
3742
return convertToCustomTypesIfPossible(defaultTransformed)
3843
} else {
44+
RenderingContext.current.cleanDiagnostic()
3945
switch name {
4046
case "filterNoParams":
4147
return self.filter(\.parameters.underlying.underlying.isEmpty)
4248
case "filterWithParams":
4349
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
4450
default:
51+
RenderingContext.current.addOrReplaceDiagnostic(
52+
.invalidTransform(
53+
transform: name,
54+
normalizedTypeName: Self.normalizedTypeName
55+
)
56+
)
4557
return nil
4658
}
4759
}

Sources/EnumeratorMacroImpl/Types/EKeyValue.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ extension EKeyValue: CustomStringConvertible {
1616
}
1717
}
1818

19+
extension EKeyValue: WithNormalizedTypeName {
20+
static var normalizedTypeName: String {
21+
"KeyValue<String, String>"
22+
}
23+
}
24+
1925
extension EKeyValue: MustacheTransformable {
2026
func transform(_ name: String) -> Any? {
2127
switch name {
@@ -24,6 +30,12 @@ extension EKeyValue: MustacheTransformable {
2430
case "value":
2531
return self.value
2632
default:
33+
RenderingContext.current.addOrReplaceDiagnostic(
34+
.invalidTransform(
35+
transform: name,
36+
normalizedTypeName: Self.normalizedTypeName
37+
)
38+
)
2739
return nil
2840
}
2941
}

Sources/EnumeratorMacroImpl/Types/EOptional.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ extension EOptional: CustomStringConvertible {
6969
}
7070
}
7171

72+
extension EOptional: WithNormalizedTypeName {
73+
static var normalizedTypeName: String {
74+
"Optional<\(bestEffortTypeName(Wrapped.self))>"
75+
}
76+
}
77+
7278
extension EOptional: MustacheTransformable {
7379
func transform(_ name: String) -> Any? {
7480
switch self {
@@ -79,6 +85,12 @@ extension EOptional: MustacheTransformable {
7985
case "exists":
8086
return false
8187
default:
88+
RenderingContext.current.addOrReplaceDiagnostic(
89+
.invalidTransform(
90+
transform: name,
91+
normalizedTypeName: Self.normalizedTypeName
92+
)
93+
)
8294
return nil
8395
}
8496
case let .some(value):
@@ -87,8 +99,24 @@ extension EOptional: MustacheTransformable {
8799
return true
88100
default:
89101
if let value = value as? MustacheTransformable {
90-
return value.transform(name)
102+
if let transformed = value.transform(name) {
103+
return transformed
104+
} else {
105+
RenderingContext.current.addOrReplaceDiagnostic(
106+
.invalidTransform(
107+
transform: name,
108+
normalizedTypeName: bestEffortTypeName(Wrapped.self)
109+
)
110+
)
111+
return nil
112+
}
91113
} else {
114+
RenderingContext.current.addOrReplaceDiagnostic(
115+
.invalidTransform(
116+
transform: name,
117+
normalizedTypeName: Self.normalizedTypeName
118+
)
119+
)
92120
return nil
93121
}
94122
}

Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ extension EOptionalsArray: CustomStringConvertible {
2929
}
3030
}
3131

32+
extension EOptionalsArray: WithNormalizedTypeName {
33+
static var normalizedTypeName: String {
34+
"[Optional<\(bestEffortTypeName(Element.self))>]"
35+
}
36+
}
37+
3238
extension EOptionalsArray: CustomReflectable {
3339
var customMirror: Mirror {
3440
Mirror(reflecting: self.underlying)
@@ -40,6 +46,7 @@ extension EOptionalsArray: MustacheTransformable {
4046
if let defaultTransformed = self.underlying.transform(name) {
4147
return convertToCustomTypesIfPossible(defaultTransformed)
4248
} else {
49+
RenderingContext.current.cleanDiagnostic()
4350
switch name {
4451
case "joined":
4552
let joined = self.underlying
@@ -68,6 +75,12 @@ extension EOptionalsArray: MustacheTransformable {
6875
}
6976
return EArray<EKeyValue>(underlying: split)
7077
default:
78+
RenderingContext.current.addOrReplaceDiagnostic(
79+
.invalidTransform(
80+
transform: name,
81+
normalizedTypeName: Self.normalizedTypeName
82+
)
83+
)
7184
return nil
7285
}
7386
}

Sources/EnumeratorMacroImpl/Types/EParameter.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ struct EParameter {
1919
}
2020
}
2121

22+
extension EParameter: WithNormalizedTypeName {
23+
static var normalizedTypeName: String {
24+
"Parameter"
25+
}
26+
}
27+
2228
private extension TypeSyntax {
2329
var isOptional: Bool {
2430
switch self.kind {

Sources/EnumeratorMacroImpl/Types/EParameters.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ extension EParameters: CustomStringConvertible {
1414
}
1515
}
1616

17+
extension EParameters: WithNormalizedTypeName {
18+
static var normalizedTypeName: String {
19+
"[Parameter]"
20+
}
21+
}
22+
1723
extension EParameters: Sequence, MustacheSequence {
1824
func makeIterator() -> Array<EParameter>.Iterator {
1925
self.underlying.makeIterator()
@@ -31,6 +37,7 @@ extension EParameters: MustacheTransformable {
3137
if let defaultTransformed = self.underlying.transform(name) {
3238
return convertToCustomTypesIfPossible(defaultTransformed)
3339
} else {
40+
RenderingContext.current.cleanDiagnostic()
3441
switch name {
3542
case "names":
3643
let names = self
@@ -69,6 +76,12 @@ extension EParameters: MustacheTransformable {
6976
return array
7077
}
7178
default:
79+
RenderingContext.current.addOrReplaceDiagnostic(
80+
.invalidTransform(
81+
transform: name,
82+
normalizedTypeName: Self.normalizedTypeName
83+
)
84+
)
7285
return nil
7386
}
7487
}

Sources/EnumeratorMacroImpl/Types/EString.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ extension EString: CustomStringConvertible {
1414
}
1515
}
1616

17+
extension EString: WithNormalizedTypeName {
18+
static var normalizedTypeName: String {
19+
"String"
20+
}
21+
}
22+
1723
extension EString: CustomReflectable {
1824
var customMirror: Mirror {
1925
self.underlying.customMirror
@@ -25,6 +31,7 @@ extension EString: MustacheTransformable {
2531
if let defaultTransformed = self.underlying.transform(name) {
2632
return convertToCustomTypesIfPossible(defaultTransformed)
2733
} else {
34+
RenderingContext.current.cleanDiagnostic()
2835
switch name {
2936
case "firstCapitalized":
3037
if self.isEmpty || self[self.startIndex].isUppercase {
@@ -60,6 +67,12 @@ extension EString: MustacheTransformable {
6067
value: EString(split.count > 1 ? split[1] : "")
6168
)
6269
default:
70+
RenderingContext.current.addOrReplaceDiagnostic(
71+
.invalidTransform(
72+
transform: name,
73+
normalizedTypeName: Self.normalizedTypeName
74+
)
75+
)
6376
return nil
6477
}
6578
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
protocol WithNormalizedTypeName {
3+
static var normalizedTypeName: String { get }
4+
}
5+
6+
func bestEffortTypeName<T>(_ type: T.Type = T.self) -> String {
7+
switch type {
8+
case let customType as WithNormalizedTypeName.Type:
9+
customType.normalizedTypeName
10+
default:
11+
Swift._typeName(type, qualified: false)
12+
}
13+
}

0 commit comments

Comments
 (0)