Skip to content

Commit

Permalink
feat: added externally tagged enum support
Browse files Browse the repository at this point in the history
  • Loading branch information
soumyamahunt committed Jan 9, 2024
1 parent 4bfeac3 commit c1097bb
Show file tree
Hide file tree
Showing 59 changed files with 2,770 additions and 309 deletions.
7 changes: 5 additions & 2 deletions Sources/CodableMacroPlugin/Attributes/Codable/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ struct Codable: Attribute {
/// attached declaration.
///
/// Builds diagnoser that validates attached declaration
/// is `struct` or `class` declaration and macro
/// is `struct`/`class`/`enum` declaration and macro
/// usage is not duplicated for the same declaration.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntaxes: StructDeclSyntax.self, ClassDeclSyntax.self)
expect(
syntaxes: StructDeclSyntax.self, ClassDeclSyntax.self,
EnumDeclSyntax.self
)
cantDuplicate()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,23 @@ struct CodingKeys: PeerAttribute {
/// has `Codable` macro attached and macro usage
/// is not duplicated for the same declaration.
///
/// For enum case declarations this attribute can be attached
/// without `Codable` macro.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
mustBeCombined(with: Codable.self)
cantDuplicate()
`if`(
isStruct || isClass || isEnum,
mustBeCombined(with: Codable.self),
else: expect(syntaxes: EnumCaseDeclSyntax.self)
)
}
}
}

