Repository: apple/swift-docc (swift)

Issue:
[SR-15868] DocC displays optional method requirements as "Required"

   |                  |                 |
   |------------------|-----------------|
   |Previous ID       | SR-15868      |
   |Radar             | rdar://88051591         |
   |Original Reporter | @franklinsch      |
   |Type              | Bug    |


Attachment: [Download](https://user-images.githubusercontent.com/2727770/164964458-9c37f736-fe46-45c0-9e47-ebc838506885.gz)

      
   <details>
  <summary>Additional Detail from JIRA</summary>

   |                  |                 |
   |------------------|-----------------|
   |Votes             | 0         |
   |Component/s       | Swift-DocC    |
   |Labels            | Bug        |
   |Assignee          | None      |
   |Priority          | Medium      |

   

   md5: 102d67a7d0186f613f6d87bf7ec482e7

  </details>





**Issue Description:**


DocC renders the "Required" label for optional method requirements of Objective-C classes. It should not render the "Required" label for those methods, since conforming types don't need to implement them.

For example, the method below gets the "Required" label (see screenshot).

``` swift
@objc public protocol MyProtocol {
    /// An optional requirement.
    @objc optional func myOptionalRequirement()
}
```

For such methods, the generated Swift symbol graph (which can be found in the project's DerivedData folder, if building using Xcode), contains a relationship of kind "optionalRequirementOf":

``` java
    {
      "kind": "optionalRequirementOf",
      "source": "c:@M@TestFramework@objc(pl)MyProtocol(im)myOptionalRequirement",
      "target": "c:@M@TestFramework@objc(pl)MyProtocol"
    }, 
```

DocC should not emit the “Required.” label for this kind of relationship.

Pull request description:
Bug/issue #, if applicable: 

## Summary

Close #182 

## Testing

```swift
@objc public protocol MyProtocol {    
    func hello1()
    @objc func hello2()
    @objc optional func hello3()
}
```

<img width="393" alt="image" src="https://user-images.githubusercontent.com/43724855/172057075-01806c2c-53f1-4d2b-814e-fcce35cd7337.png">


## Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

- [x] Added tests
- [x] Ran the `./bin/test` script and it succeeded
- [x] Updated documentation if necessary

Patch:
diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
index ef703de675..da69f2b4db 100644
--- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
+++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
@@ -1568,28 +1568,24 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
     /// - ``SymbolGraphRelationshipsBuilder``
     func buildRelationships(_ relationships: Set<SymbolGraph.Relationship>, bundle: DocumentationBundle, engine: DiagnosticEngine) {
         for edge in relationships {
-            // Build conformant type <-> protocol relationships
-            if case .conformsTo = edge.kind {
+            switch edge.kind {
+            case .conformsTo:
+                // Build conformant type <-> protocol relationships
                 SymbolGraphRelationshipsBuilder.addConformanceRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
-                continue
-            }
-            
-            // Build implementation <-> protocol requirement relationships.
-            if case .defaultImplementationOf = edge.kind {
+            case .defaultImplementationOf:
+                // Build implementation <-> protocol requirement relationships.
                 SymbolGraphRelationshipsBuilder.addImplementationRelationship(edge: edge, in: bundle, context: self, symbolIndex: &symbolIndex, engine: diagnosticEngine)
-                continue
-            }
-            
-            // Build ancestor <-> offspring relationships.
-            if case .inheritsFrom = edge.kind {
+            case .inheritsFrom:
+                // Build ancestor <-> offspring relationships.
                 SymbolGraphRelationshipsBuilder.addInheritanceRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
-                continue
-            }
-            
-            // Build required member -> protocol relationships.
-            if case .requirementOf = edge.kind {
+            case .requirementOf:
+                // Build required member -> protocol relationships.
                 SymbolGraphRelationshipsBuilder.addRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
-                continue
+            case .optionalRequirementOf:
+                // Build optional required member -> protocol relationships.
+                SymbolGraphRelationshipsBuilder.addOptionalRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
+            default:
+                break
             }
         }
     }
diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift
index 18866ed935..7095aba8ff 100644
--- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift
+++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift
@@ -1,7 +1,7 @@
 /*
  This source file is part of the Swift.org open source project
 
- Copyright (c) 2021 Apple Inc. and the Swift project authors
+ Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
  Licensed under Apache License v2.0 with Runtime Library Exception
 
  See https://swift.org/LICENSE.txt for license information
@@ -232,13 +232,34 @@ struct SymbolGraphRelationshipsBuilder {
         }
     }
     
-    /// Adds a relationship from a type member to a protocol requirement.
+    /// Adds a required relationship from a type member to a protocol requirement.
     /// - Parameters:
     ///   - edge: A symbol graph relationship with a source and a target.
     ///   - bundle: A documentation bundle.
     ///   - symbolIndex: A symbol lookup map by precise identifier.
     ///   - engine: A diagnostic collecting engine.
     static func addRequirementRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine) {
+        addProtocolRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine, required: true)
+    }
+    
+    /// Adds an optional relationship from a type member to a protocol requirement.
+    /// - Parameters:
+    ///   - edge: A symbol graph relationship with a source and a target.
+    ///   - bundle: A documentation bundle.
+    ///   - symbolIndex: A symbol lookup map by precise identifier.
+    ///   - engine: A diagnostic collecting engine.
+    static func addOptionalRequirementRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine) {
+        addProtocolRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine, required: false)
+    }
+    
+    /// Adds a relationship from a type member to a protocol requirement.
+    /// - Parameters:
+    ///   - edge: A symbol graph relationship with a source and a target.
+    ///   - bundle: A documentation bundle.
+    ///   - symbolIndex: A symbol lookup map by precise identifier.
+    ///   - engine: A diagnostic collecting engine.
+    ///   - required: A bool value indicating whether the protocol requirement is required or optional
+    private static func addProtocolRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine, required: Bool) {
         // Resolve source symbol
         guard let requiredNode = symbolIndex[edge.source],
             let requiredSymbol = requiredNode.semantic as? Symbol else {
@@ -246,8 +267,7 @@ struct SymbolGraphRelationshipsBuilder {
             engine.emit(NodeProblem.notFound(edge.source))
             return
         }
-        
-        requiredSymbol.isRequired = true
+        requiredSymbol.isRequired = required
     }
     
     /// Sets a node in the context as an inherited symbol if the origin symbol is provided in the given relationship.


Test patch:
diff --git a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift
index 1aaced4800..0eccdb1f01 100644
--- a/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift
+++ b/Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphRelationshipsBuilderTests.swift
@@ -1,7 +1,7 @@
 /*
  This source file is part of the Swift.org open source project
 
- Copyright (c) 2021 Apple Inc. and the Swift project authors
+ Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
  Licensed under Apache License v2.0 with Runtime Library Exception
 
  See https://swift.org/LICENSE.txt for license information
@@ -24,8 +24,8 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase {
         
         let moduleRef = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/MyKit", sourceLanguage: .swift)
         
-        let sourceSymbol = SymbolGraph.Symbol(identifier: sourceIdentifier, names: SymbolGraph.Symbol.Names(title: "A", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["MyKit", "A"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .class, displayName: "Class"), mixins: [:])
-        let targetSymbol = SymbolGraph.Symbol(identifier: targetIdentifier, names: SymbolGraph.Symbol.Names(title: "B", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["MyKit", "B"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .class, displayName: "Protocol"), mixins: [:])
+        let sourceSymbol = SymbolGraph.Symbol(identifier: sourceIdentifier, names: SymbolGraph.Symbol.Names(title: "A", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["MyKit", "A"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: sourceType, mixins: [:])
+        let targetSymbol = SymbolGraph.Symbol(identifier: targetIdentifier, names: SymbolGraph.Symbol.Names(title: "B", navigator: nil, subHeading: nil, prose: nil), pathComponents: ["MyKit", "B"], docComment: nil, accessLevel: .init(rawValue: "public"), kind: targetType, mixins: [:])
         
         let engine = DiagnosticEngine()
         symbolIndex["A"] = DocumentationNode(reference: sourceRef, symbol: sourceSymbol, platformName: "macOS", moduleReference: moduleRef, article: nil, engine: engine)
@@ -145,7 +145,7 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase {
         var symbolIndex = [String: DocumentationNode]()
         let engine = DiagnosticEngine()
         
-        let edge = createSymbols(in: &symbolIndex, bundle: bundle, sourceType: .init(parsedIdentifier: .class, displayName: "Class"), targetType: .init(parsedIdentifier: .protocol, displayName: "Protocol"))
+        let edge = createSymbols(in: &symbolIndex, bundle: bundle, sourceType: .init(parsedIdentifier: .method, displayName: "Method"), targetType: .init(parsedIdentifier: .protocol, displayName: "Protocol"))
         
         // Adding the relationship
         SymbolGraphRelationshipsBuilder.addRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine)
@@ -153,4 +153,18 @@ class SymbolGraphRelationshipsBuilderTests: XCTestCase {
         // Test default implementation was added
         XCTAssertTrue((symbolIndex["A"]!.semantic as! Symbol).isRequired)
     }
+    
+    func testOptionalRequirementRelationship() throws {
+        let bundle = try testBundle(named: "TestBundle")
+        var symbolIndex = [String: DocumentationNode]()
+        let engine = DiagnosticEngine()
+        
+        let edge = createSymbols(in: &symbolIndex, bundle: bundle, sourceType: .init(parsedIdentifier: .method, displayName: "Method"), targetType: .init(parsedIdentifier: .protocol, displayName: "Protocol"))
+        
+        // Adding the relationship
+        SymbolGraphRelationshipsBuilder.addOptionalRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine)
+        
+        // Test default implementation was added
+        XCTAssertFalse((symbolIndex["A"]!.semantic as! Symbol).isRequired)
+    }
 }