From 2344d33bf099bbb14b3d3a761103448e28b7cb50 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 10 Jan 2022 16:09:44 -0800 Subject: [PATCH 1/2] fix attrs on MVV within Annotated --- docs/changelog.md | 2 + pyanalyze/name_check_visitor.py | 77 +++++++++++---------------------- pyanalyze/test_attributes.py | 7 +++ pyanalyze/value.py | 6 +++ 4 files changed, 41 insertions(+), 51 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 1dec2647..b1d060ff 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix accessing attributes on Unions nested within + Annotated (#392) - Fix interaction of `register_error_code()` with new configuration mechanism (#391) - Check against invalid `Signature` objects and prepare diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py index dee54101..256d72d1 100644 --- a/pyanalyze/name_check_visitor.py +++ b/pyanalyze/name_check_visitor.py @@ -157,6 +157,8 @@ UNINITIALIZED_VALUE, NO_RETURN_VALUE, NoReturnConstraintExtension, + flatten_values, + is_union, kv_pairs_from_mapping, make_weak, unannotate_value, @@ -3959,7 +3961,9 @@ def composite_from_attribute(self, node: ast.Attribute) -> Composite: self.reexport_tracker.record_attribute_accessed( root_composite.value.val.__name__, node.attr, node, self ) - value = self._get_attribute_with_fallback(root_composite, node.attr, node) + value = self.get_attribute( + root_composite, node.attr, node, use_fallback=True + ) if self._should_use_varname_value(value): varname_value = self.checker.maybe_get_variable_name_value(node.attr) if varname_value is not None: @@ -3981,7 +3985,9 @@ def get_attribute( root_composite: Composite, attr: str, node: Optional[ast.AST] = None, + *, ignore_none: bool = False, + use_fallback: bool = False, ) -> Value: """Get an attribute of this value. @@ -3994,69 +4000,38 @@ def get_attribute( varname=root_composite.varname, node=root_composite.node, ) - if isinstance(root_composite.value, MultiValuedValue): - values = [ - self.get_attribute( - Composite(subval, root_composite.varname, root_composite.node), - attr, - node, - ignore_none=ignore_none, - ) - for subval in root_composite.value.vals - ] - if any(value is UNINITIALIZED_VALUE for value in values): - return UNINITIALIZED_VALUE - return unite_values(*values) - return self._get_attribute_no_mvv( - root_composite, attr, node, ignore_none=ignore_none - ) - - def get_attribute_from_value(self, root_value: Value, attribute: str) -> Value: - return self.get_attribute(Composite(root_value), attribute) - - def _get_attribute_no_mvv( - self, - root_composite: Composite, - attr: str, - node: Optional[ast.AST] = None, - ignore_none: bool = False, - ) -> Value: - """Get an attribute. root_value must not be a MultiValuedValue.""" - ctx = _AttrContext( - root_composite, attr, self, node=node, ignore_none=ignore_none - ) - return attributes.get_attribute(ctx) - - def _get_attribute_with_fallback( - self, root_composite: Composite, attr: str, node: ast.AST - ) -> Value: - ignore_none = self.options.get_value_for(IgnoreNoneAttributes) - if isinstance(root_composite.value, TypeVarValue): - root_composite = Composite( - value=root_composite.value.get_fallback_value(), - varname=root_composite.varname, - node=root_composite.node, - ) - if isinstance(root_composite.value, MultiValuedValue): + if is_union(root_composite.value): results = [] - for subval in root_composite.value.vals: + for subval in flatten_values(root_composite.value): composite = Composite( subval, root_composite.varname, root_composite.node ) subresult = self.get_attribute( - composite, attr, node, ignore_none=ignore_none + composite, + attr, + node, + ignore_none=ignore_none, + use_fallback=use_fallback, ) - if subresult is UNINITIALIZED_VALUE: + if ( + subresult is UNINITIALIZED_VALUE + and use_fallback + and node is not None + ): subresult = self._get_attribute_fallback(subval, attr, node) results.append(subresult) return unite_values(*results) - result = self._get_attribute_no_mvv( - root_composite, attr, node, ignore_none=ignore_none + ctx = _AttrContext( + root_composite, attr, self, node=node, ignore_none=ignore_none ) - if result is UNINITIALIZED_VALUE: + result = attributes.get_attribute(ctx) + if result is UNINITIALIZED_VALUE and use_fallback and node is not None: return self._get_attribute_fallback(root_composite.value, attr, node) return result + def get_attribute_from_value(self, root_value: Value, attribute: str) -> Value: + return self.get_attribute(Composite(root_value), attribute) + def _get_attribute_fallback( self, root_value: Value, attr: str, node: ast.AST ) -> Value: diff --git a/pyanalyze/test_attributes.py b/pyanalyze/test_attributes.py index 0a7c0029..6d89d954 100644 --- a/pyanalyze/test_attributes.py +++ b/pyanalyze/test_attributes.py @@ -250,6 +250,13 @@ def capybara(): annotated_global, MultiValuedValue([TypedValue(str), KnownValue(None)]) ) + @assert_passes() + def test_unwrap_mvv(self): + def render_task(name: str): + if not (name or "").strip(): + name = "x" + assert_is_value(name, TypedValue(str) | KnownValue("x")) + class TestHasAttrExtension(TestNameCheckVisitorBase): @assert_passes() diff --git a/pyanalyze/value.py b/pyanalyze/value.py index 9e60b2e5..4c548be7 100644 --- a/pyanalyze/value.py +++ b/pyanalyze/value.py @@ -1914,6 +1914,12 @@ def from_varname( return None +def is_union(val: Value) -> bool: + return isinstance(val, MultiValuedValue) or ( + isinstance(val, AnnotatedValue) and isinstance(val.value, MultiValuedValue) + ) + + def flatten_values(val: Value, *, unwrap_annotated: bool = False) -> Iterable[Value]: """Flatten a :class:`MultiValuedValue` into its constituent values. From 255dab080b70cc735ef71444122171e4bbb1b4cc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 10 Jan 2022 16:10:15 -0800 Subject: [PATCH 2/2] Update docs/changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index b1d060ff..39d51682 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,7 +3,7 @@ ## Unreleased - Fix accessing attributes on Unions nested within - Annotated (#392) + Annotated (#393) - Fix interaction of `register_error_code()` with new configuration mechanism (#391) - Check against invalid `Signature` objects and prepare