Skip to content

fix: Fixed compiler error when using @PolymorphicEnumCodable macro on enums defined inside nested types#22

Merged
ElonPark merged 5 commits intomainfrom
bug/elon/fix-enums-defined-inside-nested-types-bug
Apr 7, 2026
Merged

fix: Fixed compiler error when using @PolymorphicEnumCodable macro on enums defined inside nested types#22
ElonPark merged 5 commits intomainfrom
bug/elon/fix-enums-defined-inside-nested-types-bug

Conversation

@ElonPark
Copy link
Copy Markdown
Member

@ElonPark ElonPark commented Apr 7, 2026

Background (Required)

Changes

  • Changed macro attachment from @attached(extension) to @attached(member) + @attached(extension, conformances:) for PolymorphicEnumCodableMacro, PolymorphicEnumDecodableMacro, PolymorphicEnumEncodableMacro
  • Moved PolymorphicMetaCodingKey, init(from:), encode(to:) generation from extension body to member declarations
  • Extension macro now only provides protocol conformance with an empty body
  • Added nested type macro expansion tests for all three macros

Testing Methods

  • Run existing macro expansion tests to verify backward compatibility
  • Test the macro on enums defined inside nested types
swift test --filter "PolymorphicEnumCodableMacroTests|PolymorphicEnumDecodableMacroTests|PolymorphicEnumEncodableMacroTests"

Review Notes

Summary by CodeRabbit

  • Refactor

    • Reworked how polymorphic enum Codable behavior is generated internally to improve structure and maintainability.
  • Documentation

    • Reformatted inline developer docs to doc-comment style for consistency.
  • Tests

    • Updated expected macro expansion outputs and added tests covering nested-enum scenarios to validate the new generation approach.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Refactors three polymorphic codable macros from ExtensionMacro to MemberMacro, splitting generated member declarations (meta coding key, init, encode) from the empty extension conformance. Interface attributes and tests were updated; nested-enum expansion behavior was adjusted and covered by new tests.

Changes

Cohort / File(s) Summary
Macro Interface Declarations
Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumCodable.swift, Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumDecodable.swift, Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumEncodable.swift
Split single @attached(extension, ...) into @attached(member, names: ...) for generated members and a separate @attached(extension, conformances: ...) for protocol conformance. Converted block comments to /// doc-comments and added trailing commas to parameter lists.
Macro Implementation Migration
Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift, .../PolymorphicEnumDecodableMacro.swift, .../PolymorphicEnumEncodableMacro.swift
Primary macro types changed from ExtensionMacro to MemberMacro and now return [DeclSyntax] via providingMembersOf. Added secondary ExtensionMacro conformance that validates attachment and returns an empty extension Type: Protocol {}. Generated member fragments now emit meta key, init(from:), and/or encode(to:) as standalone members.
Tests & Expectations
Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumCodableMacroTests.swift, .../PolymorphicEnumDecodableMacroTests.swift, .../PolymorphicEnumEncodableMacroTests.swift
Updated expected macro expansions to show standalone empty extension Type: Codable/Decodable/Encodable {} after generated members, normalized formatting/whitespace, adjusted assertMacroExpansion args, duplicated some diagnostic specs, and added nested-enum test cases (e.g., testPolymorphicEnum*InsideNestedType).

Sequence Diagram

sequenceDiagram
    participant Compiler
    participant MacroDecl as Macro Declaration
    participant MemberMacro as MemberMacro
    participant ExtensionMacro as ExtensionMacro
    participant Output as Generated Code

    rect rgba(100,150,255,0.5)
    Note over Compiler,Output: MemberMacro + ExtensionMacro expansion split
    end

    Compiler->>MacroDecl: Parse attribute on enum
    activate MacroDecl
    MacroDecl->>MemberMacro: invoke providingMembersOf expansion
    activate MemberMacro
    MemberMacro->>Output: emit `PolymorphicMetaCodingKey`, `init(from:)`, `encode(to:)` members
    MemberMacro-->>MacroDecl: return [DeclSyntax]
    deactivate MemberMacro
    MacroDecl->>ExtensionMacro: invoke providingExtensionsOf expansion
    activate ExtensionMacro
    ExtensionMacro->>Output: emit `extension Type: Protocol {}` (empty body)
    ExtensionMacro-->>MacroDecl: return [ExtensionDeclSyntax]
    deactivate ExtensionMacro
    deactivate MacroDecl
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Improvement, Docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly describes the main fix: addressing a compiler error with @PolymorphicEnumCodable macros on enums in nested types.
Description check ✅ Passed The PR description follows the template with complete Background, Changes, Testing Methods, and Review Notes sections, providing clear context and implementation details.
Linked Issues check ✅ Passed The code changes fully address issue #19 by converting macros from @attached(extension) to @attached(member)+@attached(extension) pattern, enabling proper scope resolution for nested enum types.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing nested type support: macro interface updates, implementation refactoring, and corresponding test additions with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bug/elon/fix-enums-defined-inside-nested-types-bug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ElonPark ElonPark self-assigned this Apr 7, 2026
@ElonPark ElonPark added the Bug Something isn't working label Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumCodable.swift`:
- Around line 20-22: Finish the truncated doc comment for the
identifierCodingKey property in PolymorphicEnumCodable.swift: update the comment
that currently ends with "case of the enum during" so it completes the sentence
to mirror the Decodable variant's documentation (explain it's used to identify
the enum case during decoding/encoding), referencing the identifierCodingKey
property and the PolymorphicEnumCodable type so reviewers can locate and verify
the change.

