Request for a bit of code guidance & expectations

Hi Joe,

DocumentationNode is DocC's semantic model for any symbol/article/tutorial it produces a documentation page for. Documentation nodes get created during DocC's bundle registration process, which loads up symbols from symbol graphs files and standalone Markdown article files. This is the "Analysis and Registration" section in the Compiler Pipeline document.

The model is also used to track symbols resolved out-of-process (via OutOfProcessReferenceResolver, which isn't something that SwiftPM integrates with at the moment). @ronnqvist wrote most of that infrastructure so would be the best person to provide more details about that if you're interested.

Based on the crash trace you provided, it looks like the crash is happening during the symbol registration phase. I'll do my best to explain what each of the methods in the call hierarchy does (from the top-down):

1. DocumentationContext.registerSymbols

This method loads symbols from symbol graph files into DocC's documentation context. The documentation context is DocC's store for most of the data about symbols/articles/tutorials it tracks. It gets populated during bundle registration and contains rich information about each piece of documentation content (or "topic") and the relationship between these pieces of documentation via its Topic Graph. Something worth noting here as well is that each valid topic gets a unique identifier, which is what ultimately decides the URL of the generated documentation pages. This identifier is modeled using the ResolvedTopicReference struct.

2. GeneratedDocumentationTopics.createInheritedSymbolsAPICollections

This method creates API Collection pages for inherited symbols. Two things to define here: 1) an API Collection is a page whose purpose is to group related APIs. You can create an API Collection page in the same way you create an article, with the only difference being that you define a Topics section for API Collections, and 2) an inherited symbol is a symbol which is included in your module but isn't defined in your source code (called a "synthesized symbol" in Swift, for example the == implementation that Swift generates for you). To prevent your documentation pages to get cobbled up with a bunch of synthesized symbols, e.g., when you conform to SwiftUI.View and get a bunch of modifiers, DocC automatically creates an API Collection page that groups all the inherited symbols per protocol that's conformed to; that's what createInheritedSymbolsAPICollections does. For example: Documentation.

3. GeneratedDocumentationTopics.createCollectionNode

This method creates an API Collection documentation node and registered it in the documentation context. It's given the parent page where the API Collection should be curated and the children pages that it should curate.

4. DocumentationContext.sourceLanguages(for:)

This method returns the programming languages a topic (identified by its topic reference) is associated with. When your build multi-language documentation, each language's compiler produces a set of symbol graphs, and DocC links up symbols together into a single DocumentationNode. For example, a Swift class marked @objc will get a single DocumentationNode with 2 source languages: Swift and Objective-C. One more thing to note is that at the symbol graph level, the URL paths of a same symbol can be different in each of their languages. For example, an API can have different names in Swift vs. Objective-C ("Sloth" vs. "SLOSloth"). When you're writing a link in Markdown content, you're allowed to use either of the syntaxes (``Sloth`` or ``SLOSloth``), which @ronnqvist implemented for Swift 5.7! DocC takes the Swift URL path as the canonical representation of the page though, and you can retrieve that path via DocumentationContext.canonicalReference(for:).

sourceLanguages(for:) queries the available languages associated with a documentation node by calling DocumentationContext.entity(with:), which returns the documentation node associated with a reference, or throws an error if the node couldn't be found, which is the cause of the crash you're seeing. It seems like entity(with:) couldn't find a node associated with the reference, which shouldn't happen at this stage. Looking at DocumentationContext.entity(with:), it's possible that documentationCacheBasedLinkResolver.canonicalReference(for: reference) would return a reference that doesn't exist, which would be a bug. In fact, I don't believe that canonicalReference(for:) should ever return a reference that's different than the one you provide it in your case, since you're not building multi-language documentation at all.

It's also worth noting that the build setup you have where you're providing symbol graph files for multiple modules to DocC (which is SwiftViz's scenario, looking at the files generated in .build/symbol-graphs) isn't officially supported, however assuming there's an isolated patch that resolves the situation you're running into, I'm happy to merge it. A path forward for multi-module documentation has been discussed here: Use cases for combined documentation of multiple targets in Swift-DocC.

This seems surprising to me, because as far as I remember, the analyze functionality isn't related to the stack trace we're looking at. Are you calling docc with the exact same arguments when you run without --analyze?

4 Likes