Skip to content

Add support for conditionally compiled protocol members#137

Merged
graycampbell merged 15 commits intomainfrom
136-add-ifconfigdeclsyntax-support
Jan 31, 2026
Merged

Add support for conditionally compiled protocol members#137
graycampbell merged 15 commits intomainfrom
136-add-ifconfigdeclsyntax-support

Conversation

@bfahey
Copy link
Copy Markdown
Contributor

@bfahey bfahey commented Jan 13, 2026

📝 Summary

Protocols with #if/#elseif/#else/#endif blocks now have their members correctly mocked with the conditional compilation structure preserved. This includes support for methods, properties, initializers, and associated types within conditional blocks.

What changed

  • Conditional members are preserved structurally
    • The macro now preserves the full #if/#elseif/#else structure when generating mocked initializers, properties, and methods.
  • Associated types under #if are handled safely
    • Associated types declared inside conditional compilation blocks are extracted as generic parameters on the mock.
    • When associated types have branch-specific constraints (inheritance or where clauses), the mock no longer hardcodes a single constraint.
    • Instead, the mock conditionally conforms to the protocol via per-clause extension declarations with the correct where constraints for each branch.
  • Associated-type generic where clauses are respected
    • Constraints like where Base.Element: Equatable are now correctly carried into conditional conformance extensions.
  • Conditional conformance is only used when necessary
    • If conditional associated types are unconstrained across branches, the mock simply adds the generic parameter and conforms to the protocol unconditionally.

Known limitations / follow-ups

These cases are intentionally not handled yet and will require a more holistic approach in a future PR:

  • Multiple independent top-level #if blocks with constrained associated types
    • Correct handling requires combining constraints across all active branches to avoid duplicate protocol conformances.
  • Nested #if blocks containing associated types
    • Supporting this correctly requires recursive traversal and per-configuration constraint aggregation.
  • Cross-branch semantic equivalence of constraints
    • The macro does not attempt to detect when constraints are semantically identical but syntactically different (e.g. Equatable & Hashable vs Hashable & Equatable).

