Retain initialized constant properties flagged as assign-only#1136
Open
danwood wants to merge 1 commit into
Open
Retain initialized constant properties flagged as assign-only#1136danwood wants to merge 1 commit into
danwood wants to merge 1 commit into
Conversation
A `let` stored property whose value is supplied by an explicit initializer that is itself used was incorrectly reported as an assign-only property when it was never read. Such a property is a genuine stored value: removing its declaration would orphan the `self.x = x` assignment in the initializer body and drop the matching initializer parameter, breaking every call site that supplies the argument. This commonly arises with model types whose properties are read only through synthesized code the index does not attribute as a read, such as a struct conforming to Codable or Identifiable, or a value consumed by a SwiftUI view body. The property is now retained rather than reported. The exclusion is deliberately narrow. A `let` initialized only by a compiler-synthesized memberwise initializer remains removable, as does one whose initializer is never called, so existing assign-only detection for both `let` and `var` properties is unchanged. To distinguish a `let` constant from a `var`, the declaration visitor now records whether each variable declaration is a `let` binding, plumbed through the indexer onto the declaration.
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.
Symptom
A
letstored property that is assigned in an explicit initializer but never read is reported as an assign-only property, even though removing it is not safe.Real-world example
Here
coloris read only through SwiftUI's synthesizedbody/Hashable/Identifiablemachinery, which the index does not attribute as a read. It is therefore reported as assign-only. Scanning a real SwiftUI app surfaced hundreds of such properties.Why it's a false positive
Acting on the report by removing the declaration breaks the build:
self.color = colorassignment in the initializer body is left without a target, andcolor:initializer parameter is dropped, breaking every call site that passes it.A
letcan only be assigned once, in an initializer body, so when that initializer is written explicitly and is actually used, the property is a genuine stored value that simply isn't read — not an assign-only property.Fix
When a property would otherwise be reported as assign-only, it is instead retained if it is a
letconstant initialized by an explicit initializer that is itself referenced.The exclusion is deliberately narrow so existing detection is unchanged:
letinitialized only by a compiler-synthesized memberwise initializer is still reported (removing it is safe — the synthesizer simply stops generating the parameter);letwhose initializer is never called is still reported;varassign-only detection is untouched.To distinguish a
letfrom avar, the declaration visitor now records whether each variable declaration is aletbinding, plumbed through the indexer onto the declaration.A regression test (
testRetainsInitializedConstantProperties) covers both the retained case (used initializer) and the still-reported case (unused initializer).