Skip to content

Commit

Permalink
Understand typing.Optional in annotations (astral-sh#14397)
Browse files Browse the repository at this point in the history
  • Loading branch information
Glyphack authored Nov 17, 2024
1 parent cd80c9d commit ff19629
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Optional

## Annotation

`typing.Optional` is equivalent to using the type with a None in a Union.

```py
from typing import Optional

a: Optional[int]
a1: Optional[bool]
a2: Optional[Optional[bool]]
a3: Optional[None]

def f():
# revealed: int | None
reveal_type(a)
# revealed: bool | None
reveal_type(a1)
# revealed: bool | None
reveal_type(a2)
# revealed: None
reveal_type(a3)
```

## Assignment

```py
from typing import Optional

a: Optional[int] = 1
a = None
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int | None`"
a = ""
```

## Typing Extensions

```py
from typing_extensions import Optional

a: Optional[int]

def f():
# revealed: int | None
reveal_type(a)
```
9 changes: 7 additions & 2 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,8 @@ impl<'db> KnownClass {
pub enum KnownInstanceType<'db> {
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
Literal,
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Literal`)
Optional,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
// TODO: fill this enum out with more special forms, etc.
Expand All @@ -1816,22 +1818,23 @@ impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
KnownInstanceType::Literal => "Literal",
KnownInstanceType::Optional => "Optional",
KnownInstanceType::TypeVar(_) => "TypeVar",
}
}

/// Evaluate the known instance in boolean context
pub const fn bool(self) -> Truthiness {
match self {
Self::Literal => Truthiness::AlwaysTrue,
Self::TypeVar(_) => Truthiness::AlwaysTrue,
Self::Literal | Self::Optional | Self::TypeVar(_) => Truthiness::AlwaysTrue,
}
}

/// Return the repr of the symbol at runtime
pub fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Literal => "typing.Literal",
Self::Optional => "typing.Optional",
Self::TypeVar(typevar) => typevar.name(db),
}
}
Expand All @@ -1840,6 +1843,7 @@ impl<'db> KnownInstanceType<'db> {
pub const fn class(self) -> KnownClass {
match self {
Self::Literal => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::TypeVar(_) => KnownClass::TypeVar,
}
}
Expand All @@ -1859,6 +1863,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),
_ => None,
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4524,6 +4524,10 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::Unknown
}
},
KnownInstanceType::Optional => {
let param_type = self.infer_type_expression(parameters);
UnionType::from_elements(self.db, [param_type, Type::none(self.db)])
}
KnownInstanceType::TypeVar(_) => Type::Todo,
}
}
Expand Down
5 changes: 3 additions & 2 deletions crates/red_knot_python_semantic/src/types/mro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,9 @@ impl<'db> ClassBase<'db> {
| Type::ModuleLiteral(_)
| Type::SubclassOf(_) => None,
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::Literal => None,
KnownInstanceType::TypeVar(_) => None,
KnownInstanceType::TypeVar(_)
| KnownInstanceType::Literal
| KnownInstanceType::Optional => None,
},
}
}
Expand Down

0 comments on commit ff19629

Please sign in to comment.