Resolve doc links once in the semantic layer; fix Self::/qualified links across all backends - #116
Merged
Conversation
Resolve doc-link paths as parsed segments rather than raw strings: `Self::` substitutes the enclosing type's path (threaded through a new walk_module_docs traversal shared by validate and collect), and module-qualified paths to freestanding functions and extern values resolve instead of being gated out by a bare-name check. The shared walk also newly validates nested-item docs and visits nested enums' associated functions. Closes #114 Closes #115
Replace ad-hoc string handling in the doc-link resolution path with a
structured pipeline:
- `DocLinkPath` parses link text exactly once (leading `Self` prefix +
segments); `resolve()` and every consumer take the structured form.
- `resolve_all` resolves every doc link in one crate-wide pass during
semantic analysis, storing `ResolvedDocLink { text, target }` tables
keyed by doc-block location, per module. Validation is this pass's
failure path. The tables ride `SemanticAnalysis`/`SemanticOutput` so
backends can consume targets instead of re-resolving.
- The doc walk is now genuinely recursive: nested types' fields,
associated functions, vftables, and further-nested items are visited
(and validated) at any depth, with the enclosing type's augmented
scope.
- `Self` enclosing context now follows emitted-rustdoc reality: the
type itself for type/enum/bitflags docs and their members, the parent
type for nested constants/extern values (their docs land in the
parent's impl block), and nothing for type aliases or module-level
values — `[Self]` on a module-level constant is now rejected instead
of validating and emitting a link rustdoc can't resolve.
Behavior-preserving for the emitted corpus (byte-identical output).
The JSON backend re-resolved doc links itself with no enclosing-type context, silently dropping every `Self::` link from `doc_links` (the viewer rendered them as dead links). `DocCx` now looks links up in the location-keyed tables produced during semantic analysis, so the JSON output cannot diverge from what the compiler validated. Link tables are keyed by a `DocBlockKey` rather than a raw location: a module's `location()` is a proxy borrowed from its first item, so keying the module's own doc block by it collided with that item's doc block (the module page showed the item's links). The module doc block gets a dedicated key variant instead, exercised by new module-level doc links in the doc_self_links corpus input.
Replace the string-map link rewriting (leaf-name keyed nested_rewrites, exact-text cross-module matches, doc-driven `use` imports and `use Flat as Leaf` aliases) with target rendering: each link's resolved target — from the semantic link tables — is rendered as an absolute Rust path, flattening nested items and substituting extern-value accessors. This removes two latent failure modes of the string approach: leaf-name collisions (two types with same-named nested items shared one rewrite key, and any matching segment in an unrelated link was rewritten) and cross-module links written in non-canonical form matching no rewrite key and emitting broken rustdoc. It also rewrites docs that previously got no treatment at all (module functions, constants), and code shortcuts now become inline links so their written label survives instead of being mangled to the flattened name. The generated root allows rustdoc::redundant_explicit_links: rewriting is uniform, and knowing when rustdoc could have resolved the label alone would mean re-implementing rustdoc's resolution.
The LSP passed no enclosing type at its three doc-link resolution sites, so `Self::` links — valid to the compiler since #114 — were inert in the editor: no document-link, no hover/go-to-definition, and doc_link_occurrences missed them, leaving `Self::member` links stale after a member rename. `enclosing_type_for_doc_line` walks the file's parsed AST to find the item whose definition (or impl block) contains a doc line, mirroring the compiler's walk semantics: nested constants/extern values use the parent type, type aliases and module-level docs get none.
Rewrite each resolved intra-doc link in emitted C++ doc comments into doxygen markdown (`[label](@ref ns::Target)`), rendered from the link's semantic target under the C++ emission rules: namespaces for modules, genuine nesting for nested types, `get_<name>()` accessors for extern values, and module-scope flattened names (`Parent_NAME`) for value items under enum/bitflags parents. Targets with no documented C++ entity (primitives, out-of-tree externs) flatten to their bare label instead of emitting a dead link. test.py gains a doxygen pass over the emitted corpus that fails on any warning — unresolvable `@ref`s in particular — mirroring the cargo doc gate for Rust. It skips with a warning when doxygen is missing; a new shell.nix provides it and CI installs it. Doxygen output is generated into a temp dir and discarded; the tracked C++ corpus shows the rewritten comments.
Remove DocLinkTarget::import_path/extern_value_path and the ModuleDocLinks imports/extern-value accessors — all consumers now render targets directly. Update docs/rust_backend.md and docs/language.md for the resolve-once pipeline, Self:: and module-qualified link forms, and per-backend target rendering.
Resolution scopes are not just module paths: docs inside a type resolve under an augmented scope with the type's own path prepended (for bare nested-item references), plus use-imported item paths. find_item and find_in_modules assumed "first scope entry = current module", so for type-scoped doc blocks same-module preference anchored at the *type* path and the crate-wide bare-name search fell through to alphabetical order — picking a same-named type from another module and failing the member lookup. Caught by pyxis-defs in CI (`RenderContext::…` resolving to graphics_engine's RenderContext instead of ui::scaleform's). The resolver now locates the current module explicitly as the first scope entry that is a module.
Some doxygen builds (Ubuntu's, on CI runners with graphviz present) auto-enable dot and then fail on their own generated map files. The check only cares about ref resolution, not diagrams.
Pin HAVE_DOT = YES — distro doxygen packages disagree on the default (Ubuntu's assumes dot exists, nix's assumes it doesn't), which is how CI failed while local runs passed — and provide graphviz alongside doxygen in both shell.nix and CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #114 and #115, then restructures doc-link handling so those classes of bug can't recur: links are resolved exactly once during semantic analysis, and every consumer (validation, Rust/JSON/C++ backends, LSP) works from the resolved targets instead of re-deriving structure from strings.
Language fixes
Self::memberdoc links resolve against the enclosing type (Self:: in doc-links fails to resolve #114), including bare[Self], nested items (Self= the nested item), and nested constants/extern values (Self= the parent type, matching where their emitted docs land in rustdoc).[Self]on module-level constants/type aliases is now correctly rejected.Structural changes
DocLinkPath { self_prefixed, segments }is parsed once at the markdown boundary; the resolution path never splits strings again.doc_links::resolve_allresolves every link crate-wide in one pass; validation is its failure path. Results are stored per module asModuleDocLinks, keyed byDocBlockKey(a dedicated variant for the module's own doc block —Module::location()is a proxy borrowed from the first item, so location-keying would collide).crate::path. The leaf-name rewrite maps (which collided when two types shared a nested-item name and rewrote matching segments in unrelated links), exact-text cross-module matches, doc-drivenuseimports, anduse Flat as Leafaliases are all gone. Module-function and constant docs — previously never rewritten — get uniform treatment, and code shortcuts convert to inline links so the written label survives instead of being mangled to the flattened name. The generated root allowsrustdoc::redundant_explicit_links(deciding when rustdoc could resolve the label alone would mean re-implementing rustdoc's resolver).Self::links being silently dropped fromdoc_links(the viewer rendered them dead).Self::links get hover/go-to-definition/document-link and participate in rename.[label](@ref ns::Target)), rendered under C++ emission rules: namespaces, genuine nesting,get_<name>()accessors, module-scopeParent_NAMEflattening for value items under enum/bitflags parents. Unlinkable targets (primitives, out-of-tree externs) flatten to their bare label instead of emitting dead hrefs.Verification
python test.pygains a doxygen pass over the emitted C++ corpus that fails on any warning (unresolvable@refs in particular) — the C++ analogue of thecargo docgate. Skipped with a warning when doxygen is absent;shell.nixprovides it and CI installs it. Doxygen output is generated to a temp dir and discarded — the tracked C++ corpus shows the rewritten comments.DocLinkPathparsing,Selfenclosing semantics (nested constants, module-level rejection), nested-item validation; new LSP integration test forSelf::navigation; corpus inputs exercisingSelf::, module-qualified links, and module-doc links.