agronholm / agronholm/typeguard

Subscripted PEP 695 generic type alias skips the check entirely, so any value passes

未关闭
#579 3 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Python
星标
1.8k
派生
145
平均合并
8 天 12 小时
30 天内合并 PR
1

描述

### Things to check first

- [x] I have searched the existing issues and didn't find my bug already reported there

- [x] I have checked that my bug is still present in the latest release

### Typeguard version

4.6.0 (also present on `main` at 9f289c7fca68097542d3bde9d59496ad42e58251)

### Python version

3.12.14, 3.13.15 and 3.14.7 (all three behave the same)

### What happened?

When an annotation is a PEP 695 generic type alias with its type parameters filled in, such as `Boxed[int]` for `type Boxed[T] = list[T]`, no check is performed at all. Every value passes, including values of a completely unrelated type, and no error or warning is emitted.

A type alias without type parameters is checked correctly, so the two sit side by side in the same module and only one of them catches anything:

```
check_type(["a"], Plain) # type Plain = list[int] -> TypeCheckError, correct
check_type("hello", Boxed[int]) # type Boxed[T] = list[T] -> returns "hello"
```

I expected `check_type(["a"], Boxed[int])` to raise `TypeCheckError` the same way `check_type(["a"], list[int])` does, and I expected `@typechecked` to reject the call in the example below.

The same silence shows up wherever the parameterized alias appears, including nested inside another annotation:

```
check_type([["a"]], list[Boxed[int]]) # returns [['a']]
check_type({"k": ["a"]}, dict[str, Boxed[int]]) # returns {'k': ['a']}
check_type(["a"], Boxed[int] | None) # returns ['a']
```

This is a silent false negative rather than a crash, which is what makes it awkward in practice: a codebase that annotates with its own parameterized aliases gets no runtime checking on those annotations and nothing says so.

Possibly related: #541, where a type alias without parameters was not being resolved. That one is fixed and the non-parameterized case above confirms it.

### How can we reproduce the bug?

No third party libraries. `pip install typeguard` in a clean Python 3.12, 3.13 or 3.14 environment, then run:

```python
from typeguard import TypeCheckError, check_type, typechecked

type Plain = list[int]
type Boxed[T] = list[T]

def show(label, fn):
try:
fn()
print("no error |", label)
except TypeCheckError as exc:
print("TypeCheckError|", label, "|", exc)

# control: an alias with no type parameters is checked
show("Plain <- ['a'] ", lambda: check_type(["a"], Plain))

# the bug: the same list is accepted against a parameterized alias
show("Boxed[int] <- ['a'] ", lambda: check_type(["a"], Boxed[int]))
show("Boxed[int] <- 'hello'", lambda: check_type("hello", Boxed[int]))

@typechecked
def f(x: Boxed[int]) -> Boxed[int]:
return x

show("f('hello') ", lambda: f("hello"))
```

Output on typeguard 4.6.0 / Python 3.13.15:

```
TypeCheckError| Plain <- ['a'] | item 0 of list is not an instance of int
no error | Boxed[int] <- ['a']
no error | Boxed[int] <- 'hello'
no error | f('hello')
```

Only the first line raises. The last three all pass.

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。