diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5f1aeac8a725..0fea85894c19 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -731,6 +731,18 @@ def always_returns_none(self, node: Expression) -> bool: def defn_returns_none(self, defn: SymbolNode | None) -> bool: """Check if `defn` can _only_ return None.""" + if isinstance(defn, Decorator): + # Decorated functions (e.g. @staticmethod, @classmethod) are wrapped + # in a Decorator node; the resulting callable type is stored on the + # synthetic Var. We use that callable's return type so that + # `func-returns-value` is reported for decorated methods the same + # way it is for plain functions + # (https://github.com/python/mypy/issues/14179). The Var is marked + # is_inferred=True so we cannot reuse the Var branch below. + typ = get_proper_type(defn.var.type) + return isinstance(typ, CallableType) and isinstance( + get_proper_type(typ.ret_type), NoneType + ) if isinstance(defn, FuncDef): return isinstance(defn.type, CallableType) and isinstance( get_proper_type(defn.type.ret_type), NoneType diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 85a2264c2088..48483dbc6e83 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -682,6 +682,31 @@ def main() -> None: y = A().g() # E: "g" of "A" does not return a value (it only ever returns None) [func-returns-value] z = c() # E: Function does not return a value (it only ever returns None) [func-returns-value] +[case testErrorCodeFuncReturnsValueStaticAndClassMethod] +# Regression test for https://github.com/python/mypy/issues/14179 +# Decorated methods (staticmethod, classmethod) should report +# func-returns-value just like instance methods do. +class Example: + def instance_op(self) -> None: + return None + + @staticmethod + def static_op() -> None: + return None + + @classmethod + def class_op(cls) -> None: + return None + +def main() -> None: + ex = Example() + a = ex.instance_op() # E: "instance_op" of "Example" does not return a value (it only ever returns None) [func-returns-value] + b = ex.static_op() # E: "static_op" of "Example" does not return a value (it only ever returns None) [func-returns-value] + c = ex.class_op() # E: "class_op" of "Example" does not return a value (it only ever returns None) [func-returns-value] + d = Example.static_op() # E: "static_op" of "Example" does not return a value (it only ever returns None) [func-returns-value] + e = Example.class_op() # E: "class_op" of "Example" does not return a value (it only ever returns None) [func-returns-value] +[builtins fixtures/classmethod.pyi] + [case testErrorCodeInstantiateAbstract] from abc import abstractmethod