Repository: python/mypy

Issue:
support cached_property
The `functools.cached_property` decorator doesn't get typechecked correctly:

```python
from functools import cached_property
                                     
class Superclass:                    
    @property                        
    def prop(self) -> int:           
        ...                          
                                     
class Subclass(Superclass):          
    @cached_property                 
    def prop(self) -> int:           
        ...                          
```

`mypy` (version 0.770) reports:
```
error: Signature of "prop" incompatible with supertype "Superclass"
```



Discussion:
(similar issue to https://github.com/python/typeshed/issues/3963)
Are there any plans to support `@cached_property` in mypy soon?
We @home-assistant are looking forward to this one too
I think that something like below could work as a starting point. I am however not familiar enough with the mypy codebase to finish it. Leaving it here hoping it can be useful to someone else (treat my contribution as public domain).

I will pay $150 to the person that adds `cached_property` support to mypy. PR needs to be merged and close this issue. This offer is valid until September 1, 2021.

(PSF, let me know if you don't appreciate bounties and I'll retract my offer)


```diff
diff --git a/mypy/semanal.py b/mypy/semanal.py
index 0927e465b..268a7bb23 100644
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -999,7 +999,8 @@ class SemanticAnalyzer(NodeVisitor[None],
                 dec.var.is_classmethod = True
                 self.check_decorated_function_is_method('classmethod', dec)
             elif (refers_to_fullname(d, 'builtins.property') or
-                  refers_to_fullname(d, 'abc.abstractproperty')):
+                  refers_to_fullname(d, 'abc.abstractproperty') or
+                  refers_to_fullname(d, 'functools.cached_property')):
                 removed.append(i)
                 dec.func.is_property = True
                 dec.var.is_property = True
diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test
index 3d62fed2b..d6a6f3e2b 100644
--- a/test-data/unit/semanal-classes.test
+++ b/test-data/unit/semanal-classes.test
@@ -517,6 +517,29 @@ MypyFile:1(
         Block:4(
           PassStmt:4())))))
 
+[case testCachedProperty]
+import typing
+from functools import cached_property
+class A:
+  @cached_property
+  def f(self) -> str: pass
+[builtins fixtures/property.pyi]
+[out]
+MypyFile:1(
+  Import:1(typing)
+  ClassDef:2(
+    A
+    Decorator:3(
+      Var(f)
+      FuncDef:4(
+        f
+        Args(
+          Var(self))
+        def (self: __main__.A) -> builtins.str
+        Property
+        Block:4(
+          PassStmt:4())))))
+
 [case testClassDecorator]
 import typing
 @object
```

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -999,12 +999,15 @@ def visit_decorator(self, dec: Decorator) -> None:
                 dec.var.is_classmethod = True
                 self.check_decorated_function_is_method('classmethod', dec)
             elif (refers_to_fullname(d, 'builtins.property') or
-                  refers_to_fullname(d, 'abc.abstractproperty')):
+                  refers_to_fullname(d, 'abc.abstractproperty') or
+                  refers_to_fullname(d, 'functools.cached_property')):
                 removed.append(i)
                 dec.func.is_property = True
                 dec.var.is_property = True
                 if refers_to_fullname(d, 'abc.abstractproperty'):
                     dec.func.is_abstract = True
+                elif refers_to_fullname(d, 'functools.cached_property'):
+                    dec.var.is_settable_property = True
                 self.check_decorated_function_is_method('property', dec)
                 if len(dec.func.arguments) > 1:
                     self.fail('Too many arguments', dec.func)


Test patch:
diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test
--- a/test-data/unit/check-functools.test
+++ b/test-data/unit/check-functools.test
@@ -107,3 +107,23 @@ Ord() > 1
 Ord() >= 1  # E: Unsupported left operand type for >= ("Ord")
 [builtins fixtures/ops.pyi]
 [builtins fixtures/dict.pyi]
+
+[case testCachedProperty]
+# flags: --python-version 3.8
+from functools import cached_property
+class Parent:
+    @property
+    def f(self) -> str: pass
+class Child(Parent):
+    @cached_property
+    def f(self) -> str: pass
+    @cached_property
+    def g(self) -> int: pass
+    @cached_property
+    def h(self, arg) -> int: pass  # E: Too many arguments
+reveal_type(Parent().f)  # N: Revealed type is "builtins.str"
+reveal_type(Child().f)  # N: Revealed type is "builtins.str"
+reveal_type(Child().g)  # N: Revealed type is "builtins.int"
+Child().f = "Hello World"
+Child().g = "invalid"  # E: Incompatible types in assignment (expression has type "str", variable has type "int")
+[builtins fixtures/property.pyi]