extension Registration {
extension Registration where Key == [String] {
/// Update current registration `CodingKey` path data.
///
/// New registration is updated with the transformed `CodingKey` path
Expand All @@ -65,6 +72,6 @@ extension Registration {
) -> Self where D: AttributableDeclSyntax {
guard let attr = CodingKeys(from: decl) else { return self }
let strategy = attr.strategy
return self.updating(with: strategy.transform(keyPath: self.keyPath))
return self.updating(with: strategy.transform(keyPath: self.key))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,23 @@ struct IgnoreCodingInitialized: PeerAttribute {
/// has `Codable` macro attached and macro usage
/// is not duplicated for the same declaration.
///
/// For enum case declarations this attribute can be attached
/// without `Codable` macro.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
mustBeCombined(with: Codable.self)
shouldNotDuplicate()
`if`(
isStruct || isClass || isEnum,
mustBeCombined(with: Codable.self),
else: expect(syntaxes: EnumCaseDeclSyntax.self)
)
}
}
}

extension Registration where Var: NamedVariable {
extension Registration where Var: ValuedVariable {
/// Update registration whether decoding/encoding to be ignored.
///
/// New registration is updated with decoding and encoding condition
Expand All @@ -57,7 +64,7 @@ extension Registration where Var: NamedVariable {
/// data.
func checkInitializedCodingIgnored<D: AttributableDeclSyntax>(
attachedAt decl: D
) -> Registration<Decl, ConditionalCodingVariable<Var>> {
) -> Registration<Decl, Key, ConditionalCodingVariable<Var>> {
typealias Output = ConditionalCodingVariable<Var>
let attr = IgnoreCodingInitialized(from: decl)
let code = attr != nil ? self.variable.value == nil : nil
Expand Down
69 changes: 69 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/CodedAs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@_implementationOnly import SwiftSyntax

/// Attribute type for `CodedAs` macro-attribute.
///
/// This type can validate`CodedAs` macro-attribute
/// usage and extract data for `Codable` macro to
/// generate implementation.
struct CodedAs: PropertyAttribute {
/// The node syntax provided
/// during initialization.
let node: AttributeSyntax

/// The alternate value expression provided.
var expr: ExprSyntax {
return node.arguments!
.as(LabeledExprListSyntax.self)!.first!.expression
}

/// Creates a new instance with the provided node.
///
/// The initializer fails to create new instance if the name
/// of the provided node is different than this attribute.
///
/// - Parameter node: The attribute syntax to create with.
/// - Returns: Newly created attribute instance.
init?(from node: AttributeSyntax) {
guard
node.attributeName.as(IdentifierTypeSyntax.self)!
.description == Self.name
else { return nil }
self.node = node
}

/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is an enum-case declaration.
/// * Macro usage is not duplicated for the same declaration.
/// * This attribute isn't used combined with `IgnoreCoding`
/// attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntaxes: EnumCaseDeclSyntax.self)
cantDuplicate()
cantBeCombined(with: IgnoreCoding.self)
}
}
}

extension Registration where Key == ExprSyntax?, Decl: AttributableDeclSyntax {
/// Update registration with alternate value expression data.
///
/// New registration is updated with value expression data that will be
/// used for decoding/encoding, if provided and registration doesn't
/// already have a value.
///
/// - Returns: Newly built registration with value expression data.
func checkForAlternateValue() -> Self {
guard
self.key == nil,
let attr = CodedAs(from: self.decl)
else { return self }
return self.updating(with: attr.expr)
}
}
4 changes: 2 additions & 2 deletions Sources/CodableMacroPlugin/Attributes/CodedBy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ where
/// used for decoding/encoding, if provided.
///
/// - Returns: Newly built registration with helper expression data.
func useHelperCoderIfExists() -> Registration<Decl, CodedByOutput> {
guard let attr = CodedBy(from: self.declaration)
func useHelperCoderIfExists() -> Registration<Decl, Key, CodedByOutput> {
guard let attr = CodedBy(from: self.decl)
else { return self.updating(with: self.variable.any) }
let newVar = self.variable.with(helper: attr.expr)
return self.updating(with: newVar.any)
Expand Down
4 changes: 2 additions & 2 deletions Sources/CodableMacroPlugin/Attributes/Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ where
/// used for decoding failure and memberwise initializer(s), if provided.
///
/// - Returns: Newly built registration with default expression data.
func addDefaultValueIfExists() -> Registration<Decl, DefOutput> {
guard let attr = Default(from: self.declaration)
func addDefaultValueIfExists() -> Registration<Decl, Key, DefOutput> {
guard let attr = Default(from: self.decl)
else { return self.updating(with: self.variable.any) }
let newVar = self.variable.with(default: attr.expr)
return self.updating(with: newVar.any)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,25 @@ struct IgnoreCoding: PropertyAttribute {
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Attached variable declaration has default
/// initialization or variable is a computed property.
/// * This attribute isn't used combined with `CodedIn`
/// and `CodedAt` attribute.
/// * Additionally, warning generated if macro usage
/// is duplicated for the same declaration.
/// The following conditions are checked by the built diagnoser:
/// * Attached declaration is a variable or enum case declaration.
/// * Attached variable declaration has default initialization or
/// variable is a computed property.
/// * This attribute isn't used combined with `CodedIn` and
/// `CodedAt` attribute.
/// * Additionally, warning generated if macro usage is duplicated
/// for the same declaration.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
attachedToInitializedVariable()
cantBeCombined(with: CodedIn.self)
cantBeCombined(with: CodedAt.self)
shouldNotDuplicate()
`if`(
isVariable, attachedToInitializedVariable(),
else: expect(syntaxes: EnumCaseDeclSyntax.self)
)
}
}
}
Expand All @@ -69,12 +71,11 @@ extension Registration where Decl: AttributableDeclSyntax {
///
/// - Returns: Newly built registration with conditional decoding/encoding
/// data.
func checkCodingIgnored() -> Registration<Decl, ConditionalOutput> {
func checkCodingIgnored() -> Registration<Decl, Key, ConditionalOutput> {
typealias Output = ConditionalOutput
let declaration = self.declaration
let ignoreCoding = IgnoreCoding(from: declaration) != nil
let ignoreDecoding = IgnoreDecoding(from: declaration) != nil
let ignoreEncoding = IgnoreEncoding(from: declaration) != nil
let ignoreCoding = IgnoreCoding(from: self.decl) != nil
let ignoreDecoding = IgnoreDecoding(from: self.decl) != nil
let ignoreEncoding = IgnoreEncoding(from: self.decl) != nil
let decode = !ignoreCoding && !ignoreDecoding
let encode = !ignoreCoding && !ignoreEncoding
let options = Output.Options(decode: decode, encode: encode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ struct IgnoreDecoding: PropertyAttribute {
self.node = node
}

/// Builds diagnoser that can validate this macro
/// attached declaration.
/// Builds diagnoser that can validate this macro attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Attached variable declaration has default
/// initialization or variable is a computed property.
/// * Additionally, warning generated if macro usage
/// is duplicated for the same declaration.
/// * Additionally, warning also generated if this
/// attribute is used combined with `IgnoreCoding`
/// attribute.
/// The following conditions are checked by the built diagnoser:
/// * Attached declaration is a variable or enum case declaration.
/// * Attached variable declaration has default initialization or
/// variable is a computed property.
/// * Additionally, warning generated if macro usage is duplicated
/// for the same declaration.
/// * Additionally, warning also generated if this attribute is used
/// combined with `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
attachedToInitializedVariable()
shouldNotDuplicate()
shouldNotBeCombined(with: IgnoreCoding.self)
`if`(
isVariable, attachedToInitializedVariable(),
else: expect(syntaxes: EnumCaseDeclSyntax.self)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,19 @@ struct IgnoreEncoding: PropertyAttribute {
self.node = node
}

/// Builds diagnoser that can validate this macro
/// attached declaration.
/// Builds diagnoser that can validate this macro attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Additionally, warning generated if macro usage
/// is duplicated for the same declaration.
/// * Additionally, warning also generated if this
/// attribute is used combined with `IgnoreCoding`
/// attribute.
/// The following conditions are checked by the built diagnoser:
/// * Attached declaration is a variable or enum case declaration.
/// * Additionally, warning generated if macro usage is duplicated
/// for the same declaration.
/// * Additionally, warning also generated if this attribute is used
/// combined with `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntaxes: VariableDeclSyntax.self)
expect(syntaxes: VariableDeclSyntax.self, EnumCaseDeclSyntax.self)
shouldNotDuplicate()
shouldNotBeCombined(with: IgnoreCoding.self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ extension CodedIn: KeyPathProvider {
}
}

extension Registration where Decl: AttributableDeclSyntax {
extension Registration where Key == [String] {
/// Update registration with `CodingKey` path data.
///
/// New registration is updated with the provided `CodingKey` path from provider,
Expand All @@ -96,13 +96,13 @@ extension Registration where Decl: AttributableDeclSyntax {
/// - Returns: Newly built registration with additional `CodingKey` path data.
func registerKeyPath(
provider: KeyPathProvider
) -> Registration<Decl, KeyedVariable<Var>> {
) -> Registration<Decl, Key, KeyedVariable<Var>> {
typealias Output = KeyedVariable<Var>
let options = Output.Options(code: provider.provided)
let newVar = Output(base: self.variable, options: options)
let output = self.updating(with: newVar)
guard provider.provided else { return output }
let updatedPath = provider.keyPath(withExisting: self.keyPath)
let updatedPath = provider.keyPath(withExisting: self.key)
return output.updating(with: updatedPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@_implementationOnly import SwiftSyntax

/// Validates provided syntax is of current declaration.
///
/// Checks provided syntax type is of the syntax type `S`.
struct DeclarationCondition<S: SyntaxProtocol>: DiagnosticCondition {
/// Determines whether provided syntax passes validation.
///
/// This type checks the provided syntax with current data for validation.
/// Checks provided syntax type is of the syntax type `S`.
///
/// - Parameter syntax: The syntax to validate.
/// - Returns: Whether syntax passes validation.
func satisfied(by syntax: some SyntaxProtocol) -> Bool {
return syntax.is(S.self)
}
}

extension Attribute {
/// Whether declaration is `struct` declaration.
///
/// Uses `DeclarationCondition` to check syntax type.
var isStruct: DeclarationCondition<StructDeclSyntax> { .init() }
/// Whether declaration is `class` declaration.
///
/// Uses `DeclarationCondition` to check syntax type.
var isClass: DeclarationCondition<ClassDeclSyntax> { .init() }
/// Whether declaration is `enum` declaration.
///
/// Uses `DeclarationCondition` to check syntax type.
var isEnum: DeclarationCondition<EnumDeclSyntax> { .init() }
/// Whether declaration is `actor` declaration.
///
/// Uses `DeclarationCondition` to check syntax type.
var isActor: DeclarationCondition<ActorDeclSyntax> { .init() }
/// Whether declaration is `variable` declaration.
///
/// Uses `DeclarationCondition` to check syntax type.
var isVariable: DeclarationCondition<VariableDeclSyntax> { .init() }
}

0 comments on commit c1097bb

Please sign in to comment.