Retain properties read only via their synthesized projected value#1138
Open
danwood wants to merge 2 commits into
Open
Retain properties read only via their synthesized projected value#1138danwood wants to merge 2 commits into
danwood wants to merge 2 commits into
Conversation
A SwiftUI `@State` property read only through its projected value, e.g. passed to a child view as `Child(value: $property)`, was falsely reported as unused. Property wrappers such as `@State` cause the compiler to synthesize sibling members for a property `foo`: a projected value `$foo` (a `Binding`) plus backing storage `_foo`/`__foo`. These are marked implicit in the index store and parented to the same type as `foo`. When the property is read only via `$foo`, the index records a reference to the synthesized `$foo` member rather than to the user-declared `foo`, so `foo` has no incoming references of its own and is reported unused. I added a `StateProjectedValueRetainer` mutator that matches each synthesized projected value to its user property by name (`$foo` to `foo`) and retains the user property, but only when the projected value is actually referenced at a use site. The implicit backing members the compiler cross-references among themselves are excluded by requiring a reference whose parent is a non-implicit declaration, so a property that is never read has no referenced projected value and is still reported unused. This manifests on the Xcode 27 / Swift 6.4 `@State` macro form, where the synthesized members appear as distinct implicit declarations. The earlier property-wrapper form records a direct read of the user property, so it was not affected; the fix is a no-op there. Adds a cross-file fixture and test asserting both that a projected-value-only property is retained and that a never-read sibling is still reported unused.
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.
The bug
A property read only through its synthesized projected value is falsely reported as unused. The most common case is a SwiftUI
@Stateproperty passed to a child view as a binding:Property wrappers such as
@Stateand@Bindingcause the compiler to synthesize sibling members for a propertyfoo: a projected value$foo(aSwiftUI.Binding) plus backing storage_foo/__foo. In the index store these synthesized members are markedimplicitand are children of the same type asfoo.When the property is read only via
$foo, the index records the read as a reference to the synthesized$foomember rather than to the user-declaredfoo. As a resultfoohas no incoming references of its own and is reported unused.Which toolchains are affected
This manifests on the Xcode 27 / Swift 6.4
@Statemacro form, where the synthesized members appear in the index as distinctimplicit$foo/_foo/__foodeclarations and the$fooread does not produce a reference to the user property.On the Xcode 26 / earlier property-wrapper form the index instead records a direct
readof the user property at the$foouse site, so that form was never affected. The fix is a no-op there.The fix
A new
StateProjectedValueRetainermutator matches each synthesized projected value to its user property by name ($foo→foo) and retains the user property — but only when the projected value is actually referenced at a use site.The precision gate matters: the implicit backing members the compiler cross-references among themselves would otherwise make any wrapped property look used. The mutator therefore requires an incoming reference whose
kindis not.retained(i.e. not the synthetic self-reference the indexer adds for implicit declarations) and whose parent declaration is itself non-implicit (a real use site in user code, not the compiler's internal plumbing between a property's own backing members).Consequently a property that is never read at all has no referenced projected value and is still reported unused.
Tests
Adds a cross-file fixture and test (
SwiftUIStateRetentionTest) asserting both halves of the behavior:@Stateproperty read only via$property(passed to a child view in another file) is retained, and@Stateproperty that is never read is still reported unused.This dual assertion demonstrates the fix is targeted and does not blanket-retain wrapped properties.