Skip to content

docs: resolve ambiguous cross-references and the five autodoc signature failures - #416

Merged
JarryShaw merged 2 commits into
mainfrom
docs/sphinx-xref-cleanup
Sep 16, 2026
Merged

JarryShaw merged 2 commits into
mainfrom
docs/sphinx-xref-cleanup

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

A clean Sphinx build went from 234 warning/error lines to 91, with nothing suppressed — no :noindex:, no nitpick_ignore, no suppress_warnings. Counts verified by two independent clean builds.

before after
total WARNING/ERROR lines 234 91
error while formatting signature 5 0
more than one target found 156 31
Cannot resolve forward reference 22 10
Content block expected for the "note" directive (ERROR) 1 0
ERROR lines 4 3
duplicate object description 38 38 (out of scope)

The five signature failures were the worst of it, and they weren't about links

Autodoc was dropping members from their pages entirely — Protocol.__schema__ and four __protocol_type__ attributes. Traced to a repro: sphinx_autodoc_typehints.process_signature gates on callable(obj) alone, sees the class those attributes hold as their value, and returns a signature. Sphinx 9.1 then does signatures[0] = ... on a list it never populated → IndexError, which autodoc reports as "error while formatting signature" and skips the member.

conf.py now claims that event first for class-valued attributes, at priority 400 so it wins against the extension's default 500. All five render again.

125 of the 156 ambiguous references had one root cause

Autodoc reads an attribute's type with typing.get_type_hints, and one unresolvable TYPE_CHECKING name makes it raise for the whole class — Sphinx then falls back to raw __annotations__ strings. So pre: 'ToSPrecedence' rendered as a bare word that the Python domain resolves by suffix-matching, and both pcapkit.const.ipv4.tos_pre.ToSPrecedence and its pcapkit.vendor generator match. Roughly half the links pointed at the crawler rather than the enumeration.

conf.py now executes each module's guarded-import block up front, reusing sphinx-autodoc-typehints' own resolver (which does this lazily but skips classes, since it keys off __globals__). Verified in the HTML: ToSField.pre → pcapkit/const/ipv4.html#…ToSPrecedence.

12 forward references were real source bugs

ipv6_route.py never imported NoReturn, ProtoChain or Protocol — its sibling ah.py, with identical properties, does. ipv6.py never imported Schema; extraction.py never imported ProtocolContext. And CallbackFn/FrameConstructor quoted names resolvable only from their defining module, which broke every consumer — fixed by moving the TypeVars above the TYPE_CHECKING block and unquoting.

Every source change is inside a TYPE_CHECKING block or a #: doc comment, so there is no runtime effect. Unit tier: 649 passed, 5 skipped.

A latent build-breaker, and why it was latent

rank: ' int' — the space that belongs after the colon typed one character late. Meaningless to a type checker, and previously harmless here because these annotations failed earlier with NameError, which sphinx.util.typing.get_type_hints catches. Once they resolve, Python 3.14's annotationlib rejects the leading space with a SyntaxError that Sphinx does not catch, and it aborts the whole build.

Both instances are fixed at source (data/internet/hopopt.py, data/internet/ipv6_opts.py); a sweep finds no third. The reasoning lives on bind_type_checking_names, because a third such typo should stop the build rather than be worked around in conf.py.

A judgement call worth recording

napoleon_type_aliases = {'Type': '~typing.Type'} would have replaced eight hand edits with one line. Not used: pcapkit/protocols/application/httpv1.py:81 has #: Type: Type of HTTP receipt. where Type means its own class, and a global alias would mislink it.

