Skip to content

Commit 9476a06

Browse files
Add logic to look for overload groups when emitting warnings and
suggestions about ambiguous symbols. If exactly one of the ambiguous symbols is an overload group, then suggest the author simply remove the incorrect disambiguation.
1 parent 29332fe commit 9476a06

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Error.swift

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,30 @@ extension PathHierarchy.Error {
147147
let disambiguations = nextPathComponent.full.dropFirst(nextPathComponent.name.count)
148148
let replacementRange = SourceRange.makeRelativeRange(startColumn: validPrefix.count, length: disambiguations.count)
149149

150-
let solutions: [Solution] = candidates
151-
.sorted(by: collisionIsBefore)
152-
.map { (node: PathHierarchy.Node, disambiguation: String) -> Solution in
153-
return Solution(summary: "\(Self.replacementOperationDescription(from: disambiguations.dropFirst(), to: disambiguation)) for\n\(fullNameOfNode(node).singleQuoted)", replacements: [
154-
Replacement(range: replacementRange, replacement: "-" + disambiguation)
155-
])
156-
}
157-
150+
let solutions: [Solution]
151+
152+
// First check if any of the ambiguous symbols is an overload group.
153+
// If one is (and all the others are disfavored) then suggest a single
154+
// solution to remove the disambiguation from the path
155+
if let overloadGroupNode = overloadGroupNodeFrom(candidates: candidates) {
156+
solutions = [
157+
Solution(
158+
summary: "\(Self.replacementOperationDescription(from: "-" + disambiguations.dropFirst(), to: "")) to use overload group \n\(fullNameOfNode(overloadGroupNode).singleQuoted)",
159+
replacements: [ Replacement(range: replacementRange, replacement: "") ]
160+
)
161+
]
162+
163+
// Normally list all the ambiguous paths, with a replacement solution for each
164+
} else {
165+
solutions = candidates
166+
.sorted(by: collisionIsBefore)
167+
.map { (node: PathHierarchy.Node, disambiguation: String) -> Solution in
168+
return Solution(summary: "\(Self.replacementOperationDescription(from: disambiguations.dropFirst(), to: disambiguation)) for\n\(fullNameOfNode(node).singleQuoted)", replacements: [
169+
Replacement(range: replacementRange, replacement: "-" + disambiguation)
170+
])
171+
}
172+
}
173+
158174
return TopicReferenceResolutionErrorInfo("""
159175
\(disambiguations.dropFirst().singleQuoted) isn't a disambiguation for \(nextPathComponent.name.singleQuoted) at \(partialResult.node.pathWithoutDisambiguation().singleQuoted)
160176
""",
@@ -231,6 +247,20 @@ extension PathHierarchy.Error {
231247
}
232248
return "Replace \(from.singleQuoted) with \(to.singleQuoted)"
233249
}
250+
251+
/// Find an overload group node from an array of disambiguation candidates. Only
252+
/// return the overload group if all the remaining candidates are disfavored for link resolution.
253+
/// - Parameters:
254+
/// - candidates: An array of candidate node and disambiguation tuples.
255+
/// - Returns: The PathHierarchy node for the overload group symbol
256+
func overloadGroupNodeFrom(candidates: [(node: PathHierarchy.Node, disambiguation: String)]) -> PathHierarchy.Node? {
257+
guard FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled,
258+
let favoredCandidate = candidates.singleMatch({ !$0.node.specialBehaviors.contains(.disfavorInLinkCollision) }),
259+
favoredCandidate.node.symbol?.isOverloadGroup ?? false else {
260+
return nil
261+
}
262+
return favoredCandidate.node
263+
}
234264
}
235265

236266
private extension PathHierarchy.Node {

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+Find.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ private func == (lhs: (some StringProtocol)?, rhs: some StringProtocol) -> Bool
475475
return lhs == rhs
476476
}
477477

478-
private extension Sequence {
478+
package extension Sequence {
479479
/// Returns the only element of the sequence that satisfies the given predicate.
480480
/// - Parameters:
481481
/// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.

Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,20 @@ class PathHierarchyTests: XCTestCase {
13181318
// This overloaded protocol method should be able to resolve without a suffix at all, since it doesn't conflict with anything
13191319
let overloadedProtocolMethod = try tree.findNode(path: "/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)", onlyFindSymbols: true)
13201320
XCTAssert(overloadedProtocolMethod.symbol?.identifier.precise.hasSuffix(SymbolGraph.Symbol.overloadGroupIdentifierSuffix) == true)
1321+
1322+
}
1323+
1324+
func testAmbiguousPathsForOverloadedGroupSymbols() throws {
1325+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
1326+
let (_, context) = try testBundleAndContext(named: "OverloadedSymbols")
1327+
let tree = context.linkResolver.localResolver.pathHierarchy
1328+
try assertPathRaisesErrorMessage("/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-abc123", in: tree, context: context, expectedErrorMessage: """
1329+
'abc123' isn't a disambiguation for 'fourthTestMemberName(test:)' at '/ShapeKit/OverloadedProtocol'
1330+
""") { error in
1331+
XCTAssertEqual(error.solutions, [
1332+
.init(summary: "Remove '-abc123' to use overload group \n'func fourthTestMemberName(test: String) -> Double\'", replacements: [("", 56, 63)]),
1333+
])
1334+
}
13211335
}
13221336

13231337
func testSymbolsWithSameNameAsModule() throws {

0 commit comments

Comments
 (0)