diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/union.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/union.md new file mode 100644 index 0000000000..115bcc99ee --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/union.md @@ -0,0 +1,61 @@ +# Union + +## Annotation + +`typing.Union` can be used to construct union types same as `|` operator. + +```py +from typing import Union + +a: Union[int, str] +a1: Union[int, bool] +a2: Union[int, Union[float, str]] +a3: Union[int, None] +a4: Union[Union[float, str]] +a5: Union[int] +a6: Union[()] + +def f(): + # revealed: int | str + reveal_type(a) + # Since bool is a subtype of int we simplify to int here. But we do allow assigning boolean values (see below). + # revealed: int + reveal_type(a1) + # revealed: int | float | str + reveal_type(a2) + # revealed: int | None + reveal_type(a3) + # revealed: float | str + reveal_type(a4) + # revealed: int + reveal_type(a5) + # revealed: Never + reveal_type(a6) +``` + +## Assignment + +```py +from typing import Union + +a: Union[int, str] +a = 1 +a = "" +a1: Union[int, bool] +a1 = 1 +a1 = True +# error: [invalid-assignment] "Object of type `Literal[b""]` is not assignable to `int | str`" +a = b"" +``` + +## Typing Extensions + +```py +from typing_extensions import Union + +a: Union[int, str] + +def f(): + # revealed: int | str + reveal_type(a) +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index c26c5b50dd..f4ffa4e0a4 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1809,6 +1809,8 @@ pub enum KnownInstanceType<'db> { Literal, /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) Optional, + /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) + Union, /// A single instance of `typing.TypeVar` TypeVar(TypeVarInstance<'db>), // TODO: fill this enum out with more special forms, etc. @@ -1819,6 +1821,7 @@ impl<'db> KnownInstanceType<'db> { match self { KnownInstanceType::Literal => "Literal", KnownInstanceType::Optional => "Optional", + KnownInstanceType::Union => "Union", KnownInstanceType::TypeVar(_) => "TypeVar", } } @@ -1826,7 +1829,9 @@ impl<'db> KnownInstanceType<'db> { /// Evaluate the known instance in boolean context pub const fn bool(self) -> Truthiness { match self { - Self::Literal | Self::Optional | Self::TypeVar(_) => Truthiness::AlwaysTrue, + Self::Literal | Self::Optional | Self::TypeVar(_) | Self::Union => { + Truthiness::AlwaysTrue + } } } @@ -1835,6 +1840,7 @@ impl<'db> KnownInstanceType<'db> { match self { Self::Literal => "typing.Literal", Self::Optional => "typing.Optional", + Self::Union => "typing.Union", Self::TypeVar(typevar) => typevar.name(db), } } @@ -1844,6 +1850,7 @@ impl<'db> KnownInstanceType<'db> { match self { Self::Literal => KnownClass::SpecialForm, Self::Optional => KnownClass::SpecialForm, + Self::Union => KnownClass::SpecialForm, Self::TypeVar(_) => KnownClass::TypeVar, } } @@ -1864,6 +1871,7 @@ impl<'db> KnownInstanceType<'db> { match (module.name().as_str(), instance_name) { ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), + ("typing" | "typing_extensions", "Union") => Some(Self::Union), _ => None, } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index ecc358d0f5..3f6cc5cfa7 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4567,6 +4567,13 @@ impl<'db> TypeInferenceBuilder<'db> { let param_type = self.infer_type_expression(parameters); UnionType::from_elements(self.db, [param_type, Type::none(self.db)]) } + KnownInstanceType::Union => match parameters { + ast::Expr::Tuple(t) => UnionType::from_elements( + self.db, + t.iter().map(|elt| self.infer_type_expression(elt)), + ), + _ => self.infer_type_expression(parameters), + }, KnownInstanceType::TypeVar(_) => Type::Todo, } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index 7f5282ce9e..6883b396c6 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -373,6 +373,7 @@ impl<'db> ClassBase<'db> { Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::TypeVar(_) | KnownInstanceType::Literal + | KnownInstanceType::Union | KnownInstanceType::Optional => None, }, }