From 8cac6498f28ae0144139f64fb9b1d1ad931a8934 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 5 Mar 2022 11:25:21 -0800 Subject: [PATCH] Fix incorrectly inferred `Never` return type for some function implementations (#490) --- docs/changelog.md | 2 ++ pyanalyze/implementation.py | 6 +++++- pyanalyze/test_implementation.py | 13 +++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4b87fcd7..a3594d39 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix incorrectly inferred `Never` return type for some function + implementations (#490) - Infer precise call signatures for `TypedDict` types (#487) - Add mechanism to prevent crashes on objects with unusual `__getattr__` methods (#486) diff --git a/pyanalyze/implementation.py b/pyanalyze/implementation.py index ee3b8d3f..5d844324 100644 --- a/pyanalyze/implementation.py +++ b/pyanalyze/implementation.py @@ -24,6 +24,7 @@ ParameterKind, ) from .value import ( + NO_RETURN_VALUE, UNINITIALIZED_VALUE, AnnotatedValue, AnySource, @@ -98,8 +99,11 @@ def flatten_unions( unwrap_annotated: bool = False, ) -> ImplReturn: value_lists = [ - flatten_values(val, unwrap_annotated=unwrap_annotated) for val in values + list(flatten_values(val, unwrap_annotated=unwrap_annotated)) for val in values ] + # If the lists are empty, we end up inferring Never as the return type, which + # generally isn't right. + value_lists = [lst if lst else [NO_RETURN_VALUE] for lst in value_lists] results = [ clean_up_implementation_fn_return(callable(*vals)) for vals in product(*value_lists) diff --git a/pyanalyze/test_implementation.py b/pyanalyze/test_implementation.py index 1d0258cb..873bdc30 100644 --- a/pyanalyze/test_implementation.py +++ b/pyanalyze/test_implementation.py @@ -3,6 +3,7 @@ from .test_node_visitor import assert_passes from .value import ( + NO_RETURN_VALUE, AnnotatedValue, AnySource, AnyValue, @@ -519,6 +520,18 @@ def capybara(x: int, y: str) -> None: lst += [x] assert_is_value(lst, GenericValue(list, [TypedValue(int)])) + @assert_passes() + def test_list_iadd_never(self): + def render_feedback_text(): + z = [] + + detail_text = None + if detail_text: + assert_is_value(detail_text, NO_RETURN_VALUE) + z += detail_text + + return z + @assert_passes() def test_weak_value(self): from typing import List