Not fixed, with what each needs

  • 20 ambiguous refs in const/reg/apptype.py (19) and const/sctp/payload_protocol_identifier.py (1). Napoleon parses every attribute docstring as type: description, splitting on the first unprotected colon — so #: Echo (ECHO) [https://…] splits inside https: and renders the description as //www.nntb.no/~dreibh/rserpool/]. These pages are visibly wrong, not merely mislinked. Napoleon has no switch for it; the fix belongs in the pcapkit/vendor/reg/apptype.py crawler, followed by regenerating a 26k-line const file — which needs IANA network access.
  • 8 ambiguous refs blocked by docs: fix 45 places where the documentation contradicts the code #413's files (_AT, Type, and three whose targets are doubled .. data:: names in reassembly.rst:92-102). Same one-line fixes as the traceflow ones; apply after docs: fix 45 places where the documentation contradicts the code #413 lands.
  • The 3 remaining ERRORs are Duplicate target name: "libpcap" in foundation/engines/index.rst, which docs: fix 45 places where the documentation contradicts the code #413 already fixes (2 targets → 1). They clear on merge.
  • 3 refs on the builtin type — Sphinx's class→data→attr fallback matches the project's own attributes named type before intersphinx is consulted. Would need builtins.type spelled out; judged not worth the noise.
  • 38 duplicate object descriptions — outside this PR's three categories. 24 sit on files docs: fix 45 places where the documentation contradicts the code #413 has open; 14 are follow-up work.

One caveat on the counts

6 Failed guarded type import and 10 forward references are environment, not repo defects. The venv available here lacks pcap, pcapfile and libpcap, all of which the Pipfile's [dev-packages] lists — pipenv was never provisioned on this host. Under a real pipenv install --dev those 16 should not appear, so the true after-count is likely 75, not 91. I did not install into the shared venv to find out.

Clean Sphinx build goes from 234 warnings/errors to 92.

The five ``error while formatting signature`` failures were the worst of it:
autodoc dropped ``Protocol.__schema__`` and four ``__protocol_type__``
attributes from their pages entirely. ``sphinx_autodoc_typehints`` keys its
signature handler off ``callable(obj)``, sees the class each of those
attributes holds as its value, and hands Sphinx 9.1 a signature it stores with
``signatures[0] = ...`` on a list it never populated. ``conf.py`` now claims
that event first for class-valued attributes, and the members render again.

The 156 ``more than one target found`` warnings had one dominant cause. Autodoc
reads a class attribute's type with ``typing.get_type_hints``; one unresolvable
``TYPE_CHECKING`` name makes that raise for the whole class, and Sphinx falls
back to the raw ``__annotations__`` strings. ``pre: 'ToSPrecedence'`` therefore
became a bare word that the Python domain had to resolve by suffix-matching,
with ``pcapkit.const...ToSPrecedence`` and its ``pcapkit.vendor`` generator both
matching -- so roughly half of those links pointed at the crawler rather than
the enumeration. Binding each module's guarded imports up front lets
``get_type_hints`` succeed and the annotations carry their full dotted path.
125 of the 156 go away; the rest are qualified by hand or listed in the PR.

Also fixed, all at source rather than by suppression:

* 12 unresolvable forward references -- ``ipv6_route`` never imported
  ``NoReturn``, ``ProtoChain`` or ``Protocol``; ``ipv6`` never imported
  ``Schema``; ``extraction`` never imported ``ProtocolContext``; and the
  ``CallbackFn``/``FrameConstructor`` aliases quoted names that only their own
  module could resolve, which broke every consumer.
* ``PCAP_CT.run`` losing its trailing ``Note:`` body, because an injected
  ``:rtype: None`` landed between the directive and its content.
* ``rank:' int'`` in ``data.internet.hopopt`` -- Python 3.14 rejects the stray
  space outright and aborts the build once the name resolves.

Docs build and the full test suite (782 passed, 17 skipped) both green.
…workaround

`conf.py` was normalising `rank: ' int'` at build time because the file holding
the second instance was thought to be off-limits to this branch. It is not: #413
edits `pcapkit/protocols/internet/ipv6_opts.py`, and the defect is in
`pcapkit/protocols/data/internet/ipv6_opts.py` -- a different file that no open PR
touches.

So it is fixed at source and `strip_annotation_whitespace` is gone, along with
the warning it emitted on every build. A sweep of the package finds no third
instance.

The reason the trailing space matters is kept, moved onto
`bind_type_checking_names` where it belongs: resolving the guarded names is what
turns a malformed annotation from harmless into fatal, since these used to fail
earlier with `NameError` (which Sphinx catches) and now reach 3.14's
`annotationlib`, which raises `SyntaxError` (which Sphinx does not). A third such
typo should stop the build rather than be papered over here.

Clean build after: 91 warning/error lines, 0 signature failures, 0 stray-whitespace
notices, 516 pages. Unit tier 649 passed, 5 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are consistent with the pinned Sphinx toolchain, are scoped to documentation/type-resolution behavior, and resolve concrete build failures without altering runtime semantics.

Pull request overview

This PR improves the reliability and correctness of the Sphinx documentation build by eliminating a set of signature-rendering failures and substantially reducing ambiguous cross-references, primarily by making type information resolvable during autodoc and by disambiguating several types in docs and doc-comments.

Changes:

  • Adds Sphinx build-time hooks in docs/source/conf.py to (a) execute TYPE_CHECKING guarded imports across pcapkit modules and (b) prevent class-valued attributes from being treated as callables with signatures.
  • Fixes multiple forward-reference / guarded-import issues in TYPE_CHECKING blocks so autodoc can resolve types (plus fixes two malformed quoted annotations that would become fatal once resolvable).
  • Updates several docs and #: type comment lines to use fully-qualified targets to avoid ambiguous cross-references.
File summaries
File Description
pcapkit/protocols/transport/udp.py Disambiguates Type in doc-comment type text for protocol mapping.
pcapkit/protocols/transport/tcp.py Disambiguates Type in doc-comment type text for protocol mapping.
pcapkit/protocols/transport/sctp.py Disambiguates Type in doc-comment type text for protocol mapping.
pcapkit/protocols/misc/pcapng.py Disambiguates Type in doc-comment type text for link-type protocol mapping.
pcapkit/protocols/misc/pcap/frame.py Disambiguates Type in doc-comment type text for link-type protocol mapping.
pcapkit/protocols/link/link.py Disambiguates Type in doc-comment type text for next-layer protocol mapping.
pcapkit/protocols/internet/ipv6.py Adds missing Schema import under TYPE_CHECKING for resolvable annotations.
pcapkit/protocols/internet/ipv6_route.py Adds missing TYPE_CHECKING imports to resolve forward references in annotations.
pcapkit/protocols/internet/internet.py Disambiguates Type in doc-comment type text for next-layer protocol mapping.
pcapkit/protocols/data/internet/ipv6_opts.py Fixes malformed quoted annotation (' int' → 'int') that can break doc builds once resolved.
pcapkit/protocols/data/internet/hopopt.py Fixes malformed quoted annotation (' int' → 'int') that can break doc builds once resolved.
pcapkit/protocols/application/httpv2.py Fixes a quoted forward reference in a TYPE_CHECKING alias to be resolvable by consumers.
pcapkit/protocols/init.py Disambiguates Type in doc-comment type text for the protocol registry.
pcapkit/foundation/traceflow/traceflow.py Moves TypeVar definitions above TYPE_CHECKING so exported aliases can be resolved across modules.
pcapkit/foundation/reassembly/reassembly.py Moves TypeVar definitions above TYPE_CHECKING so exported aliases can be resolved across modules.
pcapkit/foundation/extraction.py Adds missing ProtocolContext import under TYPE_CHECKING for resolvable annotations.
docs/source/pcapkit/protocols/internet/ipv4.rst Fully-qualifies :type: targets to prevent ambiguous cross-references.
docs/source/pcapkit/foundation/traceflow/traceflow.rst Adjusts type and data targets to resolve correctly within the module context.
docs/source/conf.py Adds build-time hooks to pre-bind guarded names and to prevent signature-processing crashes for class-valued attributes.
Review details
  • Files reviewed: 17/19 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@JarryShaw
JarryShaw merged commit 5e4378d into main Sep 16, 2026
25 checks passed
@JarryShaw
JarryShaw deleted the docs/sphinx-xref-cleanup branch September 17, 2026 01:06
@JarryShaw JarryShaw added the docs Pull requests that change documentation only (docs: subject prefix) label Sep 22, 2026
@JarryShaw JarryShaw added this to the 1.5 milestone Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Pull requests that change documentation only (docs: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants