Skip to content

Commit

Permalink
add diagnosis for unavailable function usage, when possible
Browse files Browse the repository at this point in the history
  • Loading branch information
MahdiBM committed Jul 16, 2024
1 parent 8c79311 commit 403afe9
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 12 deletions.
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "db43e6862c0523c2f43b5b63513da705ad591422a0926a66ee8217e5062d2710",
"originHash" : "5fb3f3f652f6046ecafacd4cf94fd7cbc6347977cdfde61d767ba9728638b23b",
"pins" : [
{
"identity" : "swift-mustache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mahdibm/swift-mustache",
"location" : "https://github.com/hummingbird-project/swift-mustache",
"state" : {
"branch" : "mmbm-swift-6",
"revision" : "b3cd0bca48cd1883e52a1ed6b79dfccd64ff5952"
"revision" : "5bb66ac425ec25fd45e0cd7d3829d73e45096352",
"version" : "2.0.0-beta.2"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ let package = Package(
"510.0.0" ..< "610.0.0"
),
.package(
url: "https://github.com/mahdibm/swift-mustache",
branch: "mmbm-swift-6"
url: "https://github.com/hummingbird-project/swift-mustache",
from: "2.0.0-beta.2"
),
],
targets: [
Expand Down
24 changes: 19 additions & 5 deletions Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,25 @@ extension EnumeratorMacroType: MemberMacro {
let rendered = templates.compactMap {
(template, syntax) -> (rendered: String, syntax: StringLiteralExprSyntax)? in
do {
let rendered = try MustacheTemplate(
string: "{{%CONTENT_TYPE:TEXT}}\n" + template
).render([
"cases": cases
])
let rendered: String? = try RenderingContext.$current.withValue(.init()) {
let result = try MustacheTemplate(
string: "{{%CONTENT_TYPE:TEXT}}\n" + template
).render([
"cases": cases
])
if let diagnostic = RenderingContext.current.diagnostic {
context.addDiagnostics(
from: diagnostic,
node: syntax
)
return nil
} else {
return result
}
}
guard let rendered else {
return nil
}
return (rendered, syntax)
} catch {
let message: MacroError
Expand Down
8 changes: 8 additions & 0 deletions Sources/EnumeratorMacroImpl/MacroError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum MacroError: Error, CustomStringConvertible {
case couldNotFindLocationOfNode(syntax: String)
case mustacheTemplateError(message: String)
case internalError(String)
case invalidTransform(transform: String, normalizedTypeName: String)

var caseName: String {
switch self {
Expand All @@ -32,6 +33,8 @@ enum MacroError: Error, CustomStringConvertible {
"mustacheTemplateError"
case .internalError:
"internalError"
case .invalidTransform:
"invalidTransform"
}
}

Expand All @@ -55,6 +58,11 @@ enum MacroError: Error, CustomStringConvertible {
"Error while rendering the template: \(message)"
case let .internalError(message):
"An internal error occurred. Please file a bug report at https://github.com/mahdibm/enumerator-macro. Error:\n\(message)"
case let .invalidTransform(transform, normalizedTypeName):
"""
Invalid function call detected.
'\(normalizedTypeName)' doesn't have a function called '\(transform)'
"""
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/EnumeratorMacroImpl/RenderingContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// The macro works in a single thread so `@unchecked Sendable` is justified.
final class RenderingContext: @unchecked Sendable {
@TaskLocal static var current: RenderingContext!

var diagnostic: MacroError?

func cleanDiagnostic() {
self.diagnostic = nil
}

func addOrReplaceDiagnostic(_ error: MacroError) {
self.diagnostic = error
}
}
13 changes: 13 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ extension EArray: CustomStringConvertible {
}
}

extension EArray: WithNormalizedTypeName {
static var normalizedTypeName: String {
"[\(bestEffortTypeName(Element.self))]"
}
}

extension EArray: CustomReflectable {
var customMirror: Mirror {
Mirror(reflecting: self.underlying)
Expand All @@ -37,6 +43,7 @@ extension EArray: MustacheTransformable {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "joined":
let joined = self.underlying
Expand Down Expand Up @@ -68,6 +75,12 @@ extension EArray: MustacheTransformable {
let value = keyValues.first(where: { $0.key == name })?.value
return EOptional(value)
}
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/ECase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ struct ECase {
self.comments = .init(underlying: keyValueParts.map(EString.init))
}
}

extension ECase: WithNormalizedTypeName {
static var normalizedTypeName: String {
"Case"
}
}
12 changes: 12 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/ECases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ extension ECases: CustomStringConvertible {
self.underlying.description
}
}
extension ECases: WithNormalizedTypeName {
static var normalizedTypeName: String {
"[Case]"
}
}

extension ECases: Sequence, MustacheSequence {
func makeIterator() -> Array<ECase>.Iterator {
Expand All @@ -36,12 +41,19 @@ extension ECases: MustacheTransformable {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "filterNoParams":
return self.filter(\.parameters.underlying.underlying.isEmpty)
case "filterWithParams":
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EKeyValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ extension EKeyValue: CustomStringConvertible {
}
}

extension EKeyValue: WithNormalizedTypeName {
static var normalizedTypeName: String {
"KeyValue<String, String>"
}
}

extension EKeyValue: MustacheTransformable {
func transform(_ name: String) -> Any? {
switch name {
Expand All @@ -24,6 +30,12 @@ extension EKeyValue: MustacheTransformable {
case "value":
return self.value
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
30 changes: 29 additions & 1 deletion Sources/EnumeratorMacroImpl/Types/EOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ extension EOptional: CustomStringConvertible {
}
}

extension EOptional: WithNormalizedTypeName {
static var normalizedTypeName: String {
"Optional<\(bestEffortTypeName(Wrapped.self))>"
}
}

extension EOptional: MustacheTransformable {
func transform(_ name: String) -> Any? {
switch self {
Expand All @@ -79,6 +85,12 @@ extension EOptional: MustacheTransformable {
case "exists":
return false
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
case let .some(value):
Expand All @@ -87,8 +99,24 @@ extension EOptional: MustacheTransformable {
return true
default:
if let value = value as? MustacheTransformable {
return value.transform(name)
if let transformed = value.transform(name) {
return transformed
} else {
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: bestEffortTypeName(Wrapped.self)
)
)
return nil
}
} else {
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ extension EOptionalsArray: CustomStringConvertible {
}
}

extension EOptionalsArray: WithNormalizedTypeName {
static var normalizedTypeName: String {
"[Optional<\(bestEffortTypeName(Element.self))>]"
}
}

extension EOptionalsArray: CustomReflectable {
var customMirror: Mirror {
Mirror(reflecting: self.underlying)
Expand All @@ -40,6 +46,7 @@ extension EOptionalsArray: MustacheTransformable {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "joined":
let joined = self.underlying
Expand Down Expand Up @@ -68,6 +75,12 @@ extension EOptionalsArray: MustacheTransformable {
}
return EArray<EKeyValue>(underlying: split)
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EParameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ struct EParameter {
}
}

extension EParameter: WithNormalizedTypeName {
static var normalizedTypeName: String {
"Parameter"
}
}

private extension TypeSyntax {
var isOptional: Bool {
switch self.kind {
Expand Down
13 changes: 13 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ extension EParameters: CustomStringConvertible {
}
}

extension EParameters: WithNormalizedTypeName {
static var normalizedTypeName: String {
"[Parameter]"
}
}

extension EParameters: Sequence, MustacheSequence {
func makeIterator() -> Array<EParameter>.Iterator {
self.underlying.makeIterator()
Expand All @@ -31,6 +37,7 @@ extension EParameters: MustacheTransformable {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "names":
let names = self
Expand Down Expand Up @@ -69,6 +76,12 @@ extension EParameters: MustacheTransformable {
return array
}
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ extension EString: CustomStringConvertible {
}
}

extension EString: WithNormalizedTypeName {
static var normalizedTypeName: String {
"String"
}
}

extension EString: CustomReflectable {
var customMirror: Mirror {
self.underlying.customMirror
Expand All @@ -25,6 +31,7 @@ extension EString: MustacheTransformable {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "firstCapitalized":
if self.isEmpty || self[self.startIndex].isUppercase {
Expand Down Expand Up @@ -60,6 +67,12 @@ extension EString: MustacheTransformable {
value: EString(split.count > 1 ? split[1] : "")
)
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/WithNormalizedTypeName.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

protocol WithNormalizedTypeName {
static var normalizedTypeName: String { get }
}

func bestEffortTypeName<T>(_ type: T.Type = T.self) -> String {
switch type {
case let customType as WithNormalizedTypeName.Type:
customType.normalizedTypeName
default:
Swift._typeName(type, qualified: false)
}
}

0 comments on commit 403afe9

Please sign in to comment.