Skip to content

Add 'Reduce Into Instead Of Loop' rule #6078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2b0462a
Implement ReduceIntoInsteadOfLoop rule
snofla Jun 18, 2023
abf8ea9
Cleanups
snofla Jun 18, 2023
9d90eba
Fix build
snofla Jun 19, 2023
437da08
Move models to separate file
snofla Jun 19, 2023
31363d4
Move couple of extension helper functions
snofla Jun 19, 2023
4f0e6f8
Merge commit 'ad23d08cdaa7d656a6e96d6f6cc7b0d165f0a8f2'
snofla May 10, 2025
3746b58
Implement ReduceIntoInsteadOfLoop rule
snofla Jun 18, 2023
63bed40
Cleanups
snofla Jun 18, 2023
810a508
Fix build
snofla Jun 19, 2023
a4f3b78
Move models to separate file
snofla Jun 19, 2023
4df2cb9
Move couple of extension helper functions
snofla Jun 19, 2023
06cf616
Make it work with new SwiftSyntax
snofla May 11, 2025
74a4372
Rename
snofla May 11, 2025
5016d85
Fix new SwiftSyntax forStmt
snofla May 11, 2025
86560c5
Fix new SwiftSyntax-isms
snofla May 12, 2025
27c7188
Clean up helpers
snofla May 12, 2025
7ff5b30
Support array initialisation expression
snofla May 12, 2025
40c4ff6
Regenerate built-in rules to include our rule
snofla May 12, 2025
6822901
Handle assignment expression `varName = <...>`
snofla May 12, 2025
b92bce3
Clean up
snofla May 12, 2025
5ea7e80
Clean up
snofla May 12, 2025
f91b8e9
Add triggering/non-triggering examples for the unit tests
snofla May 12, 2025
1443cb7
Auto applied fixes
snofla May 12, 2025
4a67268
Make opt-in
snofla May 12, 2025
e773996
Add credit note
snofla May 12, 2025
a42d66b
Fix ChangeLog line len
snofla May 12, 2025
9e8fbbc
Add extra trailing space
snofla May 12, 2025
6ef9351
Merge pull request #2 from snofla/snofla/use-reduce-into-1
snofla May 12, 2025
ca19f7d
Remove old files
snofla May 12, 2025
5543177
Merge pull request #3 from snofla/snofla/use-reduce-into-2
snofla May 12, 2025
21446f1
Clean up merge warnings
snofla May 13, 2025
81175c1
Merge pull request #4 from snofla/snofla/use-reduce-into-2
snofla May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Cleanups
  • Loading branch information
snofla committed May 10, 2025
commit 63bed4047b9a0beb2a8a4489f0ff09d6f6759e9e
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@ struct ReduceIntoInsteadOfLoop: ConfigurationProviderRule, SwiftSyntaxRule, OptI

