diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py index 199e8285c..0f553ab08 100644 --- a/astroid/interpreter/objectmodel.py +++ b/astroid/interpreter/objectmodel.py @@ -492,6 +492,10 @@ def __init__(self): super().__init__() + @property + def attr___annotations__(self) -> node_classes.Unkown: + return node_classes.Unknown() + @property def attr___module__(self): return node_classes.Const(self._instance.root().qname()) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index efd5439b5..a66f3fc53 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1946,7 +1946,10 @@ def implicit_locals(self): """ locals_ = (("__module__", self.special_attributes.attr___module__),) # __qualname__ is defined in PEP3155 - locals_ += (("__qualname__", self.special_attributes.attr___qualname__),) + locals_ += ( + ("__qualname__", self.special_attributes.attr___qualname__), + ("__annotations__", self.special_attributes.attr___annotations__), + ) return locals_ # pylint: disable=redefined-outer-name diff --git a/tests/test_builder.py b/tests/test_builder.py index 24f4d4207..f9dac6169 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -841,12 +841,13 @@ def test_class_locals(self) -> None: klass1 = module["YO"] locals1 = klass1.locals keys = sorted(locals1.keys()) - assert_keys = ["__init__", "__module__", "__qualname__", "a"] + assert_keys = ["__annotations__", "__init__", "__module__", "__qualname__", "a"] self.assertEqual(keys, assert_keys) klass2 = module["YOUPI"] locals2 = klass2.locals keys = locals2.keys() assert_keys = [ + "__annotations__", "__init__", "__module__", "__qualname__", diff --git a/tests/test_object_model.py b/tests/test_object_model.py index 62d234b1f..9ad4d39a9 100644 --- a/tests/test_object_model.py +++ b/tests/test_object_model.py @@ -855,3 +855,47 @@ def foo(): assert wrapped.name == "foo" cache_info = next(ast_nodes[2].infer()) assert isinstance(cache_info, astroid.Instance) + + +def test_class_annotations() -> None: + """Test that the `__annotations__` attribute is avaiable in the class scope""" + annotations, klass_attribute = builder.extract_node( + """ + class Test: + __annotations__ #@ + Test.__annotations__ #@ + """ + ) + # Test that `__annotations__` attribute is available in the class scope: + assert isinstance(annotations, nodes.Name) + # The `__annotations__` attribute is `Uninferable`: + assert next(annotations.infer()) is astroid.Uninferable + + # Test that we can access the class annotations: + assert isinstance(klass_attribute, nodes.Attribute) + + +def test_class_annotations_typed_dict() -> None: + """Test that we can access class annotations on various TypedDicts""" + apple, pear = builder.extract_node( + """ + from typing import TypedDict + + + class Apple(TypedDict): + a: int + b: str + + + Pear = TypedDict('OtherTypedDict', {'a': int, 'b': str}) + + + Apple.__annotations__ #@ + Pear.__annotations__ #@ + """ + ) + + assert isinstance(apple, nodes.Attribute) + assert next(apple.infer()) is astroid.Uninferable + assert isinstance(pear, nodes.Attribute) + assert next(pear.infer()) is astroid.Uninferable diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py index ffbca4dad..209710b86 100644 --- a/tests/test_scoped_nodes.py +++ b/tests/test_scoped_nodes.py @@ -1209,6 +1209,7 @@ def registered(cls, application): astroid = builder.parse(data, __name__) cls = astroid["WebAppObject"] assert_keys = [ + "__annotations__", "__module__", "__qualname__", "appli",