mirror of https://github.com/astral-sh/ruff
[red-knot] Recognize `...` as a singleton (#16184)
This commit is contained in:
parent
d4b4f65e20
commit
4941975e74
|
|
@ -64,3 +64,39 @@ def _(flag1: bool, flag2: bool):
|
|||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## `is` for `EllipsisType` (Python 3.10+)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from types import EllipsisType
|
||||
|
||||
def _(x: int | EllipsisType):
|
||||
if x is ...:
|
||||
reveal_type(x) # revealed: EllipsisType
|
||||
else:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## `is` for `EllipsisType` (Python 3.9 and below)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = ... if flag else 42
|
||||
|
||||
reveal_type(x) # revealed: ellipsis | Literal[42]
|
||||
|
||||
if x is ...:
|
||||
reveal_type(x) # revealed: ellipsis
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[42]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -54,3 +54,41 @@ from knot_extensions import is_singleton, static_assert
|
|||
|
||||
static_assert(is_singleton(_NoDefaultType))
|
||||
```
|
||||
|
||||
## `builtins.ellipsis`/`types.EllipsisType`
|
||||
|
||||
### All Python versions
|
||||
|
||||
The type of the builtin symbol `Ellipsis` is the same as the type of an ellipsis literal (`...`).
|
||||
The type is not actually exposed from the standard library on Python \<3.10, but we still recognise
|
||||
the type as a singleton on any Python version.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from knot_extensions import is_singleton, static_assert
|
||||
|
||||
static_assert(is_singleton(Ellipsis.__class__))
|
||||
static_assert(is_singleton((...).__class__))
|
||||
```
|
||||
|
||||
### Python 3.10+
|
||||
|
||||
On Python 3.10+, the standard library exposes the type of `...` as `types.EllipsisType`, and we also
|
||||
recognise this as a singleton type when it is referenced directly:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
import types
|
||||
from knot_extensions import static_assert, is_singleton
|
||||
|
||||
static_assert(is_singleton(types.EllipsisType))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1756,6 +1756,7 @@ impl<'db> Type<'db> {
|
|||
KnownClass::NoneType
|
||||
| KnownClass::NoDefaultType
|
||||
| KnownClass::VersionInfo
|
||||
| KnownClass::EllipsisType
|
||||
| KnownClass::TypeAliasType,
|
||||
) => true,
|
||||
Some(
|
||||
|
|
@ -2865,6 +2866,9 @@ pub enum KnownClass {
|
|||
OrderedDict,
|
||||
// sys
|
||||
VersionInfo,
|
||||
// Exposed as `types.EllipsisType` on Python >=3.10;
|
||||
// backported as `builtins.ellipsis` by typeshed on Python <=3.9
|
||||
EllipsisType,
|
||||
}
|
||||
|
||||
impl<'db> KnownClass {
|
||||
|
|
@ -2872,7 +2876,7 @@ impl<'db> KnownClass {
|
|||
matches!(self, Self::Bool)
|
||||
}
|
||||
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
pub fn as_str(&self, db: &'db dyn Db) -> &'static str {
|
||||
match self {
|
||||
Self::Bool => "bool",
|
||||
Self::Object => "object",
|
||||
|
|
@ -2912,6 +2916,15 @@ impl<'db> KnownClass {
|
|||
// which is impossible to replicate in the stubs since the sole instance of the class
|
||||
// also has that name in the `sys` module.)
|
||||
Self::VersionInfo => "_version_info",
|
||||
Self::EllipsisType => {
|
||||
// Exposed as `types.EllipsisType` on Python >=3.10;
|
||||
// backported as `builtins.ellipsis` by typeshed on Python <=3.9
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
"EllipsisType"
|
||||
} else {
|
||||
"ellipsis"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2920,7 +2933,7 @@ impl<'db> KnownClass {
|
|||
}
|
||||
|
||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
known_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||
known_module_symbol(db, self.canonical_module(db), self.as_str(db))
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown())
|
||||
}
|
||||
|
|
@ -2935,7 +2948,7 @@ impl<'db> KnownClass {
|
|||
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||
/// *and* `class` is a subclass of `other`.
|
||||
pub fn is_subclass_of(self, db: &'db dyn Db, other: Class<'db>) -> bool {
|
||||
known_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||
known_module_symbol(db, self.canonical_module(db), self.as_str(db))
|
||||
.ignore_possibly_unbound()
|
||||
.and_then(Type::into_class_literal)
|
||||
.is_some_and(|ClassLiteralType { class }| class.is_subclass_of(db, other))
|
||||
|
|
@ -2979,6 +2992,15 @@ impl<'db> KnownClass {
|
|||
KnownModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
Self::EllipsisType => {
|
||||
// Exposed as `types.EllipsisType` on Python >=3.10;
|
||||
// backported as `builtins.ellipsis` by typeshed on Python <=3.9
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
KnownModule::Types
|
||||
} else {
|
||||
KnownModule::Builtins
|
||||
}
|
||||
}
|
||||
Self::ChainMap
|
||||
| Self::Counter
|
||||
| Self::DefaultDict
|
||||
|
|
@ -2991,9 +3013,14 @@ impl<'db> KnownClass {
|
|||
///
|
||||
/// A singleton class is a class where it is known that only one instance can ever exist at runtime.
|
||||
const fn is_singleton(self) -> bool {
|
||||
// TODO there are other singleton types (EllipsisType, NotImplementedType)
|
||||
// TODO there are other singleton types (NotImplementedType -- any others?)
|
||||
match self {
|
||||
Self::NoneType | Self::NoDefaultType | Self::VersionInfo | Self::TypeAliasType => true,
|
||||
Self::NoneType
|
||||
| Self::EllipsisType
|
||||
| Self::NoDefaultType
|
||||
| Self::VersionInfo
|
||||
| Self::TypeAliasType => true,
|
||||
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
|
|
@ -3060,6 +3087,12 @@ impl<'db> KnownClass {
|
|||
"_SpecialForm" => Self::SpecialForm,
|
||||
"_NoDefaultType" => Self::NoDefaultType,
|
||||
"_version_info" => Self::VersionInfo,
|
||||
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
|
||||
Self::EllipsisType
|
||||
}
|
||||
"EllipsisType" if Program::get(db).python_version(db) >= PythonVersion::PY310 => {
|
||||
Self::EllipsisType
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
|
|
@ -3096,6 +3129,7 @@ impl<'db> KnownClass {
|
|||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
| Self::BaseException
|
||||
| Self::EllipsisType
|
||||
| Self::BaseExceptionGroup
|
||||
| Self::FunctionType => module == self.canonical_module(db),
|
||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
||||
|
|
|
|||
|
|
@ -2814,17 +2814,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
fn infer_number_literal_expression(&mut self, literal: &ast::ExprNumberLiteral) -> Type<'db> {
|
||||
let ast::ExprNumberLiteral { range: _, value } = literal;
|
||||
let db = self.db();
|
||||
|
||||
match value {
|
||||
ast::Number::Int(n) => n
|
||||
.as_i64()
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
|
||||
ast::Number::Float(_) => KnownClass::Float.to_instance(self.db()),
|
||||
ast::Number::Complex { .. } => builtins_symbol(self.db(), "complex")
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown())
|
||||
.to_instance(self.db()),
|
||||
.unwrap_or_else(|| KnownClass::Int.to_instance(db)),
|
||||
ast::Number::Float(_) => KnownClass::Float.to_instance(db),
|
||||
ast::Number::Complex { .. } => KnownClass::Complex.to_instance(db),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2908,9 +2906,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
&mut self,
|
||||
_literal: &ast::ExprEllipsisLiteral,
|
||||
) -> Type<'db> {
|
||||
builtins_symbol(self.db(), "Ellipsis")
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown())
|
||||
KnownClass::EllipsisType.to_instance(self.db())
|
||||
}
|
||||
|
||||
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue