diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md
index 5ac36c4fb9..12eb74d15a 100644
--- a/crates/ty/docs/rules.md
+++ b/crates/ty/docs/rules.md
@@ -39,7 +39,7 @@ def test(): -> "int":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -95,7 +95,7 @@ f(int) # error
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -126,7 +126,7 @@ a = 1
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -158,7 +158,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -190,7 +190,7 @@ class B(A): ...
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -218,7 +218,7 @@ type B = A
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -245,7 +245,7 @@ class B(A, A): ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -357,7 +357,7 @@ def test(): -> "Literal[5]":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -387,7 +387,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -502,7 +502,7 @@ an atypical memory layout.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -557,7 +557,7 @@ a: int = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -627,7 +627,7 @@ asyncio.run(main())
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -678,7 +678,7 @@ with 1:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -707,7 +707,7 @@ a: str
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -751,7 +751,7 @@ except ZeroDivisionError:
Default level: error ·
Added in 0.0.1-alpha.28 ·
Related issues ·
-View source
+View source
@@ -793,7 +793,7 @@ class D(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -826,7 +826,7 @@ class C[U](Generic[T]): ...
Default level: error ·
Added in 0.0.1-alpha.17 ·
Related issues ·
-View source
+View source
@@ -865,7 +865,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -900,7 +900,7 @@ def f(t: TypeVar("U")): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -934,7 +934,7 @@ class B(metaclass=f): ...
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1041,7 +1041,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1095,7 +1095,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -1125,7 +1125,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1175,7 +1175,7 @@ def foo(x: int) -> int: ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1201,7 +1201,7 @@ def f(a: int = ''): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1232,7 +1232,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1266,7 +1266,7 @@ TypeError: Protocols can only inherit from other protocols, got
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1315,7 +1315,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1340,7 +1340,7 @@ def func() -> int:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1398,7 +1398,7 @@ TODO #14889
Default level: error ·
Added in 0.0.1-alpha.6 ·
Related issues ·
-View source
+View source
@@ -1425,7 +1425,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -1472,7 +1472,7 @@ Bar[int] # error: too few arguments
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1502,7 +1502,7 @@ TYPE_CHECKING = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1532,7 +1532,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -1566,7 +1566,7 @@ f(10) # Error
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -1600,7 +1600,7 @@ class C:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1635,7 +1635,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1660,7 +1660,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1693,7 +1693,7 @@ alice["age"] # KeyError
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1722,7 +1722,7 @@ func("string") # error: [no-matching-overload]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1746,7 +1746,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1772,7 +1772,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -1805,7 +1805,7 @@ class B(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1832,7 +1832,7 @@ f(1, x=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -1890,7 +1890,7 @@ def test(): -> "int":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1920,7 +1920,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1949,7 +1949,7 @@ class B(A): ... # Error raised here
Default level: error ·
Preview (since 0.0.1-alpha.30) ·
Related issues ·
-View source
+View source
@@ -1983,7 +1983,7 @@ class F(NamedTuple):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2010,7 +2010,7 @@ f("foo") # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2038,7 +2038,7 @@ def _(x: int):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2084,7 +2084,7 @@ class A:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2111,7 +2111,7 @@ f(x=1, y=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2139,7 +2139,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2164,7 +2164,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2189,7 +2189,7 @@ print(x) # NameError: name 'x' is not defined
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2226,7 +2226,7 @@ b1 < b2 < b1 # exception raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2254,7 +2254,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2279,7 +2279,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
Default level: warn ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -2320,7 +2320,7 @@ class SubProto(BaseProto, Protocol):
Default level: warn ·
Added in 0.0.1-alpha.16 ·
Related issues ·
-View source
+View source
@@ -2408,7 +2408,7 @@ a = 20 / 0 # type: ignore
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2436,7 +2436,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2468,7 +2468,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2500,7 +2500,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2527,7 +2527,7 @@ cast(int, f()) # Redundant
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2551,7 +2551,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: warn ·
Added in 0.0.1-alpha.15 ·
Related issues ·
-View source
+View source
@@ -2609,7 +2609,7 @@ def g():
Default level: warn ·
Added in 0.0.1-alpha.7 ·
Related issues ·
-View source
+View source
@@ -2648,7 +2648,7 @@ class D(C): ... # error: [unsupported-base]
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2711,7 +2711,7 @@ def foo(x: int | str) -> int | str:
Default level: ignore ·
Preview (since 0.0.1-alpha.1) ·
Related issues ·
-View source
+View source
@@ -2735,7 +2735,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
diff --git a/crates/ty_python_semantic/resources/mdtest/bidirectional.md b/crates/ty_python_semantic/resources/mdtest/bidirectional.md
index 20f28ef72a..bae848c4a7 100644
--- a/crates/ty_python_semantic/resources/mdtest/bidirectional.md
+++ b/crates/ty_python_semantic/resources/mdtest/bidirectional.md
@@ -340,7 +340,7 @@ def _(a: object, b: object, flag: bool):
else:
x = g
- # error: [unsupported-operator] "Operator `>` is not supported for types `object` and `object`"
+ # error: [unsupported-operator] "Operator `>` is not supported between two objects of type `object`"
x(f"{'a' if a > b else 'b'}")
```
diff --git a/crates/ty_python_semantic/resources/mdtest/binary/instances.md b/crates/ty_python_semantic/resources/mdtest/binary/instances.md
index dab5751d66..c981a570b0 100644
--- a/crates/ty_python_semantic/resources/mdtest/binary/instances.md
+++ b/crates/ty_python_semantic/resources/mdtest/binary/instances.md
@@ -392,7 +392,7 @@ reveal_type(A - B) # revealed: Unknown
reveal_type(A < B) # revealed: bool
reveal_type(A > B) # revealed: bool
-# error: [unsupported-operator] "Operator `<=` is not supported for types `` and ``"
+# error: [unsupported-operator] "Operator `<=` is not supported between objects of type `` and ``"
reveal_type(A <= B) # revealed: Unknown
reveal_type(A[0]) # revealed: str
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/instances/membership_test.md b/crates/ty_python_semantic/resources/mdtest/comparison/instances/membership_test.md
index b28d0d04fa..a53aaef3bb 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/instances/membership_test.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/instances/membership_test.md
@@ -21,9 +21,9 @@ class A:
reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
-# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
+# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
reveal_type(42 in A()) # revealed: bool
-# error: [unsupported-operator] "Operator `not in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
+# error: [unsupported-operator] "Operator `not in` is not supported between objects of type `Literal[42]` and `A`"
reveal_type(42 not in A()) # revealed: bool
```
@@ -127,9 +127,9 @@ class A:
reveal_type(CheckContains() in A()) # revealed: bool
-# error: [unsupported-operator] "Operator `in` is not supported for types `CheckIter` and `A`"
+# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckIter` and `A`"
reveal_type(CheckIter() in A()) # revealed: bool
-# error: [unsupported-operator] "Operator `in` is not supported for types `CheckGetItem` and `A`"
+# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckGetItem` and `A`"
reveal_type(CheckGetItem() in A()) # revealed: bool
class B:
@@ -155,9 +155,9 @@ class A:
def __getitem__(self, key: str) -> str:
return "foo"
-# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
+# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
reveal_type(42 in A()) # revealed: bool
-# error: [unsupported-operator] "Operator `in` is not supported for types `str` and `A`, in comparing `Literal["hello"]` with `A`"
+# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal["hello"]` and `A`"
reveal_type("hello" in A()) # revealed: bool
```
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md b/crates/ty_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md
index f32d65d244..85f88ab181 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md
@@ -309,7 +309,7 @@ reveal_type(A() != object()) # revealed: bool
reveal_type(object() == A()) # revealed: bool
reveal_type(object() != A()) # revealed: bool
-# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `object`"
+# error: [unsupported-operator] "Operator `<` is not supported between objects of type `A` and `object`"
# revealed: Unknown
reveal_type(A() < object())
```
@@ -327,13 +327,13 @@ reveal_type(1 >= 1.0) # revealed: bool
reveal_type(1 == 2j) # revealed: bool
reveal_type(1 != 2j) # revealed: bool
-# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
+# error: [unsupported-operator] "Operator `<` is not supported between objects of type `Literal[1]` and `complex`"
reveal_type(1 < 2j) # revealed: Unknown
-# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
+# error: [unsupported-operator] "Operator `<=` is not supported between objects of type `Literal[1]` and `complex`"
reveal_type(1 <= 2j) # revealed: Unknown
-# error: [unsupported-operator] "Operator `>` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
+# error: [unsupported-operator] "Operator `>` is not supported between objects of type `Literal[1]` and `complex`"
reveal_type(1 > 2j) # revealed: Unknown
-# error: [unsupported-operator] "Operator `>=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
+# error: [unsupported-operator] "Operator `>=` is not supported between objects of type `Literal[1]` and `complex`"
reveal_type(1 >= 2j) # revealed: Unknown
def f(x: bool, y: int):
@@ -386,3 +386,29 @@ reveal_type(A() == A()) # revealed: Literal[True]
reveal_type(A() < A()) # revealed: Literal[True]
reveal_type(A() > A()) # revealed: Literal[True]
```
+
+## Diagnostics where classes have the same name
+
+We use the fully qualified names of classes to disambiguate them where necessary:
+
+`a.py`:
+
+```py
+class Foo: ...
+```
+
+`b.py`:
+
+```py
+class Foo: ...
+```
+
+`main.py`:
+
+```py
+import a
+import b
+
+# error: [unsupported-operator] "Operator `<` is not supported between objects of type `a.Foo` and `b.Foo`"
+a.Foo() < b.Foo()
+```
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/integers.md b/crates/ty_python_semantic/resources/mdtest/comparison/integers.md
index eeccd6a60a..95eddb2e36 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/integers.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/integers.md
@@ -12,7 +12,7 @@ reveal_type(1 is 1) # revealed: bool
reveal_type(1 is not 1) # revealed: bool
reveal_type(1 is 2) # revealed: Literal[False]
reveal_type(1 is not 7) # revealed: Literal[True]
-# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `str`, in comparing `Literal[1]` with `Literal[""]`"
+# error: [unsupported-operator] "Operator `<=` is not supported between objects of type `Literal[1]` and `Literal[""]`"
reveal_type(1 <= "" and 0 < 1) # revealed: (Unknown & ~AlwaysTruthy) | Literal[True]
```
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md b/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md
index 35d7203c98..6c6d7bc827 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/intersections.md
@@ -109,6 +109,8 @@ def _(o: object):
### Unsupported operators for positive contributions
+
+
Raise an error if the given operator is unsupported for all positive contributions to the
intersection type:
@@ -121,7 +123,7 @@ def _(x: object):
if isinstance(x, NonContainer2):
reveal_type(x) # revealed: NonContainer1 & NonContainer2
- # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer1`"
+ # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `NonContainer1 & NonContainer2`"
reveal_type(2 in x) # revealed: bool
```
@@ -149,7 +151,7 @@ def _(x: object):
if not isinstance(x, NonContainer1):
reveal_type(x) # revealed: ~NonContainer1
- # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `object`, in comparing `Literal[2]` with `~NonContainer1`"
+ # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `~NonContainer1`"
reveal_type(2 in x) # revealed: bool
reveal_type(2 is x) # revealed: bool
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md b/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
index 2c99df929e..832029c881 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
@@ -79,6 +79,8 @@ def _(x: bool, y: int):
#### Comparison Unsupported
+
+
If two tuples contain types that do not support comparison, the result may be `Unknown`. However,
`==` and `!=` are exceptions and can still provide definite results.
@@ -92,14 +94,17 @@ reveal_type(a == b) # revealed: bool
# TODO: should be Literal[True], once we implement (in)equality for mismatched literals
reveal_type(a != b) # revealed: bool
-# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
+# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a < b) # revealed: Unknown
-# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
+# error: [unsupported-operator] "Operator `<=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a <= b) # revealed: Unknown
-# error: [unsupported-operator] "Operator `>` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
+# error: [unsupported-operator] "Operator `>` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a > b) # revealed: Unknown
-# error: [unsupported-operator] "Operator `>=` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
+# error: [unsupported-operator] "Operator `>=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a >= b) # revealed: Unknown
+# error: [unsupported-operator]
+# error: [unsupported-operator]
+reveal_type((object(),) < (object(),) < (object(),)) # revealed: Unknown
```
However, if the lexicographic comparison completes without reaching a point where str and int are
@@ -257,24 +262,24 @@ comparison can clearly conclude before encountering an error, the error should n
```py
def _(n: int, s: str):
class A: ...
- # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`"
+ # error: [unsupported-operator] "Operator `<` is not supported between two objects of type `A`"
A() < A()
- # error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`"
+ # error: [unsupported-operator] "Operator `<=` is not supported between two objects of type `A`"
A() <= A()
- # error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`"
+ # error: [unsupported-operator] "Operator `>` is not supported between two objects of type `A`"
A() > A()
- # error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`"
+ # error: [unsupported-operator] "Operator `>=` is not supported between two objects of type `A`"
A() >= A()
a = (0, n, A())
- # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
+ # error: [unsupported-operator] "Operator `<` is not supported between two objects of type `tuple[Literal[0], int, A]`"
reveal_type(a < a) # revealed: Unknown
- # error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
+ # error: [unsupported-operator] "Operator `<=` is not supported between two objects of type `tuple[Literal[0], int, A]`"
reveal_type(a <= a) # revealed: Unknown
- # error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
+ # error: [unsupported-operator] "Operator `>` is not supported between two objects of type `tuple[Literal[0], int, A]`"
reveal_type(a > a) # revealed: Unknown
- # error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
+ # error: [unsupported-operator] "Operator `>=` is not supported between two objects of type `tuple[Literal[0], int, A]`"
reveal_type(a >= a) # revealed: Unknown
# Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`,
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/unions.md b/crates/ty_python_semantic/resources/mdtest/comparison/unions.md
index 56924ecc7f..6a7feea646 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/unions.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/unions.md
@@ -66,14 +66,29 @@ def _(flag_s: bool, flag_l: bool):
## Unsupported operations
+
+
Make sure we emit a diagnostic if *any* of the possible comparisons is unsupported. For now, we fall
back to `bool` for the result type instead of trying to infer something more precise from the other
(supported) variants:
```py
-def _(flag: bool):
- x = [1, 2] if flag else 1
+from typing import Literal
+def _(
+ x: list[int] | Literal[1],
+ y: list[int] | Literal[1],
+ aa: tuple[int],
+ bb: tuple[int] | tuple[int, int],
+ cc: tuple[str] | tuple[str, str],
+):
result = 1 in x # error: "Operator `in` is not supported"
reveal_type(result) # revealed: bool
+
+ result2 = y in x # error: [unsupported-operator]
+ reveal_type(result) # revealed: bool
+
+ result3 = aa < cc # error: [unsupported-operator]
+ result4 = cc < aa # error: [unsupported-operator]
+ result5 = bb < cc # error: [unsupported-operator]
```
diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/unsupported.md b/crates/ty_python_semantic/resources/mdtest/comparison/unsupported.md
index f3e57c886d..c74fd57928 100644
--- a/crates/ty_python_semantic/resources/mdtest/comparison/unsupported.md
+++ b/crates/ty_python_semantic/resources/mdtest/comparison/unsupported.md
@@ -1,32 +1,34 @@
# Comparison: Unsupported operators
+
+
```py
def _(flag: bool, flag1: bool, flag2: bool):
class A: ...
- a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`"
+ a = 1 in 7 # error: "Operator `in` is not supported between objects of type `Literal[1]` and `Literal[7]`"
reveal_type(a) # revealed: bool
- b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`"
+ b = 0 not in 10 # error: "Operator `not in` is not supported between objects of type `Literal[0]` and `Literal[10]`"
reveal_type(b) # revealed: bool
- # error: [unsupported-operator] "Operator `<` is not supported for types `object` and `int`, in comparing `object` with `Literal[5]`"
+ # error: [unsupported-operator] "Operator `<` is not supported between objects of type `object` and `Literal[5]`"
c = object() < 5
reveal_type(c) # revealed: Unknown
- # error: [unsupported-operator] "Operator `<` is not supported for types `int` and `object`, in comparing `Literal[5]` with `object`"
+ # error: [unsupported-operator] "Operator `<` is not supported between objects of type `Literal[5]` and `object`"
d = 5 < object()
reveal_type(d) # revealed: Unknown
int_literal_or_str_literal = 1 if flag else "foo"
- # error: "Operator `in` is not supported for types `Literal[42]` and `Literal[1]`, in comparing `Literal[42]` with `Literal[1, "foo"]`"
+ # error: "Operator `in` is not supported between objects of type `Literal[42]` and `Literal[1, "foo"]`"
e = 42 in int_literal_or_str_literal
reveal_type(e) # revealed: bool
- # error: [unsupported-operator] "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
+ # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
f = (1, 2) < (1, "hello")
reveal_type(f) # revealed: Unknown
- # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`"
+ # error: [unsupported-operator] "Operator `<` is not supported between two objects of type `tuple[bool, A]`"
g = (flag1, A()) < (flag2, A())
reveal_type(g) # revealed: Unknown
```
diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/intersections.md_-_Comparison___Intersec…_-_Diagnostics_-_Unsupported_operator…_(27f95f68d1c826ec).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/intersections.md_-_Comparison___Intersec…_-_Diagnostics_-_Unsupported_operator…_(27f95f68d1c826ec).snap
new file mode 100644
index 0000000000..2034ded4e3
--- /dev/null
+++ b/crates/ty_python_semantic/resources/mdtest/snapshots/intersections.md_-_Comparison___Intersec…_-_Diagnostics_-_Unsupported_operator…_(27f95f68d1c826ec).snap
@@ -0,0 +1,79 @@
+---
+source: crates/ty_test/src/lib.rs
+expression: snapshot
+---
+---
+mdtest name: intersections.md - Comparison: Intersections - Diagnostics - Unsupported operators for positive contributions
+mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/intersections.md
+---
+
+# Python source files
+
+## mdtest_snippet.py
+
+```
+ 1 | class NonContainer1: ...
+ 2 | class NonContainer2: ...
+ 3 |
+ 4 | def _(x: object):
+ 5 | if isinstance(x, NonContainer1):
+ 6 | if isinstance(x, NonContainer2):
+ 7 | reveal_type(x) # revealed: NonContainer1 & NonContainer2
+ 8 |
+ 9 | # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `NonContainer1 & NonContainer2`"
+10 | reveal_type(2 in x) # revealed: bool
+11 | class Container:
+12 | def __contains__(self, x) -> bool:
+13 | return False
+14 |
+15 | def _(x: object):
+16 | if isinstance(x, NonContainer1):
+17 | if isinstance(x, Container):
+18 | if isinstance(x, NonContainer2):
+19 | reveal_type(x) # revealed: NonContainer1 & Container & NonContainer2
+20 | reveal_type(2 in x) # revealed: bool
+21 | def _(x: object):
+22 | if not isinstance(x, NonContainer1):
+23 | reveal_type(x) # revealed: ~NonContainer1
+24 |
+25 | # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `~NonContainer1`"
+26 | reveal_type(2 in x) # revealed: bool
+27 |
+28 | reveal_type(2 is x) # revealed: bool
+```
+
+# Diagnostics
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:10:25
+ |
+ 9 | # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `NonContainer1 & …
+10 | reveal_type(2 in x) # revealed: bool
+ | -^^^^-
+ | | |
+ | | Has type `NonContainer1 & NonContainer2`
+ | Has type `Literal[2]`
+11 | class Container:
+12 | def __contains__(self, x) -> bool:
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:26:21
+ |
+25 | # error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[2]` and `~NonContainer1`"
+26 | reveal_type(2 in x) # revealed: bool
+ | -^^^^-
+ | | |
+ | | Has type `~NonContainer1`
+ | Has type `Literal[2]`
+27 |
+28 | reveal_type(2 is x) # revealed: bool
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Heterogeneous_-_Value_Comparisons_-_Comparison_Unsupport…_(966dd82bd3668d0e).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Heterogeneous_-_Value_Comparisons_-_Comparison_Unsupport…_(966dd82bd3668d0e).snap
new file mode 100644
index 0000000000..32b12ff278
--- /dev/null
+++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Heterogeneous_-_Value_Comparisons_-_Comparison_Unsupport…_(966dd82bd3668d0e).snap
@@ -0,0 +1,157 @@
+---
+source: crates/ty_test/src/lib.rs
+expression: snapshot
+---
+---
+mdtest name: tuples.md - Comparison: Tuples - Heterogeneous - Value Comparisons - Comparison Unsupported
+mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
+---
+
+# Python source files
+
+## mdtest_snippet.py
+
+```
+ 1 | a = (1, 2)
+ 2 | b = (1, "hello")
+ 3 |
+ 4 | # TODO: should be Literal[False], once we implement (in)equality for mismatched literals
+ 5 | reveal_type(a == b) # revealed: bool
+ 6 |
+ 7 | # TODO: should be Literal[True], once we implement (in)equality for mismatched literals
+ 8 | reveal_type(a != b) # revealed: bool
+ 9 |
+10 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
+11 | reveal_type(a < b) # revealed: Unknown
+12 | # error: [unsupported-operator] "Operator `<=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
+13 | reveal_type(a <= b) # revealed: Unknown
+14 | # error: [unsupported-operator] "Operator `>` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
+15 | reveal_type(a > b) # revealed: Unknown
+16 | # error: [unsupported-operator] "Operator `>=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
+17 | reveal_type(a >= b) # revealed: Unknown
+18 | # error: [unsupported-operator]
+19 | # error: [unsupported-operator]
+20 | reveal_type((object(),) < (object(),) < (object(),)) # revealed: Unknown
+21 | a = (1, 2)
+22 | b = (999999, "hello")
+23 |
+24 | reveal_type(a == b) # revealed: Literal[False]
+25 | reveal_type(a != b) # revealed: Literal[True]
+26 | reveal_type(a < b) # revealed: Literal[True]
+27 | reveal_type(a <= b) # revealed: Literal[True]
+28 | reveal_type(a > b) # revealed: Literal[False]
+29 | reveal_type(a >= b) # revealed: Literal[False]
+```
+
+# Diagnostics
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:11:13
+ |
+10 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lite…
+11 | reveal_type(a < b) # revealed: Unknown
+ | -^^^-
+ | | |
+ | | Has type `tuple[Literal[1], Literal["hello"]]`
+ | Has type `tuple[Literal[1], Literal[2]]`
+12 | # error: [unsupported-operator] "Operator `<=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lit…
+13 | reveal_type(a <= b) # revealed: Unknown
+ |
+info: Operation fails because operator `<` is not supported between the tuple elements at index 2 (of type `Literal[2]` and `Literal["hello"]`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<=` operation
+ --> src/mdtest_snippet.py:13:13
+ |
+11 | reveal_type(a < b) # revealed: Unknown
+12 | # error: [unsupported-operator] "Operator `<=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lit…
+13 | reveal_type(a <= b) # revealed: Unknown
+ | -^^^^-
+ | | |
+ | | Has type `tuple[Literal[1], Literal["hello"]]`
+ | Has type `tuple[Literal[1], Literal[2]]`
+14 | # error: [unsupported-operator] "Operator `>` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lite…
+15 | reveal_type(a > b) # revealed: Unknown
+ |
+info: Operation fails because operator `<=` is not supported between the tuple elements at index 2 (of type `Literal[2]` and `Literal["hello"]`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `>` operation
+ --> src/mdtest_snippet.py:15:13
+ |
+13 | reveal_type(a <= b) # revealed: Unknown
+14 | # error: [unsupported-operator] "Operator `>` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lite…
+15 | reveal_type(a > b) # revealed: Unknown
+ | -^^^-
+ | | |
+ | | Has type `tuple[Literal[1], Literal["hello"]]`
+ | Has type `tuple[Literal[1], Literal[2]]`
+16 | # error: [unsupported-operator] "Operator `>=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lit…
+17 | reveal_type(a >= b) # revealed: Unknown
+ |
+info: Operation fails because operator `>` is not supported between the tuple elements at index 2 (of type `Literal[2]` and `Literal["hello"]`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `>=` operation
+ --> src/mdtest_snippet.py:17:13
+ |
+15 | reveal_type(a > b) # revealed: Unknown
+16 | # error: [unsupported-operator] "Operator `>=` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Lit…
+17 | reveal_type(a >= b) # revealed: Unknown
+ | -^^^^-
+ | | |
+ | | Has type `tuple[Literal[1], Literal["hello"]]`
+ | Has type `tuple[Literal[1], Literal[2]]`
+18 | # error: [unsupported-operator]
+19 | # error: [unsupported-operator]
+ |
+info: Operation fails because operator `>=` is not supported between the tuple elements at index 2 (of type `Literal[2]` and `Literal["hello"]`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:20:13
+ |
+18 | # error: [unsupported-operator]
+19 | # error: [unsupported-operator]
+20 | reveal_type((object(),) < (object(),) < (object(),)) # revealed: Unknown
+ | -----------^^^-----------
+ | |
+ | Both operands have type `tuple[object]`
+21 | a = (1, 2)
+22 | b = (999999, "hello")
+ |
+info: Operation fails because operator `<` is not supported between the tuple elements at index 1 (both of type `object`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:20:27
+ |
+18 | # error: [unsupported-operator]
+19 | # error: [unsupported-operator]
+20 | reveal_type((object(),) < (object(),) < (object(),)) # revealed: Unknown
+ | -----------^^^-----------
+ | |
+ | Both operands have type `tuple[object]`
+21 | a = (1, 2)
+22 | b = (999999, "hello")
+ |
+info: Operation fails because operator `<` is not supported between the tuple elements at index 1 (both of type `object`)
+info: rule `unsupported-operator` is enabled by default
+
+```
diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unions.md_-_Comparison___Unions_-_Unsupported_operatio…_(e15acf820f65e3e4).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unions.md_-_Comparison___Unions_-_Unsupported_operatio…_(e15acf820f65e3e4).snap
new file mode 100644
index 0000000000..219bb20795
--- /dev/null
+++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unions.md_-_Comparison___Unions_-_Unsupported_operatio…_(e15acf820f65e3e4).snap
@@ -0,0 +1,123 @@
+---
+source: crates/ty_test/src/lib.rs
+expression: snapshot
+---
+---
+mdtest name: unions.md - Comparison: Unions - Unsupported operations
+mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/unions.md
+---
+
+# Python source files
+
+## mdtest_snippet.py
+
+```
+ 1 | from typing import Literal
+ 2 |
+ 3 | def _(
+ 4 | x: list[int] | Literal[1],
+ 5 | y: list[int] | Literal[1],
+ 6 | aa: tuple[int],
+ 7 | bb: tuple[int] | tuple[int, int],
+ 8 | cc: tuple[str] | tuple[str, str],
+ 9 | ):
+10 | result = 1 in x # error: "Operator `in` is not supported"
+11 | reveal_type(result) # revealed: bool
+12 |
+13 | result2 = y in x # error: [unsupported-operator]
+14 | reveal_type(result) # revealed: bool
+15 |
+16 | result3 = aa < cc # error: [unsupported-operator]
+17 | result4 = cc < aa # error: [unsupported-operator]
+18 | result5 = bb < cc # error: [unsupported-operator]
+```
+
+# Diagnostics
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:10:14
+ |
+ 8 | cc: tuple[str] | tuple[str, str],
+ 9 | ):
+10 | result = 1 in x # error: "Operator `in` is not supported"
+ | -^^^^-
+ | | |
+ | | Has type `list[int] | Literal[1]`
+ | Has type `Literal[1]`
+11 | reveal_type(result) # revealed: bool
+ |
+info: Operation fails because operator `in` is not supported between two objects of type `Literal[1]`
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:13:15
+ |
+11 | reveal_type(result) # revealed: bool
+12 |
+13 | result2 = y in x # error: [unsupported-operator]
+ | -^^^^-
+ | |
+ | Both operands have type `list[int] | Literal[1]`
+14 | reveal_type(result) # revealed: bool
+ |
+info: Operation fails because operator `in` is not supported between objects of type `list[int]` and `Literal[1]`
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:16:15
+ |
+14 | reveal_type(result) # revealed: bool
+15 |
+16 | result3 = aa < cc # error: [unsupported-operator]
+ | --^^^--
+ | | |
+ | | Has type `tuple[str] | tuple[str, str]`
+ | Has type `tuple[int]`
+17 | result4 = cc < aa # error: [unsupported-operator]
+18 | result5 = bb < cc # error: [unsupported-operator]
+ |
+info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:17:15
+ |
+16 | result3 = aa < cc # error: [unsupported-operator]
+17 | result4 = cc < aa # error: [unsupported-operator]
+ | --^^^--
+ | | |
+ | | Has type `tuple[int]`
+ | Has type `tuple[str] | tuple[str, str]`
+18 | result5 = bb < cc # error: [unsupported-operator]
+ |
+info: Operation fails because operator `<` is not supported between objects of type `str` and `int`
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:18:15
+ |
+16 | result3 = aa < cc # error: [unsupported-operator]
+17 | result4 = cc < aa # error: [unsupported-operator]
+18 | result5 = bb < cc # error: [unsupported-operator]
+ | --^^^--
+ | | |
+ | | Has type `tuple[str] | tuple[str, str]`
+ | Has type `tuple[int] | tuple[int, int]`
+ |
+info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
+info: rule `unsupported-operator` is enabled by default
+
+```
diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported.md_-_Comparison___Unsuppor…_(c13dd5902282489a).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported.md_-_Comparison___Unsuppor…_(c13dd5902282489a).snap
new file mode 100644
index 0000000000..c1593bfd0a
--- /dev/null
+++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unsupported.md_-_Comparison___Unsuppor…_(c13dd5902282489a).snap
@@ -0,0 +1,162 @@
+---
+source: crates/ty_test/src/lib.rs
+expression: snapshot
+---
+---
+mdtest name: unsupported.md - Comparison: Unsupported operators
+mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/unsupported.md
+---
+
+# Python source files
+
+## mdtest_snippet.py
+
+```
+ 1 | def _(flag: bool, flag1: bool, flag2: bool):
+ 2 | class A: ...
+ 3 | a = 1 in 7 # error: "Operator `in` is not supported between objects of type `Literal[1]` and `Literal[7]`"
+ 4 | reveal_type(a) # revealed: bool
+ 5 |
+ 6 | b = 0 not in 10 # error: "Operator `not in` is not supported between objects of type `Literal[0]` and `Literal[10]`"
+ 7 | reveal_type(b) # revealed: bool
+ 8 |
+ 9 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `object` and `Literal[5]`"
+10 | c = object() < 5
+11 | reveal_type(c) # revealed: Unknown
+12 |
+13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `Literal[5]` and `object`"
+14 | d = 5 < object()
+15 | reveal_type(d) # revealed: Unknown
+16 |
+17 | int_literal_or_str_literal = 1 if flag else "foo"
+18 | # error: "Operator `in` is not supported between objects of type `Literal[42]` and `Literal[1, "foo"]`"
+19 | e = 42 in int_literal_or_str_literal
+20 | reveal_type(e) # revealed: bool
+21 |
+22 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[Literal[1], Literal["hello"]]`"
+23 | f = (1, 2) < (1, "hello")
+24 | reveal_type(f) # revealed: Unknown
+25 |
+26 | # error: [unsupported-operator] "Operator `<` is not supported between two objects of type `tuple[bool, A]`"
+27 | g = (flag1, A()) < (flag2, A())
+28 | reveal_type(g) # revealed: Unknown
+```
+
+# Diagnostics
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:3:9
+ |
+1 | def _(flag: bool, flag1: bool, flag2: bool):
+2 | class A: ...
+3 | a = 1 in 7 # error: "Operator `in` is not supported between objects of type `Literal[1]` and `Literal[7]`"
+ | -^^^^-
+ | | |
+ | | Has type `Literal[7]`
+ | Has type `Literal[1]`
+4 | reveal_type(a) # revealed: bool
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `not in` operation
+ --> src/mdtest_snippet.py:6:9
+ |
+4 | reveal_type(a) # revealed: bool
+5 |
+6 | b = 0 not in 10 # error: "Operator `not in` is not supported between objects of type `Literal[0]` and `Literal[10]`"
+ | -^^^^^^^^--
+ | | |
+ | | Has type `Literal[10]`
+ | Has type `Literal[0]`
+7 | reveal_type(b) # revealed: bool
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:10:9
+ |
+ 9 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `object` and `Literal[5]`"
+10 | c = object() < 5
+ | --------^^^-
+ | | |
+ | | Has type `Literal[5]`
+ | Has type `object`
+11 | reveal_type(c) # revealed: Unknown
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:14:9
+ |
+13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `Literal[5]` and `object`"
+14 | d = 5 < object()
+ | -^^^--------
+ | | |
+ | | Has type `object`
+ | Has type `Literal[5]`
+15 | reveal_type(d) # revealed: Unknown
+ |
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `in` operation
+ --> src/mdtest_snippet.py:19:9
+ |
+17 | int_literal_or_str_literal = 1 if flag else "foo"
+18 | # error: "Operator `in` is not supported between objects of type `Literal[42]` and `Literal[1, "foo"]`"
+19 | e = 42 in int_literal_or_str_literal
+ | --^^^^--------------------------
+ | | |
+ | | Has type `Literal[1, "foo"]`
+ | Has type `Literal[42]`
+20 | reveal_type(e) # revealed: bool
+ |
+info: Operation fails because operator `in` is not supported between objects of type `Literal[42]` and `Literal[1]`
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:23:9
+ |
+22 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[Literal[1], Literal[2]]` and `tuple[…
+23 | f = (1, 2) < (1, "hello")
+ | ------^^^------------
+ | | |
+ | | Has type `tuple[Literal[1], Literal["hello"]]`
+ | Has type `tuple[Literal[1], Literal[2]]`
+24 | reveal_type(f) # revealed: Unknown
+ |
+info: Operation fails because operator `<` is not supported between the tuple elements at index 2 (of type `Literal[2]` and `Literal["hello"]`)
+info: rule `unsupported-operator` is enabled by default
+
+```
+
+```
+error[unsupported-operator]: Unsupported `<` operation
+ --> src/mdtest_snippet.py:27:9
+ |
+26 | # error: [unsupported-operator] "Operator `<` is not supported between two objects of type `tuple[bool, A]`"
+27 | g = (flag1, A()) < (flag2, A())
+ | ------------^^^------------
+ | |
+ | Both operands have type `tuple[bool, A]`
+28 | reveal_type(g) # revealed: Unknown
+ |
+info: Operation fails because operator `<` is not supported between the tuple elements at index 2 (both of type `A`)
+info: rule `unsupported-operator` is enabled by default
+
+```
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index 5a5c990ab1..8b20466b51 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -18,12 +18,14 @@ use crate::types::class::{
CodeGeneratorKind, DisjointBase, DisjointBaseKind, Field, MethodDecorator,
};
use crate::types::function::{FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral};
+use crate::types::infer::UnsupportedComparisonError;
use crate::types::overrides::MethodKind;
use crate::types::string_annotation::{
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
+use crate::types::tuple::TupleSpec;
use crate::types::{
BoundTypeVarInstance, ClassType, DynamicType, LintDiagnosticGuard, Protocol,
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
@@ -2410,7 +2412,7 @@ pub(super) fn report_invalid_assignment<'db>(
}
let settings =
- DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), target_ty, value_ty);
+ DisplaySettings::from_possibly_ambiguous_types(context.db(), [target_ty, value_ty]);
let diagnostic_range = if let Some(value_node) = value_node {
// Expand the range to include parentheses around the value, if any. This allows
@@ -2548,7 +2550,7 @@ pub(super) fn report_invalid_return_type(
};
let settings =
- DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), expected_ty, actual_ty);
+ DisplaySettings::from_possibly_ambiguous_types(context.db(), [expected_ty, actual_ty]);
let return_type_span = context.span(return_type_range);
let mut diag = builder.into_diagnostic("Return type does not match returned value");
@@ -4054,6 +4056,112 @@ pub(super) fn report_overridden_final_method<'db>(
}
}
+pub(super) fn report_unsupported_comparison<'db>(
+ context: &InferContext<'db, '_>,
+ error: &UnsupportedComparisonError<'db>,
+ range: TextRange,
+ left: &ast::Expr,
+ right: &ast::Expr,
+ left_ty: Type<'db>,
+ right_ty: Type<'db>,
+) {
+ let db = context.db();
+
+ let Some(diagnostic_builder) = context.report_lint(&UNSUPPORTED_OPERATOR, range) else {
+ return;
+ };
+
+ let display_settings = DisplaySettings::from_possibly_ambiguous_types(
+ db,
+ [error.left_ty, error.right_ty, left_ty, right_ty],
+ );
+
+ let mut diagnostic =
+ diagnostic_builder.into_diagnostic(format_args!("Unsupported `{}` operation", error.op));
+
+ if left_ty == right_ty {
+ diagnostic.set_primary_message(format_args!(
+ "Both operands have type `{}`",
+ left_ty.display_with(db, display_settings.clone())
+ ));
+ diagnostic.annotate(context.secondary(left));
+ diagnostic.annotate(context.secondary(right));
+ diagnostic.set_concise_message(format_args!(
+ "Operator `{}` is not supported between two objects of type `{}`",
+ error.op,
+ left_ty.display_with(db, display_settings.clone())
+ ));
+ } else {
+ for (ty, expr) in [(left_ty, left), (right_ty, right)] {
+ diagnostic.annotate(context.secondary(expr).message(format_args!(
+ "Has type `{}`",
+ ty.display_with(db, display_settings.clone())
+ )));
+ }
+ diagnostic.set_concise_message(format_args!(
+ "Operator `{}` is not supported between objects of type `{}` and `{}`",
+ error.op,
+ left_ty.display_with(db, display_settings.clone()),
+ right_ty.display_with(db, display_settings.clone())
+ ));
+ }
+
+ // For non-atomic types like unions and tuples, we now provide context
+ // on the underlying elements that caused the error.
+ // If we're emitting a diagnostic for something like `(1, "foo") < (2, 3)`:
+ //
+ // - `left_ty` is `tuple[Literal[1], Literal["foo"]]`
+ // - `right_ty` is `tuple[Literal[2], Literal[3]]
+ // - `error.left_ty` is `Literal["foo"]`
+ // - `error.right_ty` is `Literal[3]`
+ if (error.left_ty, error.right_ty) != (left_ty, right_ty) {
+ if let Some(TupleSpec::Fixed(lhs_spec)) = left_ty.tuple_instance_spec(db).as_deref()
+ && let Some(TupleSpec::Fixed(rhs_spec)) = right_ty.tuple_instance_spec(db).as_deref()
+ && lhs_spec.len() == rhs_spec.len()
+ && let Some(position) = lhs_spec
+ .elements()
+ .zip(rhs_spec.elements())
+ .position(|tup| tup == (&error.left_ty, &error.right_ty))
+ {
+ if error.left_ty == error.right_ty {
+ diagnostic.info(format_args!(
+ "Operation fails because operator `{}` is not supported between \
+ the tuple elements at index {} (both of type `{}`)",
+ error.op,
+ position + 1,
+ error.left_ty.display_with(db, display_settings),
+ ));
+ } else {
+ diagnostic.info(format_args!(
+ "Operation fails because operator `{}` is not supported between \
+ the tuple elements at index {} (of type `{}` and `{}`)",
+ error.op,
+ position + 1,
+ error.left_ty.display_with(db, display_settings.clone()),
+ error.right_ty.display_with(db, display_settings),
+ ));
+ }
+ } else {
+ if error.left_ty == error.right_ty {
+ diagnostic.info(format_args!(
+ "Operation fails because operator `{}` is not supported \
+ between two objects of type `{}`",
+ error.op,
+ error.left_ty.display_with(db, display_settings),
+ ));
+ } else {
+ diagnostic.info(format_args!(
+ "Operation fails because operator `{}` is not supported \
+ between objects of type `{}` and `{}`",
+ error.op,
+ error.left_ty.display_with(db, display_settings.clone()),
+ error.right_ty.display_with(db, display_settings)
+ ));
+ }
+ }
+ }
+}
+
/// This function receives an unresolved `from foo import bar` import,
/// where `foo` can be resolved to a module but that module does not
/// have a `bar` member or submodule.
diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs
index e248845034..cadd54eef1 100644
--- a/crates/ty_python_semantic/src/types/display.rs
+++ b/crates/ty_python_semantic/src/types/display.rs
@@ -72,14 +72,15 @@ impl<'db> DisplaySettings<'db> {
}
#[must_use]
- pub fn from_possibly_ambiguous_type_pair(
+ pub fn from_possibly_ambiguous_types(
db: &'db dyn Db,
- type_1: Type<'db>,
- type_2: Type<'db>,
+ types: impl IntoIterator- >,
) -> Self {
let collector = AmbiguousClassCollector::default();
- collector.visit_type(db, type_1);
- collector.visit_type(db, type_2);
+
+ for ty in types {
+ collector.visit_type(db, ty);
+ }
Self {
qualified: Rc::new(
diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs
index fe39deb36a..b9adc93eb2 100644
--- a/crates/ty_python_semantic/src/types/infer.rs
+++ b/crates/ty_python_semantic/src/types/infer.rs
@@ -57,6 +57,7 @@ use crate::types::{
};
use crate::unpack::Unpack;
use builder::TypeInferenceBuilder;
+pub(super) use builder::UnsupportedComparisonError;
mod builder;
#[cfg(test)]
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index f5870c9b08..d3a65e88b2 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -79,7 +79,7 @@ use crate::types::diagnostic::{
report_invalid_type_checking_constant, report_named_tuple_field_with_leading_underscore,
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
report_possibly_missing_attribute, report_possibly_unresolved_reference,
- report_rebound_typevar, report_slice_step_size_zero,
+ report_rebound_typevar, report_slice_step_size_zero, report_unsupported_comparison,
};
use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
@@ -158,7 +158,7 @@ impl<'db> DeclaredAndInferredType<'db> {
type BinaryComparisonVisitor<'db> = CycleDetector<
ast::CmpOp,
(Type<'db>, ast::CmpOp, Type<'db>),
- Result, CompareUnsupportedError<'db>>,
+ Result, UnsupportedComparisonError<'db>>,
>;
/// We currently store one dataclass field-specifiers inline, because that covers standard
@@ -10056,26 +10056,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
&BinaryComparisonVisitor::new(Ok(Type::BooleanLiteral(true))),
)
.unwrap_or_else(|error| {
- if let Some(diagnostic_builder) =
- builder.context.report_lint(&UNSUPPORTED_OPERATOR, range)
- {
- // Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
- diagnostic_builder.into_diagnostic(format_args!(
- "Operator `{}` is not supported for types `{}` and `{}`{}",
- error.op,
- error.left_ty.display(builder.db()),
- error.right_ty.display(builder.db()),
- if (left_ty, right_ty) == (error.left_ty, error.right_ty) {
- String::new()
- } else {
- format!(
- ", in comparing `{}` with `{}`",
- left_ty.display(builder.db()),
- right_ty.display(builder.db())
- )
- }
- ));
- }
+ report_unsupported_comparison(
+ &builder.context,
+ &error,
+ range,
+ left,
+ right,
+ left_ty,
+ right_ty,
+ );
match op {
// `in, not in, is, is not` always return bool instances
@@ -10101,13 +10090,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
intersection_on: IntersectionOn,
range: TextRange,
visitor: &BinaryComparisonVisitor<'db>,
- ) -> Result, CompareUnsupportedError<'db>> {
+ ) -> Result, UnsupportedComparisonError<'db>> {
enum State<'db> {
// We have not seen any positive elements (yet)
NoPositiveElements,
// The operator was unsupported on all elements that we have seen so far.
// Contains the first error we encountered.
- UnsupportedOnAllElements(CompareUnsupportedError<'db>),
+ UnsupportedOnAllElements(UnsupportedComparisonError<'db>),
// The operator was supported on at least one positive element.
Supported,
}
@@ -10263,7 +10252,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right: Type<'db>,
range: TextRange,
visitor: &BinaryComparisonVisitor<'db>,
- ) -> Result, CompareUnsupportedError<'db>> {
+ ) -> Result, UnsupportedComparisonError<'db>> {
// Note: identity (is, is not) for equal builtin types is unreliable and not part of the
// language spec.
// - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
@@ -10335,7 +10324,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
IntersectionOn::Left,
range,
visitor,
- ))
+ ).map_err(|err|UnsupportedComparisonError { op, left_ty: left, right_ty: err.right_ty }))
}
(left, Type::Intersection(intersection)) => {
Some(self.infer_binary_intersection_type_comparison(
@@ -10345,7 +10334,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
IntersectionOn::Right,
range,
visitor,
- ))
+ ).map_err(|err|UnsupportedComparisonError { op, left_ty: err.left_ty, right_ty: right }))
}
(Type::TypeAlias(alias), right) => Some(
@@ -10392,7 +10381,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// Undefined for (int, int)
- ast::CmpOp::In | ast::CmpOp::NotIn => Err(CompareUnsupportedError {
+ ast::CmpOp::In | ast::CmpOp::NotIn => Err(UnsupportedComparisonError {
op,
left_ty: left,
right_ty: right,
@@ -10405,7 +10394,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right,
range,
visitor,
- ))
+ ).map_err(|_| UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
}
(Type::NominalInstance(_), Type::IntLiteral(_)) => {
Some(self.infer_binary_type_comparison(
@@ -10414,7 +10403,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
KnownClass::Int.to_instance(self.db()),
range,
visitor,
- ))
+ ).map_err(|_|UnsupportedComparisonError { op, left_ty: left, right_ty: right }))
}
// Booleans are coded as integers (False = 0, True = 1)
@@ -10425,7 +10414,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::IntLiteral(i64::from(b)),
range,
visitor,
- ))
+ ).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
}
(Type::BooleanLiteral(b), Type::IntLiteral(m)) => {
Some(self.infer_binary_type_comparison(
@@ -10434,7 +10423,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::IntLiteral(m),
range,
visitor,
- ))
+ ).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
}
(Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => {
Some(self.infer_binary_type_comparison(
@@ -10443,37 +10432,37 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::IntLiteral(i64::from(b)),
range,
visitor,
- ))
+ ).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
}
(Type::StringLiteral(salsa_s1), Type::StringLiteral(salsa_s2)) => {
let s1 = salsa_s1.value(self.db());
let s2 = salsa_s2.value(self.db());
let result = match op {
- ast::CmpOp::Eq => Ok(Type::BooleanLiteral(s1 == s2)),
- ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(s1 != s2)),
- ast::CmpOp::Lt => Ok(Type::BooleanLiteral(s1 < s2)),
- ast::CmpOp::LtE => Ok(Type::BooleanLiteral(s1 <= s2)),
- ast::CmpOp::Gt => Ok(Type::BooleanLiteral(s1 > s2)),
- ast::CmpOp::GtE => Ok(Type::BooleanLiteral(s1 >= s2)),
- ast::CmpOp::In => Ok(Type::BooleanLiteral(s2.contains(s1))),
- ast::CmpOp::NotIn => Ok(Type::BooleanLiteral(!s2.contains(s1))),
+ ast::CmpOp::Eq => Type::BooleanLiteral(s1 == s2),
+ ast::CmpOp::NotEq => Type::BooleanLiteral(s1 != s2),
+ ast::CmpOp::Lt => Type::BooleanLiteral(s1 < s2),
+ ast::CmpOp::LtE => Type::BooleanLiteral(s1 <= s2),
+ ast::CmpOp::Gt => Type::BooleanLiteral(s1 > s2),
+ ast::CmpOp::GtE => Type::BooleanLiteral(s1 >= s2),
+ ast::CmpOp::In => Type::BooleanLiteral(s2.contains(s1)),
+ ast::CmpOp::NotIn => Type::BooleanLiteral(!s2.contains(s1)),
ast::CmpOp::Is => {
if s1 == s2 {
- Ok(KnownClass::Bool.to_instance(self.db()))
+ KnownClass::Bool.to_instance(self.db())
} else {
- Ok(Type::BooleanLiteral(false))
+ Type::BooleanLiteral(false)
}
}
ast::CmpOp::IsNot => {
if s1 == s2 {
- Ok(KnownClass::Bool.to_instance(self.db()))
+ KnownClass::Bool.to_instance(self.db())
} else {
- Ok(Type::BooleanLiteral(true))
+ Type::BooleanLiteral(true)
}
}
};
- Some(result)
+ Some(Ok(result))
}
(Type::StringLiteral(_), _) => Some(self.infer_binary_type_comparison(
KnownClass::Str.to_instance(self.db()),
@@ -10481,14 +10470,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right,
range,
visitor,
- )),
+ ).map_err(|err|UnsupportedComparisonError {op, left_ty: left, right_ty: err.right_ty})),
(_, Type::StringLiteral(_)) => Some(self.infer_binary_type_comparison(
left,
op,
KnownClass::Str.to_instance(self.db()),
range,
visitor,
- )),
+ ).map_err(|err|UnsupportedComparisonError {op, left_ty: err.left_ty, right_ty: right})),
(Type::LiteralString, _) => Some(self.infer_binary_type_comparison(
KnownClass::Str.to_instance(self.db()),
@@ -10496,47 +10485,47 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right,
range,
visitor,
- )),
+ ).map_err(|err|UnsupportedComparisonError {op, left_ty: left, right_ty: err.right_ty})),
(_, Type::LiteralString) => Some(self.infer_binary_type_comparison(
left,
op,
KnownClass::Str.to_instance(self.db()),
range,
visitor,
- )),
+ ).map_err(|err|UnsupportedComparisonError {op, left_ty: err.left_ty, right_ty: right})),
(Type::BytesLiteral(salsa_b1), Type::BytesLiteral(salsa_b2)) => {
let b1 = salsa_b1.value(self.db());
let b2 = salsa_b2.value(self.db());
let result = match op {
- ast::CmpOp::Eq => Ok(Type::BooleanLiteral(b1 == b2)),
- ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(b1 != b2)),
- ast::CmpOp::Lt => Ok(Type::BooleanLiteral(b1 < b2)),
- ast::CmpOp::LtE => Ok(Type::BooleanLiteral(b1 <= b2)),
- ast::CmpOp::Gt => Ok(Type::BooleanLiteral(b1 > b2)),
- ast::CmpOp::GtE => Ok(Type::BooleanLiteral(b1 >= b2)),
+ ast::CmpOp::Eq => Type::BooleanLiteral(b1 == b2),
+ ast::CmpOp::NotEq => Type::BooleanLiteral(b1 != b2),
+ ast::CmpOp::Lt => Type::BooleanLiteral(b1 < b2),
+ ast::CmpOp::LtE => Type::BooleanLiteral(b1 <= b2),
+ ast::CmpOp::Gt => Type::BooleanLiteral(b1 > b2),
+ ast::CmpOp::GtE => Type::BooleanLiteral(b1 >= b2),
ast::CmpOp::In => {
- Ok(Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_some()))
+ Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_some())
}
ast::CmpOp::NotIn => {
- Ok(Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_none()))
+ Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_none())
}
ast::CmpOp::Is => {
if b1 == b2 {
- Ok(KnownClass::Bool.to_instance(self.db()))
+ KnownClass::Bool.to_instance(self.db())
} else {
- Ok(Type::BooleanLiteral(false))
+ Type::BooleanLiteral(false)
}
}
ast::CmpOp::IsNot => {
if b1 == b2 {
- Ok(KnownClass::Bool.to_instance(self.db()))
+ KnownClass::Bool.to_instance(self.db())
} else {
- Ok(Type::BooleanLiteral(true))
+ Type::BooleanLiteral(true)
}
}
};
- Some(result)
+ Some(Ok(result))
}
(Type::BytesLiteral(_), _) => Some(self.infer_binary_type_comparison(
KnownClass::Bytes.to_instance(self.db()),
@@ -10544,14 +10533,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right,
range,
visitor,
- )),
+ ).map_err(|err| UnsupportedComparisonError { op, left_ty: left, right_ty: err.right_ty })),
(_, Type::BytesLiteral(_)) => Some(self.infer_binary_type_comparison(
left,
op,
KnownClass::Bytes.to_instance(self.db()),
range,
visitor,
- )),
+ ).map_err(|err| UnsupportedComparisonError { op, left_ty: err.left_ty, right_ty: right })),
(Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2))
if op == ast::CmpOp::Eq =>
@@ -10683,7 +10672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right: Type<'db>,
op: RichCompareOperator,
policy: MemberLookupPolicy,
- ) -> Result, CompareUnsupportedError<'db>> {
+ ) -> Result, UnsupportedComparisonError<'db>> {
let db = self.db();
// The following resource has details about the rich comparison algorithm:
// https://snarky.ca/unravelling-rich-comparison-operators/
@@ -10719,7 +10708,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
None
}
})
- .ok_or_else(|| CompareUnsupportedError {
+ .ok_or_else(|| UnsupportedComparisonError {
op: op.into(),
left_ty: left,
right_ty: right,
@@ -10736,7 +10725,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right: Type<'db>,
op: MembershipTestCompareOperator,
range: TextRange,
- ) -> Result, CompareUnsupportedError<'db>> {
+ ) -> Result, UnsupportedComparisonError<'db>> {
let db = self.db();
let contains_dunder = right.class_member(db, "__contains__".into()).place;
@@ -10773,7 +10762,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
MembershipTestCompareOperator::NotIn => truthiness.negate().into_type(db),
}
})
- .ok_or_else(|| CompareUnsupportedError {
+ .ok_or_else(|| UnsupportedComparisonError {
op: op.into(),
left_ty: left,
right_ty: right,
@@ -10792,7 +10781,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
right: &TupleSpec<'db>,
range: TextRange,
visitor: &BinaryComparisonVisitor<'db>,
- ) -> Result, CompareUnsupportedError<'db>> {
+ ) -> Result, UnsupportedComparisonError<'db>> {
// If either tuple is variable length, we can make no assumptions about the relative
// lengths of the tuples, and therefore neither about how they compare lexicographically.
// TODO: Consider comparing the prefixes of the tuples, since that could give a comparison
@@ -12382,11 +12371,22 @@ impl From for ast::CmpOp {
}
}
+/// Context for a failed comparison operation.
+///
+/// `left_ty` and `right_ty` are the "low-level" types
+/// that cannot be compared using `op`. For example,
+/// when evaluating `(1, "foo") < (2, 3)`, the "high-level"
+/// types of the operands are `tuple[Literal[1], Literal["foo"]]`
+/// and `tuple[Literal[2], Literal[3]]`. Those aren't captured
+/// in this struct, but the "low-level" types that mean that
+/// the high-level types cannot be compared *are* captured in
+/// this struct. In this case, those would be `Literal["foo"]`
+/// and `Literal[3]`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-struct CompareUnsupportedError<'db> {
- op: ast::CmpOp,
- left_ty: Type<'db>,
- right_ty: Type<'db>,
+pub(crate) struct UnsupportedComparisonError<'db> {
+ pub(crate) op: ast::CmpOp,
+ pub(crate) left_ty: Type<'db>,
+ pub(crate) right_ty: Type<'db>,
}
fn format_import_from_module(level: u32, module: Option<&str>) -> String {