From 36ef3d28297ec8493eb4ca587129d3a2b62dbefc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 12 May 2023 10:42:09 -0700 Subject: [PATCH] Avoid false positives on generic aliases with nested classes (#624) --- docs/changelog.md | 5 +++++ pyanalyze/name_check_visitor.py | 18 ++++++++++++++++++ pyanalyze/test_annotations.py | 13 +++++++++++++ 3 files changed, 36 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 7d04b1d7..56e3cc19 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Fix false positive error when annotations refer to classes defined + inside functions (#624) + ## Version 0.10.0 (May 10, 2023) - Infer the signature for built-in static methods, such as `dict.fromkeys` (#619) diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py index 5995f36d..0b8009a1 100644 --- a/pyanalyze/name_check_visitor.py +++ b/pyanalyze/name_check_visitor.py @@ -225,6 +225,13 @@ class ExceptionGroup: pass +try: + from types import GenericAlias +except ImportError: + # 3.8 and lower + GenericAlias = None + + T = TypeVar("T") U = TypeVar("U") AwaitableValue = GenericValue(collections.abc.Awaitable, [TypeVarValue(T)]) @@ -4467,6 +4474,17 @@ def _composite_from_subscript_no_mvv( return_value = self.check_call( node.value, cgi, [index_composite], allow_call=True ) + # Special case to avoid "Unrecognized annotation types.GenericAlias" later; + # ideally we'd be more precise. + if GenericAlias is not None and return_value == TypedValue( + GenericAlias + ): + return_value = self.check_call( + node.value, + cgi, + [Composite(KnownValue(Any))], + allow_call=True, + ) if ( self._should_use_varname_value(return_value) diff --git a/pyanalyze/test_annotations.py b/pyanalyze/test_annotations.py index fe7338c0..e08fe366 100644 --- a/pyanalyze/test_annotations.py +++ b/pyanalyze/test_annotations.py @@ -705,6 +705,19 @@ def capybara( ), ) + @skip_before((3, 9)) + @assert_passes() + def test_genericalias_nested_class(self): + def capybara(): + class Base: + pass + + class Child: + attrs: list[Base] + + # Ideally should not be Any, but this is better than throwing a false positive error. + assert_is_value(Child().attrs, AnyValue(AnySource.from_another)) + class TestCallable(TestNameCheckVisitorBase): @assert_passes()