@@ -3328,6 +3328,73 @@ class PathHierarchyTests: XCTestCase {
33283328 try assertFindsPath ( " /ModuleName/ContainerName " , in: tree, asSymbolID: containerID)
33293329 }
33303330
3331+ func testMinimalTypeDisambiguationForClosureParameterWithVoidReturnType( ) throws {
3332+ // Create a `doSomething(with:and:)` function with a `String` parameter (same in every overload) and a `(TYPE)->()` closure parameter.
3333+ func makeSymbolOverload( closureParameterType: SymbolGraph . Symbol . DeclarationFragments . Fragment ) -> SymbolGraph . Symbol {
3334+ makeSymbol (
3335+ id: " some-function-overload- \( closureParameterType. spelling. lowercased ( ) ) " ,
3336+ kind: . method,
3337+ pathComponents: [ " doSomething(with:and:) " ] ,
3338+ signature: . init(
3339+ parameters: [
3340+ . init( name: " first " , externalName: " with " , declarationFragments: [
3341+ . init( kind: . externalParameter, spelling: " with " , preciseIdentifier: nil ) ,
3342+ . init( kind: . text, spelling: " " , preciseIdentifier: nil ) ,
3343+ . init( kind: . internalParameter, spelling: " first " , preciseIdentifier: nil ) ,
3344+ . init( kind: . text, spelling: " " , preciseIdentifier: nil ) ,
3345+ . init( kind: . typeIdentifier, spelling: " String " , preciseIdentifier: " s:SS " )
3346+ ] , children: [ ] ) ,
3347+
3348+ . init( name: " second " , externalName: " and " , declarationFragments: [
3349+ . init( kind: . externalParameter, spelling: " and " , preciseIdentifier: nil ) ,
3350+ . init( kind: . text, spelling: " " , preciseIdentifier: nil ) ,
3351+ . init( kind: . internalParameter, spelling: " second " , preciseIdentifier: nil ) ,
3352+ . init( kind: . text, spelling: " ( " , preciseIdentifier: nil ) ,
3353+ closureParameterType,
3354+ . init( kind: . text, spelling: " ) -> () " , preciseIdentifier: nil ) ,
3355+ ] , children: [ ] )
3356+ ] ,
3357+ returns: [ . init( kind: . typeIdentifier, spelling: " Void " , preciseIdentifier: " s:s4Voida " ) ]
3358+ )
3359+ )
3360+ }
3361+
3362+ let catalog = Folder ( name: " unit-test.docc " , content: [
3363+ JSONFile ( name: " ModuleName.symbols.json " , content: makeSymbolGraph (
3364+ moduleName: " ModuleName " ,
3365+ symbols: [
3366+ makeSymbolOverload ( closureParameterType: . init( kind: . typeIdentifier, spelling: " Int " , preciseIdentifier: " s:Si " ) ) , // (String, (Int)->()) -> Void
3367+ makeSymbolOverload ( closureParameterType: . init( kind: . typeIdentifier, spelling: " Double " , preciseIdentifier: " s:Sd " ) ) , // (String, (Double)->()) -> Void
3368+ makeSymbolOverload ( closureParameterType: . init( kind: . typeIdentifier, spelling: " Float " , preciseIdentifier: " s:Sf " ) ) , // (String, (Float)->()) -> Void
3369+ ] ,
3370+ relationships: [ ]
3371+ ) )
3372+ ] )
3373+
3374+ let ( _, context) = try loadBundle ( catalog: catalog)
3375+ let tree = context. linkResolver. localResolver. pathHierarchy
3376+
3377+ let link = " /ModuleName/doSomething(with:and:) "
3378+ try assertPathRaisesErrorMessage ( link, in: tree, context: context, expectedErrorMessage: " 'doSomething(with:and:)' is ambiguous at '/ModuleName' " ) { errorInfo in
3379+ XCTAssertEqual ( errorInfo. solutions. count, 3 , " There should be one suggestion per overload " )
3380+ for solution in errorInfo. solutions {
3381+ // Apply the suggested replacements for each solution and verify that _that_ link resolves to a single symbol.
3382+ var linkWithSuggestion = link
3383+ XCTAssertFalse ( solution. replacements. isEmpty, " Diagnostics about ambiguous links should have some replacements for each solution. " )
3384+ for (replacementText, start, end) in solution. replacements {
3385+ let range = linkWithSuggestion. index ( linkWithSuggestion. startIndex, offsetBy: start) ..< linkWithSuggestion. index ( linkWithSuggestion. startIndex, offsetBy: end)
3386+ linkWithSuggestion. replaceSubrange ( range, with: replacementText)
3387+ }
3388+
3389+ XCTAssertNotNil ( try ? tree. findSymbol ( path: linkWithSuggestion) , """
3390+ Failed to resolve \( linkWithSuggestion) after applying replacements \( solution. replacements. map { " ' \( $0. 0 ) '@ \( $0. start) - \( $0. end) " } . joined ( separator: " , " ) ) to ' \( link) '.
3391+
3392+ The replacement that DocC suggests in its warnings should unambiguously refer to a single symbol match.
3393+ """ )
3394+ }
3395+ }
3396+ }
3397+
33313398 func testMissingMemberOfAnonymousStructInsideUnion( ) throws {
33323399 let outerContainerID = " some-outer-container-symbol-id "
33333400 let innerContainerID = " some-inner-container-symbol-id "
@@ -3665,7 +3732,7 @@ class PathHierarchyTests: XCTestCase {
36653732 let voidType = DeclToken . typeIdentifier ( " Void " , precise: " s:s4Voida " )
36663733
36673734 func makeParameter( _ name: String , decl: [ DeclToken ] ) -> SymbolGraph . Symbol . FunctionSignature . FunctionParameter {
3668- . init( name: name, externalName: nil , declarationFragments: makeFragments ( [ . internalParameter( name) , . text( " " ) ] + decl) , children: [ ] )
3735+ . init( name: name, externalName: nil , declarationFragments: makeFragments ( [ . internalParameter( name) , . text( " " ) ] + decl) , children: [ ] )
36693736 }
36703737
36713738 func makeSignature( first: DeclToken ... , second: DeclToken ... , third: DeclToken ... ) -> SymbolGraph . Symbol . FunctionSignature {
@@ -3772,6 +3839,56 @@ class PathHierarchyTests: XCTestCase {
37723839 ] )
37733840 }
37743841
3842+ // Each overload has a unique closure parameter with a "()" literal closure return type
3843+ do {
3844+ func makeSignature( first: DeclToken ... , second: DeclToken ... ) -> SymbolGraph . Symbol . FunctionSignature {
3845+ . init(
3846+ parameters: [
3847+ . init( name: " first " , externalName: nil , declarationFragments: makeFragments ( first) , children: [ ] ) ,
3848+ . init( name: " second " , externalName: nil , declarationFragments: makeFragments ( second) , children: [ ] )
3849+ ] ,
3850+ returns: makeFragments ( [ voidType] )
3851+ )
3852+ }
3853+
3854+ // String (Int)->()
3855+ // String (Double)->()
3856+ // String (Float)->()
3857+ let catalog = Folder ( name: " unit-test.docc " , content: [
3858+ JSONFile ( name: " ModuleName.symbols.json " , content: makeSymbolGraph (
3859+ moduleName: " ModuleName " ,
3860+ symbols: [
3861+ // String (Int)->Void
3862+ makeSymbol ( id: " function-overload-1 " , kind: . func, pathComponents: [ " doSomething(first:second:) " ] , signature: makeSignature (
3863+ first: stringType, // String
3864+ second: " ( " , intType, " ) -> () " // (Int)->()
3865+ ) ) ,
3866+
3867+ // String (Double)->Void
3868+ makeSymbol ( id: " function-overload-2 " , kind: . func, pathComponents: [ " doSomething(first:second:) " ] , signature: makeSignature (
3869+ first: stringType, // String
3870+ second: " ( " , doubleType, " ) -> () " // (Double)->()
3871+ ) ) ,
3872+
3873+ // String (Float)->Void
3874+ makeSymbol ( id: " function-overload-3 " , kind: . func, pathComponents: [ " doSomething(first:second:) " ] , signature: makeSignature (
3875+ first: stringType, // String
3876+ second: " ( " , floatType, " ) -> () " // (Double)->()
3877+ ) ) ,
3878+ ]
3879+ ) )
3880+ ] )
3881+
3882+ let ( _, context) = try loadBundle ( catalog: catalog)
3883+ let tree = context. linkResolver. localResolver. pathHierarchy
3884+
3885+ try assertPathCollision ( " ModuleName/doSomething(first:second:) " , in: tree, collisions: [
3886+ ( symbolID: " function-overload-1 " , disambiguation: " -(_,(Int)->()) " ) , // _ (Int)->()
3887+ ( symbolID: " function-overload-2 " , disambiguation: " -(_,(Double)->()) " ) , // _ (Double)->()
3888+ ( symbolID: " function-overload-3 " , disambiguation: " -(_,(Float)->()) " ) , // _ (Float)->()
3889+ ] )
3890+ }
3891+
37753892 // The second overload refers to the metatype of the parameter
37763893 do {
37773894 func makeSignature( first: DeclToken ... ) -> SymbolGraph . Symbol . FunctionSignature {
@@ -4350,8 +4467,8 @@ class PathHierarchyTests: XCTestCase {
43504467 XCTFail ( " Symbol for \( path. singleQuoted) not found in tree " , file: file, line: line)
43514468 } catch PathHierarchy . Error . unknownName {
43524469 XCTFail ( " Symbol for \( path. singleQuoted) not found in tree. Only part of path is found. " , file: file, line: line)
4353- } catch PathHierarchy . Error . unknownDisambiguation {
4354- XCTFail ( " Symbol for \( path. singleQuoted) not found in tree. Unknown disambiguation. " , file: file, line: line)
4470+ } catch PathHierarchy . Error . unknownDisambiguation( _ , _ , let candidates ) {
4471+ XCTFail ( " Symbol for \( path. singleQuoted) not found in tree. Unknown disambiguation. Suggested disambiguations: \( candidates . map ( \ . disambiguation . singleQuoted ) . sorted ( ) . joined ( separator : " , " ) ) " , file: file, line: line)
43554472 } catch PathHierarchy . Error . lookupCollision( _, _, let collisions) {
43564473 let symbols = collisions. map { $0. node. symbol! }
43574474 XCTFail ( " Unexpected collision for \( path. singleQuoted) ; \( symbols. map { return " \( $0. names. title) - \( $0. kind. identifier. identifier) - \( $0. identifier. precise. stableHashString) " } ) " , file: file, line: line)
0 commit comments