These deferred cases all require branch-combination logic and are planned to be addressed together in a follow-up. An issue (#139) has been created to track these edge cases.

🛠️ Type of Change

  • Bug fix (change that fixes an issue)
  • New feature (change that adds functionality)
  • Breaking change (bug fix or feature that is not backwards compatible)
  • Documentation (DocC, API docs, markdown files, templates, etc.)
  • Testing (new tests, updated tests, etc.)
  • Refactoring or code formatting (no logic changes)
  • Updating dependencies (Swift packages, Homebrew, etc.)
  • CI/CD (change to automated workflows)
  • Chore (other maintenance)

🧪 How Has This Been Tested?

  • New unit tests have been added.
  • All existing unit tests still pass.

🔗 Related PRs or Issues

Closes #136

✅ Checklist

  • I have added relevant tests
  • I have verified all tests pass
  • I have formatted my code using SwiftFormat
  • I have updated documentation (if needed)
  • I have added the appropriate label to my PR
  • I have read the contributing guidelines
  • I agree to follow this project's Code of Conduct

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 13, 2026

Codecov Report

❌ Patch coverage is 99.23664% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.34%. Comparing base (ef30af4) to head (5e1561b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...MockingMacros/Macros/MockedMacro/MockedMacro.swift 99.23% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #137      +/-   ##
==========================================
+ Coverage   69.76%   73.34%   +3.58%     
==========================================
  Files          73       73              
  Lines        3678     3846     +168     
==========================================
+ Hits         2566     2821     +255     
+ Misses       1112     1025      -87     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bfahey bfahey added the enhancement New feature or request label Jan 13, 2026
@bfahey bfahey changed the title Add IfConfigDeclSyntax support for conditional compilation in protocols Add IfConfigDeclSyntax support for conditional compilation Jan 13, 2026
Comment thread Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_IfConfigDeclTests.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Copy link
Copy Markdown
Collaborator

@graycampbell graycampbell left a comment

Choose a reason for hiding this comment

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

Some comments/suggestions from my first pass through this

Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
@graycampbell graycampbell force-pushed the 136-add-ifconfigdeclsyntax-support branch from a0fc588 to dce8d14 Compare January 15, 2026 16:40
@graycampbell graycampbell changed the base branch from main to formatting/run-swiftformat January 15, 2026 16:41
Comment on lines +151 to +152
var seenNames: Set<String> = []
let uniqueAssociatedTypes = associatedTypeDeclarations.filter { decl in
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
var seenNames: Set<String> = []
let uniqueAssociatedTypes = associatedTypeDeclarations.filter { decl in
var seenNames = Set<String>()
let uniqueAssociatedTypes = associatedTypeDeclarations.filter {
seenNames.insert($0.name.text).inserted
}

Q: Does order matter? If not then Set(associatedTypeDeclarations) will work

Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Copy link
Copy Markdown
Collaborator

@graycampbell graycampbell left a comment

Choose a reason for hiding this comment

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

There's a hole in how all of this is implemented at the moment. See my comment below about the mock's generic parameter constraints.

Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
Base automatically changed from formatting/run-swiftformat to main January 16, 2026 20:34
bfahey and others added 6 commits January 16, 2026 14:35
Protocols with #if/#elseif/#else/#endif blocks now have their members
correctly mocked with the conditional compilation structure preserved.
This includes support for methods, properties, initializers, and
associated types within conditional blocks.

Closes #136

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…sted conditionals

- Move ifConfig processing inline in mockMemberBlock to match structure of other declaration types
- Add unit tests for OR conditions (#if DEBUG || TESTFLIGHT)
- Add unit tests for nested #if conditionals

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract mockMemberBlockItems helper that works on MemberBlockItemListSyntax
- mockMemberBlock delegates to shared helper
- mockIfConfigDeclaration uses shared helper directly
- Remove mockIfConfigClause entirely (-46 lines)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
bfahey and others added 4 commits January 16, 2026 14:35
When a protocol defines the same associated type with different
constraints across #if branches (e.g., Item: Equatable in DEBUG vs
Item: Hashable otherwise), generate a mock class without constraints
plus conditional extensions that conform with the appropriate
constraints for each branch.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@graycampbell graycampbell force-pushed the 136-add-ifconfigdeclsyntax-support branch from 0a1efbf to 835b842 Compare January 16, 2026 20:38
Copy link
Copy Markdown
Collaborator

@graycampbell graycampbell left a comment

Choose a reason for hiding this comment

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

There are still some issues with this implementation:

1) Nested #if blocks are not detected

conflictingConditionalClauses only checks for AssociatedTypeDeclSyntax that are direct children of each IfConfigDeclSyntax clause.

If a clause contains another #if (i.e. a nested IfConfigDeclSyntax), this finds zero associated types and returns nil, so conditional conformance is not triggered.

Example:

protocol SomeCollection {
    #if DEBUG
        #if os(iOS)
        associatedtype Item: Hashable
        #else
        associatedtype Item: Equatable
        #endif
    #else
        #if os(iOS)
        associatedtype Item: Sendable
        #else
        associatedtype Item: Codable
        #endif
    #endif
}

Here, the outer #if clauses only contain another #if, so no associated types are detected at that level.

2) Associated type generic where clauses are missed

whereClause(from:) only extracts constraints from the associated type’s inheritance clause and ignores the associated type’s own genericWhereClause.

For example:

protocol SomeCollection {
    #if os(iOS)
    associatedtype Base: RandomAccessCollection & Equatable where Base.Element: Equatable
    #elseif os(macOS)
    associatedtype Base: RandomAccessCollection & Hashable where Base.Element: Hashable
    #else
    associatedtype Base: RandomAccessCollection & Codable where Base.Element: Codable
    #endif
}

In this scenario, the generated extensions that provide conditional conformance won't include the generic where clauses from the associate type declarations, so conditional conformance will be incomplete.

Comment thread Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift Outdated
@graycampbell graycampbell changed the title Add IfConfigDeclSyntax support for conditional compilation Add support for conditionally compiled protocol members Jan 30, 2026
@graycampbell graycampbell merged commit 323f413 into main Jan 31, 2026
9 checks passed
@graycampbell graycampbell deleted the 136-add-ifconfigdeclsyntax-support branch January 31, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add support for conditionally compiled protocol members

4 participants