Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

in and not in always return bool #480

Merged
merged 1 commit into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- The `in` and `not in` operators always return
booleans (#480)
- Allow `NotImplemented` to be returned from special
methods that support it (#479)
- Fix bug affecting type compatibility between
Expand Down
5 changes: 4 additions & 1 deletion pyanalyze/name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2727,7 +2727,10 @@ def _visit_single_compare(
)
else:
constraint = NULL_CONSTRAINT
if isinstance(op, (ast.Is, ast.IsNot)):
# is, is not, in, and not in always return a boolean, but the other
# comparisons may return arbitrary objects. We should get the
# return value out of the dunder methods, but we don't do that yet.
if isinstance(op, (ast.Is, ast.IsNot, ast.In, ast.NotIn)):
val = TypedValue(bool)
else:
val = AnyValue(AnySource.inference)
Expand Down
184 changes: 3 additions & 181 deletions pyanalyze/test_name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,44 +448,6 @@ def test_cant_assign_tuple(self):
def run():
tpl[1] = 1 # E: unsupported_operation

@assert_passes()
def test_binop(self):
from typing import Union

def tucotuco():
assert_is_value(2 + 3, KnownValue(5))

def capybara(x: Union[int, float], y: Union[int, float]) -> float:
return x + y

@assert_passes()
def test_inplace_binop(self):
class Capybara:
def __add__(self, x: int) -> str:
return ""

def __iadd__(self, x: str) -> int:
return 0

def tucotuco():
x = Capybara()
assert_is_value(x + 1, TypedValue(str))
x += "a"
assert_is_value(x, TypedValue(int))

@assert_passes()
def test_binop_notimplemented(self):
from pyanalyze.extensions import assert_type

class Capybara:
def __add__(self, x: str) -> bool:
if not isinstance(x, str):
return NotImplemented
return len(x) > 3

def pacarana():
assert_type(Capybara() + "x", bool)

@assert_passes()
def test_global_sets_value(self):
capybara = None
Expand Down Expand Up @@ -737,57 +699,6 @@ def caller() -> None:
capybara(str)


class TestBoolOp(TestNameCheckVisitorBase):
@assert_passes()
def test(self):
def capybara(x):
if x:
cond = str(x)
cond2 = True
else:
cond = None
cond2 = None
assert_is_value(cond, MultiValuedValue([TypedValue(str), KnownValue(None)]))
assert_is_value(
cond2, MultiValuedValue([KnownValue(True), KnownValue(None)])
)
assert_is_value(
cond and 1,
MultiValuedValue([TypedValue(str), KnownValue(None), KnownValue(1)]),
skip_annotated=True,
)
assert_is_value(
cond2 and 1,
MultiValuedValue([KnownValue(None), KnownValue(1)]),
skip_annotated=True,
)
assert_is_value(
cond or 1,
MultiValuedValue([TypedValue(str), KnownValue(1)]),
skip_annotated=True,
)
assert_is_value(
cond2 or 1,
MultiValuedValue([KnownValue(True), KnownValue(1)]),
skip_annotated=True,
)

def hutia(x=None):
assert_is_value(x, AnyValue(AnySource.unannotated) | KnownValue(None))
assert_is_value(
x or 1,
AnyValue(AnySource.unannotated) | KnownValue(1),
skip_annotated=True,
)
y = x or 1
assert_is_value(
y, AnyValue(AnySource.unannotated) | KnownValue(1), skip_annotated=True
)
assert_is_value(
(True if x else False) or None, KnownValue(True) | KnownValue(None)
)


class TestReturn(TestNameCheckVisitorBase):
@assert_passes()
def test_type_inference(self):
Expand Down Expand Up @@ -1134,10 +1045,10 @@ class TestImports(TestNameCheckVisitorBase):
def test_star_import(self):
self.assert_passes(
"""
from qcore.asserts import *
from qcore.asserts import *

assert_eq(1, 1)
"""
assert_eq(1, 1)
"""
)

@assert_passes()
Expand Down Expand Up @@ -1574,73 +1485,6 @@ def capybara():
return "foo" + b"bar" # E: unsupported_operation


class TestOperators(TestNameCheckVisitorBase):
@assert_passes(settings={ErrorCode.value_always_true: False})
def test_not(self):
def capybara(x):
assert_is_value(not x, TypedValue(bool), skip_annotated=True)
assert_is_value(not True, KnownValue(False))

@assert_passes()
def test_unary_op(self):
def capybara(x):
assert_is_value(~x, AnyValue(AnySource.from_another))
assert_is_value(~3, KnownValue(-4))

@assert_passes()
def test_binop_type_inference(self):
def capybara(x):
assert_is_value(1 + int(x), TypedValue(int))
assert_is_value(3 * int(x), TypedValue(int))
assert_is_value("foo" + str(x), TypedValue(str))
assert_is_value(1 + float(x), TypedValue(float))
assert_is_value(1.0 + int(x), TypedValue(float))
assert_is_value(3 * 3.0 + 1, KnownValue(10.0))

@assert_passes()
def test_union(self):
from typing import Union

def capybara(x: Union[int, str]) -> None:
assert_is_value(x * 3, MultiValuedValue([TypedValue(int), TypedValue(str)]))

@assert_passes()
def test_rop(self):
class HasAdd:
def __add__(self, other: int) -> "HasAdd":
raise NotImplementedError

class HasRadd:
def __radd__(self, other: int) -> "HasRadd":
raise NotImplementedError

class HasBoth:
def __add__(self, other: "HasBoth") -> "HasBoth":
raise NotImplementedError

def __radd__(self, other: "HasBoth") -> int:
raise NotImplementedError

def capybara(x):
ha = HasAdd()
hr = HasRadd()
assert_is_value(1 + hr, TypedValue(HasRadd))
assert_is_value(x + hr, AnyValue(AnySource.from_another))
assert_is_value(ha + 1, TypedValue(HasAdd))
assert_is_value(ha + x, AnyValue(AnySource.from_another))
assert_is_value(HasBoth() + HasBoth(), TypedValue(HasBoth))

@assert_passes()
def test_unsupported_unary_op(self):
def capybara():
~"capybara" # E: unsupported_operation

@assert_passes()
def test_int_float_product(self):
def capybara(f: float, i: int):
assert_is_value(i * f, TypedValue(float))


class TestTaskNeedsYield(TestNameCheckVisitorBase):
@assert_fails(ErrorCode.task_needs_yield)
def test_constfuture(self):
Expand Down Expand Up @@ -1801,15 +1645,6 @@ def capybara(condition):
assert_is_value(val3, KnownValue(4) | AnyValue(AnySource.inference))


class TestAugAssign(TestNameCheckVisitorBase):
@assert_passes()
def test_aug_assign(self):
def capybara(condition):
x = 1
x += 2
assert_is_value(x, KnownValue(3))


class TestUnpacking(TestNameCheckVisitorBase):
@assert_passes()
def test_dict_unpacking(self):
Expand Down Expand Up @@ -2289,19 +2124,6 @@ def test_static_hasattr():
assert not _static_hasattr(hgat, "random_attribute")


class TestCompare(TestNameCheckVisitorBase):
@assert_passes()
def test_multi(self):
from typing_extensions import Literal

def capybara(i: Literal[1, 2, 3], x: Literal[3, 4]) -> None:
assert_is_value(i, KnownValue(1) | KnownValue(2) | KnownValue(3))
assert_is_value(x, KnownValue(3) | KnownValue(4))
if 1 < i < 3 != x:
assert_is_value(i, KnownValue(2))
assert_is_value(x, KnownValue(4))


class TestIncompatibleOverride(TestNameCheckVisitorBase):
@assert_passes()
def test_simple(self):
Expand Down
Loading