Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
markkohdev opened this issue Jan 2, 2025 · 0 comments
Labels
bug mypy got something wrong

Comments

@markkohdev
Copy link

markkohdev commented Jan 2, 2025

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. -->
@markkohdev markkohdev added the bug mypy got something wrong label Jan 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

1 participant