Don't rewrite is/as cast operands to Self in prefer_self_in_static_references - #6765
Merged
SimplyDanny merged 2 commits intoJun 8, 2026
Merged
Conversation
`prefer_self_in_static_references` rewrote the type operand of an `is` / `as?` / `as!` cast to `Self` inside class-like scopes. Because `Self` is the dynamic type, this silently changed runtime behavior for non-final classes: `x is A` (true for an `A` instance regardless of the caller) became `x is Self`, which is false when the method is invoked on a subclass instance. The rule already guards the analogous `X.self` case; `is`/`as` was an inconsistent gap. Skip the type operand of is/as casts in class-like scopes via `visit(TypeExprSyntax)`. Static member references (`A.f()`) and value-type scopes (`struct`/`enum`, where `Self` == the type) are unaffected. Two examples that pinned the old behavior become non-triggering: the `is A.Type` example from realm#6749 (the metatype is an is-operand, now left alone) and the pre-existing `class T { ... (input as! T).child ... }` example (its only violation was the `as! T` rewrite). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes prefer_self_in_static_references to avoid rewriting cast type operands (e.g. x is A, x as? A) to Self in class-like scopes where that rewrite can change runtime semantics for non-final types.
Changes:
- Skip visiting
TypeExprSyntaxwhen it’s the operand ofis/ascasts in class-like scopes to prevent incorrect rewrites. - Add helper logic to detect cast operands in sequence expressions.
- Update rule examples and changelog to reflect the corrected behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift | Moves cast-operand cases into non-triggering examples and removes corresponding corrections. |
| Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift | Adds cast-operand detection and skips rewriting type operands in class-like scopes. |
| CHANGELOG.md | Documents the bug fix and links the issue/author. |
- `isCastOperand` now does an O(1) parent check: `ExprListSyntax` is used
solely for a sequence expression's elements, where a bare type expression
only appears as an `is`/`as` operand, so no scan is needed.
- Mention plain `as` in the visitor comment (it is handled too).
- Drop the unreferenced `class B: A {}` from the non-triggering example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generated by 🚫 Danger |
SimplyDanny
approved these changes
Jun 8, 2026
SimplyDanny
left a comment
Collaborator
There was a problem hiding this comment.
Thank you, @Brett-Best!
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
prefer_self_in_static_referencesrewrote the type operand of anis/as?/as!cast toSelfinside class-like scopes. BecauseSelfis the dynamic type, this silently changes runtime behavior for non-final classes (the code still compiles):This is more dangerous than the existing composition/existential cases (which produced uncompilable code that fails fast) — this compiles and quietly breaks logic. The rule already guards the analogous
X.selfcase in class-like scopes for exactly this dynamic-vs-static reason;is/aswas an inconsistent gap.Fix
Skip the type operand of
is/as/as?/as!casts in class-like scopes, viavisit(TypeExprSyntax)(detecting the operand as the element following an unresolvedis/asoperator in the sequence expression). This mirrors the existingX.selfskip.Unaffected (still corrected, as before):
A.f()→Self.f()struct/enum,Self== the type, sox is S→x is Selfis safe and stays corrected[A]()and other non-cast type-as-value expressionsTests
x is A,x as? Ain a class extension).is A.Typeexample added in Don't rewrite composition/existential types toSelfin prefer_self_in_static_references #6749 and the pre-existingclass T { … (input as! T).child … }example both become non-triggering (their cast operands are now left alone), reflecting the corrected behavior.B().isA(A())staystrueafter--fix, and thatswiftlint lint --strictand the rule's generated tests pass.Fixes #6764
🤖 Generated with Claude Code