Understand typing.Optional in annotations (#14397)

This commit is contained in:
Shaygan Hooshyari 2024-11-17 18:04:58 +01:00 committed by GitHub
parent cd80c9d907
commit ff19629b11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 4 deletions

View file

@ -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)
```

View file

@ -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.
@ -1816,6 +1818,7 @@ impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
KnownInstanceType::Literal => "Literal",
KnownInstanceType::Optional => "Optional",
KnownInstanceType::TypeVar(_) => "TypeVar",
}
}
@ -1823,8 +1826,7 @@ impl<'db> KnownInstanceType<'db> {
/// 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,
}
}
@ -1832,6 +1834,7 @@ impl<'db> KnownInstanceType<'db> {
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),
}
}
@ -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,
}
}
@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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,
},
}
}