fix(core): promote private Codable witnesses to fileprivate#26
Merged
Conversation
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.
Contributor
There was a problem hiding this comment.
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.witnessSafeto promoteprivate→fileprivatefor synthesized Codable witnesses. - Applied
witnessSafein both macro generation paths (extension-based init generation and member-based encode/decode generation). - Added regression macro-expansion tests covering
private/fileprivatestructs andprivateclasses.
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 } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@Codable(and@Decodable/@Encodable) on aprivatetype generatedprivate init(from:)/private func encode(to:), which fails to compile:A protocol-requirement witness must be at least as accessible as its enclosing type. A top-level
privatetype is effectivelyfileprivatefor conformance purposes, so aprivatewitness can't satisfyCodable.Changes
witnessSafehelper onDeclModifierSyntaxthat promotesprivate→fileprivate(preserving the original token's trivia) and leaves every other access level untouched. This mirrors what the Swift compiler does for its own synthesizedCodableconformance.CodableMacro.swift:init(from:))decodeModifiers/encodeModifiersfor structs and classes)@Codable,@Decodable, and@Encodableall route throughCodableMacro, the single fix covers all three.Test plan
private struct,fileprivate struct,private class, andprivate@Decodable/@Encodable.🤖 Generated with Claude Code