Skip to content

Commit

Permalink
Substitute type variables in return type of static methods (#16670)
Browse files Browse the repository at this point in the history
`add_class_tvars` correctly instantiates type variables in the return
type for class methods but not for static methods. Check if the analyzed
member is a static method in `analyze_class_attribute_access` and
substitute the type variable in the return type in `add_class_tvars`
accordingly.

Fixes #16668.
  • Loading branch information
kourbou authored Dec 17, 2023
1 parent 43ffb49 commit 1dd8e7f
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 2 deletions.
15 changes: 13 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,11 +1063,14 @@ def analyze_class_attribute_access(
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
isinstance(node.node, FuncBase) and node.node.is_class
)
is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or (
isinstance(node.node, FuncBase) and node.node.is_static
)
t = get_proper_type(t)
if isinstance(t, FunctionLike) and is_classmethod:
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
result = add_class_tvars(
t, isuper, is_classmethod, mx.self_type, original_vars=original_vars
t, isuper, is_classmethod, is_staticmethod, mx.self_type, original_vars=original_vars
)
if not mx.is_lvalue:
result = analyze_descriptor_access(result, mx)
Expand Down Expand Up @@ -1177,6 +1180,7 @@ def add_class_tvars(
t: ProperType,
isuper: Instance | None,
is_classmethod: bool,
is_staticmethod: bool,
original_type: Type,
original_vars: Sequence[TypeVarLikeType] | None = None,
) -> Type:
Expand All @@ -1195,6 +1199,7 @@ class B(A[str]): pass
isuper: Current instance mapped to the superclass where method was defined, this
is usually done by map_instance_to_supertype()
is_classmethod: True if this method is decorated with @classmethod
is_staticmethod: True if this method is decorated with @staticmethod
original_type: The value of the type B in the expression B.foo() or the corresponding
component in case of a union (this is used to bind the self-types)
original_vars: Type variables of the class callable on which the method was accessed
Expand All @@ -1220,6 +1225,7 @@ class B(A[str]): pass
t = freshen_all_functions_type_vars(t)
if is_classmethod:
t = bind_self(t, original_type, is_classmethod=True)
if is_classmethod or is_staticmethod:
assert isuper is not None
t = expand_type_by_instance(t, isuper)
freeze_all_type_vars(t)
Expand All @@ -1230,7 +1236,12 @@ class B(A[str]): pass
cast(
CallableType,
add_class_tvars(
item, isuper, is_classmethod, original_type, original_vars=original_vars
item,
isuper,
is_classmethod,
is_staticmethod,
original_type,
original_vars=original_vars,
),
)
for item in t.items
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,19 @@ def func(x: S) -> S:
return C[S].get()
[builtins fixtures/classmethod.pyi]

[case testGenericStaticMethodInGenericFunction]
from typing import Generic, TypeVar
T = TypeVar('T')
S = TypeVar('S')

class C(Generic[T]):
@staticmethod
def get() -> T: ...

def func(x: S) -> S:
return C[S].get()
[builtins fixtures/staticmethod.pyi]

[case testMultipleAssignmentFromAnyIterable]
from typing import Any
class A:
Expand Down

0 comments on commit 1dd8e7f

Please sign in to comment.