@@ -7,9 +7,12 @@ struct PatternMatchingKeywordsRule: Rule {
77 static let description = RuleDescription (
88 identifier: " pattern_matching_keywords " ,
99 name: " Pattern Matching Keywords " ,
10- description: " Combine multiple pattern matching bindings by moving keywords out of tuples " ,
10+ description: """
11+ Combine multiple pattern matching bindings by moving keywords out of tuples
12+ and enum associated values
13+ """ ,
1114 kind: . idiomatic,
12- nonTriggeringExamples: [
15+ nonTriggeringExamples: switchWrapped ( examples : [
1316 Example ( " default " ) ,
1417 Example ( " case 1 " ) ,
1518 Example ( " case bar " ) ,
@@ -22,88 +25,238 @@ struct PatternMatchingKeywordsRule: Rule {
2225 Example ( " case .foo(var x) " ) ,
2326 Example ( " case var .foo(x, y) " ) ,
2427 Example ( " case (y, let x, z) " ) ,
25- ] . map ( wrapInSwitch) ,
26- triggeringExamples: [
27- Example ( " case (↓let x, ↓let y) " ) ,
28- Example ( " case (↓let x, ↓let y, .foo) " ) ,
29- Example ( " case (↓let x, ↓let y, _) " ) ,
30- Example ( " case (↓let x, ↓let y, f()) " ) ,
31- Example ( " case (↓let x, ↓let y, s.f()) " ) ,
32- Example ( " case (↓let x, ↓let y, s.t) " ) ,
28+ Example ( " case (foo, let x) " ) ,
29+ Example ( " case (foo, let x, let y) " ) ,
30+ Example ( " case .foo(bar, let x) " ) ,
31+ Example ( " case (let x, y) " ) ,
32+ Example ( " case .foo(let x, y) " ) ,
33+ Example ( " case (.foo(let x), y) " ) ,
34+ Example ( " case let .foo(x, y), let .bar(x, y) " ) ,
35+ Example ( " case var .foo(x, y), var .bar(x, y) " ) ,
36+ Example ( " case let .foo(x, y), let .bar(x, y), let .baz(x, y) " ) ,
37+ Example ( " case .foo(bar: let x, baz: var y) " ) ,
38+ Example ( " case (.yamlParsing(var x), (.yamlParsing(var y), z)) " ) ,
39+ Example ( " case (.foo(let x), (y, let z)) " ) ,
40+ ] ) + [
41+ Example ( " if case let (x, y) = foo {} " ) ,
42+ Example ( " guard case let (x, y) = foo else { return } " ) ,
43+ Example ( " while case let (x, y) = foo {} " ) ,
44+ Example ( " for case let (x, y) in foos {} " ) ,
45+ Example ( " if case (foo, let x) = value {} " ) ,
46+ Example ( " guard case .foo(bar, let x) = value else { return } " ) ,
47+ Example ( " do {} catch let Pattern.error(x, y) {} " ) ,
48+ Example ( " do {} catch (foo, let x) {} " ) ,
49+ ] ,
50+ triggeringExamples: switchWrapped ( examples: [
51+ Example ( " case (↓let x, ↓let y) " ) ,
52+ Example ( " case (↓let x, ↓let y, .foo) " ) ,
53+ Example ( " case (↓let x, ↓let y, _) " ) ,
54+ Example ( " case (↓let x, ↓let y, 1) " ) ,
55+ Example ( " case (↓let x, ↓let y, f()) " ) ,
56+ Example ( " case (↓let x, ↓let y, s.f()) " ) ,
57+ Example ( " case (↓let x, ↓let y, s.t) " ) ,
3358 Example ( " case .foo(↓let x, ↓let y) " ) ,
34- Example ( " case (.yamlParsing(↓let x), .yamlParsing(↓let y)) " ) ,
35- Example ( " case (↓var x, ↓var y) " ) ,
59+ Example ( " case .foo(bar: ↓let x, baz: ↓let y) " ) ,
3660 Example ( " case .foo(↓var x, ↓var y) " ) ,
37- Example ( " case (.yamlParsing(↓var x), .yamlParsing(↓var y)) " ) ,
38- ] . map ( wrapInSwitch)
61+ Example ( " case .foo(bar: ↓var x, baz: ↓var y) " ) ,
62+ Example ( " case (.yamlParsing(↓let x), .yamlParsing(↓let y)) " ) ,
63+ Example ( " case (.yamlParsing(↓var x), (.yamlParsing(↓var y), _)) " ) ,
64+ Example ( " case ((↓let x, ↓let y), z) " ) ,
65+ Example ( " case .foo((↓let x, ↓let y), z) " ) ,
66+ Example ( " case (.foo(↓let x, ↓let y), z) " ) ,
67+ Example ( " case .foo(.bar(↓let x), .bar(↓let y)) " ) ,
68+ Example ( " case .foo(.bar(↓let x), .bar(↓let y), .baz) " ) ,
69+ Example ( " case .foo(↓let x, ↓let y), .bar(↓let x, ↓let y) " ) ,
70+ Example ( " case .foo(↓var x, ↓var y), .bar(↓var x, ↓var y) " ) ,
71+ ] ) + [
72+ Example ( " if case (↓let x, ↓let y) = foo {} " ) ,
73+ Example ( " guard case (↓let x, ↓let y) = foo else { return } " ) ,
74+ Example ( " while case (↓let x, ↓let y) = foo {} " ) ,
75+ Example ( " for case (↓let x, ↓let y) in foos {} " ) ,
76+ Example ( " if case .foo(bar: ↓let x, baz: ↓let y) = value {} " ) ,
77+ Example ( " do {} catch Pattern.error(↓let x, ↓let y) {} " ) ,
78+ Example ( " do {} catch (↓let x, ↓let y) {} " ) ,
79+ Example ( " do {} catch Foo.outer(.inner(↓let x), .inner(↓let y)) {} " ) ,
80+ ]
3981 )
4082
41- private static func wrapInSwitch( _ example: Example ) -> Example {
42- example. with ( code: """
43- switch foo {
44- \( example. code) : break
45- }
46- """ )
83+ private static func switchWrapped( examples: [ Example ] ) -> [ Example ] {
84+ examples. map { example in
85+ example. with (
86+ code: """
87+ switch foo {
88+ \( example. code) : break
89+ }
90+ """
91+ )
92+ }
4793 }
4894}
4995
5096private extension PatternMatchingKeywordsRule {
5197 final class Visitor : ViolationsSyntaxVisitor < ConfigurationType > {
5298 override func visitPost( _ node: SwitchCaseItemSyntax ) {
53- let localViolations = TupleVisitor ( configuration: configuration, file: file)
54- . walk ( tree: node. pattern, handler: \. violations)
55- violations. append ( contentsOf: localViolations)
99+ appendViolations ( from: node. pattern)
100+ }
101+
102+ override func visitPost( _ node: MatchingPatternConditionSyntax ) {
103+ appendViolations ( from: node. pattern)
104+ }
105+
106+ override func visitPost( _ node: ForStmtSyntax ) {
107+ guard node. caseKeyword != nil else {
108+ return
109+ }
110+
111+ appendViolations ( from: node. pattern)
112+ }
113+
114+ override func visitPost( _ node: CatchItemSyntax ) {
115+ guard let pattern = node. pattern else {
116+ return
117+ }
118+
119+ appendViolations ( from: pattern)
120+ }
121+
122+ private func appendViolations( from pattern: PatternSyntax ) {
123+ violations. append ( contentsOf: PatternViolationCollector . collect ( in: pattern) )
56124 }
57125 }
58126}
59127
60- private final class TupleVisitor < Configuration: RuleConfiguration > : ViolationsSyntaxVisitor < Configuration > {
61- override func visitPost( _ node: LabeledExprListSyntax ) {
62- let list = node. flatteningEnumPatterns ( ) . map ( \. expression. categorized)
63- if list. contains ( where: \. isReference) {
128+ private enum PatternViolationCollector {
129+ static func collect( in pattern: PatternSyntax ) -> [ AbsolutePosition ] {
130+ var violations : [ AbsolutePosition ] = [ ]
131+ collect ( from: pattern, into: & violations)
132+ return violations
133+ }
134+
135+ private static func collect( from pattern: PatternSyntax , into violations: inout [ AbsolutePosition ] ) {
136+ if let binding = pattern. as ( ValueBindingPatternSyntax . self) {
137+ collect ( from: binding. pattern, into: & violations)
64138 return
65139 }
66- let specifiers = list . compactMap { if case let . binding ( specifier ) = $0 { specifier } else { nil } }
67- if specifiers . count > 1 , specifiers . allSatisfy ( { $0 . tokenKind == specifiers . first ? . tokenKind } ) {
68- violations . append ( contentsOf : specifiers . map ( \ . positionAfterSkippingLeadingTrivia ) )
140+
141+ guard let expression = pattern . as ( ExpressionPatternSyntax . self ) ? . expression else {
142+ return
69143 }
144+
145+ collect ( from: expression, into: & violations)
70146 }
71- }
72147
73- private extension LabeledExprListSyntax {
74- func flatteningEnumPatterns ( ) -> [ LabeledExprSyntax ] {
75- flatMap { elem in
76- guard let pattern = elem . expression . as ( FunctionCallExprSyntax . self ) ,
77- pattern . calledExpression . is ( MemberAccessExprSyntax . self ) else {
78- return [ elem ]
148+ private static func collect ( from expression : ExprSyntax , into violations : inout [ AbsolutePosition ] ) {
149+ if let childExpressions = expression . immediatePatternGroupChildren {
150+ appendViolationsIfBindingsCanBeLifted ( from : childExpressions , into : & violations )
151+
152+ for childExpression in childExpressions {
153+ collect ( from : childExpression , into : & violations )
79154 }
80155
81- return Array ( pattern. arguments)
156+ return
157+ }
158+
159+ if let pattern = expression. as ( PatternExprSyntax . self) ? . pattern {
160+ collect ( from: pattern, into: & violations)
161+ }
162+ }
163+
164+ private static func appendViolationsIfBindingsCanBeLifted(
165+ from expressions: [ ExprSyntax ] ,
166+ into violations: inout [ AbsolutePosition ]
167+ ) {
168+ let categories = expressions. map ( GroupCategoryResolver . category ( for: ) )
169+
170+ if categories. contains ( where: \. isReference) {
171+ return
172+ }
173+
174+ let specifiers = categories. compactMap ( \. bindingSpecifier)
175+ guard specifiers. count > 1 , let first = specifiers. first else {
176+ return
177+ }
178+
179+ if specifiers. allSatisfy ( { $0. tokenKind == first. tokenKind } ) {
180+ violations. append ( contentsOf: specifiers. map ( \. positionAfterSkippingLeadingTrivia) )
82181 }
83182 }
84183}
85184
86- private enum ArgumentType {
185+ private enum GroupCategory {
87186 case binding( specifier: TokenSyntax )
88187 case reference
89- case constant
188+ case neutral
90189
91190 var isReference : Bool {
92191 switch self {
93- case . reference: true
94- default : false
192+ case . reference:
193+ return true
194+ default :
195+ return false
196+ }
197+ }
198+
199+ var bindingSpecifier : TokenSyntax ? {
200+ switch self {
201+ case let . binding( specifier) :
202+ return specifier
203+ default :
204+ return nil
95205 }
96206 }
97207}
98208
99- private extension ExprSyntax {
100- var categorized : ArgumentType {
101- if let binding = `as` ( PatternExprSyntax . self) ? . pattern. as ( ValueBindingPatternSyntax . self) {
209+ private enum GroupCategoryResolver {
210+ static func category ( for expression : ExprSyntax ) -> GroupCategory {
211+ if let binding = expression . as ( PatternExprSyntax . self) ? . pattern. as ( ValueBindingPatternSyntax . self) {
102212 return . binding( specifier: binding. bindingSpecifier)
103213 }
104- if `is` ( DeclReferenceExprSyntax . self) {
214+
215+ // `case (foo, let x)` must not become `case let (foo, x)`,
216+ // because `foo` would stop matching an existing value and
217+ // would instead become a newly introduced binding.
218+ if expression. is ( DeclReferenceExprSyntax . self) {
105219 return . reference
106220 }
107- return . constant
221+
222+ if let childExpressions = expression. immediatePatternGroupChildren {
223+ return liftedCategory ( for: childExpressions)
224+ }
225+
226+ return . neutral
227+ }
228+
229+ private static func liftedCategory( for expressions: [ ExprSyntax ] ) -> GroupCategory {
230+ let categories = expressions. map ( category ( for: ) )
231+
232+ if categories. contains ( where: \. isReference) {
233+ return . neutral
234+ }
235+
236+ let specifiers = categories. compactMap ( \. bindingSpecifier)
237+ guard let first = specifiers. first else {
238+ return . neutral
239+ }
240+
241+ if specifiers. allSatisfy ( { $0. tokenKind == first. tokenKind } ) {
242+ return . binding( specifier: first)
243+ }
244+
245+ return . neutral
246+ }
247+ }
248+
249+ private extension ExprSyntax {
250+ var immediatePatternGroupChildren : [ ExprSyntax ] ? {
251+ if let tuple = `as` ( TupleExprSyntax . self) {
252+ return tuple. elements. map ( \. expression)
253+ }
254+
255+ if let call = `as` ( FunctionCallExprSyntax . self) ,
256+ call. calledExpression. is ( MemberAccessExprSyntax . self) {
257+ return call. arguments. map ( \. expression)
258+ }
259+
260+ return nil
108261 }
109262}
0 commit comments