diff --git a/pytype/overlays/typed_dict.py b/pytype/overlays/typed_dict.py index 50ff658c8..72ee102e2 100644 --- a/pytype/overlays/typed_dict.py +++ b/pytype/overlays/typed_dict.py @@ -221,7 +221,9 @@ def make_class_from_pyi(self, cls_name, pytd_cls): ) for c in pytd_cls.constants: - typ = self.ctx.convert.constant_to_value(c.type) + # The field types may refer back to the class being built. + with self.ctx.allow_recursive_convert(): + typ = self.ctx.convert.constant_to_value(c.type) props.add(c.name, typ, total) # Process base classes and generate the __init__ signature. diff --git a/pytype/tests/test_typed_dict.py b/pytype/tests/test_typed_dict.py index f162477f4..6c02e30cc 100644 --- a/pytype/tests/test_typed_dict.py +++ b/pytype/tests/test_typed_dict.py @@ -784,6 +784,24 @@ class Bar: """, ) + def test_recursive(self): + with self.DepTree([( + "foo.pyi", + """ + from typing import Union, Optional + from typing_extensions import TypedDict + class Foo(TypedDict, total=False): + name: str + nested: 'Foo' + """, + )]): + self.CheckWithErrors(""" + import foo + foo.Foo(name='foo', a=1) # wrong-keyword-args + # 'nested' cannot be checked because Foo is recursive. + foo.Foo(name='foo', nested=45) + """) + def test_total_false(self): with self.DepTree([ (