[ty] Avoid double-analyzing tuple in Final subscript (#21828)

## Summary

As-is, a single-element tuple gets destructured via:

```rust
let arguments = if let ast::Expr::Tuple(tuple) = slice {
    &*tuple.elts
} else {
    std::slice::from_ref(slice)
};
```

But then, because it's a single element, we call
`infer_annotation_expression_impl`, passing in the tuple, rather than
the first element.

Closes https://github.com/astral-sh/ty/issues/1793.
Closes https://github.com/astral-sh/ty/issues/1768.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Charlie Marsh
2025-12-07 09:27:14 -05:00
committed by GitHub
parent cbff09b9af
commit 285d6410d3
4 changed files with 78 additions and 8 deletions

View File

@@ -101,6 +101,37 @@ class C:
x: ClassVar[int, str] = 1
```
## Trailing comma creates a tuple
A trailing comma in a subscript creates a single-element tuple. We need to handle this gracefully
and emit a proper error rather than crashing (see
[ty#1793](https://github.com/astral-sh/ty/issues/1793)).
```py
from typing import ClassVar
class C:
# error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?"
x: ClassVar[(),]
# error: [invalid-attribute-access] "Cannot assign to ClassVar `x` from an instance of type `C`"
C().x = 42
reveal_type(C.x) # revealed: Unknown
```
This also applies when the trailing comma is inside the brackets (see
[ty#1768](https://github.com/astral-sh/ty/issues/1768)):
```py
from typing import ClassVar
class D:
# A trailing comma here doesn't change the meaning; it's still one argument.
a: ClassVar[int,] = 1
reveal_type(D.a) # revealed: int
```
## Illegal `ClassVar` in type expression
```py

View File

@@ -340,6 +340,22 @@ class C:
x: Final[int, str] = 1
```
### Trailing comma creates a tuple
A trailing comma in a subscript creates a single-element tuple. We need to handle this gracefully
and emit a proper error rather than crashing (see
[ty#1793](https://github.com/astral-sh/ty/issues/1793)).
```py
from typing import Final
# error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?"
x: Final[(),] = 42
# error: [invalid-assignment] "Reassignment of `Final` symbol `x` is not allowed"
x = 56
```
### Illegal `Final` in type expression
```py

View File

@@ -112,6 +112,25 @@ class Wrong:
x: InitVar[int, str] # error: [invalid-type-form] "Type qualifier `InitVar` expected exactly 1 argument, got 2"
```
A trailing comma in a subscript creates a single-element tuple. We need to handle this gracefully
and emit a proper error rather than crashing (see
[ty#1793](https://github.com/astral-sh/ty/issues/1793)).
```py
from dataclasses import InitVar, dataclass
@dataclass
class AlsoWrong:
# error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?"
x: InitVar[(),]
# revealed: (self: AlsoWrong, x: Unknown) -> None
reveal_type(AlsoWrong.__init__)
# error: [unresolved-attribute]
reveal_type(AlsoWrong(42).x) # revealed: Unknown
```
A bare `InitVar` is not allowed according to the [type annotation grammar]:
```py