Skip to content

Retain properties read only via their synthesized projected value#1138

Open
danwood wants to merge 2 commits into
peripheryapp:masterfrom
danwood:fix-state-projected-value
Open

Retain properties read only via their synthesized projected value#1138
danwood wants to merge 2 commits into
peripheryapp:masterfrom
danwood:fix-state-projected-value

Conversation

@danwood

@danwood danwood commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

The bug

A property read only through its synthesized projected value is falsely reported as unused. The most common case is a SwiftUI @State property passed to a child view as a binding:

struct ParentView: View {
    @State private var settings = Settings()   // reported unused

    var body: some View {
        ChildView(settings: $settings)         // read only via $settings
    }
}

Property wrappers such as @State and @Binding cause the compiler to synthesize sibling members for a property foo: a projected value $foo (a SwiftUI.Binding) plus backing storage _foo/__foo. In the index store these synthesized members are marked implicit and are children of the same type as foo.

When the property is read only via $foo, the index records the read as a reference to the synthesized $foo member rather than to the user-declared foo. As a result foo has no incoming references of its own and is reported unused.

Which toolchains are affected

This manifests on the Xcode 27 / Swift 6.4 @State macro form, where the synthesized members appear in the index as distinct implicit $foo/_foo/__foo declarations and the $foo read does not produce a reference to the user property.

On the Xcode 26 / earlier property-wrapper form the index instead records a direct read of the user property at the $foo use site, so that form was never affected. The fix is a no-op there.

The fix

A new StateProjectedValueRetainer mutator matches each synthesized projected value to its user property by name ($foofoo) 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 kind is 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:

  • a @State property read only via $property (passed to a child view in another file) is retained, and
  • a sibling @State property that is never read is still reported unused.

This dual assertion demonstrates the fix is targeted and does not blanket-retain wrapped properties.

danwood and others added 2 commits June 15, 2026 14:51
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.
@danwood danwood marked this pull request as ready for review June 15, 2026 23:22
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.

1 participant