[red-knot] Fix false positives on types.UnionType instances in type expressions (#17297)

This commit is contained in:
Alex Waygood 2025-04-09 18:33:16 +01:00 committed by GitHub
parent 484a8ed36d
commit 781b653511
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 30 additions and 1 deletions

View file

@ -2,7 +2,7 @@
## Annotation
`typing.Union` can be used to construct union types same as `|` operator.
`typing.Union` can be used to construct union types in the same way as the `|` operator.
```py
from typing import Union
@ -69,3 +69,20 @@ from typing import Union
def f(x: Union) -> None:
reveal_type(x) # revealed: Unknown
```
## Implicit type aliases using new-style unions
We don't recognise these as type aliases yet, but we also don't emit false-positive diagnostics if
you use them in type expressions:
```toml
[environment]
python-version = "3.10"
```
```py
X = int | str
def f(y: X):
reveal_type(y) # revealed: @Todo(Support for `types.UnionType` instances in type expressions)
```

View file

@ -4035,6 +4035,9 @@ impl<'db> Type<'db> {
Some(KnownClass::GenericAlias) => Ok(todo_type!(
"Support for `typing.GenericAlias` instances in type expressions"
)),
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self

View file

@ -1170,6 +1170,7 @@ pub(crate) enum KnownClass {
MethodType,
MethodWrapperType,
WrapperDescriptorType,
UnionType,
// Typeshed
NoneType, // Part of `types` for Python >= 3.10
// Typing
@ -1233,6 +1234,7 @@ impl<'db> KnownClass {
| Self::ParamSpecKwargs
| Self::TypeVarTuple
| Self::WrapperDescriptorType
| Self::UnionType
| Self::MethodWrapperType => Truthiness::AlwaysTrue,
Self::NoneType => Truthiness::AlwaysFalse,
@ -1305,6 +1307,7 @@ impl<'db> KnownClass {
Self::ModuleType => "ModuleType",
Self::FunctionType => "FunctionType",
Self::MethodType => "MethodType",
Self::UnionType => "UnionType",
Self::MethodWrapperType => "MethodWrapperType",
Self::WrapperDescriptorType => "WrapperDescriptorType",
Self::NoneType => "NoneType",
@ -1487,6 +1490,7 @@ impl<'db> KnownClass {
| Self::FunctionType
| Self::MethodType
| Self::MethodWrapperType
| Self::UnionType
| Self::WrapperDescriptorType => KnownModule::Types,
Self::NoneType => KnownModule::Typeshed,
Self::Any
@ -1539,6 +1543,7 @@ impl<'db> KnownClass {
| Self::VersionInfo
| Self::EllipsisType
| Self::TypeAliasType
| Self::UnionType
| Self::NotImplementedType => true,
Self::Any
@ -1643,6 +1648,7 @@ impl<'db> KnownClass {
| Self::Sized
| Self::Enum
| Self::Super
| Self::UnionType
| Self::NewType => false,
}
}
@ -1681,6 +1687,7 @@ impl<'db> KnownClass {
"ModuleType" => Self::ModuleType,
"FunctionType" => Self::FunctionType,
"MethodType" => Self::MethodType,
"UnionType" => Self::UnionType,
"MethodWrapperType" => Self::MethodWrapperType,
"WrapperDescriptorType" => Self::WrapperDescriptorType,
"NewType" => Self::NewType,
@ -1758,6 +1765,7 @@ impl<'db> KnownClass {
| Self::Enum
| Self::Super
| Self::NotImplementedType
| Self::UnionType
| Self::WrapperDescriptorType => module == self.canonical_module(db),
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
Self::SpecialForm
@ -2259,6 +2267,7 @@ mod tests {
for class in KnownClass::iter() {
let version_added = match class {
KnownClass::UnionType => PythonVersion::PY310,
KnownClass::BaseExceptionGroup => PythonVersion::PY311,
KnownClass::GenericAlias => PythonVersion::PY39,
_ => PythonVersion::PY37,