From a9908abbd667ef04a48b556f0c8fe4b3c9bc299a Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Fri, 22 Nov 2024 14:36:54 +0100 Subject: [PATCH 1/5] Add composite name handling in switch cases. --- Sources/SwiftLexicalLookup/LookupName.swift | 33 +++++++++++++++ .../Scopes/ScopeImplementations.swift | 35 +++++++++++++++- Tests/SwiftLexicalLookupTest/Assertions.swift | 10 +++-- .../SwiftLexicalLookupTest/ExpectedName.swift | 28 ++++++++++--- .../NameLookupTests.swift | 42 +++++++++++++++++-- 5 files changed, 133 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index fb0d2b2ecb9..e7e564afda5 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -139,6 +139,19 @@ import SwiftSyntax case implicit(ImplicitDecl) /// Dollar identifier introduced by a closure without parameters. case dollarIdentifier(ClosureExprSyntax, strRepresentation: String) + /// Equivalent identifiers grouped together. + /// The associated array of names is always non-empty. + /// + /// ### Example: + /// ```swift + /// switch x { + /// case .a(let smth), .b(let smth): + /// print(smth) // <-- lookup here + /// } + /// ``` + /// For lookup at the given position, the result + /// contains only one (composite) name + case compositeName([LookupName]) /// Syntax associated with this name. @_spi(Experimental) public var syntax: SyntaxProtocol { @@ -151,6 +164,8 @@ import SwiftSyntax return implicitName.syntax case .dollarIdentifier(let closureExpr, _): return closureExpr + case .compositeName(let names): + return names.first!.syntax } } @@ -165,6 +180,8 @@ import SwiftSyntax return kind.identifier case .dollarIdentifier(_, strRepresentation: _): return nil + case .compositeName(let names): + return names.first!.identifier } } @@ -185,6 +202,8 @@ import SwiftSyntax return implicitName.position case .dollarIdentifier(let closureExpr, _): return closureExpr.positionAfterSkippingLeadingTrivia + case .compositeName(let names): + return names.first!.position } } @@ -319,6 +338,20 @@ import SwiftSyntax return "implicit: \(strName)" case .dollarIdentifier(_, strRepresentation: let str): return "dollarIdentifier: \(str)" + case .compositeName(let names): + var result = "Composite name: [ " + + for (index, name) in names.enumerated() { + result += name.debugDescription + + if index < names.count - 1 { + result += ", " + } else { + result += " ]" + } + } + + return result } } } diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 0a6f406caf8..cb9853e2332 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -574,13 +574,44 @@ import SwiftSyntax @_spi(Experimental) extension SwitchCaseSyntax: SequentialScopeSyntax { /// Names introduced within `case` items. var namesFromLabel: [LookupName] { - label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in + guard let switchCaseItemList = label.as(SwitchCaseLabelSyntax.self)?.caseItems else { return [] } + + let extractedNames = switchCaseItemList.flatMap { child in if let exprPattern = child.pattern.as(ExpressionPatternSyntax.self) { return LookupName.getNames(from: exprPattern.expression) } else { return LookupName.getNames(from: child.pattern) } - } ?? [] + } + + if switchCaseItemList.count <= 1 { + return extractedNames + } + + var orderedKeys: [Identifier] = [] + var partitioned: [Identifier: [LookupName]] = [:] + + for extractedName in extractedNames { + guard let identifier = extractedName.identifier else { continue } + + if !partitioned.keys.contains(identifier) { + orderedKeys.append(identifier) + } + + if partitioned[identifier] == nil { + partitioned[identifier] = [extractedName] + } else { + partitioned[identifier]?.append(extractedName) + } + } + + return + orderedKeys + .compactMap { key in + guard let names = partitioned[key] else { return nil } + + return .compositeName(names) + } } /// Names introduced within `case` items diff --git a/Tests/SwiftLexicalLookupTest/Assertions.swift b/Tests/SwiftLexicalLookupTest/Assertions.swift index ee70e4835c6..ba62607bf2d 100644 --- a/Tests/SwiftLexicalLookupTest/Assertions.swift +++ b/Tests/SwiftLexicalLookupTest/Assertions.swift @@ -105,14 +105,18 @@ func assertLexicalNameLookup( ResultExpectation.assertResult(marker: marker, result: result, expectedValues: expectedValues) return result.flatMap { lookUpResult in - lookUpResult.names.map { lookupName in - lookupName.syntax + lookUpResult.names.flatMap { lookupName in + if case .compositeName(let names) = lookupName { + return names.map(\.syntax) + } else { + return [lookupName.syntax] + } } } }, expected: references.mapValues { expectations in expectations.flatMap { expectation in - expectation.expectedNames.map { expectedName in + expectation.expectedNames.flatMap { expectedName in expectedName.marker } } diff --git a/Tests/SwiftLexicalLookupTest/ExpectedName.swift b/Tests/SwiftLexicalLookupTest/ExpectedName.swift index 059a4a6d1db..68f3b154df5 100644 --- a/Tests/SwiftLexicalLookupTest/ExpectedName.swift +++ b/Tests/SwiftLexicalLookupTest/ExpectedName.swift @@ -16,12 +16,12 @@ import XCTest /// Used to define lookup name assertion. protocol ExpectedName { - var marker: String { get } + var marker: [String] { get } } extension String: ExpectedName { - var marker: String { - self + var marker: [String] { + [self] } } @@ -63,15 +63,22 @@ enum NameExpectation: ExpectedName { case declaration(String) case implicit(ImplicitNameExpectation) case dollarIdentifier(String, String) + case compositeName([NameExpectation]) - var marker: String { + var marker: [String] { switch self { case .identifier(let marker), .declaration(let marker), .dollarIdentifier(let marker, _): - return marker + return [marker] case .implicit(let implicitName): - return implicitName.marker + return [implicitName.marker] + case .compositeName(let expectedNames): + return + expectedNames + .flatMap { expectedName in + expectedName.marker + } } } @@ -86,6 +93,15 @@ enum NameExpectation: ExpectedName { actualStr == expectedStr, "For marker \(marker), actual identifier \(actualStr) doesn't match expected \(expectedStr)" ) + case (.compositeName(let actualNames), .compositeName(let expectedNames)): + XCTAssert( + actualNames.count == expectedNames.count, + "For marker \(marker), actual composite name count \(actualNames.count) doesn't match expected \(expectedNames.count)" + ) + + for (actualName, expectedName) in zip(actualNames, expectedNames) { + expectedName.assertExpectation(marker: marker, for: actualName) + } default: XCTFail("For marker \(marker), actual name kind \(name) doesn't match expected \(self)") } diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index d9fe4f2ccff..83708efe063 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -791,9 +791,11 @@ final class testNameLookup: XCTestCase { assertLexicalNameLookup( source: """ switch { - case .x(let 1️⃣a, let 2️⃣b), .y(.c(let 3️⃣c), .z): - print(4️⃣a, 5️⃣b, 6️⃣c) - case .z(let 7️⃣a), .smth(let 8️⃣a) + case .x(let 1️⃣a, let 2️⃣b): + print(4️⃣a, 5️⃣b) + case .y(.c(let 3️⃣c), .z): + print(6️⃣c) + case .z(let 7️⃣a) print(9️⃣a) default: print(0️⃣a) @@ -803,13 +805,45 @@ final class testNameLookup: XCTestCase { "4️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["1️⃣"])], "5️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["2️⃣"])], "6️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["3️⃣"])], - "9️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["7️⃣", "8️⃣"])], + "9️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["7️⃣"])], "0️⃣": [], ], expectedResultTypes: .all(IdentifierPatternSyntax.self) ) } + func testCompositeNames() { + assertLexicalNameLookup( + source: """ + switch X { + case .x(let 1️⃣a, let 2️⃣b), .y(let 3️⃣a, let 4️⃣b): + print(5️⃣x) + case .z(let 7️⃣a), .smth(let 8️⃣a): + print(9️⃣x) + } + """, + references: [ + "5️⃣": [ + .fromScope( + SwitchCaseSyntax.self, + expectedNames: [ + NameExpectation.compositeName([.identifier("1️⃣"), .identifier("3️⃣")]), + NameExpectation.compositeName([.identifier("2️⃣"), .identifier("4️⃣")]), + ] + ) + ], + "9️⃣": [ + .fromScope( + SwitchCaseSyntax.self, + expectedNames: [NameExpectation.compositeName([.identifier("7️⃣"), .identifier("8️⃣")])] + ) + ], + ], + expectedResultTypes: .all(IdentifierPatternSyntax.self), + useNilAsTheParameter: true + ) + } + func testSimpleGenericParameterScope() { assertLexicalNameLookup( source: """ From 862b5269aaee91e2fccfdb23798102f8cdd32bd8 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Thu, 28 Nov 2024 16:22:11 +0100 Subject: [PATCH 2/5] Fix ordering of closure capture names and filtering of names in switch case and variable declaration scopes. --- Sources/SwiftLexicalLookup/LookupName.swift | 12 +++---- .../Scopes/ScopeImplementations.swift | 34 ++++++++++++++++--- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index e7e564afda5..4f504cf38b2 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -103,7 +103,7 @@ import SwiftSyntax ?? subscriptDecl.endPositionBeforeTrailingTrivia case .variableDecl(let variableDecl): return variableDecl.bindings.first?.accessorBlock?.positionAfterSkippingLeadingTrivia - ?? variableDecl.endPosition + ?? variableDecl.bindings.positionAfterSkippingLeadingTrivia case .accessorDecl(let accessorDecl): return accessorDecl.accessorSpecifier.positionAfterSkippingLeadingTrivia case .deinitializerDecl(let deinitializerDecl): @@ -139,14 +139,14 @@ import SwiftSyntax case implicit(ImplicitDecl) /// Dollar identifier introduced by a closure without parameters. case dollarIdentifier(ClosureExprSyntax, strRepresentation: String) - /// Equivalent identifiers grouped together. - /// The associated array of names is always non-empty. + /// Represents equivalent identifiers grouped together. + /// - Important: The array should be non-empty. /// /// ### Example: /// ```swift - /// switch x { - /// case .a(let smth), .b(let smth): - /// print(smth) // <-- lookup here + /// switch X { + /// case .a(let x), .b(let x): + /// print(x) // <-- lookup here /// } /// ``` /// For lookup at the given position, the result diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index cb9853e2332..14fae0b0f3a 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -144,7 +144,7 @@ import SwiftSyntax /// Capture, parameter and body names introduced in this scope. @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { - captureNames + parameterNames + introducedNamesInBody + parameterNames + captureNames + introducedNamesInBody } @_spi(Experimental) public var scopeDebugName: String { @@ -202,7 +202,7 @@ import SwiftSyntax } else { signatureResults = LookupResult.getResultArray( for: self, - withNames: (captureNames + parameterNames).filter { name in + withNames: (parameterNames + captureNames).filter { name in checkIdentifier(identifier, refersTo: name, at: lookUpPosition) } ) @@ -638,7 +638,7 @@ import SwiftSyntax checkIdentifier(identifier, refersTo: name, at: lookUpPosition) } - if label.range.contains(lookUpPosition) { + if label.range.contains(lookUpPosition) && !isInWhereClause(lookUpPosition: lookUpPosition) { return config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config) } else if config.finishInSequentialScope { return sequentialLookup( @@ -660,6 +660,20 @@ import SwiftSyntax + lookupInParent(identifier, at: lookUpPosition, with: config) } } + + /// Returns `true` if `lookUpPosition` is inside a `where` + /// clause associated with one of the case items of this scope. + private func isInWhereClause(lookUpPosition: AbsolutePosition) -> Bool { + guard let switchCaseItemList = label.as(SwitchCaseLabelSyntax.self)?.caseItems else { return false } + + for item in switchCaseItemList { + if item.whereClause?.range.contains(lookUpPosition) ?? false { + return true + } + } + + return false + } } @_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax, LookInMembersScopeSyntax { @@ -934,7 +948,9 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - if bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false { + if (bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false) + || shouldIntroduceSelfIfLazy(lookUpPosition: lookUpPosition) + { return defaultLookupImplementation( in: (isMember ? [.implicit(.self(self))] : LookupName.getNames(from: self)), identifier, @@ -960,6 +976,16 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe return resultsToInterleave + lookupInParent(identifier, at: lookUpPosition, with: config) } + + /// Returns `true`, if `lookUpPosition` is in initializer of + /// this variable declaration and the declaration is lazy. + private func shouldIntroduceSelfIfLazy(lookUpPosition: AbsolutePosition) -> Bool { + guard bindings.first?.initializer?.range.contains(lookUpPosition) ?? false else { return false } + + return modifiers.contains { + $0.name.tokenKind == .keyword(.lazy) + } + } } @_spi(Experimental) extension DeinitializerDeclSyntax: ScopeSyntax { From f4164d0c14ee76822387b61dd12047c8652b160a Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 4 Dec 2024 14:12:09 +0100 Subject: [PATCH 3/5] Rename `compositeName` to `multipleNames`. Simplify `LookupName.debugDescription` string concatenation for `multipleNames` case. --- Sources/SwiftLexicalLookup/LookupName.swift | 28 ++++++------------- .../Scopes/ScopeImplementations.swift | 8 ++---- Tests/SwiftLexicalLookupTest/Assertions.swift | 2 +- .../SwiftLexicalLookupTest/ExpectedName.swift | 2 +- 4 files changed, 12 insertions(+), 28 deletions(-) diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 4f504cf38b2..a5b10a0996a 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -139,7 +139,7 @@ import SwiftSyntax case implicit(ImplicitDecl) /// Dollar identifier introduced by a closure without parameters. case dollarIdentifier(ClosureExprSyntax, strRepresentation: String) - /// Represents equivalent identifiers grouped together. + /// Represents equivalent names grouped together. /// - Important: The array should be non-empty. /// /// ### Example: @@ -150,8 +150,8 @@ import SwiftSyntax /// } /// ``` /// For lookup at the given position, the result - /// contains only one (composite) name - case compositeName([LookupName]) + /// contains only one name, that represents both `let x` declarations. + case equivalentNames([LookupName]) /// Syntax associated with this name. @_spi(Experimental) public var syntax: SyntaxProtocol { @@ -164,7 +164,7 @@ import SwiftSyntax return implicitName.syntax case .dollarIdentifier(let closureExpr, _): return closureExpr - case .compositeName(let names): + case .equivalentNames(let names): return names.first!.syntax } } @@ -180,7 +180,7 @@ import SwiftSyntax return kind.identifier case .dollarIdentifier(_, strRepresentation: _): return nil - case .compositeName(let names): + case .equivalentNames(let names): return names.first!.identifier } } @@ -202,7 +202,7 @@ import SwiftSyntax return implicitName.position case .dollarIdentifier(let closureExpr, _): return closureExpr.positionAfterSkippingLeadingTrivia - case .compositeName(let names): + case .equivalentNames(let names): return names.first!.position } } @@ -338,20 +338,8 @@ import SwiftSyntax return "implicit: \(strName)" case .dollarIdentifier(_, strRepresentation: let str): return "dollarIdentifier: \(str)" - case .compositeName(let names): - var result = "Composite name: [ " - - for (index, name) in names.enumerated() { - result += name.debugDescription - - if index < names.count - 1 { - result += ", " - } else { - result += " ]" - } - } - - return result + case .equivalentNames(let names): + return "Composite name: [ \(names.map(\.debugDescription).joined(separator: ", ")) ]" } } } diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 14fae0b0f3a..348b57b5def 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -598,11 +598,7 @@ import SwiftSyntax orderedKeys.append(identifier) } - if partitioned[identifier] == nil { - partitioned[identifier] = [extractedName] - } else { - partitioned[identifier]?.append(extractedName) - } + partitioned[identifier, default: []].append(extractedName) } return @@ -610,7 +606,7 @@ import SwiftSyntax .compactMap { key in guard let names = partitioned[key] else { return nil } - return .compositeName(names) + return .equivalentNames(names) } } diff --git a/Tests/SwiftLexicalLookupTest/Assertions.swift b/Tests/SwiftLexicalLookupTest/Assertions.swift index ba62607bf2d..6b64adc3a63 100644 --- a/Tests/SwiftLexicalLookupTest/Assertions.swift +++ b/Tests/SwiftLexicalLookupTest/Assertions.swift @@ -106,7 +106,7 @@ func assertLexicalNameLookup( return result.flatMap { lookUpResult in lookUpResult.names.flatMap { lookupName in - if case .compositeName(let names) = lookupName { + if case .equivalentNames(let names) = lookupName { return names.map(\.syntax) } else { return [lookupName.syntax] diff --git a/Tests/SwiftLexicalLookupTest/ExpectedName.swift b/Tests/SwiftLexicalLookupTest/ExpectedName.swift index 68f3b154df5..39e260fd90e 100644 --- a/Tests/SwiftLexicalLookupTest/ExpectedName.swift +++ b/Tests/SwiftLexicalLookupTest/ExpectedName.swift @@ -93,7 +93,7 @@ enum NameExpectation: ExpectedName { actualStr == expectedStr, "For marker \(marker), actual identifier \(actualStr) doesn't match expected \(expectedStr)" ) - case (.compositeName(let actualNames), .compositeName(let expectedNames)): + case (.equivalentNames(let actualNames), .compositeName(let expectedNames)): XCTAssert( actualNames.count == expectedNames.count, "For marker \(marker), actual composite name count \(actualNames.count) doesn't match expected \(expectedNames.count)" From 848a7f014bc98d681b22a955f36ff62db3265589 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 4 Dec 2024 14:24:41 +0100 Subject: [PATCH 4/5] Rename `compositeName` to `equivalentNames` in `NameExpectation`. --- Tests/SwiftLexicalLookupTest/ExpectedName.swift | 6 +++--- Tests/SwiftLexicalLookupTest/NameLookupTests.swift | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/SwiftLexicalLookupTest/ExpectedName.swift b/Tests/SwiftLexicalLookupTest/ExpectedName.swift index 39e260fd90e..d9f1da738a5 100644 --- a/Tests/SwiftLexicalLookupTest/ExpectedName.swift +++ b/Tests/SwiftLexicalLookupTest/ExpectedName.swift @@ -63,7 +63,7 @@ enum NameExpectation: ExpectedName { case declaration(String) case implicit(ImplicitNameExpectation) case dollarIdentifier(String, String) - case compositeName([NameExpectation]) + case equivalentNames([NameExpectation]) var marker: [String] { switch self { @@ -73,7 +73,7 @@ enum NameExpectation: ExpectedName { return [marker] case .implicit(let implicitName): return [implicitName.marker] - case .compositeName(let expectedNames): + case .equivalentNames(let expectedNames): return expectedNames .flatMap { expectedName in @@ -93,7 +93,7 @@ enum NameExpectation: ExpectedName { actualStr == expectedStr, "For marker \(marker), actual identifier \(actualStr) doesn't match expected \(expectedStr)" ) - case (.equivalentNames(let actualNames), .compositeName(let expectedNames)): + case (.equivalentNames(let actualNames), .equivalentNames(let expectedNames)): XCTAssert( actualNames.count == expectedNames.count, "For marker \(marker), actual composite name count \(actualNames.count) doesn't match expected \(expectedNames.count)" diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 83708efe063..711b6950baf 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -827,15 +827,15 @@ final class testNameLookup: XCTestCase { .fromScope( SwitchCaseSyntax.self, expectedNames: [ - NameExpectation.compositeName([.identifier("1️⃣"), .identifier("3️⃣")]), - NameExpectation.compositeName([.identifier("2️⃣"), .identifier("4️⃣")]), + NameExpectation.equivalentNames([.identifier("1️⃣"), .identifier("3️⃣")]), + NameExpectation.equivalentNames([.identifier("2️⃣"), .identifier("4️⃣")]), ] ) ], "9️⃣": [ .fromScope( SwitchCaseSyntax.self, - expectedNames: [NameExpectation.compositeName([.identifier("7️⃣"), .identifier("8️⃣")])] + expectedNames: [NameExpectation.equivalentNames([.identifier("7️⃣"), .identifier("8️⃣")])] ) ], ], From 6feca329ad09149e672da6d1f66ac71f5b3d673e Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 4 Dec 2024 21:01:45 +0100 Subject: [PATCH 5/5] Split `equivalentNames` assertion message string in test harness into two lines. --- Tests/SwiftLexicalLookupTest/ExpectedName.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftLexicalLookupTest/ExpectedName.swift b/Tests/SwiftLexicalLookupTest/ExpectedName.swift index d9f1da738a5..4bc408a6dab 100644 --- a/Tests/SwiftLexicalLookupTest/ExpectedName.swift +++ b/Tests/SwiftLexicalLookupTest/ExpectedName.swift @@ -96,7 +96,8 @@ enum NameExpectation: ExpectedName { case (.equivalentNames(let actualNames), .equivalentNames(let expectedNames)): XCTAssert( actualNames.count == expectedNames.count, - "For marker \(marker), actual composite name count \(actualNames.count) doesn't match expected \(expectedNames.count)" + "For marker \(marker), actual composite name count " + + "\(actualNames.count) doesn't match expected \(expectedNames.count)" ) for (actualName, expectedName) in zip(actualNames, expectedNames) {