fileprivate extension ReduceIntoInsteadOfLoop {
final class Visitor: ViolationsSyntaxVisitor {
/// Collects all VariableDecl of collection types, finds a ForInStmt, and checks if the ForInStmts
/// body references one of the earlier encounterd VariableDecls.
override func visitPost(_ node: CodeBlockItemListSyntax) {
// reduce into forInStmts and variableDecls map
guard let all = node.allVariableDeclsForInStatmts() else {
return
}
// reduce variableDecls into the ones we're interested in
let selected = all.reduce(into: [ForInStmtSyntax: [VariableDeclSyntax]]()) { partialResult, element in
// we're interested in a couple of variable decls:
// * fully type declared
// * implicitly declared by initializer
// we're interested fully type declared and implicitly declared by initializer
let interestingVariableDecls = element.value.filter { variableDecl in
return variableDecl.isTypeAnnotatedAndInitializer
|| variableDecl.isCollectionTypeInitializer
Expand Down Expand Up @@ -98,8 +94,7 @@ private extension CodeBlockItemListSyntax {
guard !indexed.isEmpty else {
return nil
}
// Attach VariableDecls to ForInStmt. Note that we only attach VariableDecls that
// are at the same level of scope of the ForInStmt.
// only VariableDecls on same level of scope of the ForInStmt.
let result = self.reduce(into: [ForInStmtSyntax: [VariableDeclSyntax]]()) { partialResult, codeBlockItem in
guard let variableDecl = codeBlockItem.as(VariableDeclSyntax.self) else {
return
Expand All @@ -119,9 +114,7 @@ private extension CodeBlockItemListSyntax {
}

private extension VariableDeclSyntax {
/// Is type declared with initializer
/// E.g:
/// `: Set<> = []`, `: Array<> = []`, or `: Dictionary<> = [:]`
/// Is type declared with initializer: `: Set<> = []`, `: Array<> = []`, or `: Dictionary<> = [:]`
var isTypeAnnotatedAndInitializer: Bool {
guard self.isVar && self.identifier != nil,
let idIndex = self.firstIndexOf(IdentifierPatternSyntax.self) else {
Expand All @@ -137,9 +130,7 @@ private extension VariableDeclSyntax {
return initializerClause.isTypeInitializer(for: type)
}

/// Is initialized with empty collection
/// E.g.
/// `= Set<Int>(), = Array<Int>(), = Dictionary[:]`
/// Is initialized with empty collection: `= Set<Int>(), = Array<Int>(), = Dictionary[:]`
/// but a couple of more, see `InitializerClauseExprSyntax.isCollectionInitializer`
var isCollectionTypeInitializer: Bool {
guard self.isVar && self.identifier != nil,
Expand Down Expand Up @@ -167,7 +158,6 @@ private extension VariableDeclSyntax {
}

private extension TypeAnnotationSyntax {
/// Returns collection's type name, or `nil` if unknown
func collectionDeclarationType() -> CollectionType? {
if let genericTypeName = self.genericCollectionDeclarationType() {
return genericTypeName
Expand All @@ -180,9 +170,7 @@ private extension TypeAnnotationSyntax {
}
}

/// var x: Set<>
/// var x: Array<>
/// var x: Dictionary<>
/// var x: Set<>, var x: Array<>, var x: Dictionary<>
func genericCollectionDeclarationType() -> CollectionType? {
guard let simpleTypeIdentifier = self.type.as(SimpleTypeIdentifierSyntax.self),
let genericArgumentClause = simpleTypeIdentifier.genericArgumentClause,
Expand Down Expand Up @@ -221,16 +209,11 @@ private extension TypeAnnotationSyntax {

private extension InitializerClauseSyntax {
/// ---
/// If `nil` we don't know the type and investigate the following, which is a
/// `FunctionCallExpr`:
///
/// If `nil` we don't know the type and investigate the following, which is a`FunctionCallExpr`:
/// var x = Set<T>(...)
/// var y = Array<T>(...)
/// var z = Dictionary<K, V>(...)
///
/// Otherwise we investigate, which checks for `FunctionCallExpr`,
/// `MemberAccessExpr`, `DictionaryExpr` and `ArrayExpr`:
///
/// Otherwise checks for `FunctionCallExpr`,`MemberAccessExpr`,`DictionaryExpr` and `ArrayExpr`:
/// 1. `= Set<T>(...)` | `Set(...)` | `.init(...)` | `[]`
/// 2. `= Array<T>(...)` | `Array(...)` | `.init(...)` | `[]`
/// 3. `= Dictionary<K, V>()` | `Dictionary()` | `.init(..)` | `[:]`
Expand All @@ -242,9 +225,7 @@ private extension InitializerClauseSyntax {
return CollectionType.names[name] != nil
}
}
guard self.equal.tokenKind == .equal else {
return false
}
guard self.equal.tokenKind == .equal else { return false }
if let functionCallExpr = self.value.as(FunctionCallExprSyntax.self) {
// either construction using explicit specialisation, or general construction
if let specializeExpr = functionCallExpr.calledExpression.as(SpecializeExprSyntax.self),
Expand All @@ -270,24 +251,18 @@ private extension InitializerClauseSyntax {
}

private extension ForInStmtSyntax {
/// Checks whether a ForInStmtSyntax references the vars in `variables`.
/// Defers to `CodeBlockItemSyntax.referencedVariable(for:)`
func referencedVariables(for variables: [VariableDeclSyntax]?) -> Set<ReferencedVariable>? {
guard let variables, !variables.isEmpty,
let codeBlock = self.body.as(CodeBlockSyntax.self),
let codeBlockItemList = codeBlock.statements.as(CodeBlockItemListSyntax.self) else {
return nil
}
// check whether each of the items references a var
let references: Set<ReferencedVariable> = codeBlockItemList.reduce(into: .init(), { partialResult, codeBlock in
// see if each codeblock item references variable
// one variable reference should be plenty, so it covers semicolon-ized statements:
// someMutation(); someOtherMutation()
// no need to cover one liner: someMutation(); someOtherMutation()
variables.forEach { variableDecl in
guard let referenced = codeBlock.referencedVariable(for: variableDecl) else {
return
if let referenced = codeBlock.referencedVariable(for: variableDecl) {
partialResult.insert(referenced)
}
partialResult.insert(referenced)
}
})
return references.isEmpty ? nil : references
Expand Down