Merge remote-tracking branch 'origin/main' into dcreager/gradual-bounds

* origin/main:
  [ty] disallow explicit specialization of type variables themselves (#21938)
  [ty] Improve diagnostics for unsupported binary operations and unsupported augmented assignments (#21947)
  [ty] update implicit root docs (#21955)
This commit is contained in:
Douglas Creager 2025-12-12 21:39:17 -05:00
commit 431a9d31d7
24 changed files with 510 additions and 119 deletions

View File

@ -158,7 +158,7 @@ If left unspecified, ty will try to detect common project layouts and initialize
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
* otherwise, default to `.` (flat layout) * otherwise, default to `.` (flat layout)
Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file), Additionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
it will also be included in the first party search path. it will also be included in the first party search path.
**Default value**: `null` **Default value**: `null`
@ -443,7 +443,7 @@ If left unspecified, ty will try to detect common project layouts and initialize
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
* otherwise, default to `.` (flat layout) * otherwise, default to `.` (flat layout)
Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), Additionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
it will also be included in the first party search path. it will also be included in the first party search path.
**Default value**: `null` **Default value**: `null`

View File

@ -516,7 +516,7 @@ pub struct EnvironmentOptions {
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path /// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
/// * otherwise, default to `.` (flat layout) /// * otherwise, default to `.` (flat layout)
/// ///
/// Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file), /// Additionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
/// it will also be included in the first party search path. /// it will also be included in the first party search path.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[option( #[option(
@ -658,7 +658,7 @@ pub struct SrcOptions {
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path /// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
/// * otherwise, default to `.` (flat layout) /// * otherwise, default to `.` (flat layout)
/// ///
/// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), /// Additionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
/// it will also be included in the first party search path. /// it will also be included in the first party search path.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[option( #[option(

View File

@ -0,0 +1,7 @@
from typing import TypeAlias, TypeVar
T = TypeVar("T", bound="A[0]")
A: TypeAlias = T
def _(x: A):
if x:
pass

View File

@ -0,0 +1,3 @@
def _[T: T[0]](x: T):
if x:
pass

View File

@ -38,6 +38,8 @@ reveal_type(x) # revealed: int
## Unsupported types ## Unsupported types
<!-- snapshot-diagnostics -->
```py ```py
class C: class C:
def __isub__(self, other: str) -> int: def __isub__(self, other: str) -> int:

View File

@ -79,31 +79,31 @@ reveal_type(Sub() & Sub()) # revealed: Literal["&"]
reveal_type(Sub() // Sub()) # revealed: Literal["//"] reveal_type(Sub() // Sub()) # revealed: Literal["//"]
# No does not implement any of the dunder methods. # No does not implement any of the dunder methods.
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `No`"
reveal_type(No() + No()) # revealed: Unknown reveal_type(No() + No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `-` is not supported between two objects of type `No`"
reveal_type(No() - No()) # revealed: Unknown reveal_type(No() - No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `*` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `*` is not supported between two objects of type `No`"
reveal_type(No() * No()) # revealed: Unknown reveal_type(No() * No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `@` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `@` is not supported between two objects of type `No`"
reveal_type(No() @ No()) # revealed: Unknown reveal_type(No() @ No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `/` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `/` is not supported between two objects of type `No`"
reveal_type(No() / No()) # revealed: Unknown reveal_type(No() / No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `%` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `%` is not supported between two objects of type `No`"
reveal_type(No() % No()) # revealed: Unknown reveal_type(No() % No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `**` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `**` is not supported between two objects of type `No`"
reveal_type(No() ** No()) # revealed: Unknown reveal_type(No() ** No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `<<` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `<<` is not supported between two objects of type `No`"
reveal_type(No() << No()) # revealed: Unknown reveal_type(No() << No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `>>` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `>>` is not supported between two objects of type `No`"
reveal_type(No() >> No()) # revealed: Unknown reveal_type(No() >> No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `|` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `|` is not supported between two objects of type `No`"
reveal_type(No() | No()) # revealed: Unknown reveal_type(No() | No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `^` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `^` is not supported between two objects of type `No`"
reveal_type(No() ^ No()) # revealed: Unknown reveal_type(No() ^ No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `&` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `&` is not supported between two objects of type `No`"
reveal_type(No() & No()) # revealed: Unknown reveal_type(No() & No()) # revealed: Unknown
# error: [unsupported-operator] "Operator `//` is not supported between objects of type `No` and `No`" # error: [unsupported-operator] "Operator `//` is not supported between two objects of type `No`"
reveal_type(No() // No()) # revealed: Unknown reveal_type(No() // No()) # revealed: Unknown
# Yes does not implement any of the reflected dunder methods. # Yes does not implement any of the reflected dunder methods.
@ -293,6 +293,8 @@ reveal_type(Yes() // No()) # revealed: Literal["//"]
## Classes ## Classes
<!-- snapshot-diagnostics -->
Dunder methods defined in a class are available to instances of that class, but not to the class Dunder methods defined in a class are available to instances of that class, but not to the class
itself. (For these operators to work on the class itself, they would have to be defined on the itself. (For these operators to work on the class itself, they would have to be defined on the
class's type, i.e. `type`.) class's type, i.e. `type`.)
@ -307,11 +309,11 @@ class Yes:
class Sub(Yes): ... class Sub(Yes): ...
class No: ... class No: ...
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `<class 'Yes'>` and `<class 'Yes'>`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Yes'>`"
reveal_type(Yes + Yes) # revealed: Unknown reveal_type(Yes + Yes) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `<class 'Sub'>` and `<class 'Sub'>`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
reveal_type(Sub + Sub) # revealed: Unknown reveal_type(Sub + Sub) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `<class 'No'>` and `<class 'No'>`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'No'>`"
reveal_type(No + No) # revealed: Unknown reveal_type(No + No) # revealed: Unknown
``` ```
@ -336,11 +338,11 @@ def sub() -> type[Sub]:
def no() -> type[No]: def no() -> type[No]:
return No return No
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `type[Yes]` and `type[Yes]`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Yes]`"
reveal_type(yes() + yes()) # revealed: Unknown reveal_type(yes() + yes()) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `type[Sub]` and `type[Sub]`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Sub]`"
reveal_type(sub() + sub()) # revealed: Unknown reveal_type(sub() + sub()) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `type[No]` and `type[No]`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[No]`"
reveal_type(no() + no()) # revealed: Unknown reveal_type(no() + no()) # revealed: Unknown
``` ```
@ -350,30 +352,54 @@ reveal_type(no() + no()) # revealed: Unknown
def f(): def f():
pass pass
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f + f) # revealed: Unknown reveal_type(f + f) # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `-` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f - f) # revealed: Unknown reveal_type(f - f) # revealed: Unknown
# error: [unsupported-operator] "Operator `*` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `*` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f * f) # revealed: Unknown reveal_type(f * f) # revealed: Unknown
# error: [unsupported-operator] "Operator `@` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `@` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f @ f) # revealed: Unknown reveal_type(f @ f) # revealed: Unknown
# error: [unsupported-operator] "Operator `/` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `/` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f / f) # revealed: Unknown reveal_type(f / f) # revealed: Unknown
# error: [unsupported-operator] "Operator `%` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `%` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f % f) # revealed: Unknown reveal_type(f % f) # revealed: Unknown
# error: [unsupported-operator] "Operator `**` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `**` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f**f) # revealed: Unknown reveal_type(f**f) # revealed: Unknown
# error: [unsupported-operator] "Operator `<<` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `<<` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f << f) # revealed: Unknown reveal_type(f << f) # revealed: Unknown
# error: [unsupported-operator] "Operator `>>` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `>>` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f >> f) # revealed: Unknown reveal_type(f >> f) # revealed: Unknown
# error: [unsupported-operator] "Operator `|` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `|` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f | f) # revealed: Unknown reveal_type(f | f) # revealed: Unknown
# error: [unsupported-operator] "Operator `^` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `^` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f ^ f) # revealed: Unknown reveal_type(f ^ f) # revealed: Unknown
# error: [unsupported-operator] "Operator `&` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `&` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f & f) # revealed: Unknown reveal_type(f & f) # revealed: Unknown
# error: [unsupported-operator] "Operator `//` is not supported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" # error: [unsupported-operator] "Operator `//` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f // f) # revealed: Unknown reveal_type(f // f) # revealed: Unknown
``` ```
## Classes from different modules with the same name
We use the fully qualified names in diagnostics if the two classes have the same unqualified name,
but are nonetheless different.
<!-- snapshot-diagnostics -->
`mod1.py`:
```py
class A: ...
```
`mod2.py`:
```py
import mod1
class A: ...
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `mod2.A` and `mod1.A`"
A() + mod1.A()
```

View File

@ -412,7 +412,7 @@ class A:
def __init__(self): def __init__(self):
self.__add__ = add_impl self.__add__ = add_impl
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `A` and `A`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `A`"
# revealed: Unknown # revealed: Unknown
reveal_type(A() + A()) reveal_type(A() + A())
``` ```

View File

@ -18,7 +18,7 @@ cannot be added, because that would require addition of `int` and `str` or vice
def f2(i: int, s: str, int_or_str: int | str): def f2(i: int, s: str, int_or_str: int | str):
i + i i + i
s + s s + s
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `int | str` and `int | str`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `int | str`"
reveal_type(int_or_str + int_or_str) # revealed: Unknown reveal_type(int_or_str + int_or_str) # revealed: Unknown
``` ```

View File

@ -277,7 +277,7 @@ T = TypeVar("T", int, str)
def same_constrained_types(t1: T, t2: T) -> T: def same_constrained_types(t1: T, t2: T) -> T:
# TODO: no error # TODO: no error
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `T@same_constrained_types`"
return t1 + t2 return t1 + t2
``` ```
@ -287,7 +287,7 @@ and an `int` and a `str` cannot be added together:
```py ```py
def unions_are_different(t1: int | str, t2: int | str) -> int | str: def unions_are_different(t1: int | str, t2: int | str) -> int | str:
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `int | str` and `int | str`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `int | str`"
return t1 + t2 return t1 + t2
``` ```

View File

@ -104,6 +104,34 @@ S = TypeVar("S", **{"bound": int})
reveal_type(S) # revealed: TypeVar reveal_type(S) # revealed: TypeVar
``` ```
### No explicit specialization
A type variable itself cannot be explicitly specialized; the result of the specialization is
`Unknown`. However, generic PEP 613 type aliases that point to type variables can be explicitly
specialized.
```py
from typing import TypeVar, TypeAlias
T = TypeVar("T")
ImplicitPositive = T
Positive: TypeAlias = T
def _(
# error: [invalid-type-form] "A type variable itself cannot be specialized"
a: T[int],
# error: [invalid-type-form] "A type variable itself cannot be specialized"
b: T[T],
# error: [invalid-type-form] "A type variable itself cannot be specialized"
c: ImplicitPositive[int],
d: Positive[int],
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: int
```
### Type variables with a default ### Type variables with a default
Note that the `__default__` property is only available in Python ≥3.13. Note that the `__default__` property is only available in Python ≥3.13.

View File

@ -246,7 +246,7 @@ methods that are compatible with the return type, so the `return` expression is
```py ```py
def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T: def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T:
# TODO: no error # TODO: no error
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `T@same_constrained_types`"
return t1 + t2 return t1 + t2
``` ```
@ -256,7 +256,7 @@ and an `int` and a `str` cannot be added together:
```py ```py
def unions_are_different(t1: int | str, t2: int | str) -> int | str: def unions_are_different(t1: int | str, t2: int | str) -> int | str:
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `int | str` and `int | str`" # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `int | str`"
return t1 + t2 return t1 + t2
``` ```

View File

@ -98,6 +98,26 @@ def f[T: (int,)]():
pass pass
``` ```
### No explicit specialization
A type variable itself cannot be explicitly specialized; the result of the specialization is
`Unknown`. However, generic type aliases that point to type variables can be explicitly specialized.
```py
type Positive[T] = T
def _[T](
# error: [invalid-type-form] "A type variable itself cannot be specialized"
a: T[int],
# error: [invalid-type-form] "A type variable itself cannot be specialized"
b: T[T],
c: Positive[int],
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: int
```
## Invalid uses ## Invalid uses
Note that many of the invalid uses of legacy typevars do not apply to PEP 695 typevars, since the Note that many of the invalid uses of legacy typevars do not apply to PEP 695 typevars, since the

View File

@ -214,7 +214,7 @@ def _(int_or_int: IntOrInt, list_of_int_or_list_of_int: ListOfIntOrListOfInt):
`NoneType` has no special or-operator behavior, so this is an error: `NoneType` has no special or-operator behavior, so this is an error:
```py ```py
None | None # error: [unsupported-operator] "Operator `|` is not supported between objects of type `None` and `None`" None | None # error: [unsupported-operator] "Operator `|` is not supported between two objects of type `None`"
``` ```
When constructing something nonsensical like `int | 1`, we emit a diagnostic for the expression When constructing something nonsensical like `int | 1`, we emit a diagnostic for the expression
@ -414,6 +414,7 @@ def _(
list_or_tuple_legacy: ListOrTupleLegacy[int], list_or_tuple_legacy: ListOrTupleLegacy[int],
my_callable: MyCallable[[str, bytes], int], my_callable: MyCallable[[str, bytes], int],
annotated_int: AnnotatedType[int], annotated_int: AnnotatedType[int],
# error: [invalid-type-form] "A type variable itself cannot be specialized"
transparent_alias: TransparentAlias[int], transparent_alias: TransparentAlias[int],
optional_int: MyOptional[int], optional_int: MyOptional[int],
): ):
@ -427,7 +428,7 @@ def _(
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...] reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
reveal_type(my_callable) # revealed: (str, bytes, /) -> int reveal_type(my_callable) # revealed: (str, bytes, /) -> int
reveal_type(annotated_int) # revealed: int reveal_type(annotated_int) # revealed: int
reveal_type(transparent_alias) # revealed: int reveal_type(transparent_alias) # revealed: Unknown
reveal_type(optional_int) # revealed: int | None reveal_type(optional_int) # revealed: int | None
``` ```

View File

@ -19,12 +19,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/assignment/annotations.m
# Diagnostics # Diagnostics
``` ```
error[unsupported-operator]: Operator `|` is not supported between objects of type `<class 'int'>` and `<class 'str'>` error[unsupported-operator]: Unsupported `|` operation
--> src/mdtest_snippet.py:2:12 --> src/mdtest_snippet.py:2:12
| |
1 | # error: [unsupported-operator] 1 | # error: [unsupported-operator]
2 | IntOrStr = int | str 2 | IntOrStr = int | str
| ^^^^^^^^^ | ---^^^---
| | |
| | Has type `<class 'str'>`
| Has type `<class 'int'>`
| |
info: Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later info: Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later
info: Python 3.9 was assumed when resolving types because it was specified on the command line info: Python 3.9 was assumed when resolving types because it was specified on the command line

View File

@ -0,0 +1,44 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: augmented.md - Augmented assignment - Unsupported types
mdtest path: crates/ty_python_semantic/resources/mdtest/assignment/augmented.md
---
# Python source files
## mdtest_snippet.py
```
1 | class C:
2 | def __isub__(self, other: str) -> int:
3 | return 42
4 |
5 | x = C()
6 | # error: [unsupported-operator] "Operator `-=` is not supported between objects of type `C` and `Literal[1]`"
7 | x -= 1
8 |
9 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[unsupported-operator]: Unsupported `-=` operation
--> src/mdtest_snippet.py:7:1
|
5 | x = C()
6 | # error: [unsupported-operator] "Operator `-=` is not supported between objects of type `C` and `Literal[1]`"
7 | x -= 1
| -^^^^-
| | |
| | Has type `Literal[1]`
| Has type `C`
8 |
9 | reveal_type(x) # revealed: int
|
info: rule `unsupported-operator` is enabled by default
```

View File

@ -0,0 +1,80 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: custom.md - Custom binary operations - Classes
mdtest path: crates/ty_python_semantic/resources/mdtest/binary/custom.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import Literal
2 |
3 | class Yes:
4 | def __add__(self, other) -> Literal["+"]:
5 | return "+"
6 |
7 | class Sub(Yes): ...
8 | class No: ...
9 |
10 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Yes'>`"
11 | reveal_type(Yes + Yes) # revealed: Unknown
12 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
13 | reveal_type(Sub + Sub) # revealed: Unknown
14 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'No'>`"
15 | reveal_type(No + No) # revealed: Unknown
```
# Diagnostics
```
error[unsupported-operator]: Unsupported `+` operation
--> src/mdtest_snippet.py:11:13
|
10 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Yes'>`"
11 | reveal_type(Yes + Yes) # revealed: Unknown
| ---^^^---
| |
| Both operands have type `<class 'Yes'>`
12 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
13 | reveal_type(Sub + Sub) # revealed: Unknown
|
info: rule `unsupported-operator` is enabled by default
```
```
error[unsupported-operator]: Unsupported `+` operation
--> src/mdtest_snippet.py:13:13
|
11 | reveal_type(Yes + Yes) # revealed: Unknown
12 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
13 | reveal_type(Sub + Sub) # revealed: Unknown
| ---^^^---
| |
| Both operands have type `<class 'Sub'>`
14 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'No'>`"
15 | reveal_type(No + No) # revealed: Unknown
|
info: rule `unsupported-operator` is enabled by default
```
```
error[unsupported-operator]: Unsupported `+` operation
--> src/mdtest_snippet.py:15:13
|
13 | reveal_type(Sub + Sub) # revealed: Unknown
14 | # error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'No'>`"
15 | reveal_type(No + No) # revealed: Unknown
| --^^^--
| |
| Both operands have type `<class 'No'>`
|
info: rule `unsupported-operator` is enabled by default
```

View File

@ -0,0 +1,44 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: custom.md - Custom binary operations - Classes from different modules with the same name
mdtest path: crates/ty_python_semantic/resources/mdtest/binary/custom.md
---
# Python source files
## mod1.py
```
1 | class A: ...
```
## mod2.py
```
1 | import mod1
2 |
3 | class A: ...
4 |
5 | # error: [unsupported-operator] "Operator `+` is not supported between objects of type `mod2.A` and `mod1.A`"
6 | A() + mod1.A()
```
# Diagnostics
```
error[unsupported-operator]: Unsupported `+` operation
--> src/mod2.py:6:1
|
5 | # error: [unsupported-operator] "Operator `+` is not supported between objects of type `mod2.A` and `mod1.A`"
6 | A() + mod1.A()
| ---^^^--------
| | |
| | Has type `mod1.A`
| Has type `mod2.A`
|
info: rule `unsupported-operator` is enabled by default
```

View File

@ -24,11 +24,11 @@ reveal_type(+Sub()) # revealed: bool
reveal_type(-Sub()) # revealed: str reveal_type(-Sub()) # revealed: str
reveal_type(~Sub()) # revealed: int reveal_type(~Sub()) # revealed: int
# error: [unsupported-operator] "Unary operator `+` is not supported for type `No`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `No`"
reveal_type(+No()) # revealed: Unknown reveal_type(+No()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `No`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `No`"
reveal_type(-No()) # revealed: Unknown reveal_type(-No()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `No`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `No`"
reveal_type(~No()) # revealed: Unknown reveal_type(~No()) # revealed: Unknown
``` ```
@ -52,25 +52,25 @@ class Yes:
class Sub(Yes): ... class Sub(Yes): ...
class No: ... class No: ...
# error: [unsupported-operator] "Unary operator `+` is not supported for type `<class 'Yes'>`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `<class 'Yes'>`"
reveal_type(+Yes) # revealed: Unknown reveal_type(+Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `<class 'Yes'>`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `<class 'Yes'>`"
reveal_type(-Yes) # revealed: Unknown reveal_type(-Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `<class 'Yes'>`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `<class 'Yes'>`"
reveal_type(~Yes) # revealed: Unknown reveal_type(~Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is not supported for type `<class 'Sub'>`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `<class 'Sub'>`"
reveal_type(+Sub) # revealed: Unknown reveal_type(+Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `<class 'Sub'>`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `<class 'Sub'>`"
reveal_type(-Sub) # revealed: Unknown reveal_type(-Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `<class 'Sub'>`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `<class 'Sub'>`"
reveal_type(~Sub) # revealed: Unknown reveal_type(~Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `<class 'No'>`"
reveal_type(+No) # revealed: Unknown reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `<class 'No'>`"
reveal_type(-No) # revealed: Unknown reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `<class 'No'>`"
reveal_type(~No) # revealed: Unknown reveal_type(~No) # revealed: Unknown
``` ```
@ -80,11 +80,11 @@ reveal_type(~No) # revealed: Unknown
def f(): def f():
pass pass
# error: [unsupported-operator] "Unary operator `+` is not supported for type `def f() -> Unknown`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `def f() -> Unknown`"
reveal_type(+f) # revealed: Unknown reveal_type(+f) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `def f() -> Unknown`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `def f() -> Unknown`"
reveal_type(-f) # revealed: Unknown reveal_type(-f) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `def f() -> Unknown`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `def f() -> Unknown`"
reveal_type(~f) # revealed: Unknown reveal_type(~f) # revealed: Unknown
``` ```
@ -113,25 +113,25 @@ def sub() -> type[Sub]:
def no() -> type[No]: def no() -> type[No]:
return No return No
# error: [unsupported-operator] "Unary operator `+` is not supported for type `type[Yes]`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `type[Yes]`"
reveal_type(+yes()) # revealed: Unknown reveal_type(+yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `type[Yes]`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `type[Yes]`"
reveal_type(-yes()) # revealed: Unknown reveal_type(-yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `type[Yes]`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `type[Yes]`"
reveal_type(~yes()) # revealed: Unknown reveal_type(~yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is not supported for type `type[Sub]`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `type[Sub]`"
reveal_type(+sub()) # revealed: Unknown reveal_type(+sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `type[Sub]`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `type[Sub]`"
reveal_type(-sub()) # revealed: Unknown reveal_type(-sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `type[Sub]`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `type[Sub]`"
reveal_type(~sub()) # revealed: Unknown reveal_type(~sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is not supported for type `type[No]`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `type[No]`"
reveal_type(+no()) # revealed: Unknown reveal_type(+no()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `type[No]`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `type[No]`"
reveal_type(-no()) # revealed: Unknown reveal_type(-no()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `type[No]`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `type[No]`"
reveal_type(~no()) # revealed: Unknown reveal_type(~no()) # revealed: Unknown
``` ```
@ -160,10 +160,10 @@ reveal_type(+Sub) # revealed: bool
reveal_type(-Sub) # revealed: str reveal_type(-Sub) # revealed: str
reveal_type(~Sub) # revealed: int reveal_type(~Sub) # revealed: int
# error: [unsupported-operator] "Unary operator `+` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `<class 'No'>`"
reveal_type(+No) # revealed: Unknown reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `<class 'No'>`"
reveal_type(-No) # revealed: Unknown reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is not supported for type `<class 'No'>`" # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `<class 'No'>`"
reveal_type(~No) # revealed: Unknown reveal_type(~No) # revealed: Unknown
``` ```

View File

@ -27,7 +27,7 @@ reveal_type(~a) # revealed: Literal[True]
class NoDunder: ... class NoDunder: ...
b = NoDunder() b = NoDunder()
+b # error: [unsupported-operator] "Unary operator `+` is not supported for type `NoDunder`" +b # error: [unsupported-operator] "Unary operator `+` is not supported for object of type `NoDunder`"
-b # error: [unsupported-operator] "Unary operator `-` is not supported for type `NoDunder`" -b # error: [unsupported-operator] "Unary operator `-` is not supported for object of type `NoDunder`"
~b # error: [unsupported-operator] "Unary operator `~` is not supported for type `NoDunder`" ~b # error: [unsupported-operator] "Unary operator `~` is not supported for object of type `NoDunder`"
``` ```

View File

@ -7994,7 +7994,7 @@ impl<'db> Type<'db> {
) { ) {
let matching_typevar = |bound_typevar: &BoundTypeVarInstance<'db>| { let matching_typevar = |bound_typevar: &BoundTypeVarInstance<'db>| {
match bound_typevar.typevar(db).kind(db) { match bound_typevar.typevar(db).kind(db) {
TypeVarKind::Legacy | TypeVarKind::TypingSelf TypeVarKind::Legacy | TypeVarKind::Pep613Alias | TypeVarKind::TypingSelf
if binding_context.is_none_or(|binding_context| { if binding_context.is_none_or(|binding_context| {
bound_typevar.binding_context(db) bound_typevar.binding_context(db)
== BindingContext::Definition(binding_context) == BindingContext::Definition(binding_context)
@ -9472,6 +9472,8 @@ pub enum TypeVarKind {
ParamSpec, ParamSpec,
/// `def foo[**P]() -> None: ...` /// `def foo[**P]() -> None: ...`
Pep695ParamSpec, Pep695ParamSpec,
/// `Alias: typing.TypeAlias = T`
Pep613Alias,
} }
impl TypeVarKind { impl TypeVarKind {

View File

@ -40,7 +40,7 @@ use ruff_db::{
use ruff_diagnostics::{Edit, Fix}; use ruff_diagnostics::{Edit, Fix};
use ruff_python_ast::name::Name; use ruff_python_ast::name::Name;
use ruff_python_ast::token::parentheses_iterator; use ruff_python_ast::token::parentheses_iterator;
use ruff_python_ast::{self as ast, AnyNodeRef, StringFlags}; use ruff_python_ast::{self as ast, AnyNodeRef, PythonVersion, StringFlags};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::fmt::{self, Formatter}; use std::fmt::{self, Formatter};
@ -4155,6 +4155,120 @@ pub(super) fn report_unsupported_comparison<'db>(
} }
} }
pub(super) fn report_unsupported_augmented_assignment<'db>(
context: &InferContext<'db, '_>,
stmt: &ast::StmtAugAssign,
left_ty: Type<'db>,
right_ty: Type<'db>,
) {
report_unsupported_binary_operation_impl(
context,
stmt.range(),
&stmt.target,
&stmt.value,
left_ty,
right_ty,
OperatorDisplay {
operator: stmt.op,
is_augmented_assignment: true,
},
);
}
pub(super) fn report_unsupported_binary_operation<'db>(
context: &InferContext<'db, '_>,
binary_expression: &ast::ExprBinOp,
left_ty: Type<'db>,
right_ty: Type<'db>,
operator: ast::Operator,
) {
let Some(mut diagnostic) = report_unsupported_binary_operation_impl(
context,
binary_expression.range(),
&binary_expression.left,
&binary_expression.right,
left_ty,
right_ty,
OperatorDisplay {
operator,
is_augmented_assignment: false,
},
) else {
return;
};
let db = context.db();
if operator == ast::Operator::BitOr
&& (left_ty.is_subtype_of(db, KnownClass::Type.to_instance(db))
|| right_ty.is_subtype_of(db, KnownClass::Type.to_instance(db)))
&& Program::get(db).python_version(db) < PythonVersion::PY310
{
diagnostic.info(
"Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later",
);
add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving types");
}
}
#[derive(Debug, Copy, Clone)]
struct OperatorDisplay {
operator: ast::Operator,
is_augmented_assignment: bool,
}
impl std::fmt::Display for OperatorDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_augmented_assignment {
write!(f, "{}=", self.operator)
} else {
write!(f, "{}", self.operator)
}
}
}
fn report_unsupported_binary_operation_impl<'a>(
context: &'a InferContext<'a, 'a>,
range: TextRange,
left: &ast::Expr,
right: &ast::Expr,
left_ty: Type<'a>,
right_ty: Type<'a>,
operator: OperatorDisplay,
) -> Option<LintDiagnosticGuard<'a, 'a>> {
let db = context.db();
let diagnostic_builder = context.report_lint(&UNSUPPORTED_OPERATOR, range)?;
let display_settings = DisplaySettings::from_possibly_ambiguous_types(db, [left_ty, right_ty]);
let mut diagnostic =
diagnostic_builder.into_diagnostic(format_args!("Unsupported `{operator}` operation"));
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 `{operator}` is not supported between two objects of type `{}`",
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 `{operator}` is not supported between objects of type `{}` and `{}`",
left_ty.display_with(db, display_settings.clone()),
right_ty.display_with(db, display_settings.clone())
));
}
Some(diagnostic)
}
/// This function receives an unresolved `from foo import bar` import, /// This function receives an unresolved `from foo import bar` import,
/// where `foo` can be resolved to a module but that module does not /// where `foo` can be resolved to a module but that module does not
/// have a `bar` member or submodule. /// have a `bar` member or submodule.

View File

@ -80,7 +80,8 @@ use crate::types::diagnostic::{
report_invalid_type_checking_constant, report_named_tuple_field_with_leading_underscore, 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_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
report_possibly_missing_attribute, report_possibly_unresolved_reference, report_possibly_missing_attribute, report_possibly_unresolved_reference,
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_comparison, report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
report_unsupported_binary_operation, report_unsupported_comparison,
}; };
use crate::types::function::{ use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
@ -5865,6 +5866,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}; };
if is_pep_613_type_alias { if is_pep_613_type_alias {
let inferred_ty =
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = inferred_ty {
let identity = TypeVarIdentity::new(
self.db(),
typevar.identity(self.db()).name(self.db()),
typevar.identity(self.db()).definition(self.db()),
TypeVarKind::Pep613Alias,
);
Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
self.db(),
identity,
typevar._bound_or_constraints(self.db()),
typevar.explicit_variance(self.db()),
typevar._default(self.db()),
)))
} else {
inferred_ty
};
self.add_declaration_with_binding( self.add_declaration_with_binding(
target.into(), target.into(),
definition, definition,
@ -5925,22 +5944,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let op = assignment.op; let op = assignment.op;
let db = self.db(); let db = self.db();
let report_unsupported_augmented_op = |ctx: &mut InferContext| {
let Some(builder) = ctx.report_lint(&UNSUPPORTED_OPERATOR, assignment) else {
return;
};
builder.into_diagnostic(format_args!(
"Operator `{op}=` is not supported between objects of type `{}` and `{}`",
target_type.display(db),
value_type.display(db)
));
};
// Fall back to non-augmented binary operator inference. // Fall back to non-augmented binary operator inference.
let mut binary_return_ty = || { let mut binary_return_ty = || {
self.infer_binary_expression_type(assignment.into(), false, target_type, value_type, op) self.infer_binary_expression_type(assignment.into(), false, target_type, value_type, op)
.unwrap_or_else(|| { .unwrap_or_else(|| {
report_unsupported_augmented_op(&mut self.context); report_unsupported_augmented_assignment(
&self.context,
assignment,
target_type,
value_type,
);
Type::unknown() Type::unknown()
}) })
}; };
@ -5964,7 +5977,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
UnionType::from_elements(db, [outcome.return_type(db), binary_return_ty()]) UnionType::from_elements(db, [outcome.return_type(db), binary_return_ty()])
} }
Err(CallDunderError::CallError(_, bindings)) => { Err(CallDunderError::CallError(_, bindings)) => {
report_unsupported_augmented_op(&mut self.context); report_unsupported_augmented_assignment(
&self.context,
assignment,
target_type,
value_type,
);
bindings.return_type(db) bindings.return_type(db)
} }
} }
@ -9699,7 +9717,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.context.report_lint(&UNSUPPORTED_OPERATOR, unary) self.context.report_lint(&UNSUPPORTED_OPERATOR, unary)
{ {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Unary operator `{op}` is not supported for type `{}`", "Unary operator `{op}` is not supported for object of type `{}`",
operand_type.display(self.db()), operand_type.display(self.db()),
)); ));
} }
@ -9732,26 +9750,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_binary_expression_type(binary.into(), false, left_ty, right_ty, *op) self.infer_binary_expression_type(binary.into(), false, left_ty, right_ty, *op)
.unwrap_or_else(|| { .unwrap_or_else(|| {
let db = self.db(); report_unsupported_binary_operation(&self.context, binary, left_ty, right_ty, *op);
if let Some(builder) = self.context.report_lint(&UNSUPPORTED_OPERATOR, binary) {
let mut diag = builder.into_diagnostic(format_args!(
"Operator `{op}` is not supported between objects of type `{}` and `{}`",
left_ty.display(db),
right_ty.display(db)
));
if op == &ast::Operator::BitOr
&& (left_ty.is_subtype_of(db, KnownClass::Type.to_instance(db))
|| right_ty.is_subtype_of(db, KnownClass::Type.to_instance(db)))
&& Program::get(db).python_version(db) < PythonVersion::PY310
{
diag.info(
"Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later",
);
add_inferred_python_version_hint_to_diagnostic(db, &mut diag, "resolving types");
}
}
Type::unknown() Type::unknown()
}) })
} }

View File

@ -16,8 +16,8 @@ use crate::types::tuple::{TupleSpecBuilder, TupleType};
use crate::types::{ use crate::types::{
BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass, BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass,
KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType, KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType,
Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, TypeVarKind, UnionBuilder,
any_over_type, todo_type, UnionType, any_over_type, todo_type,
}; };
/// Type expressions /// Type expressions
@ -995,8 +995,26 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
} }
Type::unknown() Type::unknown()
} }
KnownInstanceType::TypeVar(_) => { KnownInstanceType::TypeVar(typevar) => {
// The type variable designated as a generic type alias by `typing.TypeAlias` can be explicitly specialized.
// ```py
// from typing import TypeVar, TypeAlias
// T = TypeVar('T')
// Annotated: TypeAlias = T
// _: Annotated[int] = 1 # valid
// ```
if typevar.identity(self.db()).kind(self.db()) == TypeVarKind::Pep613Alias {
self.infer_explicit_type_alias_specialization(subscript, value_ty, false) self.infer_explicit_type_alias_specialization(subscript, value_ty, false)
} else {
if let Some(builder) =
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
{
builder.into_diagnostic(format_args!(
"A type variable itself cannot be specialized",
));
}
Type::unknown()
}
} }
KnownInstanceType::UnionType(_) KnownInstanceType::UnionType(_)

4
ty.schema.json generated
View File

@ -112,7 +112,7 @@
] ]
}, },
"root": { "root": {
"description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)\n* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path\n* otherwise, default to `.` (flat layout)\n\nBesides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),\nit will also be included in the first party search path.", "description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)\n* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path\n* otherwise, default to `.` (flat layout)\n\nAdditionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),\nit will also be included in the first party search path.",
"type": [ "type": [
"array", "array",
"null" "null"
@ -1171,7 +1171,7 @@
] ]
}, },
"root": { "root": {
"description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)\n* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path\n* otherwise, default to `.` (flat layout)\n\nBesides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),\nit will also be included in the first party search path.", "description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)\n* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path\n* otherwise, default to `.` (flat layout)\n\nAdditionally, if a `./python` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),\nit will also be included in the first party search path.",
"anyOf": [ "anyOf": [
{ {
"$ref": "#/definitions/RelativePathBuf" "$ref": "#/definitions/RelativePathBuf"