diff --git a/mypy/checker.py b/mypy/checker.py index 0639340d30bb..8eb8a066bba4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4400,9 +4400,9 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: refers to the variable (lvalue). If var is None, do nothing. """ if var and not self.current_node_deferred: - # TODO: should we also set 'is_ready = True' here? var.type = type var.is_inferred = True + var.is_ready = True if var not in self.var_decl_frames: # Used for the hack to improve optional type inference in conditionals self.var_decl_frames[var] = {frame.id for frame in self.binder.frames} @@ -4412,9 +4412,23 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: self.inferred_attribute_types[lvalue.def_var] = type self.store_type(lvalue, type) p_type = get_proper_type(type) - if isinstance(p_type, CallableType) and is_node_static(p_type.definition): - # TODO: handle aliases to class methods (similarly). - var.is_staticmethod = True + definition = None + if isinstance(p_type, CallableType): + definition = p_type.definition + elif isinstance(p_type, Overloaded): + # Randomly select first item, if items are different, there will + # be an error during semantic analysis. + definition = p_type.items[0].definition + if definition: + if is_node_static(definition): + var.is_staticmethod = True + elif is_classmethod_node(definition): + var.is_classmethod = True + elif is_property(definition): + var.is_property = True + if isinstance(p_type, Overloaded): + # TODO: in theory we can have a property with a deleter only. + var.is_settable_property = True def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: """Store best known type for variable if type inference failed. @@ -8531,15 +8545,21 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: return t.copy_modified(args=[a.accept(self) for a in t.args]) +def is_classmethod_node(node: Node | None) -> bool | None: + """Find out if a node describes a classmethod.""" + if isinstance(node, FuncDef): + return node.is_class + if isinstance(node, Var): + return node.is_classmethod + return None + + def is_node_static(node: Node | None) -> bool | None: """Find out if a node describes a static function method.""" - if isinstance(node, FuncDef): return node.is_static - if isinstance(node, Var): return node.is_staticmethod - return None @@ -8786,6 +8806,8 @@ def is_static(func: FuncBase | Decorator) -> bool: def is_property(defn: SymbolNode) -> bool: + if isinstance(defn, FuncDef): + return defn.is_property if isinstance(defn, Decorator): return defn.func.is_property if isinstance(defn, OverloadedFuncDef): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 1633eaf52983..75197d5dbf2a 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -371,8 +371,6 @@ def analyze_instance_member_access( signature, mx.self_type, method.is_class, mx.context, name, mx.msg ) signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class) - # TODO: should we skip these steps for static methods as well? - # Since generic static methods should not be allowed. typ = map_instance_to_supertype(typ, method.info) member_type = expand_type_by_instance(signature, typ) freeze_all_type_vars(member_type) @@ -1218,8 +1216,11 @@ def analyze_class_attribute_access( # C[int].x -> int t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars}) - is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or ( - isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class + is_classmethod = ( + (is_decorated and cast(Decorator, node.node).func.is_class) + or (isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class) + or isinstance(node.node, Var) + and node.node.is_classmethod ) is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or ( isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_static @@ -1231,7 +1232,12 @@ def analyze_class_attribute_access( is_trivial_self = node.node.func.is_trivial_self and not node.node.decorators elif isinstance(node.node, (FuncDef, OverloadedFuncDef)): is_trivial_self = node.node.is_trivial_self - if isinstance(t, FunctionLike) and is_classmethod and not is_trivial_self: + if ( + isinstance(t, FunctionLike) + and is_classmethod + and not is_trivial_self + and not t.bound() + ): t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) t = add_class_tvars( t, @@ -1400,7 +1406,7 @@ class B(A[str]): pass tvars = original_vars if original_vars is not None else [] if not mx.preserve_type_var_ids: t = freshen_all_functions_type_vars(t) - if is_classmethod: + if is_classmethod and not t.is_bound: if is_trivial_self: t = bind_self_fast(t, mx.self_type) else: diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 39e6c4fa3ff1..23db0bf50a4e 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -587,6 +587,54 @@ class C(B): class B: ... [builtins fixtures/classmethod.pyi] +[case testClassMethodAliasInClass] +from typing import overload + +class C: + @classmethod + def foo(cls) -> int: ... + + bar = foo + + @overload + @classmethod + def foo2(cls, x: int) -> int: ... + @overload + @classmethod + def foo2(cls, x: str) -> str: ... + @classmethod + def foo2(cls, x): + ... + + bar2 = foo2 + +reveal_type(C.bar) # N: Revealed type is "def () -> builtins.int" +reveal_type(C().bar) # N: Revealed type is "def () -> builtins.int" +reveal_type(C.bar2) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.int, def (x: builtins.str) -> builtins.str)" +reveal_type(C().bar2) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.int, def (x: builtins.str) -> builtins.str)" +[builtins fixtures/classmethod.pyi] + +[case testPropertyAliasInClassBody] +class A: + @property + def f(self) -> int: ... + + g = f + + @property + def f2(self) -> int: ... + @f2.setter + def f2(self, val: int) -> None: ... + + g2 = f2 + +reveal_type(A().g) # N: Revealed type is "builtins.int" +reveal_type(A().g2) # N: Revealed type is "builtins.int" +A().g = 1 # E: Property "g" defined in "A" is read-only +A().g2 = 1 +A().g2 = "no" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/property.pyi] + [case testCallableUnionCallback] from typing import Union, Callable, TypeVar diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f4bbaf41dc47..3d99ccb302c6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4588,6 +4588,23 @@ reveal_type(a.a) # N: Revealed type is "def (a: builtins.int)" reveal_type(a.c) # N: Revealed type is "def (a: builtins.int)" [builtins fixtures/staticmethod.pyi] +[case testClassStaticMethodIndirectOverloaded] +from typing import overload +class A: + @overload + @staticmethod + def a(x: int) -> int: ... + @overload + @staticmethod + def a(x: str) -> str: ... + @staticmethod + def a(x): + ... + c = a +reveal_type(A.c) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.int, def (x: builtins.str) -> builtins.str)" +reveal_type(A().c) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.int, def (x: builtins.str) -> builtins.str)" +[builtins fixtures/staticmethod.pyi] + [case testClassStaticMethodSubclassing] class A: @staticmethod