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 {