What is the purpose of synthesized symbols in a symbolgraph?

unfortunately, serving documentation for synthetics is an inherently dynamic problem, and a static solution like emitting synthesized symbols into a symbol graph does not actually solve it.

i spent a lot of time trying to wrap my head around this concept, and i think i’ve finally zeroed in on the error in thinking that was causing me so many headaches with inherited members, which i’ll summarize below:

  • false statement: synthetic symbols are part of a module’s symbolgraph, and every module comes with a set of natural symbols and synthetic symbols. the API of a collection of modules (e.g., a package) is the union of the symbolgraphs of its constituent modules.

  • true statement: synthetic symbols are not part of any symbolgraph; rather, they arise through interactions between arbitrary subsets of modules. the API of a collection of modules (e.g., a package) cannot be computed in advance without considering every possible subset of modules in that collection.

to walk through a concrete example, suppose we have four single-module packages with a “Z”-shaped dependency graph:

legend: swift-x <- swift-y ::= “swift-y depends on swift-x”
swift-foo <- swift-baz 
          ↙            
swift-bar <- swift-qux
  • swift-foo declares enum FooType
  • swift-bar declares protocol Barable
  • swift-baz conforms FooType to Barable
  • swift-qux extends Barable with Barable.qux(_:)

the natural symbolgraph for these four packages might look like

swift-foo                   swift-bar                    swift-qux 
 FooType  -- conforms to ->  Barable  -- has member ->  Barable.qux(_:)
       (perpetrator: swift-baz)   (perpetrator: swift-qux)

now, you would expect that if you import both BazModule and QuxModule, then FooType should have a synthetic member FooType.qux(_:).

    swift-foo                               swift-??? 
     FooType  -- has synthetic member ->  FooType.qux(_:)
               (perpetrator: swift-???)   

but this symbol doesn’t actually belong to either of swift-{foo, bar, baz, qux}, nor can we really “blame” its existence on any single module. it actually belongs to an imaginary client package that imports both BazModule and QuxModule.

swift-foo <- swift-baz <- (swift-baz × swift-qux)
          ↙            ↙ 
swift-bar <- swift-qux

so, at a minimum, the number of possible imaginary client modules grows with O(n2) of the number of modules involved.

1 Like