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:
|
else:
|
||||||
reveal_type(x) # revealed: Literal[1]
|
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))
|
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::NoneType
|
||||||
| KnownClass::NoDefaultType
|
| KnownClass::NoDefaultType
|
||||||
| KnownClass::VersionInfo
|
| KnownClass::VersionInfo
|
||||||
|
| KnownClass::EllipsisType
|
||||||
| KnownClass::TypeAliasType,
|
| KnownClass::TypeAliasType,
|
||||||
) => true,
|
) => true,
|
||||||
Some(
|
Some(
|
||||||
|
|
@ -2865,6 +2866,9 @@ pub enum KnownClass {
|
||||||
OrderedDict,
|
OrderedDict,
|
||||||
// sys
|
// sys
|
||||||
VersionInfo,
|
VersionInfo,
|
||||||
|
// Exposed as `types.EllipsisType` on Python >=3.10;
|
||||||
|
// backported as `builtins.ellipsis` by typeshed on Python <=3.9
|
||||||
|
EllipsisType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> KnownClass {
|
impl<'db> KnownClass {
|
||||||
|
|
@ -2872,7 +2876,7 @@ impl<'db> KnownClass {
|
||||||
matches!(self, Self::Bool)
|
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 {
|
match self {
|
||||||
Self::Bool => "bool",
|
Self::Bool => "bool",
|
||||||
Self::Object => "object",
|
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
|
// which is impossible to replicate in the stubs since the sole instance of the class
|
||||||
// also has that name in the `sys` module.)
|
// also has that name in the `sys` module.)
|
||||||
Self::VersionInfo => "_version_info",
|
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> {
|
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()
|
.ignore_possibly_unbound()
|
||||||
.unwrap_or(Type::unknown())
|
.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,
|
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||||
/// *and* `class` is a subclass of `other`.
|
/// *and* `class` is a subclass of `other`.
|
||||||
pub fn is_subclass_of(self, db: &'db dyn Db, other: Class<'db>) -> bool {
|
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()
|
.ignore_possibly_unbound()
|
||||||
.and_then(Type::into_class_literal)
|
.and_then(Type::into_class_literal)
|
||||||
.is_some_and(|ClassLiteralType { class }| class.is_subclass_of(db, other))
|
.is_some_and(|ClassLiteralType { class }| class.is_subclass_of(db, other))
|
||||||
|
|
@ -2979,6 +2992,15 @@ impl<'db> KnownClass {
|
||||||
KnownModule::TypingExtensions
|
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::ChainMap
|
||||||
| Self::Counter
|
| Self::Counter
|
||||||
| Self::DefaultDict
|
| 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.
|
/// 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 {
|
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 {
|
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::Bool
|
||||||
| Self::Object
|
| Self::Object
|
||||||
| Self::Bytes
|
| Self::Bytes
|
||||||
|
|
@ -3060,6 +3087,12 @@ impl<'db> KnownClass {
|
||||||
"_SpecialForm" => Self::SpecialForm,
|
"_SpecialForm" => Self::SpecialForm,
|
||||||
"_NoDefaultType" => Self::NoDefaultType,
|
"_NoDefaultType" => Self::NoDefaultType,
|
||||||
"_version_info" => Self::VersionInfo,
|
"_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,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3096,6 +3129,7 @@ impl<'db> KnownClass {
|
||||||
| Self::ModuleType
|
| Self::ModuleType
|
||||||
| Self::VersionInfo
|
| Self::VersionInfo
|
||||||
| Self::BaseException
|
| Self::BaseException
|
||||||
|
| Self::EllipsisType
|
||||||
| Self::BaseExceptionGroup
|
| Self::BaseExceptionGroup
|
||||||
| Self::FunctionType => module == self.canonical_module(db),
|
| Self::FunctionType => module == self.canonical_module(db),
|
||||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
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> {
|
fn infer_number_literal_expression(&mut self, literal: &ast::ExprNumberLiteral) -> Type<'db> {
|
||||||
let ast::ExprNumberLiteral { range: _, value } = literal;
|
let ast::ExprNumberLiteral { range: _, value } = literal;
|
||||||
|
let db = self.db();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
ast::Number::Int(n) => n
|
ast::Number::Int(n) => n
|
||||||
.as_i64()
|
.as_i64()
|
||||||
.map(Type::IntLiteral)
|
.map(Type::IntLiteral)
|
||||||
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
|
.unwrap_or_else(|| KnownClass::Int.to_instance(db)),
|
||||||
ast::Number::Float(_) => KnownClass::Float.to_instance(self.db()),
|
ast::Number::Float(_) => KnownClass::Float.to_instance(db),
|
||||||
ast::Number::Complex { .. } => builtins_symbol(self.db(), "complex")
|
ast::Number::Complex { .. } => KnownClass::Complex.to_instance(db),
|
||||||
.ignore_possibly_unbound()
|
|
||||||
.unwrap_or(Type::unknown())
|
|
||||||
.to_instance(self.db()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2908,9 +2906,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
&mut self,
|
&mut self,
|
||||||
_literal: &ast::ExprEllipsisLiteral,
|
_literal: &ast::ExprEllipsisLiteral,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
builtins_symbol(self.db(), "Ellipsis")
|
KnownClass::EllipsisType.to_instance(self.db())
|
||||||
.ignore_possibly_unbound()
|
|
||||||
.unwrap_or(Type::unknown())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue