Skip to content

Nitpick flags Literal annotation values as missing py:class (swev-id: sphinx-doc__sphinx-9602) #85

@rowan-stein

Description

@rowan-stein

User request

Nitpick flags Literal annotation values as missing py:class

Describe the bug:
When a value is present in a type annotation as Literal, Sphinx treats the value as a py:class. With nitpick enabled, values like Literal[True] fail because True is not a class. This blocks builds using -n -W.

How to reproduce:

import typing
@typing.overload
def foo(x: "typing.Literal[True]") -> int: ...
@typing.overload
def foo(x: "typing.Literal[False]") -> str: ...
def foo(x: bool):
    """a func"""
    return 1 if x else "foo"

A failing example project: https://github.com/sirosen/repro/tree/master/sphinxdoc/literal (run ./doc.sh).

Expected behavior:
Literal[True] (or any literal value) should be present in the type annotation but should not trigger nitpick warnings.

Environment:

  • OS: Linux
  • Python: 3.8, 3.9
  • Sphinx: 4.1.2
  • Extensions: autodoc

Research specification (by Emerson Gray)

Summary:

  • Literals are values (True/False/None, numbers, strings, enum members) and should not be cross-referenced as classes/objects under nitpicky mode. Current Python domain parsing wraps tokens from Literal[...] into pending xrefs.

Proposed changes:

  1. Modify sphinx/domains/python.py in _parse_annotation and its unparse helper to carry an in_literal flag when traversing typing.Literal[...] (and typing_extensions.Literal[...]). When in_literal is true, emit plain literal text nodes for the arguments (e.g., booleans, numbers, strings, enum members) and do not create pending_xref nodes for them.

    • Detect Literal base via AST (ast.Name(id='Literal') or ast.Attribute(..., attr='Literal')).
    • Handle Python 3.8+ (ast.Constant) and <3.8 (ast.NameConstant) appropriately. Preserve punctuation and ellipsis handling.
  2. Extend PyXrefMixin.make_xrefs (same file) to suppress xref creation for tokens inside Literal[...] when parsing typed docfields (e.g., :type a: Literal[True, 1, "x", None]). Maintain xrefs for actual type names (Literal, Union, Annotated, bool, etc.).

    • Use a simple bracket depth state machine activated when encountering Literal[ or qualified typing.Literal[/typing_extensions.Literal[.

Compatibility:

  • Works with typing.Literal and typing_extensions.Literal without import-time resolution.
  • Handles nested generics (Union[Literal[True], bool]) and Annotated[Literal[...], ...].
  • Known limitation: aliases like from typing import Literal as L won’t be detected as Literal.

Tests to add:

  • New tests under tests/test_domain_py.py and a dedicated root at tests/roots/test-domain-py-literal/ with nitpicky = True. Verify that:
    • Pending xrefs are created for type names (e.g., Literal, Union, Annotated, bool).
    • No xrefs or nitpicky warnings are created for literal values inside Literal[...] (True, 1, "x", None, SomeEnum.VALUE).
    • Include docfield case: :type a: Literal["A", "B"].

Observed failure & reproduction (current behavior):

  • Build a minimal project with nitpicky enabled and signatures using Literal[...].
  • Observed warnings before fix include (representative examples):
    • WARNING: py:class reference target not found: True
    • WARNING: py:class reference target not found: 1
    • WARNING: py:class reference target not found: 'x'
    • WARNING: py:obj reference target not found: None
    • WARNING: py:class reference target not found: SomeEnum.VALUE
      These originate from unresolved pending_xref nodes created for the literal tokens.

Acceptance criteria:

  • Under nitpicky mode, builds with Literal[...]-containing annotations do not emit missing-reference warnings for literal values, while correctly cross-referencing actual types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions