Skip to content

Fix stubtest false positive for an alias of a classmethod - #21863

Open
koleharv wants to merge 1 commit into
python:masterfrom
koleharv:stubtest-classmethod-alias
Open

koleharv wants to merge 1 commit into
python:masterfrom
koleharv:stubtest-classmethod-alias

Conversation

@koleharv

Copy link
Copy Markdown

Fixes #15717

Binding a classmethod to a second name makes stubtest reject a stub that matches the runtime exactly:

# mod.py
class T:
    def a(self): pass
    b = a
    @classmethod
    def c(cls): pass
    d = c
# mod.pyi
class T:
    def a(self) -> None: ...
    b = a
    @classmethod
    def c(cls) -> None: ...
    d = c
error: mod.T.d variable differs from runtime type def () -> Any
Stub: in file mod.pyi:6
def (cls: type[mod.T])
Runtime: in file mod.py:4
def ()

The plain-method alias b is fine; only the classmethod alias errors.

Cause

Both sides are right, and the comparison is what is wrong. d = c in the stub names the classmethod object, whose type keeps the cls argument, whereas reading T.d at runtime goes through the descriptor and yields a method already bound to the class. verify_var then compares an unbound signature against a bound one and reports a difference that does not exist.

Change

When the runtime value is a method bound to a class, verify_var now compares against the stub type with its leading cls removed.

The check is deliberately narrow. It applies only when:

  • the runtime object is a bound method whose __self__ is a class — an instance method bound to an instance is untouched,
  • the stub type is a callable with at least one argument, and
  • that first argument is a type[...], or is named cls / mcls / metacls.

So a stub that already accounts for the binding, or one whose first parameter is a genuine value parameter, keeps being compared as before. A real mismatch still fails: annotating the alias as c_alias: int against a runtime method is still an error, and there is a test for it.

Tests

Two cases added to test_static_class_method:

  • a class with both a plain-method alias and a classmethod alias, matching at runtime: no error (fails before this change)
  • a classmethod alias annotated as int in the stub: still an error

mypy/test/teststubtest.py passes (67 tests), self-check on mypy/stubtest.py is clean, and ruff / black are clean.

Note

The enum branch of verify_var now reads the same local variable rather than stub.type directly. The value is identical there — an enum member is never a bound method — but self-check cannot narrow stub.type to non-None once the surrounding condition tests the local.

A stub assignment such as d = c refers to the classmethod object and keeps
its cls argument, while reading the same name off the class at runtime
yields a method already bound to it.
JelleZijlstra pushed a commit that referenced this pull request Oct 19, 2026
…d cls (#21941)

fixes #16583

the first parameter of a classmethod can be called anything . networkx
really does write
`def construct(EdgeComponentAuxGraph, G)` . stubtest checked that name
against a list of four
and raised `StubtestFailure` when it did not match :

```
StubtestFailure: unexpected class parameter name 'EdgeComponentAuxGraph' in pkg.EdgeComponentAuxGraph.construct
```

that is not a normal stubtest error , it stops the whole run , so one
oddly named parameter
takes out stub checking for a whole distribution . that is what happens
on
python/typeshed#13587 .

the name is never used for anything . the line right under it drops the
argument by position ,
so the check was not guarding the code below it . it looks at the
argument kind now instead .

while testing i found a second way onto the same line . a stub written
`def f(*args)` under
`@classmethod` also crashed . there is nothing bound to drop in that
case , and
`inspect.signature` of the runtime classmethod keeps the star argument
too , so both sides
already match and it returns unchanged . dropping it would have made a
false mismatch .

two cases added to `test_static_class_method` , one for the odd name and
one for `*args` .
both fail on master with the crash above .

`teststubtest.py` and `teststubgen.py` give 440 passed , 1 skipped , 2
xfailed . self check
on the two files is clean , and so are black and ruff .

small heads up , #21863 adds its own cases to `test_static_class_method`
starting on the same
line , so whichever goes in second will need a rebase . it is only the
test file , that one
changes `verify_var` and this one changes
`_resolve_funcitem_from_decorator` , so they do not
touch the same code .
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.

Classmethod bound with an additional name trigger stubtest errors

1 participant