|  | 
| 28 | 28 |     transforms, | 
| 29 | 29 |     util, | 
| 30 | 30 | ) | 
| 31 |  | -from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, Context | 
|  | 31 | +from astroid.const import IS_PYPY, PY310_PLUS, PY311_PLUS, PY312_PLUS, Context | 
| 32 | 32 | from astroid.context import InferenceContext | 
| 33 | 33 | from astroid.exceptions import ( | 
| 34 | 34 |     AstroidBuildingError, | 
| @@ -927,67 +927,274 @@ def test(self): | 
| 927 | 927 | 
 | 
| 928 | 928 | 
 | 
| 929 | 929 | class BoundMethodNodeTest(unittest.TestCase): | 
| 930 |  | -    def test_is_property(self) -> None: | 
|  | 930 | +    def _is_property(self, ast: nodes.Module, prop: str) -> None: | 
|  | 931 | +        inferred = next(ast[prop].infer()) | 
|  | 932 | +        self.assertIsInstance(inferred, nodes.Const, prop) | 
|  | 933 | +        self.assertEqual(inferred.value, 42, prop) | 
|  | 934 | + | 
|  | 935 | +    def test_is_standard_property(self) -> None: | 
|  | 936 | +        # Test to make sure the Python-provided property decorators | 
|  | 937 | +        # are properly interpreted as properties | 
| 931 | 938 |         ast = builder.parse( | 
| 932 | 939 |             """ | 
| 933 | 940 |         import abc | 
|  | 941 | +        import functools | 
| 934 | 942 | 
 | 
| 935 |  | -        def cached_property(): | 
| 936 |  | -            # Not a real decorator, but we don't care | 
| 937 |  | -            pass | 
| 938 |  | -        def reify(): | 
| 939 |  | -            # Same as cached_property | 
| 940 |  | -            pass | 
| 941 |  | -        def lazy_property(): | 
| 942 |  | -            pass | 
| 943 |  | -        def lazyproperty(): | 
| 944 |  | -            pass | 
| 945 |  | -        def lazy(): pass | 
| 946 | 943 |         class A(object): | 
| 947 | 944 |             @property | 
| 948 |  | -            def builtin_property(self): | 
| 949 |  | -                return 42 | 
|  | 945 | +            def builtin_property(self): return 42 | 
|  | 946 | +
 | 
| 950 | 947 |             @abc.abstractproperty | 
| 951 |  | -            def abc_property(self): | 
| 952 |  | -                return 42 | 
|  | 948 | +            def abc_property(self): return 42 | 
|  | 949 | +
 | 
|  | 950 | +            @property | 
|  | 951 | +            @abc.abstractmethod | 
|  | 952 | +            def abstractmethod_property(self): return 42 | 
|  | 953 | +
 | 
|  | 954 | +            @functools.cached_property | 
|  | 955 | +            def functools_property(self): return 42 | 
|  | 956 | +
 | 
|  | 957 | +        cls = A() | 
|  | 958 | +        builtin_p = cls.builtin_property | 
|  | 959 | +        abc_p = cls.abc_property | 
|  | 960 | +        abstractmethod_p = cls.abstractmethod_property | 
|  | 961 | +        functools_p = cls.functools_property | 
|  | 962 | +        """ | 
|  | 963 | +        ) | 
|  | 964 | +        for prop in ( | 
|  | 965 | +            "builtin_p", | 
|  | 966 | +            "abc_p", | 
|  | 967 | +            "abstractmethod_p", | 
|  | 968 | +            "functools_p", | 
|  | 969 | +        ): | 
|  | 970 | +            self._is_property(ast, prop) | 
|  | 971 | + | 
|  | 972 | +    @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") | 
|  | 973 | +    def test_is_standard_property_py311(self) -> None: | 
|  | 974 | +        # Test to make sure the Python-provided property decorators | 
|  | 975 | +        # are properly interpreted as properties | 
|  | 976 | +        ast = builder.parse( | 
|  | 977 | +            """ | 
|  | 978 | +        import enum | 
|  | 979 | +
 | 
|  | 980 | +        class A(object): | 
|  | 981 | +            @enum.property | 
|  | 982 | +            def enum_property(self): return 42 | 
|  | 983 | +
 | 
|  | 984 | +        cls = A() | 
|  | 985 | +        enum_p = cls.enum_property | 
|  | 986 | +        """ | 
|  | 987 | +        ) | 
|  | 988 | +        self._is_property(ast, "enum_p") | 
|  | 989 | + | 
|  | 990 | +    def test_is_possible_property(self) -> None: | 
|  | 991 | +        # Test to make sure that decorators with POSSIBLE_PROPERTIES names | 
|  | 992 | +        # are properly interpreted as properties | 
|  | 993 | +        ast = builder.parse( | 
|  | 994 | +            """ | 
|  | 995 | +        # Not real decorators, but we don't care | 
|  | 996 | +        def cachedproperty(): pass | 
|  | 997 | +        def cached_property(): pass | 
|  | 998 | +        def reify(): pass | 
|  | 999 | +        def lazy_property(): pass | 
|  | 1000 | +        def lazyproperty(): pass | 
|  | 1001 | +        def lazy(): pass | 
|  | 1002 | +        def lazyattribute(): pass | 
|  | 1003 | +        def lazy_attribute(): pass | 
|  | 1004 | +        def LazyProperty(): pass | 
|  | 1005 | +        def DynamicClassAttribute(): pass | 
|  | 1006 | +
 | 
|  | 1007 | +        class A(object): | 
|  | 1008 | +            @cachedproperty | 
|  | 1009 | +            def cachedproperty(self): return 42 | 
|  | 1010 | +
 | 
| 953 | 1011 |             @cached_property | 
| 954 | 1012 |             def cached_property(self): return 42 | 
|  | 1013 | +
 | 
| 955 | 1014 |             @reify | 
| 956 | 1015 |             def reified(self): return 42 | 
|  | 1016 | +
 | 
| 957 | 1017 |             @lazy_property | 
| 958 | 1018 |             def lazy_prop(self): return 42 | 
|  | 1019 | +
 | 
| 959 | 1020 |             @lazyproperty | 
| 960 | 1021 |             def lazyprop(self): return 42 | 
| 961 |  | -            def not_prop(self): pass | 
|  | 1022 | +
 | 
| 962 | 1023 |             @lazy | 
| 963 | 1024 |             def decorated_with_lazy(self): return 42 | 
| 964 | 1025 | 
 | 
|  | 1026 | +            @lazyattribute | 
|  | 1027 | +            def lazyattribute(self): return 42 | 
|  | 1028 | +
 | 
|  | 1029 | +            @lazy_attribute | 
|  | 1030 | +            def lazy_attribute(self): return 42 | 
|  | 1031 | +
 | 
|  | 1032 | +            @LazyProperty | 
|  | 1033 | +            def LazyProperty(self): return 42 | 
|  | 1034 | +
 | 
|  | 1035 | +            @DynamicClassAttribute | 
|  | 1036 | +            def DynamicClassAttribute(self): return 42 | 
|  | 1037 | +
 | 
| 965 | 1038 |         cls = A() | 
| 966 |  | -        builtin_property = cls.builtin_property | 
| 967 |  | -        abc_property = cls.abc_property | 
|  | 1039 | +        cachedp = cls.cachedproperty | 
| 968 | 1040 |         cached_p = cls.cached_property | 
| 969 | 1041 |         reified = cls.reified | 
| 970 |  | -        not_prop = cls.not_prop | 
| 971 | 1042 |         lazy_prop = cls.lazy_prop | 
| 972 | 1043 |         lazyprop = cls.lazyprop | 
| 973 | 1044 |         decorated_with_lazy = cls.decorated_with_lazy | 
|  | 1045 | +        lazya = cls.lazyattribute | 
|  | 1046 | +        lazy_a = cls.lazy_attribute | 
|  | 1047 | +        LazyP = cls.LazyProperty | 
|  | 1048 | +        DynamicClassA = cls.DynamicClassAttribute | 
| 974 | 1049 |         """ | 
| 975 | 1050 |         ) | 
| 976 | 1051 |         for prop in ( | 
| 977 |  | -            "builtin_property", | 
| 978 |  | -            "abc_property", | 
|  | 1052 | +            "cachedp", | 
| 979 | 1053 |             "cached_p", | 
| 980 | 1054 |             "reified", | 
| 981 | 1055 |             "lazy_prop", | 
| 982 | 1056 |             "lazyprop", | 
| 983 | 1057 |             "decorated_with_lazy", | 
|  | 1058 | +            "lazya", | 
|  | 1059 | +            "lazy_a", | 
|  | 1060 | +            "LazyP", | 
|  | 1061 | +            "DynamicClassA", | 
| 984 | 1062 |         ): | 
| 985 |  | -            inferred = next(ast[prop].infer()) | 
| 986 |  | -            self.assertIsInstance(inferred, nodes.Const, prop) | 
| 987 |  | -            self.assertEqual(inferred.value, 42, prop) | 
|  | 1063 | +            self._is_property(ast, prop) | 
|  | 1064 | + | 
|  | 1065 | +    def test_is_standard_property_subclass(self) -> None: | 
|  | 1066 | +        # Test to make sure that subclasses of the Python-provided property decorators | 
|  | 1067 | +        # are properly interpreted as properties | 
|  | 1068 | +        ast = builder.parse( | 
|  | 1069 | +            """ | 
|  | 1070 | +        import abc | 
|  | 1071 | +        import functools | 
|  | 1072 | +        from typing import Generic, TypeVar | 
|  | 1073 | +
 | 
|  | 1074 | +        class user_property(property): pass | 
|  | 1075 | +        class user_abc_property(abc.abstractproperty): pass | 
|  | 1076 | +        class user_functools_property(functools.cached_property): pass | 
|  | 1077 | +        T = TypeVar('T') | 
|  | 1078 | +        class annotated_user_functools_property(functools.cached_property[T], Generic[T]): pass | 
|  | 1079 | +
 | 
|  | 1080 | +        class A(object): | 
|  | 1081 | +            @user_property | 
|  | 1082 | +            def user_property(self): return 42 | 
| 988 | 1083 | 
 | 
| 989 |  | -        inferred = next(ast["not_prop"].infer()) | 
| 990 |  | -        self.assertIsInstance(inferred, bases.BoundMethod) | 
|  | 1084 | +            @user_abc_property | 
|  | 1085 | +            def user_abc_property(self): return 42 | 
|  | 1086 | +
 | 
|  | 1087 | +            @user_functools_property | 
|  | 1088 | +            def user_functools_property(self): return 42 | 
|  | 1089 | +
 | 
|  | 1090 | +            @annotated_user_functools_property | 
|  | 1091 | +            def annotated_user_functools_property(self): return 42 | 
|  | 1092 | +
 | 
|  | 1093 | +        cls = A() | 
|  | 1094 | +        user_p = cls.user_property | 
|  | 1095 | +        user_abc_p = cls.user_abc_property | 
|  | 1096 | +        user_functools_p = cls.user_functools_property | 
|  | 1097 | +        annotated_user_functools_p = cls.annotated_user_functools_property | 
|  | 1098 | +        """ | 
|  | 1099 | +        ) | 
|  | 1100 | +        for prop in ( | 
|  | 1101 | +            "user_p", | 
|  | 1102 | +            "user_abc_p", | 
|  | 1103 | +            "user_functools_p", | 
|  | 1104 | +            "annotated_user_functools_p", | 
|  | 1105 | +        ): | 
|  | 1106 | +            self._is_property(ast, prop) | 
|  | 1107 | + | 
|  | 1108 | +    @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11") | 
|  | 1109 | +    def test_is_standard_property_subclass_py311(self) -> None: | 
|  | 1110 | +        # Test to make sure that subclasses of the Python-provided property decorators | 
|  | 1111 | +        # are properly interpreted as properties | 
|  | 1112 | +        ast = builder.parse( | 
|  | 1113 | +            """ | 
|  | 1114 | +        import enum | 
|  | 1115 | +
 | 
|  | 1116 | +        class user_enum_property(enum.property): pass | 
|  | 1117 | +
 | 
|  | 1118 | +        class A(object): | 
|  | 1119 | +            @user_enum_property | 
|  | 1120 | +            def user_enum_property(self): return 42 | 
|  | 1121 | +
 | 
|  | 1122 | +        cls = A() | 
|  | 1123 | +        user_enum_p = cls.user_enum_property | 
|  | 1124 | +        """ | 
|  | 1125 | +        ) | 
|  | 1126 | +        self._is_property(ast, "user_enum_p") | 
|  | 1127 | + | 
|  | 1128 | +    @pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 generic typing syntax") | 
|  | 1129 | +    def test_is_standard_property_subclass_py312(self) -> None: | 
|  | 1130 | +        ast = builder.parse( | 
|  | 1131 | +            """ | 
|  | 1132 | +        from functools import cached_property | 
|  | 1133 | +
 | 
|  | 1134 | +        class annotated_user_cached_property[T](cached_property[T]): | 
|  | 1135 | +            pass | 
|  | 1136 | +
 | 
|  | 1137 | +        class A(object): | 
|  | 1138 | +            @annotated_user_cached_property | 
|  | 1139 | +            def annotated_user_cached_property(self): return 42 | 
|  | 1140 | +
 | 
|  | 1141 | +        cls = A() | 
|  | 1142 | +        annotated_user_cached_p = cls.annotated_user_cached_property | 
|  | 1143 | +        """ | 
|  | 1144 | +        ) | 
|  | 1145 | +        self._is_property(ast, "annotated_user_cached_p") | 
|  | 1146 | + | 
|  | 1147 | +    def test_is_not_property(self) -> None: | 
|  | 1148 | +        ast = builder.parse( | 
|  | 1149 | +            """ | 
|  | 1150 | +        from collections.abc import Iterator | 
|  | 1151 | +
 | 
|  | 1152 | +        class cached_property: pass | 
|  | 1153 | +        # If a decorator is named cached_property, we will accept it as a property, | 
|  | 1154 | +        # even if it isn't functools.cached_property. | 
|  | 1155 | +        # However, do not extend the same leniency to superclasses of decorators. | 
|  | 1156 | +        class wrong_superclass_type1(cached_property): pass | 
|  | 1157 | +        class wrong_superclass_type2(cached_property[float]): pass | 
|  | 1158 | +        cachedproperty = { float: int } | 
|  | 1159 | +        class wrong_superclass_type3(cachedproperty[float]): pass | 
|  | 1160 | +        class wrong_superclass_type4(Iterator[float]): pass | 
|  | 1161 | +
 | 
|  | 1162 | +        class A(object): | 
|  | 1163 | +            def no_decorator(self): return 42 | 
|  | 1164 | +
 | 
|  | 1165 | +            def property(self): return 42 | 
|  | 1166 | +
 | 
|  | 1167 | +            @wrong_superclass_type1 | 
|  | 1168 | +            def wrong_superclass_type1(self): return 42 | 
|  | 1169 | +
 | 
|  | 1170 | +            @wrong_superclass_type2 | 
|  | 1171 | +            def wrong_superclass_type2(self): return 42 | 
|  | 1172 | +
 | 
|  | 1173 | +            @wrong_superclass_type3 | 
|  | 1174 | +            def wrong_superclass_type3(self): return 42 | 
|  | 1175 | +
 | 
|  | 1176 | +            @wrong_superclass_type4 | 
|  | 1177 | +            def wrong_superclass_type4(self): return 42 | 
|  | 1178 | +
 | 
|  | 1179 | +        cls = A() | 
|  | 1180 | +        no_decorator = cls.no_decorator | 
|  | 1181 | +        not_prop = cls.property | 
|  | 1182 | +        bad_superclass1 = cls.wrong_superclass_type1 | 
|  | 1183 | +        bad_superclass2 = cls.wrong_superclass_type2 | 
|  | 1184 | +        bad_superclass3 = cls.wrong_superclass_type3 | 
|  | 1185 | +        bad_superclass4 = cls.wrong_superclass_type4 | 
|  | 1186 | +        """ | 
|  | 1187 | +        ) | 
|  | 1188 | +        for prop in ( | 
|  | 1189 | +            "no_decorator", | 
|  | 1190 | +            "not_prop", | 
|  | 1191 | +            "bad_superclass1", | 
|  | 1192 | +            "bad_superclass2", | 
|  | 1193 | +            "bad_superclass3", | 
|  | 1194 | +            "bad_superclass4", | 
|  | 1195 | +        ): | 
|  | 1196 | +            inferred = next(ast[prop].infer()) | 
|  | 1197 | +            self.assertIsInstance(inferred, bases.BoundMethod) | 
| 991 | 1198 | 
 | 
| 992 | 1199 | 
 | 
| 993 | 1200 | class AliasesTest(unittest.TestCase): | 
|  | 
0 commit comments