Fix mock code gen for dependency cycles through Instantiator boundaries#219
Merged
Fix mock code gen for dependency cycles through Instantiator boundaries#219
Conversation
When a child type receives its parent through an Instantiator boundary (partially-lazy cycle), the mock code gen was passing the optional mock parameter where a non-optional was expected. Now uses the production cycle-breaking pattern: reconstructs the cycled type inline from available scope bindings. Also adds mock tests for fully-lazy cycles and self-forwarding cycles, matching the production code gen test coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #219 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 40 40
Lines 6045 6055 +10
=========================================
+ Hits 6045 6055 +10
🚀 New features to boost your workflow:
|
…dowing Mock method parameters now use `externalLabel __safeDI_mock_externalLabel:` syntax. This prevents optional mock parameters from shadowing non-optional local bindings, which is critical for cycles through Instantiator boundaries where nested functions must capture the non-optional local via forward reference. Without this fix, a partially-lazy cycle (Parent → Instantiator<Child> → Child → Parent) would pass Player? where Player is expected, because the nested builder function captured the optional parameter instead of the non-optional local. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closure-typed defaults use @escaping (not @autoclosure), so they were previously passed directly to the init without a local binding. With the __safeDI_mock_ prefix, the bare parameter name in the init call resolved to the stored property instead of the mock parameter. Now emits `let X = __safeDI_mock_X` (no parens) for closure-typed defaults, bridging the prefixed parameter to a local. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… review rule defaultValueBindings and uncoveredDependencyBindings inside builder functions were generating `let X = X()` instead of `let X = __safeDI_mock_X()`, causing the bare name to resolve to stored properties or self-reference instead of the mock parameter. Also handles closure-typed defaults in builders (`let X = __safeDI_mock_X` without parens). Added CLAUDE.md rule: when updating test expected output, verify the new expected code would compile as valid Swift. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
Fixes mock code generation for dependency cycles through
Instantiatorboundaries, and prevents optional mock parameters from shadowing non-optional local bindings.Fix:
__safeDI_mock_internal parameter label prefixAll mock method parameters now use Swift's external/internal label syntax:
The external label is unchanged — call-site API is unaffected (
Parent.mock(player: customPlayer)). The internal__safeDI_mock_prefix prevents optional parameters from shadowing the non-optional local bindings that child scopes reference. Without this, closure-typed defaults and builder-internal bindings would resolve to stored properties or self-reference instead of the mock parameter.Known issue: partially-lazy cycle forward references
The partially-lazy cycle pattern (A → B → Instantiator<C> → C → A, where C receives A through a single lazy hop) generates code with forward variable references that Swift's compiler rejects:
closure captures 'X' before it is declared.This is a pre-existing production code gen bug, not introduced by this PR. The production test
run_writesConvenienceExtensionOnRootOfTree_whenPartiallyLazyInstantiationCycleExistsonly does string comparison — it never compiles the generated output. The bug is latent in production because types involved in partially-lazy cycles are never roots (so no convenience init is generated). Mock generation exposes it becausegenerateMock: truegenerates for non-root types.The
mock_optionalNotPassedAsNonOptional_whenReceivedPropertyCyclesThroughInstantiatorBoundarytest documents this case but its expected output contains the same forward-reference pattern that won't compile. A proper fix requires reworking howScopeGeneratorhandles partially-lazy cycles for both production and mock code gen — tracked separately.Fully-lazy cycles (all Instantiator hops) and self-forwarding cycles work correctly.
Cycle test coverage added
mock_optionalNotPassedAsNonOptional_...mock_generatedForFullyLazyInstantiationCyclemock_generatedForLazySelfForwardingInstantiationCycleOther fixes in this PR
let X = __safeDI_mock_Xbody bindings (no parens, since@escapingnot@autoclosure)defaultValueBindingsanduncoveredDependencyBindingsinside builder functions use__safeDI_mock_prefixTest plan
__safeDI_mock_prefix verified to compile viaswiftc -o /dev/null(actual compilation, not just-typecheck)🤖 Generated with Claude Code