From 6f296b0a911676b6f53a2f9ffc994413b3236be1 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Fri, 10 Jan 2020 12:57:09 +0200 Subject: [PATCH] Implement @cached_property attribute inference (#292) --- django-stubs/utils/functional.pyi | 15 ++++++++++----- scripts/enabled_test_modules.py | 2 +- test-data/typecheck/utils/test_functional.yml | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 test-data/typecheck/utils/test_functional.yml diff --git a/django-stubs/utils/functional.pyi b/django-stubs/utils/functional.pyi index d70f30e..40e415a 100644 --- a/django-stubs/utils/functional.pyi +++ b/django-stubs/utils/functional.pyi @@ -1,16 +1,21 @@ -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union, TypeVar, Generic, overload from functools import wraps as wraps # noqa: F401 from django.db.models.base import Model def curry(_curried_func: Any, *args: Any, **kwargs: Any): ... -class cached_property: - func: Callable = ... +_T = TypeVar("_T") + +class cached_property(Generic[_T]): + func: Callable[..., _T] = ... __doc__: Any = ... name: str = ... - def __init__(self, func: Callable, name: Optional[str] = ...) -> None: ... - def __get__(self, instance: Any, cls: Type[Any] = ...) -> Any: ... + def __init__(self, func: Callable[..., _T], name: Optional[str] = ...): ... + @overload + def __get__(self, instance: None, cls: Type[Any] = ...) -> "cached_property[_T]": ... + @overload + def __get__(self, instance: object, cls: Type[Any] = ...) -> _T: ... class Promise: ... diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index b1b5345..89d0bb9 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -457,7 +457,7 @@ IGNORED_ERRORS = { 'No overload variant of "join" matches argument types "str", "None"', 'Argument 1 to "Archive" has incompatible type "None"; expected "str"', 'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"', - + 'Cannot infer type argument 1 of "cached_property"', ], 'view_tests': [ "Module 'django.views.debug' has no attribute 'Path'", diff --git a/test-data/typecheck/utils/test_functional.yml b/test-data/typecheck/utils/test_functional.yml new file mode 100644 index 0000000..0407c18 --- /dev/null +++ b/test-data/typecheck/utils/test_functional.yml @@ -0,0 +1,18 @@ +- case: cached_property_class_vs_instance_attributes + main: | + from django.utils.functional import cached_property + from typing import List + + class Foo: + @cached_property + def attr(self) -> List[str]: ... + + reveal_type(attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]' + reveal_type(attr.name) # N: Revealed type is 'builtins.str' + + reveal_type(Foo.attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]' + reveal_type(Foo.attr.func) # N: Revealed type is 'def (*Any, **Any) -> builtins.list*[builtins.str]' + + f = Foo() + reveal_type(f.attr) # N: Revealed type is 'builtins.list*[builtins.str]' + f.attr.name # E: "List[str]" has no attribute "name"