From 89e49482601e5718f76b0265d0b254055a7afb67 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 20 Nov 2019 23:40:29 -0800 Subject: [PATCH] Preserve can_be_true and can_be_false when copying types This pull request modifies the `last_known_value` eraser so it always copies over the previously inferred value of the `can_be_true` and `can_be_false` fields. This isn't entirely typesafe -- for example, we can sometimes accidentally infer the wrong result if we try reassigning variables or call a mutating function : def bad1(a: List[str], b: bool) -> bool: x = a and b a = ["foo"] y: bool return x or y # Should be an error def bad2(a: List[str], b: bool) -> bool: x = a and b a.append("foo") y: bool return x or y # Should be an error But this was apparently the old behavior/something mypy wasn't previously able to detect, so I guess this is fine for now. I also decided against modifying every `copy_modified` method in all the type classes because of this reason: I wanted to limit the spread of this potentially misleading additional inferred info. Fixes https://github.com/python/mypy/issues/7986, probably. --- mypy/erasetype.py | 5 ++++- test-data/unit/check-expressions.test | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 12c10648525d..eb7c98e86df4 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -142,10 +142,13 @@ class LastKnownValueEraser(TypeTranslator): def visit_instance(self, t: Instance) -> Type: if not t.last_known_value and not t.args: return t - return t.copy_modified( + new_t = t.copy_modified( args=[a.accept(self) for a in t.args], last_known_value=None, ) + new_t.can_be_true = t.can_be_true + new_t.can_be_false = t.can_be_false + return new_t def visit_type_alias_type(self, t: TypeAliasType) -> Type: # Type aliases can't contain literal values, because they are diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 969cf026a467..944f1e8a6901 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -343,6 +343,15 @@ b = bool() reveal_type(s and b or b) # N: Revealed type is 'builtins.bool' [builtins fixtures/bool.pyi] +[case testRestrictedBoolAndOrWithGenerics] +from typing import List + +def f(a: List[str], b: bool) -> bool: + x = a and b + y: bool + return reveal_type(x or y) # N: Revealed type is 'builtins.bool' +[builtins fixtures/list.pyi] + [case testNonBooleanOr] c, d, b = None, None, None # type: (C, D, bool) if int():