In
`@Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumDecodable.swift`:
- Around line 19-21: The doc comment for the identifierCodingKey property in
PolymorphicEnumDecodable is truncated ("...case of the enum during"); update the
documentation to match the complete pattern used in the other interface files by
finishing the sentence to explain that this key is used to identify the specific
enum case during decoding, state the default value is "type", and mention it is
used when encoding/decoding polymorphic enums; locate the identifierCodingKey
documentation within the PolymorphicEnumDecodable declaration and replace the
truncated text with the full explanatory sentence.

In
`@Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumEncodable.swift`:
- Around line 19-21: The doc-comment for the identifierCodingKey property on
PolymorphicEnumEncodable is truncated mid-sentence; update the documentation to
complete the sentence and explain its purpose (e.g., "used to identify the
specific case of the enum during encoding and decoding"), ensuring the
doc-comment for identifierCodingKey and surrounding docs on the
PolymorphicEnumEncodable protocol/struct are complete and clear so consumers
understand how the type identifier is stored and used during encoding/decoding.

In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift`:
- Around line 56-68: The expansion currently only checks
declaration.is(EnumDeclSyntax.self) and always emits extension \(type.trimmed):
Codable {}, so change PolymorphicEnumCodableMacro.expansion to run the same
preflight/validation used by the member role (the check that validates case
layout and fallback configuration) after confirming the declaration is an enum;
if that validation fails, swallow the error and return [] (do not emit the
Codable extension) so only the member role reports the diagnostic; ensure you
call the existing validation routine (the same function invoked by the member
role) rather than duplicating logic, and add a negative expansion test that
asserts no Codable extension is emitted for an invalid case shape.

In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumEncodableMacro.swift`:
- Around line 33-35: The macro currently declares a PolymorphicMetaCodingKey
attachment but only emits the encode(to:) method; update
PolymorphicEnumEncodableMacro to also generate the PolymorphicMetaCodingKey enum
(matching the Decodable variant) so the macro contract is fulfilled: locate the
macro implementation that returns ["\(raw: encodeToEncoderSyntax)"] and extend
it to emit the enum declaration named PolymorphicMetaCodingKey (with the same
cases/structure used by the Decodable macro), ensuring the generated symbol name
PolymorphicMetaCodingKey and the encode(to:) implementation reference the same
coding-key cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5c74da97-52c6-4a63-b182-bd1d724951c2

📥 Commits

Reviewing files that changed from the base of the PR and between 59b2d2a and e6bf128.

📒 Files selected for processing (9)
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumCodable.swift
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumDecodable.swift
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumEncodable.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumDecodableMacro.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumEncodableMacro.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumCodableMacroTests.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumDecodableMacroTests.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumEncodableMacroTests.swift

ElonPark added 2 commits April 7, 2026 18:55
- Complete truncated identifierCodingKey doc comments in PolymorphicEnumCodable,
  PolymorphicEnumDecodable, and PolymorphicEnumEncodable interface files
- Remove unused named(PolymorphicMetaCodingKey) from PolymorphicEnumEncodable
  macro declaration since encode(to:) does not use PolymorphicMetaCodingKey
Add enum and argument validation to the ExtensionMacro role of
PolymorphicEnumCodable/Decodable/Encodable macros. Previously, when
the MemberMacro failed validation, the ExtensionMacro could still emit
protocol conformance without actual implementations, causing confusing
secondary compiler errors. Now both roles validate consistently and
throw on invalid input.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift (1)

19-28: 🛠️ Refactor suggestion | 🟠 Major

[HIGH] Extract a single preflight for both macro roles.

The enum/identifier/case/fallback preflight is copied in both expansions. That makes these roles easy to drift out of sync again, which can reintroduce the same member/extension desynchronization this PR is fixing. Move it into one helper or factory entry point and have both roles call it.

As per coding guidelines, "Evaluate separation of concerns between validation, generation, and expansion" and "Assess error handling and propagation across macro layers."

Also applies to: 64-70

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift`
around lines 19 - 28, The repeated enum/identifier/case/fallback preflight logic
in PolymorphicEnumCodableMacro should be consolidated into a single helper on
PolymorphicEnumCodableFactory (e.g., a new preflight(node:declaration:) that
returns the identifierCodingKey, caseInfos, and fallbackCaseName) and both macro
expansion paths should call that helper instead of duplicating calls to
validateIdentifierCodingKey(in:), extractCaseInfos(from:), and
validateFallbackCaseName(in:caseInfos:); update PolymorphicEnumCodableMacro to
use the new factory entry point and remove the duplicated blocks so both roles
share identical validation and error propagation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumEncodableMacro.swift`:
- Around line 39-55: Extract the duplicate validation into a shared helper
(e.g., PolymorphicEnumCodableFactory.validateAttachment(...) or
validatePolymorphicEnumAttachment(...)) that performs the enum check, calls
PolymorphicEnumCodableFactory.validateIdentifierCodingKey and
PolymorphicEnumCodableFactory.extractCaseInfos and returns the validated
EnumDeclSyntax (or cached result); then replace the current validation calls in
PolymorphicEnumEncodableMacro.expansion and the MemberMacro expansion with a
single call to that helper so diagnostics are emitted once and both roles still
prevent orphaned conformances.

---

Duplicate comments:
In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift`:
- Around line 19-28: The repeated enum/identifier/case/fallback preflight logic
in PolymorphicEnumCodableMacro should be consolidated into a single helper on
PolymorphicEnumCodableFactory (e.g., a new preflight(node:declaration:) that
returns the identifierCodingKey, caseInfos, and fallbackCaseName) and both macro
expansion paths should call that helper instead of duplicating calls to
validateIdentifierCodingKey(in:), extractCaseInfos(from:), and
validateFallbackCaseName(in:caseInfos:); update PolymorphicEnumCodableMacro to
use the new factory entry point and remove the duplicated blocks so both roles
share identical validation and error propagation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 73025f9a-efad-481b-aec7-ddfdcce12ee3

📥 Commits

Reviewing files that changed from the base of the PR and between e6bf128 and c97f340.

📒 Files selected for processing (9)
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumCodable.swift
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumDecodable.swift
  • Sources/KarrotCodableKit/PolymorphicCodable/Interface/PolymorphicEnumEncodable.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumCodableMacro.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumDecodableMacro.swift
  • Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumEncodableMacro.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumCodableMacroTests.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumDecodableMacroTests.swift
  • Tests/KarrotCodableMacrosTests/PolymorphicEnumCodableMacroTests/PolymorphicEnumEncodableMacroTests.swift

Comment on lines +39 to +55
extension PolymorphicEnumEncodableMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo _: [TypeSyntax],
in _: some MacroExpansionContext,
) throws -> [ExtensionDeclSyntax] {
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else {
throw CodableKitError.message("`@PolymorphicEnumEncodable` can only be attached to enums")
}

try PolymorphicEnumCodableFactory.validateIdentifierCodingKey(in: node)
_ = try PolymorphicEnumCodableFactory.extractCaseInfos(from: enumDecl)

return try [ExtensionDeclSyntax("extension \(type.trimmed): Encodable {}")]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

[MEDIUM] Both macro roles duplicate validation, causing duplicate diagnostics.

The MemberMacro and ExtensionMacro expansions independently perform the same validations (enum check, validateIdentifierCodingKey, extractCaseInfos). When validation fails, both roles throw, resulting in duplicate error messages shown to the user.

This is consistent with commit message noting "added validation to the ExtensionMacro role to prevent emitting orphaned protocol conformances when MemberMacro validation fails." However, consider extracting validation into a shared helper that caches results, or accept the duplicate diagnostics as an intentional design trade-off for preventing orphaned extensions.

The tests explicitly expect duplicate DiagnosticSpec entries, so this appears intentional—just flagging for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Sources/KarrotCodableKitMacros/PolymorphicEnumCodableMacros/PolymorphicEnumEncodableMacro.swift`
around lines 39 - 55, Extract the duplicate validation into a shared helper
(e.g., PolymorphicEnumCodableFactory.validateAttachment(...) or
validatePolymorphicEnumAttachment(...)) that performs the enum check, calls
PolymorphicEnumCodableFactory.validateIdentifierCodingKey and
PolymorphicEnumCodableFactory.extractCaseInfos and returns the validated
EnumDeclSyntax (or cached result); then replace the current validation calls in
PolymorphicEnumEncodableMacro.expansion and the MemberMacro expansion with a
single call to that helper so diagnostics are emitted once and both roles still
prevent orphaned conformances.

@ElonPark ElonPark merged commit 4aade81 into main Apr 7, 2026
4 checks passed
@ElonPark ElonPark deleted the bug/elon/fix-enums-defined-inside-nested-types-bug branch April 7, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compiler error when using @PolymorphicEnumCodable macro on enums defined inside nested types

1 participant