Why does SymbolGraphGen ignore associated type constraints?

i was curious as to why some generated DocC/Unidoc symbol pages are missing certain crucial generic constraints gating the symbols.

to my surprise, i found that lib/SymbolGraphGen ignores associatedtype constraints entirely when it computes the generic context for a declaration. in the following example, only the Self constraint is detected by SymbolGraphGen; the RawValue constraint is ignored entirely.

public
protocol Protocol
{
}
extension Protocol where Self:RawRepresentable, RawValue:Protocol
{
    public
    func f() { }
}
    {
      ...

      "pathComponents": [
        "Protocol",
        "f()"
      ],
      "swiftGenerics": {
        "constraints": [
          {
            "kind": "conformance",
            "lhs": "Self",
            "rhs": "RawRepresentable",
            "rhsPrecise": "s:SY"
          }
        ]
      },
      "swiftExtension": {
        "extendedModule": "ConditionalDefaultImplementations",
        "typeKind": "swift.protocol",
        "constraints": [
          {
            "kind": "conformance",
            "lhs": "Self",
            "rhs": "RawRepresentable",
            "rhsPrecise": "s:SY"
          }
        ]
      },

      ...
    },

is there a reason why SymbolGraphGen does not consider associated type constraints when it computes generics?

While I don't follow the logic in the code, I believe they're getting filtered out here: swift/lib/SymbolGraphGen/JSON.cpp at main · swiftlang/swift · GitHub

I think this part is trying to filter out requirements of a member that are already satisfied by the extension itself:

    // extension /* protocol */ Q {
    // func foo() {}
    // }
    // ignore Self : Q, obvious
    if (Req.getSecondType()->getAnyNominal() == Self) {
      continue;
    }

This can be achieved with requirementsNotSatisfiedBy() instead. It will also handle cases like

extension Q where Self.A : P {
  func foo() // we want to filter out `Self.A: P` here presumably too and not just `Self: Q`
}

The immediate problem might be that containsParams() finds an archetype whose interface type is a DependentMemberType (Self.RawValue) and not a GenericTypeParameterType, so containsParams() returns false.

This part is also worth revisiting:

    if (Req.getKind() == RequirementKind::Layout) {
      continue;
    }

Self.A: AnyObject is a Layout requirement. Those should not be simply dropped, instead they can also be transformed in various ways, but they only have a getFirstType() and not a getSecondType(), so we just need to handle that case.

@taylorswift I've seen other posts from you where you've been playing with SymbolGraph, if you wanted to try to learn about this code and refactor some of the above a little, I'd be happy to review PRs and offer guidance.

2 Likes

cc @QuietMisdreavus