Skip to content

sphinx-autodoc-pytest-fixtures: Resolve TypeAlias names in fixture return types#9

Merged
tony merged 5 commits intomainfrom
feat/type-checking-aliases
Apr 6, 2026
Merged

sphinx-autodoc-pytest-fixtures: Resolve TypeAlias names in fixture return types#9
tony merged 5 commits intomainfrom
feat/type-checking-aliases

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Apr 6, 2026

Problem

Fixtures whose return type is a TypeAlias showed the expanded underlying type in the fixture summary table rather than the alias name. For example, a fixture annotated -> UnihanTestOptions (where UnihanTestOptions: TypeAlias = Options | Mapping[str, Any]) rendered as Options | Mapping[str, Any] instead of UnihanTestOptions.

Three bugs compounded.

get_type_hints() silently expands TypeAlias names. _get_return_annotation calls get_type_hints(), which resolves the annotation string "UnihanTestOptions" against the module namespace and returns the underlying union type — losing the alias name entirely before _qualify_forward_ref ever runs.

Fast path in _qualify_forward_ref returns the alias value's identity, not the alias name. When a TypeAlias is defined at module level, getattr(mod, name) returns the alias value (e.g. the union type, whose __qualname__ is "Union"). The fast path then returned "typing.Union" instead of "module.AliasName".

_qualify_forward_ref only scanned if TYPE_CHECKING: blocks for ImportFrom nodes. TypeAlias definitions — whether inside a TYPE_CHECKING guard (Name: TypeAlias = ...) or at module level — were not handled.

Solution

_get_return_annotation: after get_type_hints() succeeds, check whether the raw annotation was a bare identifier that resolved to a non-class type (union, generic alias). If so, return the raw string instead of the expanded type. The discriminator is isinstance(ret, type): real classes (including import-aliased ones) return True; union/generic TypeAlias expansions return False.

_qualify_forward_ref fast path: guard the fast path so it only fires when obj.__qualname__ == name. TypeAlias values have __qualname__ == "Union", so they no longer steal the cross-reference.

_qualify_forward_ref slow path: add _is_type_alias_annotation() to recognise TypeAlias and t.TypeAlias/typing.TypeAlias annotation forms. Extend the AST scan to handle two new cases: Name: TypeAlias = ... inside a TYPE_CHECKING block (Case 2), and Name: TypeAlias = ... at module level (Case 3). TYPE_CHECKING ImportFrom still takes priority when the same name appears in both locations.

Test plan

  • All 18 unit tests in test_type_checking_alias.py pass, covering: bare vs attribute TypeAlias form, TYPE_CHECKING vs module-level placement, non-alias AnnAssign ignored, import-wins-over-alias priority, fast-path TypeAlias value guard, import-aliased class still resolves to the class
  • Full suite passes with no regressions

@tony tony force-pushed the feat/type-checking-aliases branch 5 times, most recently from fdabadf to 0fb9de2 Compare April 6, 2026 13:32
tony added 4 commits April 6, 2026 08:35
…E_CHECKING blocks

why: _qualify_forward_ref only resolved ImportFrom nodes inside TYPE_CHECKING
guards, leaving TypeAlias definitions (AnnAssign with TypeAlias annotation)
unqualified — fixture tables showed bare unlinked alias names.
what:
- _metadata.py: add _is_type_alias_annotation() helper; extend the
  TYPE_CHECKING AST walk in _qualify_forward_ref to also match AnnAssign
  nodes whose annotation is TypeAlias (bare or t.TypeAlias attribute form)
- tests: new test_type_checking_alias.py with 10 unit + 1 integration test
  covering both `t.TypeAlias` and bare `TypeAlias` annotation forms, the
  non-alias AnnAssign guard, import-wins-over-alias priority, and full
  Sphinx build verification of return_display qualification
…s in fixture table

why: Fixtures annotated with a TypeAlias (e.g. ``-> UnihanTestOptions``)
showed the expanded union (``Options | Mapping[str, Any]``) instead of
the alias name, because get_type_hints() resolved the alias away.

what:
- _detection.py: after get_type_hints, if raw annotation is a bare
  identifier but the resolved type's name doesn't match, return the raw
  string to preserve the alias name for cross-reference resolution
- _metadata.py: _qualify_forward_ref now scans tree.body (module-level)
  for TypeAlias AnnAssign nodes in addition to TYPE_CHECKING blocks
- tests: 6 new TDD tests covering module-level TypeAlias resolution and
  _get_return_annotation alias preservation
…lias values

why: _qualify_forward_ref fast path used the alias VALUE's __module__/
__qualname__ (e.g. "typing.Union") when a module-level TypeAlias was
accessible at runtime, returning "typing.Union" instead of the alias name.

what:
- fast path: only accept obj when obj.__qualname__ == name (the requested
  identifier), so TypeAlias union values don't steal the cross-reference
- test: test_qualify_forward_ref_fast_path_skips_typealias_value verifies
  module-level TypeAlias values do not trigger the fast path
…ypeAlias expansions

why: Previous guard (resolved_name != raw_ann) falsely classified import-aliased
classes (e.g. UnihanOptions = Options) as TypeAlias expansions because
Options.__qualname__ != "UnihanOptions".  This caused the fixture to show
the raw string instead of resolving to the class.

what:
- _detection.py: replace qualname-mismatch guard with isinstance(ret, type)
  — real classes return True (use resolved), union/generic TypeAlias
  expansions return False (preserve raw alias name)
- test: test_get_return_annotation_class_import_alias_resolves_to_class
  verifies that import-aliased classes are still resolved to the class
@tony tony force-pushed the feat/type-checking-aliases branch from 0fb9de2 to 3728a8c Compare April 6, 2026 13:35
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 96.77419% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.87%. Comparing base (3a3e8fb) to head (6e5b5fe).

Files with missing lines Patch % Lines
...ocutils/src/sphinx_autodoc_docutils/_documenter.py 0.00% 2 Missing ⚠️
scripts/ci/package_tools.py 75.00% 2 Missing ⚠️
...hinx-argparse-neo/src/sphinx_argparse_neo/nodes.py 0.00% 1 Missing ⚠️
...ext/pytest_fixtures/test_sphinx_pytest_fixtures.py 50.00% 1 Missing ⚠️
...ts/ext/pytest_fixtures/test_type_checking_alias.py 99.40% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main       #9      +/-   ##
==========================================
+ Coverage   88.59%   88.87%   +0.27%     
==========================================
  Files          71       72       +1     
  Lines        6955     7107     +152     
==========================================
+ Hits         6162     6316     +154     
+ Misses        793      791       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony merged commit 5bbb1ae into main Apr 6, 2026
30 checks passed
@tony tony deleted the feat/type-checking-aliases branch April 6, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants