agronholm / agronholm/typeguard

Never and NoReturn are not checked when nested inside another annotation, so any value passes

Aberta
#580 0 comentários 0 reações 0 responsáveis Ver no GitHub
Linguagem predominante
Python
Estrelas
1.8k
Forks
146
Merge médio
8d 12h
PRs com merge (30d)
1

Descrição

### 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 `123f1abc7e82bbe980c779005de858533e1786c3`)

### Python version

3.12.14

### What happened?

`Never` and `NoReturn` are handled correctly when either one is the entire argument or return annotation. In the example below `bare(3)` raises `TypeCheckError`, which is what I expected.

When either one appears inside another annotation, no check is performed and every value passes. `list[Never]` accepts `[1]`, `dict[str, Never]` accepts `{"a": 1}`, `Optional[Never]` accepts `1`, and `Sequence[NoReturn]` accepts `[1]`. Nothing is raised and nothing is warned. The two forms disagree with each other in the same argument position:

```
def bare(x: Never) rejects 3, correct
def nested(x: list[Never]) accepts [1]
```

The case I found most surprising is `Union[int, Never]`, which accepts `"x"`. A plain `str` is not an `int`, so that union passes on its `Never` member alone.

I expected `check_type([1], list[Never])` to raise `TypeCheckError` the same way `check_type(["a"], list[int])` does.

`docs/features.rst` lists `typing.Never` and `typing.NoReturn` as "Supported in argument and return type annotations", and `list[Never]` is an argument annotation, so I could not work out from the docs which of the two behaviours is intended. If nesting is deliberately out of scope, then narrowing that table entry would have saved me the confusion, because the bare form working is exactly what led me to expect the nested form to work.

This is separate from #579. That one is about PEP 695 aliases; this reproduces with no alias involved, and the bare form here already works.

### How can we reproduce the bug?

Standard library only, no third party packages beyond typeguard itself.

```python
from typing import Never, NoReturn, Optional, Sequence, Union

from typeguard import TypeCheckError, check_type, typechecked

@typechecked
def bare(x: Never) -> int:
return 1

@typechecked
def nested(x: list[Never]) -> int:
return 1

try:
bare(3)
print("bare(3) accepted")
except TypeCheckError:
print("bare(3) TypeCheckError")

try:
nested([1])
print("nested([1]) accepted")
except TypeCheckError:
print("nested([1]) TypeCheckError")

for annotation, value in [
(list[Never], [1]),
(dict[str, Never], {"a": 1}),
(Union[int, Never], "x"),
(Optional[Never], 1),
(tuple[Never, ...], (1, 2)),
(Sequence[NoReturn], [1]),
]:
try:
check_type(value, annotation)
print(f"check_type accepted {value!r:12} against {annotation}")
except TypeCheckError:
print(f"check_type TypeCheckError {value!r} against {annotation}")
```

Output on 4.6.0 under Python 3.12.14, in a container with only typeguard installed:

```
bare(3) TypeCheckError
nested([1]) accepted
check_type accepted [1] against list[typing.Never]
check_type accepted {'a': 1} against dict[str, typing.Never]
check_type accepted 'x' against typing.Union[int, typing.Never]
check_type accepted 1 against typing.Optional[typing.Never]
check_type accepted (1, 2) against tuple[typing.Never, ...]
check_type accepted [1] against typing.Sequence[typing.NoReturn]
```

The first line is the control and is correct. Every line after it is a value passing a check I expected it to fail. The output is byte for byte the same at `main` HEAD `123f1abc`.

Guia de contribuição

Abrir o guia de contribuição

Direção de pesquisa

Start at the check_type() and @typechecked entry points and run the supplied Python reproduction covering nested Never and NoReturn annotations. Compare the behavior with the support stated in docs/features.rst; done means nested values are rejected with TypeCheckError consistently with bare Never and NoReturn annotations.

Escrita pelo modelo de indexação a partir do texto da issue.

Avaliação

Stack de tecnologia
python
Domínio
testing-qa, tooling
Tipo de issue
Bug
Dificuldade
3/5
Tempo estimado
1-2 dias
Status de atividade
Ativa
Clareza
Claramente especificada
Facilidade para iniciantes
74/100

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.