Skip to content

Build the __class_getitem__ of PEP 695 classes without transforms - #3410

Open
b84330808 wants to merge 2 commits into
pylint-dev:mainfrom
b84330808:pep695-class-getitem-without-transforms
Open

b84330808 wants to merge 2 commits into
pylint-dev:mainfrom
b84330808:pep695-class-getitem-without-transforms

Conversation

@b84330808

Copy link
Copy Markdown

Type of Changes

Type
✓ 🐛 Bug fix

Description

In pylint-dev/pylint#10352, a pylint plugin with a FunctionDef transform that only returns its node makes pylint report missing-function-docstring on a method that overrides a method of a PEP 695 generic class, because the subclass loses its ancestors:

class Base[T]: ...
class Middle[T](Base[T]): ...
class Child(Middle[int]): ...

Child.ancestors() gives Middle, Base, object, but only Middle when such a transform is registered and Middle's base was inferred before (pylint does a lot of inference before the docstring checker runs).

What happens:

  1. infer_typing_generic_class_pep695 parses a __class_getitem__ template each time a class with type parameters is inferred.
  2. Parsing applies the registered transforms. TransformVisitor._transform treats any non-None return value as a replacement and calls _invalidate_cache(), which clears the global inference cache.
  3. This happens in the middle of inferring Child's ancestors. Further down, Base[T] is inferred again while it is already on the inference path. Before, the cache answered that. Now it hits the recursion guard, InferenceError is raised, and ancestors() skips the base.

The fix builds the template with AstroidBuilder(..., apply_transforms=False), the same way brain_namedtuple_enum builds its fake enum classes during inference. The template is a fixed classmethod, and running user transforms on it was not useful anyway.

I first tried skipping _invalidate_cache() when a transform returns the same node it got. That fixes this case but breaks test_tuple_type, test_callable_type, test_namedtuple_advanced_inference and test_mro_with_factories, and three of pylint's typing functional tests: some brains change a node in place, return it, and rely on that invalidation. So I limited the fix to the brain.

The new test fails without the change. The rest of the astroid tests and pylint's functional tests (944) pass with it. 6 symlink and .pyz tests fail on my Windows machine with and without this change. The example from the issue no longer gets missing-function-docstring.

Closes pylint-dev/pylint#10352

I used Claude Code for this PR and reviewed the change and the results.

b84330808 and others added 2 commits October 19, 2026 14:00
The inference tip for a class with type parameters parses a
`__class_getitem__` template every time the class is inferred. Parsing
applies the registered transforms, and a transform that returns a node
clears the global inference cache. That happens in the middle of an
inference, and an inference further down that had been answered from the
cache now hits the recursion guard instead, so a subclass of a PEP 695
generic class can lose ancestors. A pylint plugin with a FunctionDef
transform that returns its node is enough to trigger it.

Build the template like the enum brain builds its fake classes, without
applying transforms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Pierre-Sassoulas Pierre-Sassoulas added Enhancement ✨ Improvement to a component python 3.12 labels Oct 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement ✨ Improvement to a component python 3.12

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Empty transform plugin leads to false-positive missing-function-docstring issues

2 participants