Skip to content

Commit

Permalink
[red-knot] support typing.Union in type annotations (#14499)
Browse files Browse the repository at this point in the history
Fix #14498

## Summary

This PR adds `typing.Union` support

## Test Plan

I created new tests in mdtest.

---------

Co-authored-by: Carl Meyer <[email protected]>
  • Loading branch information
Glyphack and carljm authored Nov 20, 2024
1 parent 3c52d2d commit aecdb8c
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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)
```
10 changes: 9 additions & 1 deletion crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -1819,14 +1821,17 @@ impl<'db> KnownInstanceType<'db> {
match self {
KnownInstanceType::Literal => "Literal",
KnownInstanceType::Optional => "Optional",
KnownInstanceType::Union => "Union",
KnownInstanceType::TypeVar(_) => "TypeVar",
}
}

/// 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
}
}
}

Expand All @@ -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),
}
}
Expand All @@ -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,
}
}
Expand All @@ -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,
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/red_knot_python_semantic/src/types/mro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl<'db> ClassBase<'db> {
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeVar(_)
| KnownInstanceType::Literal
| KnownInstanceType::Union
| KnownInstanceType::Optional => None,
},
}
Expand Down

0 comments on commit aecdb8c

Please sign in to comment.