diff --git a/pytype/load_pytd.py b/pytype/load_pytd.py index ac422811e..b314e8d01 100644 --- a/pytype/load_pytd.py +++ b/pytype/load_pytd.py @@ -737,6 +737,11 @@ def finish_and_verify_ast(self, mod_ast): self._modules[k].ast ) mod_ast = self._resolve_external_types(mod_ast) + # Circular imports can leave type params (e.g. ParamSpecArgs) + # unresolved. External type parameters are added to the AST in + # visitors.AdjustTypeParameters, after resolving local types. But those + # are needed to resolve e.g. `_P.args` references. + mod_ast = self._resolver.resolve_local_types(mod_ast) self._resolver.verify(mod_ast) return mod_ast diff --git a/pytype/load_pytd_test.py b/pytype/load_pytd_test.py index cfd7b2ba3..5268db13e 100644 --- a/pytype/load_pytd_test.py +++ b/pytype/load_pytd_test.py @@ -280,6 +280,43 @@ class Popen: ... ) ) + def test_circular_dependency_with_type_param(self): + with test_utils.Tempdir() as d: + d.create_file( + "bar.pyi", + """ + from typing import Callable, ParamSpec + + from foo import Foo + + _P = ParamSpec("_P") + + class Bar: + foo: Foo | None + def bar(obj: Callable[_P, None], /, *args: _P.args, **kwargs: _P.kwargs) -> Bar: ... + """, + ) + d.create_file( + "foo.pyi", + """ + from bar import bar as _bar + + class Foo: ... + bar = _bar + """, + ) + loader = load_pytd.Loader( + config.Options.create( + module_name="base", + python_version=self.python_version, + pythonpath=d.path, + ) + ) + bar = loader.import_name("bar") + foo = loader.import_name("foo") + self.assertTrue(bar.Lookup("bar.bar")) + self.assertTrue(foo.Lookup("foo.bar")) + def test_cache(self): with test_utils.Tempdir() as d: d.create_file("foo.pyi", "def get_bar() -> bar.Bar: ...")