Skip to content

fix(core): promote private Codable witnesses to fileprivate#26

Merged
WendellXY merged 1 commit into
mainfrom
fix/private-codable-witness-access
May 26, 2026
Merged

fix(core): promote private Codable witnesses to fileprivate#26
WendellXY merged 1 commit into
mainfrom
fix/private-codable-witness-access

Conversation

@WendellXY
Copy link
Copy Markdown
Owner

Summary

@Codable (and @Decodable / @Encodable) on a private type generated private init(from:) / private func encode(to:), which fails to compile:

error: initializer 'init(from:)' must be as accessible as its enclosing type
       because it matches a requirement in protocol 'Decodable'
note: mark the initializer as 'fileprivate' to satisfy the requirement

A protocol-requirement witness must be at least as accessible as its enclosing type. A top-level private type is effectively fileprivate for conformance purposes, so a private witness can't satisfy Codable.

Changes

  • Add a witnessSafe helper on DeclModifierSyntax that promotes privatefileprivate (preserving the original token's trivia) and leaves every other access level untouched. This mirrors what the Swift compiler does for its own synthesized Codable conformance.
  • Apply it in both generation paths in CodableMacro.swift:
    • ExtensionMacro path (struct init(from:))
    • MemberMacro path (decodeModifiers / encodeModifiers for structs and classes)
  • Since @Codable, @Decodable, and @Encodable all route through CodableMacro, the single fix covers all three.

Test plan

  • Reproduced the original compile failure; confirmed gone after the fix.
  • End-to-end decode/encode verified for private struct, fileprivate struct, private class, and private @Decodable / @Encodable.
  • Added regression tests: private struct, fileprivate struct, private class.
  • Full suite passes: 297 tests (was 294).

🤖 Generated with Claude Code

A `private` type produced `private init(from:)` / `private func encode(to:)`,
which the compiler rejects because a witness matching a protocol requirement
must be as accessible as its enclosing type. A top-level `private` type is
effectively `fileprivate` for conformance purposes, so the `private` witness
fails to satisfy `Codable`.

Add a `witnessSafe` helper that promotes `private` to `fileprivate` (preserving
trivia) and leaves all other access levels unchanged, mirroring the compiler's
own synthesized Codable conformance. Apply it in both the extension and member
generation paths, covering @codable, @decodable, and @encodable.

Adds regression tests for private struct, fileprivate struct, and private class.
Copilot AI review requested due to automatic review settings May 26, 2026 10:19
@WendellXY WendellXY self-assigned this May 26, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a Swift access-control issue in the @Codable / @Decodable / @Encodable macros where applying the macro to a private type could synthesize private protocol-requirement witnesses (init(from:) / encode(to:)) that fail to compile. The change introduces a helper to promote private witnesses to fileprivate (mirroring the compiler’s Codable synthesis behavior) and adds regression tests.

Changes:

  • Added DeclModifierSyntax.witnessSafe to promote privatefileprivate for synthesized Codable witnesses.
  • Applied witnessSafe in both macro generation paths (extension-based init generation and member-based encode/decode generation).
  • Added regression macro-expansion tests covering private/fileprivate structs and private classes.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
Tests/CodableKitTests/CodableMacroTests+struct.swift Adds regression tests asserting private struct witnesses are promoted to fileprivate and fileprivate remains unchanged.
Tests/CodableKitTests/CodableMacroTests+class.swift Adds regression test asserting private class witness requirements use fileprivate access.
Sources/CodableKitMacros/SwiftSyntaxHelper.swift Introduces DeclModifierSyntax.witnessSafe helper for access-level promotion while preserving trivia.
Sources/CodableKitMacros/CodableMacro.swift Uses witnessSafe when generating synthesized init/encode members.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +50 to +58
/// The access modifier to apply to synthesized protocol witnesses (`init(from:)` / `encode(to:)`).
///
/// A `private` member matching a protocol requirement must be "as accessible as its enclosing
/// type". For a `private` type the enclosing type is effectively `fileprivate`, so a `private`
/// witness fails to satisfy the `Codable` requirement. Promoting `private` to `fileprivate`
/// mirrors what the compiler does for its own synthesized `Codable` conformance. All other
/// access levels are already as accessible as the type and are left unchanged.
internal var witnessSafe: DeclModifierSyntax {
guard name.text == TokenSyntax.keyword(.private).text else { return self }
@WendellXY WendellXY merged commit 030f607 into main May 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants