python / python/mypy

Incorrect `Any` class method return type inference with Generic TypeVars in separate modules

Aperta
#18,411 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug
Lingua principale
Python
Stelle
20.6k
Fork
3.3k
Merge medio
1g 18h
PR unite (30g)
54

Descrizione

Bug Report

The setup:
I have a generic class (Instantiator) which contains a class method (instantiate()) which is passed a desired type (MyClass) and instantiates an instance of the generic type. I then have another function (returns_instance()) outside of the class which calls instantiate and returns an instance of the passed desired type.

What is happening:
When the definition for the generic type and the generic class lives in the same module as the calling function, there are no mypy errors. However, if the definition for the generic type and the generic class live in a different module from the calling function, mypy throws a error: Returning Any from function declared to return "MyClass" [no-any-return] error.

To Reproduce

File 1: a.py

from typing import Any, Dict, Generic, Type, TypeVar
from dataclasses import dataclass
from spotify_typed_config.b import InstantiatorB


TYPEVAR_A = TypeVar("TYPEVAR_A")


@dataclass
class MyClass:
    i: int = 10

    def print(self) -> None:
        print(self.i)


class InstantiatorA(Generic[TYPEVAR_A]):
    @classmethod
    def instantiate(cls, class_to_instantiate: Type[TYPEVAR_A], x: Dict[str, Any]) -> TYPEVAR_A:
        return class_to_instantiate(**x)


# This works fine -- no errors!
def returns_instance_a(i: int) -> MyClass:
    return InstantiatorA.instantiate(MyClass, {"i": i})

# a.py:29: error: Returning Any from function declared to return "MyClass"  [no-any-return]
def returns_instance_b(i: int) -> MyClass:
    return InstantiatorB.instantiate(MyClass, {"i": i})

# b.py:14: error: Redundant cast to "TYPEVAR_B"  [redundant-cast]
# a.py:33: error: Returning Any from function declared to return "MyClass"  [no-any-return]
def returns_instance_b_with_cast(i: int) -> MyClass:
    return InstantiatorB.instantiate_with_cast(MyClass, {"i": i})

# a.py:37: error: Returning Any from function declared to return "MyClass"  [no-any-return]
def returns_instance_b_no_params(i: int) -> MyClass:
    return InstantiatorB.instantiate_no_params(MyClass)

# This works fine -- no errors!
def returns_instance_b_reassignment(i: int) -> MyClass:
    b: MyClass = InstantiatorB.instantiate_no_params(MyClass)
    return b

# a.py:47: error: Returning Any from function declared to return "MyClass"  [no-any-return]
def returns_instance_b_inner_reassign(i: int) -> MyClass:
    return InstantiatorB.instantiate_with_reassign(MyClass)

# a.py:51: error: Returning Any from function declared to return "MyClass"  [no-any-return]
def returns_instance_b_with_generic(i: int) -> MyClass:
    return InstantiatorB[MyClass].instantiate(MyClass, {"i": i})

File 2: b.py

from typing import Any, Dict, Generic, Type, TypeVar, cast

TYPEVAR_B = TypeVar("TYPEVAR_B")

class InstantiatorB(Generic[TYPEVAR_B]):
    @classmethod
    def instantiate(cls, class_to_instantiate: Type[TYPEVAR_B], x: Dict[str, Any]) -> TYPEVAR_B:
        return class_to_instantiate(**x)

    @classmethod
    def instantiate_with_cast(cls, class_to_instantiate: Type[TYPEVAR_B], x: Dict[str, Any]) -> TYPEVAR_B:
        return cast(TYPEVAR_B, class_to_instantiate(**x))

    @classmethod
    def instantiate_no_params(cls, class_to_instantiate: Type[TYPEVAR_B]) -> TYPEVAR_B:
        return class_to_instantiate()

    @classmethod
    def instantiate_with_reassign(cls, class_to_instantiate: Type[TYPEVAR_B]) -> TYPEVAR_B:
        result: TYPEVAR_B = class_to_instantiate()
        return result

Expected Behavior

I expect that mypy would be able to correctly infer that the return type of the instantiate_* methods as it does for InstantiatorA, but for InstantiatorB it incorrectly reports Any as the return type.
I would expect that maybe passing the desired return type as a subscript was needed such as in returns_instance_b_with_generic, and that actually does result in VSCode showing the correct return type, but mypy still does not.

Actual Behavior

Errors are reported inline above.

Your Environment

  • Mypy version used: 1.14.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files):
# Mypy configuration equal to mypy --strict. Read more about the tools at
# https://backstage.spotify.net/docs/python/
[tool.mypy]
ignore_missing_imports = true
warn_unused_configs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
no_implicit_reexport = true
strict_equality = true

- Python version used: 3.8.12

<!-- You can freely edit this text, please remove all the lines you believe are unnecessary. -->

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con il reproducer in a.py e b.py usando mypy 1.14.1 con la configurazione strict mostrata, confrontando il comportamento nello stesso modulo e nei moduli importati. Traccia come vengono inferiti i Generic TypeVars dei metodi di classe instantiate_* oltre il confine tra i moduli; il lavoro è completato quando le chiamate riprodotte inferiscono MyClass e non segnalano più Returning Any né il relativo errore redundant-cast.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
devtools
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
38/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.