How does `ReversedCollection.reverse` typecheck?

ReversedCollection has a member reversed, which shadows the protocol extension member BidirectionalCollection.reversed, which would otherwise return a ReversedCollection<ReversedCollection<C>>.

  1. does reversed need to be called with type context to disambiguate?
  2. why does the BidirectionalCollection extension member not appear as a (synthesized) member of ReversedCollection in its symbolgraph?
func foo<C>(_ collection:C) where C:BidirectionalCollection
{
    let reversed:ReversedCollection<C> = collection.reversed()
    let _:ReversedCollection<ReversedCollection<C>> = reversed.reversed()
    let _:C = reversed.reversed()
}

I haven’t checked the source, but based on your (gorgeous!) site, the conformance of ReversedCollection to BidirectionalCollection is stated as a conditional one (even as the condition is redundant).

1 Like

thank you Xiaodi! does the condition make it a less-favored overload when the compiler performs overload resolution?

members in conditional extension blocks should still be inherited by conforming types in their symbolgraph. i’m not sure if this is a bug in SymbolGraphGen or swift-biome itself, will have to investigate further…

Methods on concrete types (here, ReversedCollection.reversed) are favored over methods on protocol extensions (BidirectionalCollection) in overload resolution.

so

  1. does reversed need to be called with type context to disambiguate?

No it doesn't. Remove the explicit types in your example and the same methods are called.

I'm not sure what you mean by conditional extension, I don't think either of these methods are from conditional conformances. ReversedCollection requires its base be bidirectional, but we don't normally describe that as conditional.

I don't know enough about symbol graph to say if your question 2 is behaving as expected.

As per my caveat, the observation was based on reading @taylorswift's documentation website, which uses language stating that the conformance to BidirectionalCollection exists only when Base conforms also: that would be redundant since Base always conforms, but also not impossible to spell (extension ReversedCollection: BidirectionalCollection where Base: BidirectionalCollection).

Checking the original source, ReversedCollection indeed conforms unconditionally to BidirectionalCollection (which is what I'd have guessed to begin with), so this can't explain the symbol graph issue.

1 Like

the version of the documentation site running on https://swiftinit.org/reference/swift/ regurgitates the swiftExtension.constraints field of the corresponding symbolgraph vertex.

grepping the raw symbolgraph emitted from the compiler, it looks like SymbolGraphGen includes ReversedCollection’s generic constraints in the extension block constraints of all its extension block members:

{
  "kind": {
    "identifier": "swift.method",
    "displayName": "Instance Method"
  },
  "identifier": {
    "precise": "s:s18ReversedCollectionV8reversedxyF",
    "interfaceLanguage": "swift"
  },
  "pathComponents": [
    "ReversedCollection",
    "reversed()"
  ],
  "names": {
    "title": "reversed()",
    "subHeading": [
      {
        "kind": "keyword",
        "spelling": "func"
      },
      {
        "kind": "text",
        "spelling": " "
      },
      {
        "kind": "identifier",
        "spelling": "reversed"
      },
      {
        "kind": "text",
        "spelling": "() -> "
      },
      {
        "kind": "typeIdentifier",
        "spelling": "Base"
      }
    ]
  },
  "docComment": {
    "lines": [
      {
        "text": "Reversing a reversed collection returns the original collection."
      },
      {
        "text": ""
      },
      {
        "text": "- Complexity: O(1)"
      }
    ]
  },
  "functionSignature": {
    "returns": [
      {
        "kind": "typeIdentifier",
        "spelling": "Base"
      }
    ]
  },
  "swiftGenerics": {
    "parameters": [
      {
        "name": "Base",
        "index": 0,
        "depth": 0
      }
    ],
    "constraints": [
      {
        "kind": "conformance",
        "lhs": "Base",
        "rhs": "BidirectionalCollection",
        "rhsPrecise": "s:SK"
      }
    ]
  },
  "swiftExtension": {
    "extendedModule": "Swift",
    "constraints": [
      {
        "kind": "conformance",
        "lhs": "Base",
        "rhs": "BidirectionalCollection",
        "rhsPrecise": "s:SK"
      }
    ]
  },
  "declarationFragments": [
    {
      "kind": "keyword",
      "spelling": "func"
    },
    {
      "kind": "text",
      "spelling": " "
    },
    {
      "kind": "identifier",
      "spelling": "reversed"
    },
    {
      "kind": "text",
      "spelling": "() -> "
    },
    {
      "kind": "typeIdentifier",
      "spelling": "Base"
    }
  ],
  "accessLevel": "public",
  "availability": [
    {
      "domain": "Swift",
      "introduced": {
        "major": 4,
        "minor": 2
      }
    }
  ]
}

i don’t know if this is intended behavior. but it doesn’t seem particularly useful. it should appear in swiftGenerics only.

Ah right, it's easy to misread because conditional conformance declarations use where for their condition, whereas type and protocol declarations use where for unconditional requirements. And then kind of unhelpfully, the canonicalization turns the (more readable IMO) struct ReversedCollection<Base: BidirectionalCollection> into struct ReversedCollection<Base> where Base: BidirectionalCollection.

2 Likes