@@ -3328,6 +3328,73 @@ class PathHierarchyTests: XCTestCase {
3328
3328
try assertFindsPath ( " /ModuleName/ContainerName " , in: tree, asSymbolID: containerID)
3329
3329
}
3330
3330
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
+
3331
3398
func testMissingMemberOfAnonymousStructInsideUnion( ) throws {
3332
3399
let outerContainerID = " some-outer-container-symbol-id "
3333
3400
let innerContainerID = " some-inner-container-symbol-id "
@@ -3665,7 +3732,7 @@ class PathHierarchyTests: XCTestCase {
3665
3732
let voidType = DeclToken . typeIdentifier ( " Void " , precise: " s:s4Voida " )
3666
3733
3667
3734
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: [ ] )
3669
3736
}
3670
3737
3671
3738
func makeSignature( first: DeclToken ... , second: DeclToken ... , third: DeclToken ... ) -> SymbolGraph . Symbol . FunctionSignature {
@@ -3772,6 +3839,56 @@ class PathHierarchyTests: XCTestCase {
3772
3839
] )
3773
3840
}
3774
3841
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
+
3775
3892
// The second overload refers to the metatype of the parameter
3776
3893
do {
3777
3894
func makeSignature( first: DeclToken ... ) -> SymbolGraph . Symbol . FunctionSignature {
@@ -4350,8 +4467,8 @@ class PathHierarchyTests: XCTestCase {
4350
4467
XCTFail ( " Symbol for \( path. singleQuoted) not found in tree " , file: file, line: line)
4351
4468
} catch PathHierarchy . Error . unknownName {
4352
4469
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)
4355
4472
} catch PathHierarchy . Error . lookupCollision( _, _, let collisions) {
4356
4473
let symbols = collisions. map { $0. node. symbol! }
4357
4474
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