mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-31 08:53:47 +00:00
[red-knot] rename {Class,Module,Function} => {Class,Module,Function}Literal (#13873)
## Summary * Rename `Type::Class` => `Type::ClassLiteral` * Rename `Type::Function` => `Type::FunctionLiteral` * Do not rename `Type::Module` * Remove `*Literal` suffixes in `display::LiteralTypeKind` variants, as per clippy suggestion * Get rid of `Type::is_class()` in favor of `is_subtype_of(…, 'type')`; modifiy `is_subtype_of` to support this. * Add new `Type::is_xyz()` methods and use them instead of matching on `Type` variants. closes #13863 ## Test Plan New `is_subtype_of_class_literals` unit test. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
c6ce52c29e
commit
f335fe4d4a
6 changed files with 163 additions and 117 deletions
|
@ -175,7 +175,6 @@ mod tests {
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::program::{Program, SearchPathSettings};
|
use crate::program::{Program, SearchPathSettings};
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
use crate::types::Type;
|
|
||||||
use crate::{HasTy, ProgramSettings, SemanticModel};
|
use crate::{HasTy, ProgramSettings, SemanticModel};
|
||||||
|
|
||||||
fn setup_db<'a>(files: impl IntoIterator<Item = (&'a str, &'a str)>) -> anyhow::Result<TestDb> {
|
fn setup_db<'a>(files: impl IntoIterator<Item = (&'a str, &'a str)>) -> anyhow::Result<TestDb> {
|
||||||
|
@ -205,7 +204,7 @@ mod tests {
|
||||||
let model = SemanticModel::new(&db, foo);
|
let model = SemanticModel::new(&db, foo);
|
||||||
let ty = function.ty(&model);
|
let ty = function.ty(&model);
|
||||||
|
|
||||||
assert!(matches!(ty, Type::Function(_)));
|
assert!(ty.is_function_literal());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -222,7 +221,7 @@ mod tests {
|
||||||
let model = SemanticModel::new(&db, foo);
|
let model = SemanticModel::new(&db, foo);
|
||||||
let ty = class.ty(&model);
|
let ty = class.ty(&model);
|
||||||
|
|
||||||
assert!(matches!(ty, Type::Class(_)));
|
assert!(ty.is_class_literal());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -243,7 +242,7 @@ mod tests {
|
||||||
let model = SemanticModel::new(&db, bar);
|
let model = SemanticModel::new(&db, bar);
|
||||||
let ty = alias.ty(&model);
|
let ty = alias.ty(&model);
|
||||||
|
|
||||||
assert!(matches!(ty, Type::Class(_)));
|
assert!(ty.is_class_literal());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,11 +255,11 @@ pub enum Type<'db> {
|
||||||
/// `Todo` would change the output type.
|
/// `Todo` would change the output type.
|
||||||
Todo,
|
Todo,
|
||||||
/// A specific function object
|
/// A specific function object
|
||||||
Function(FunctionType<'db>),
|
FunctionLiteral(FunctionType<'db>),
|
||||||
/// A specific module object
|
/// A specific module object
|
||||||
Module(File),
|
ModuleLiteral(File),
|
||||||
/// A specific class object
|
/// A specific class object
|
||||||
Class(ClassType<'db>),
|
ClassLiteral(ClassType<'db>),
|
||||||
/// The set of Python objects with the given class in their __class__'s method resolution order
|
/// The set of Python objects with the given class in their __class__'s method resolution order
|
||||||
Instance(ClassType<'db>),
|
Instance(ClassType<'db>),
|
||||||
/// The set of objects in any of the types in the union
|
/// The set of objects in any of the types in the union
|
||||||
|
@ -292,28 +292,32 @@ impl<'db> Type<'db> {
|
||||||
matches!(self, Type::Never)
|
matches!(self, Type::Never)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_class_type(self) -> Option<ClassType<'db>> {
|
pub const fn into_class_literal_type(self) -> Option<ClassType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Type::Class(class_type) => Some(class_type),
|
Type::ClassLiteral(class_type) => Some(class_type),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_class(self) -> ClassType<'db> {
|
pub fn expect_class_literal(self) -> ClassType<'db> {
|
||||||
self.into_class_type()
|
self.into_class_literal_type()
|
||||||
.expect("Expected a Type::Class variant")
|
.expect("Expected a Type::ClassLiteral variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_module_type(self) -> Option<File> {
|
pub const fn is_class_literal(&self) -> bool {
|
||||||
|
matches!(self, Type::ClassLiteral(..))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn into_module_literal_type(self) -> Option<File> {
|
||||||
match self {
|
match self {
|
||||||
Type::Module(file) => Some(file),
|
Type::ModuleLiteral(file) => Some(file),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_module(self) -> File {
|
pub fn expect_module_literal(self) -> File {
|
||||||
self.into_module_type()
|
self.into_module_literal_type()
|
||||||
.expect("Expected a Type::Module variant")
|
.expect("Expected a Type::ModuleLiteral variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_union_type(self) -> Option<UnionType<'db>> {
|
pub const fn into_union_type(self) -> Option<UnionType<'db>> {
|
||||||
|
@ -328,6 +332,10 @@ impl<'db> Type<'db> {
|
||||||
.expect("Expected a Type::Union variant")
|
.expect("Expected a Type::Union variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn is_union(&self) -> bool {
|
||||||
|
matches!(self, Type::Union(..))
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn into_intersection_type(self) -> Option<IntersectionType<'db>> {
|
pub const fn into_intersection_type(self) -> Option<IntersectionType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Type::Intersection(intersection_type) => Some(intersection_type),
|
Type::Intersection(intersection_type) => Some(intersection_type),
|
||||||
|
@ -340,16 +348,20 @@ impl<'db> Type<'db> {
|
||||||
.expect("Expected a Type::Intersection variant")
|
.expect("Expected a Type::Intersection variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_function_type(self) -> Option<FunctionType<'db>> {
|
pub const fn into_function_literal_type(self) -> Option<FunctionType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Type::Function(function_type) => Some(function_type),
|
Type::FunctionLiteral(function_type) => Some(function_type),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_function(self) -> FunctionType<'db> {
|
pub fn expect_function_literal(self) -> FunctionType<'db> {
|
||||||
self.into_function_type()
|
self.into_function_literal_type()
|
||||||
.expect("Expected a Type::Function variant")
|
.expect("Expected a Type::FunctionLiteral variant")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_function_literal(&self) -> bool {
|
||||||
|
matches!(self, Type::FunctionLiteral(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_int_literal_type(self) -> Option<i64> {
|
pub const fn into_int_literal_type(self) -> Option<i64> {
|
||||||
|
@ -364,6 +376,14 @@ impl<'db> Type<'db> {
|
||||||
.expect("Expected a Type::IntLiteral variant")
|
.expect("Expected a Type::IntLiteral variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn is_boolean_literal(&self) -> bool {
|
||||||
|
matches!(self, Type::BooleanLiteral(..))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_literal_string(&self) -> bool {
|
||||||
|
matches!(self, Type::LiteralString)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn may_be_unbound(&self, db: &'db dyn Db) -> bool {
|
pub fn may_be_unbound(&self, db: &'db dyn Db) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::Unbound => true,
|
Type::Unbound => true,
|
||||||
|
@ -387,18 +407,8 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
pub fn is_stdlib_symbol(&self, db: &'db dyn Db, module_name: &str, name: &str) -> bool {
|
pub fn is_stdlib_symbol(&self, db: &'db dyn Db, module_name: &str, name: &str) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::Class(class) => class.is_stdlib_symbol(db, module_name, name),
|
Type::ClassLiteral(class) => class.is_stdlib_symbol(db, module_name, name),
|
||||||
Type::Function(function) => function.is_stdlib_symbol(db, module_name, name),
|
Type::FunctionLiteral(function) => function.is_stdlib_symbol(db, module_name, name),
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return true if the type is a class or a union of classes.
|
|
||||||
pub fn is_class(&self, db: &'db dyn Db) -> bool {
|
|
||||||
match self {
|
|
||||||
Type::Union(union) => union.elements(db).iter().all(|ty| ty.is_class(db)),
|
|
||||||
Type::Class(_) => true,
|
|
||||||
// / TODO include type[X], once we add that type
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,6 +446,11 @@ impl<'db> Type<'db> {
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
(Type::ClassLiteral(..), Type::Instance(class))
|
||||||
|
if class.is_known(db, KnownClass::Type) =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
(Type::Union(union), ty) => union
|
(Type::Union(union), ty) => union
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -517,17 +532,17 @@ impl<'db> Type<'db> {
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..)
|
| Type::BytesLiteral(..)
|
||||||
| Type::Function(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::Module(..)
|
| Type::ModuleLiteral(..)
|
||||||
| Type::Class(..)),
|
| Type::ClassLiteral(..)),
|
||||||
right @ (Type::None
|
right @ (Type::None
|
||||||
| Type::BooleanLiteral(..)
|
| Type::BooleanLiteral(..)
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..)
|
| Type::BytesLiteral(..)
|
||||||
| Type::Function(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::Module(..)
|
| Type::ModuleLiteral(..)
|
||||||
| Type::Class(..)),
|
| Type::ClassLiteral(..)),
|
||||||
) => left != right,
|
) => left != right,
|
||||||
|
|
||||||
(Type::None, Type::Instance(class_type)) | (Type::Instance(class_type), Type::None) => {
|
(Type::None, Type::Instance(class_type)) | (Type::Instance(class_type), Type::None) => {
|
||||||
|
@ -577,12 +592,12 @@ impl<'db> Type<'db> {
|
||||||
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
||||||
|
|
||||||
(
|
(
|
||||||
Type::Function(..) | Type::Module(..) | Type::Class(..),
|
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
||||||
Type::Instance(class_type),
|
Type::Instance(class_type),
|
||||||
)
|
)
|
||||||
| (
|
| (
|
||||||
Type::Instance(class_type),
|
Type::Instance(class_type),
|
||||||
Type::Function(..) | Type::Module(..) | Type::Class(..),
|
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
||||||
) => !class_type.is_known(db, KnownClass::Object),
|
) => !class_type.is_known(db, KnownClass::Object),
|
||||||
|
|
||||||
(Type::Instance(..), Type::Instance(..)) => {
|
(Type::Instance(..), Type::Instance(..)) => {
|
||||||
|
@ -643,7 +658,7 @@ impl<'db> Type<'db> {
|
||||||
// are both of type Literal[345], for example.
|
// are both of type Literal[345], for example.
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Type::None | Type::BooleanLiteral(_) | Type::Function(..) | Type::Class(..) | Type::Module(..) => true,
|
Type::None | Type::BooleanLiteral(_) | Type::FunctionLiteral(..) | Type::ClassLiteral(..) | Type::ModuleLiteral(..) => true,
|
||||||
Type::Tuple(..) => {
|
Type::Tuple(..) => {
|
||||||
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
||||||
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
|
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
|
||||||
|
@ -675,9 +690,9 @@ impl<'db> Type<'db> {
|
||||||
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::None
|
Type::None
|
||||||
| Type::Function(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::Module(..)
|
| Type::ModuleLiteral(..)
|
||||||
| Type::Class(..)
|
| Type::ClassLiteral(..)
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::BooleanLiteral(..)
|
| Type::BooleanLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
|
@ -747,12 +762,12 @@ impl<'db> Type<'db> {
|
||||||
// TODO: attribute lookup on None type
|
// TODO: attribute lookup on None type
|
||||||
Type::Todo
|
Type::Todo
|
||||||
}
|
}
|
||||||
Type::Function(_) => {
|
Type::FunctionLiteral(_) => {
|
||||||
// TODO: attribute lookup on function type
|
// TODO: attribute lookup on function type
|
||||||
Type::Todo
|
Type::Todo
|
||||||
}
|
}
|
||||||
Type::Module(file) => global_symbol_ty(db, *file, name),
|
Type::ModuleLiteral(file) => global_symbol_ty(db, *file, name),
|
||||||
Type::Class(class) => class.class_member(db, name),
|
Type::ClassLiteral(class) => class.class_member(db, name),
|
||||||
Type::Instance(_) => {
|
Type::Instance(_) => {
|
||||||
// TODO MRO? get_own_instance_member, get_instance_member
|
// TODO MRO? get_own_instance_member, get_instance_member
|
||||||
Type::Todo
|
Type::Todo
|
||||||
|
@ -800,9 +815,9 @@ impl<'db> Type<'db> {
|
||||||
Truthiness::Ambiguous
|
Truthiness::Ambiguous
|
||||||
}
|
}
|
||||||
Type::None => Truthiness::AlwaysFalse,
|
Type::None => Truthiness::AlwaysFalse,
|
||||||
Type::Function(_) => Truthiness::AlwaysTrue,
|
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
||||||
Type::Module(_) => Truthiness::AlwaysTrue,
|
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
|
||||||
Type::Class(_) => {
|
Type::ClassLiteral(_) => {
|
||||||
// TODO: lookup `__bool__` and `__len__` methods on the class's metaclass
|
// TODO: lookup `__bool__` and `__len__` methods on the class's metaclass
|
||||||
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
|
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
|
||||||
Truthiness::Ambiguous
|
Truthiness::Ambiguous
|
||||||
|
@ -847,7 +862,7 @@ impl<'db> Type<'db> {
|
||||||
fn call(self, db: &'db dyn Db, arg_types: &[Type<'db>]) -> CallOutcome<'db> {
|
fn call(self, db: &'db dyn Db, arg_types: &[Type<'db>]) -> CallOutcome<'db> {
|
||||||
match self {
|
match self {
|
||||||
// TODO validate typed call arguments vs callable signature
|
// TODO validate typed call arguments vs callable signature
|
||||||
Type::Function(function_type) => match function_type.known(db) {
|
Type::FunctionLiteral(function_type) => match function_type.known(db) {
|
||||||
None => CallOutcome::callable(function_type.return_type(db)),
|
None => CallOutcome::callable(function_type.return_type(db)),
|
||||||
Some(KnownFunction::RevealType) => CallOutcome::revealed(
|
Some(KnownFunction::RevealType) => CallOutcome::revealed(
|
||||||
function_type.return_type(db),
|
function_type.return_type(db),
|
||||||
|
@ -856,7 +871,7 @@ impl<'db> Type<'db> {
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||||
Type::Class(class) => {
|
Type::ClassLiteral(class) => {
|
||||||
CallOutcome::callable(match class.known(db) {
|
CallOutcome::callable(match class.known(db) {
|
||||||
// If the class is the builtin-bool class (for example `bool(1)`), we try to
|
// If the class is the builtin-bool class (for example `bool(1)`), we try to
|
||||||
// return the specific truthiness value of the input arg, `Literal[True]` for
|
// return the specific truthiness value of the input arg, `Literal[True]` for
|
||||||
|
@ -976,7 +991,7 @@ impl<'db> Type<'db> {
|
||||||
Type::Unknown => Type::Unknown,
|
Type::Unknown => Type::Unknown,
|
||||||
Type::Unbound => Type::Unknown,
|
Type::Unbound => Type::Unknown,
|
||||||
Type::Never => Type::Never,
|
Type::Never => Type::Never,
|
||||||
Type::Class(class) => Type::Instance(*class),
|
Type::ClassLiteral(class) => Type::Instance(*class),
|
||||||
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
||||||
// TODO: we can probably do better here: --Alex
|
// TODO: we can probably do better here: --Alex
|
||||||
Type::Intersection(_) => Type::Todo,
|
Type::Intersection(_) => Type::Todo,
|
||||||
|
@ -984,9 +999,9 @@ impl<'db> Type<'db> {
|
||||||
// since they already indicate that the object is an instance of some kind:
|
// since they already indicate that the object is an instance of some kind:
|
||||||
Type::BooleanLiteral(_)
|
Type::BooleanLiteral(_)
|
||||||
| Type::BytesLiteral(_)
|
| Type::BytesLiteral(_)
|
||||||
| Type::Function(_)
|
| Type::FunctionLiteral(_)
|
||||||
| Type::Instance(_)
|
| Type::Instance(_)
|
||||||
| Type::Module(_)
|
| Type::ModuleLiteral(_)
|
||||||
| Type::IntLiteral(_)
|
| Type::IntLiteral(_)
|
||||||
| Type::StringLiteral(_)
|
| Type::StringLiteral(_)
|
||||||
| Type::Tuple(_)
|
| Type::Tuple(_)
|
||||||
|
@ -1002,17 +1017,17 @@ impl<'db> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Type::Unbound => Type::Unbound,
|
Type::Unbound => Type::Unbound,
|
||||||
Type::Never => Type::Never,
|
Type::Never => Type::Never,
|
||||||
Type::Instance(class) => Type::Class(*class),
|
Type::Instance(class) => Type::ClassLiteral(*class),
|
||||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db),
|
Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db),
|
||||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db),
|
Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db),
|
||||||
Type::IntLiteral(_) => KnownClass::Int.to_class(db),
|
Type::IntLiteral(_) => KnownClass::Int.to_class(db),
|
||||||
Type::Function(_) => KnownClass::FunctionType.to_class(db),
|
Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class(db),
|
||||||
Type::Module(_) => KnownClass::ModuleType.to_class(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db),
|
||||||
Type::Tuple(_) => KnownClass::Tuple.to_class(db),
|
Type::Tuple(_) => KnownClass::Tuple.to_class(db),
|
||||||
Type::None => KnownClass::NoneType.to_class(db),
|
Type::None => KnownClass::NoneType.to_class(db),
|
||||||
// TODO not accurate if there's a custom metaclass...
|
// TODO not accurate if there's a custom metaclass...
|
||||||
Type::Class(_) => KnownClass::Type.to_class(db),
|
Type::ClassLiteral(_) => KnownClass::Type.to_class(db),
|
||||||
// TODO can we do better here? `type[LiteralString]`?
|
// TODO can we do better here? `type[LiteralString]`?
|
||||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db),
|
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db),
|
||||||
// TODO: `type[Any]`?
|
// TODO: `type[Any]`?
|
||||||
|
@ -1642,7 +1657,7 @@ impl<'db> ClassType<'db> {
|
||||||
// TODO: we need to iterate over the *MRO* here, not the bases
|
// TODO: we need to iterate over the *MRO* here, not the bases
|
||||||
(other == self)
|
(other == self)
|
||||||
|| self.bases(db).any(|base| match base {
|
|| self.bases(db).any(|base| match base {
|
||||||
Type::Class(base_class) => base_class == other,
|
Type::ClassLiteral(base_class) => base_class == other,
|
||||||
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
|
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
|
||||||
// participate, so we should not return `True` if we find `Any/Unknown` in the
|
// participate, so we should not return `True` if we find `Any/Unknown` in the
|
||||||
// bases.
|
// bases.
|
||||||
|
@ -1923,6 +1938,32 @@ mod tests {
|
||||||
assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_subtype_of_class_literals() {
|
||||||
|
let mut db = setup_db();
|
||||||
|
db.write_dedented(
|
||||||
|
"/src/module.py",
|
||||||
|
"
|
||||||
|
class A: ...
|
||||||
|
class B: ...
|
||||||
|
U = A if flag else B
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
|
||||||
|
|
||||||
|
let type_a = super::global_symbol_ty(&db, module, "A");
|
||||||
|
let type_u = super::global_symbol_ty(&db, module, "U");
|
||||||
|
|
||||||
|
assert!(type_a.is_class_literal());
|
||||||
|
assert!(type_a.is_subtype_of(&db, Ty::BuiltinInstance("type").into_type(&db)));
|
||||||
|
assert!(type_a.is_subtype_of(&db, Ty::BuiltinInstance("object").into_type(&db)));
|
||||||
|
|
||||||
|
assert!(type_u.is_union());
|
||||||
|
assert!(type_u.is_subtype_of(&db, Ty::BuiltinInstance("type").into_type(&db)));
|
||||||
|
assert!(type_u.is_subtype_of(&db, Ty::BuiltinInstance("object").into_type(&db)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(
|
#[test_case(
|
||||||
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]),
|
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]),
|
||||||
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)])
|
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)])
|
||||||
|
@ -2005,19 +2046,19 @@ mod tests {
|
||||||
"
|
"
|
||||||
class A: ...
|
class A: ...
|
||||||
class B: ...
|
class B: ...
|
||||||
x = A if flag else B
|
U = A if flag else B
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
|
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
|
||||||
|
|
||||||
let type_a = super::global_symbol_ty(&db, module, "A");
|
let type_a = super::global_symbol_ty(&db, module, "A");
|
||||||
let type_x = super::global_symbol_ty(&db, module, "x");
|
let type_u = super::global_symbol_ty(&db, module, "U");
|
||||||
|
|
||||||
assert!(matches!(type_a, Type::Class(_)));
|
assert!(type_a.is_class_literal());
|
||||||
assert!(matches!(type_x, Type::Union(_)));
|
assert!(type_u.is_union());
|
||||||
|
|
||||||
assert!(!type_a.is_disjoint_from(&db, type_x));
|
assert!(!type_a.is_disjoint_from(&db, type_u));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(Ty::None)]
|
#[test_case(Ty::None)]
|
||||||
|
|
|
@ -231,7 +231,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
if let Some(&Type::BooleanLiteral(value)) = self
|
if let Some(&Type::BooleanLiteral(value)) = self
|
||||||
.negative
|
.negative
|
||||||
.iter()
|
.iter()
|
||||||
.find(|element| matches!(element, Type::BooleanLiteral(..)))
|
.find(|element| element.is_boolean_literal())
|
||||||
{
|
{
|
||||||
*self = Self::new();
|
*self = Self::new();
|
||||||
self.positive.insert(Type::BooleanLiteral(!value));
|
self.positive.insert(Type::BooleanLiteral(!value));
|
||||||
|
|
|
@ -34,8 +34,8 @@ impl Display for DisplayType<'_> {
|
||||||
| Type::BooleanLiteral(_)
|
| Type::BooleanLiteral(_)
|
||||||
| Type::StringLiteral(_)
|
| Type::StringLiteral(_)
|
||||||
| Type::BytesLiteral(_)
|
| Type::BytesLiteral(_)
|
||||||
| Type::Class(_)
|
| Type::ClassLiteral(_)
|
||||||
| Type::Function(_)
|
| Type::FunctionLiteral(_)
|
||||||
) {
|
) {
|
||||||
write!(f, "Literal[{representation}]")
|
write!(f, "Literal[{representation}]")
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,13 +69,13 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||||
// any other type
|
// any other type
|
||||||
Type::Todo => f.write_str("@Todo"),
|
Type::Todo => f.write_str("@Todo"),
|
||||||
Type::Module(file) => {
|
Type::ModuleLiteral(file) => {
|
||||||
write!(f, "<module '{:?}'>", file.path(self.db))
|
write!(f, "<module '{:?}'>", file.path(self.db))
|
||||||
}
|
}
|
||||||
// TODO functions and classes should display using a fully qualified name
|
// TODO functions and classes should display using a fully qualified name
|
||||||
Type::Class(class) => f.write_str(class.name(self.db)),
|
Type::ClassLiteral(class) => f.write_str(class.name(self.db)),
|
||||||
Type::Instance(class) => f.write_str(class.name(self.db)),
|
Type::Instance(class) => f.write_str(class.name(self.db)),
|
||||||
Type::Function(function) => f.write_str(function.name(self.db)),
|
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||||
Type::Union(union) => union.display(self.db).fmt(f),
|
Type::Union(union) => union.display(self.db).fmt(f),
|
||||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||||
Type::IntLiteral(n) => n.fmt(f),
|
Type::IntLiteral(n) => n.fmt(f),
|
||||||
|
@ -119,13 +119,13 @@ impl Display for DisplayUnionType<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
let elements = self.ty.elements(self.db);
|
let elements = self.ty.elements(self.db);
|
||||||
|
|
||||||
// Group literal types by kind.
|
// Group condensed-display types by kind.
|
||||||
let mut grouped_literals = FxHashMap::default();
|
let mut grouped_condensed_kinds = FxHashMap::default();
|
||||||
|
|
||||||
for element in elements {
|
for element in elements {
|
||||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
|
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
|
||||||
grouped_literals
|
grouped_condensed_kinds
|
||||||
.entry(literal_kind)
|
.entry(kind)
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
.push(*element);
|
.push(*element);
|
||||||
}
|
}
|
||||||
|
@ -134,15 +134,15 @@ impl Display for DisplayUnionType<'_> {
|
||||||
let mut join = f.join(" | ");
|
let mut join = f.join(" | ");
|
||||||
|
|
||||||
for element in elements {
|
for element in elements {
|
||||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
|
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
|
||||||
let Some(mut literals) = grouped_literals.remove(&literal_kind) else {
|
let Some(mut condensed_kind) = grouped_condensed_kinds.remove(&kind) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if literal_kind == LiteralTypeKind::IntLiteral {
|
if kind == CondensedDisplayTypeKind::Int {
|
||||||
literals.sort_unstable_by_key(|ty| ty.expect_int_literal());
|
condensed_kind.sort_unstable_by_key(|ty| ty.expect_int_literal());
|
||||||
}
|
}
|
||||||
join.entry(&DisplayLiteralGroup {
|
join.entry(&DisplayLiteralGroup {
|
||||||
literals,
|
literals: condensed_kind,
|
||||||
db: self.db,
|
db: self.db,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,7 +152,7 @@ impl Display for DisplayUnionType<'_> {
|
||||||
|
|
||||||
join.finish()?;
|
join.finish()?;
|
||||||
|
|
||||||
debug_assert!(grouped_literals.is_empty());
|
debug_assert!(grouped_condensed_kinds.is_empty());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -179,25 +179,31 @@ impl Display for DisplayLiteralGroup<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumeration of literal types that are displayed in a "condensed way" inside `Literal` slices.
|
||||||
|
///
|
||||||
|
/// For example, `Literal[1] | Literal[2]` is displayed as `"Literal[1, 2]"`.
|
||||||
|
/// Not all `Literal` types are displayed using `Literal` slices
|
||||||
|
/// (e.g. it would be inappropriate to display `LiteralString`
|
||||||
|
/// as `Literal[LiteralString]`).
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
enum LiteralTypeKind {
|
enum CondensedDisplayTypeKind {
|
||||||
Class,
|
Class,
|
||||||
Function,
|
Function,
|
||||||
IntLiteral,
|
Int,
|
||||||
StringLiteral,
|
String,
|
||||||
BytesLiteral,
|
Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Type<'_>> for LiteralTypeKind {
|
impl TryFrom<Type<'_>> for CondensedDisplayTypeKind {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(value: Type<'_>) -> Result<Self, Self::Error> {
|
fn try_from(value: Type<'_>) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
Type::Class(_) => Ok(Self::Class),
|
Type::ClassLiteral(_) => Ok(Self::Class),
|
||||||
Type::Function(_) => Ok(Self::Function),
|
Type::FunctionLiteral(_) => Ok(Self::Function),
|
||||||
Type::IntLiteral(_) => Ok(Self::IntLiteral),
|
Type::IntLiteral(_) => Ok(Self::Int),
|
||||||
Type::StringLiteral(_) => Ok(Self::StringLiteral),
|
Type::StringLiteral(_) => Ok(Self::String),
|
||||||
Type::BytesLiteral(_) => Ok(Self::BytesLiteral),
|
Type::BytesLiteral(_) => Ok(Self::Bytes),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -510,12 +510,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
assigned_ty: Type<'db>,
|
assigned_ty: Type<'db>,
|
||||||
) {
|
) {
|
||||||
match declared_ty {
|
match declared_ty {
|
||||||
Type::Class(class) => {
|
Type::ClassLiteral(class) => {
|
||||||
self.add_diagnostic(node, "invalid-assignment", format_args!(
|
self.add_diagnostic(node, "invalid-assignment", format_args!(
|
||||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||||
class.name(self.db)));
|
class.name(self.db)));
|
||||||
}
|
}
|
||||||
Type::Function(function) => {
|
Type::FunctionLiteral(function) => {
|
||||||
self.add_diagnostic(node, "invalid-assignment", format_args!(
|
self.add_diagnostic(node, "invalid-assignment", format_args!(
|
||||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||||
function.name(self.db)));
|
function.name(self.db)));
|
||||||
|
@ -778,7 +778,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let function_ty = Type::Function(FunctionType::new(
|
let function_ty = Type::FunctionLiteral(FunctionType::new(
|
||||||
self.db,
|
self.db,
|
||||||
name.id.clone(),
|
name.id.clone(),
|
||||||
function_kind,
|
function_kind,
|
||||||
|
@ -887,7 +887,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|module| KnownClass::maybe_from_module(module, name.as_str()));
|
.and_then(|module| KnownClass::maybe_from_module(module, name.as_str()));
|
||||||
|
|
||||||
let class_ty = Type::Class(ClassType::new(
|
let class_ty = Type::ClassLiteral(ClassType::new(
|
||||||
self.db,
|
self.db,
|
||||||
name.id.clone(),
|
name.id.clone(),
|
||||||
definition,
|
definition,
|
||||||
|
@ -1056,13 +1056,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// anything else is invalid and should lead to a diagnostic being reported --Alex
|
// anything else is invalid and should lead to a diagnostic being reported --Alex
|
||||||
match node_ty {
|
match node_ty {
|
||||||
Type::Any | Type::Unknown => node_ty,
|
Type::Any | Type::Unknown => node_ty,
|
||||||
Type::Class(class_ty) => Type::Instance(class_ty),
|
Type::ClassLiteral(class_ty) => Type::Instance(class_ty),
|
||||||
Type::Tuple(tuple) => UnionType::from_elements(
|
Type::Tuple(tuple) => UnionType::from_elements(
|
||||||
self.db,
|
self.db,
|
||||||
tuple
|
tuple.elements(self.db).iter().map(|ty| {
|
||||||
.elements(self.db)
|
ty.into_class_literal_type()
|
||||||
.iter()
|
.map_or(Type::Todo, Type::Instance)
|
||||||
.map(|ty| ty.into_class_type().map_or(Type::Todo, Type::Instance)),
|
}),
|
||||||
),
|
),
|
||||||
_ => Type::Todo,
|
_ => Type::Todo,
|
||||||
}
|
}
|
||||||
|
@ -1311,7 +1311,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let value_ty = if matches!(value_ty, Type::LiteralString) {
|
let value_ty = if value_ty.is_literal_string() {
|
||||||
Type::LiteralString
|
Type::LiteralString
|
||||||
} else {
|
} else {
|
||||||
value_ty
|
value_ty
|
||||||
|
@ -1773,7 +1773,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_ty_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
|
fn module_ty_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
|
||||||
resolve_module(self.db, module_name).map(|module| Type::Module(module.file()))
|
resolve_module(self.db, module_name).map(|module| Type::ModuleLiteral(module.file()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {
|
fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {
|
||||||
|
@ -3311,7 +3311,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// even if the target version is Python 3.8 or lower,
|
// even if the target version is Python 3.8 or lower,
|
||||||
// despite the fact that there will be no corresponding `__class_getitem__`
|
// despite the fact that there will be no corresponding `__class_getitem__`
|
||||||
// method in these `sys.version_info` branches.
|
// method in these `sys.version_info` branches.
|
||||||
if value_ty.is_class(self.db) {
|
if value_ty.is_subtype_of(self.db, KnownClass::Type.to_instance(self.db)) {
|
||||||
let dunder_class_getitem_method = value_ty.member(self.db, "__class_getitem__");
|
let dunder_class_getitem_method = value_ty.member(self.db, "__class_getitem__");
|
||||||
if !dunder_class_getitem_method.is_unbound() {
|
if !dunder_class_getitem_method.is_unbound() {
|
||||||
return dunder_class_getitem_method
|
return dunder_class_getitem_method
|
||||||
|
@ -3331,7 +3331,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(value_ty, Type::Class(class) if class.is_known(self.db, KnownClass::Type))
|
if matches!(value_ty, Type::ClassLiteral(class) if class.is_known(self.db, KnownClass::Type))
|
||||||
{
|
{
|
||||||
return KnownClass::GenericAlias.to_instance(self.db);
|
return KnownClass::GenericAlias.to_instance(self.db);
|
||||||
}
|
}
|
||||||
|
@ -3907,7 +3907,7 @@ mod tests {
|
||||||
let mod_file = system_path_to_file(&db, "src/mod.py").expect("file to exist");
|
let mod_file = system_path_to_file(&db, "src/mod.py").expect("file to exist");
|
||||||
let ty = global_symbol_ty(&db, mod_file, "Sub");
|
let ty = global_symbol_ty(&db, mod_file, "Sub");
|
||||||
|
|
||||||
let class = ty.expect_class();
|
let class = ty.expect_class_literal();
|
||||||
|
|
||||||
let base_names: Vec<_> = class
|
let base_names: Vec<_> = class
|
||||||
.bases(&db)
|
.bases(&db)
|
||||||
|
@ -3933,9 +3933,9 @@ mod tests {
|
||||||
|
|
||||||
let mod_file = system_path_to_file(&db, "src/mod.py").unwrap();
|
let mod_file = system_path_to_file(&db, "src/mod.py").unwrap();
|
||||||
let ty = global_symbol_ty(&db, mod_file, "C");
|
let ty = global_symbol_ty(&db, mod_file, "C");
|
||||||
let class_id = ty.expect_class();
|
let class_id = ty.expect_class_literal();
|
||||||
let member_ty = class_id.class_member(&db, &Name::new_static("f"));
|
let member_ty = class_id.class_member(&db, &Name::new_static("f"));
|
||||||
let func = member_ty.expect_function();
|
let func = member_ty.expect_function_literal();
|
||||||
|
|
||||||
assert_eq!(func.name(&db), "f");
|
assert_eq!(func.name(&db), "f");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -4113,7 +4113,7 @@ mod tests {
|
||||||
db.write_file("src/a.py", "def example() -> int: return 42")?;
|
db.write_file("src/a.py", "def example() -> int: return 42")?;
|
||||||
|
|
||||||
let mod_file = system_path_to_file(&db, "src/a.py").unwrap();
|
let mod_file = system_path_to_file(&db, "src/a.py").unwrap();
|
||||||
let function = global_symbol_ty(&db, mod_file, "example").expect_function();
|
let function = global_symbol_ty(&db, mod_file, "example").expect_function_literal();
|
||||||
let returns = function.return_type(&db);
|
let returns = function.return_type(&db);
|
||||||
assert_eq!(returns.display(&db).to_string(), "int");
|
assert_eq!(returns.display(&db).to_string(), "int");
|
||||||
|
|
||||||
|
@ -4142,14 +4142,14 @@ mod tests {
|
||||||
|
|
||||||
let a = system_path_to_file(&db, "src/a.py").expect("file to exist");
|
let a = system_path_to_file(&db, "src/a.py").expect("file to exist");
|
||||||
let c_ty = global_symbol_ty(&db, a, "C");
|
let c_ty = global_symbol_ty(&db, a, "C");
|
||||||
let c_class = c_ty.expect_class();
|
let c_class = c_ty.expect_class_literal();
|
||||||
let mut c_bases = c_class.bases(&db);
|
let mut c_bases = c_class.bases(&db);
|
||||||
let b_ty = c_bases.next().unwrap();
|
let b_ty = c_bases.next().unwrap();
|
||||||
let b_class = b_ty.expect_class();
|
let b_class = b_ty.expect_class_literal();
|
||||||
assert_eq!(b_class.name(&db), "B");
|
assert_eq!(b_class.name(&db), "B");
|
||||||
let mut b_bases = b_class.bases(&db);
|
let mut b_bases = b_class.bases(&db);
|
||||||
let a_ty = b_bases.next().unwrap();
|
let a_ty = b_bases.next().unwrap();
|
||||||
let a_class = a_ty.expect_class();
|
let a_class = a_ty.expect_class_literal();
|
||||||
assert_eq!(a_class.name(&db), "A");
|
assert_eq!(a_class.name(&db), "A");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -4299,7 +4299,7 @@ mod tests {
|
||||||
let ty = global_symbol_ty(&db, file, "C");
|
let ty = global_symbol_ty(&db, file, "C");
|
||||||
|
|
||||||
let base = ty
|
let base = ty
|
||||||
.expect_class()
|
.expect_class_literal()
|
||||||
.bases(&db)
|
.bases(&db)
|
||||||
.next()
|
.next()
|
||||||
.expect("there should be at least one base");
|
.expect("there should be at least one base");
|
||||||
|
|
|
@ -142,7 +142,7 @@ fn lint_bad_override(context: &SemanticLintContext, class: &ast::StmtClassDef) {
|
||||||
|
|
||||||
let override_ty = semantic.global_symbol_ty(&typing, "override");
|
let override_ty = semantic.global_symbol_ty(&typing, "override");
|
||||||
|
|
||||||
let Type::Class(class_ty) = class.ty(semantic) else {
|
let Type::ClassLiteral(class_ty) = class.ty(semantic) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ fn lint_bad_override(context: &SemanticLintContext, class: &ast::StmtClassDef) {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|stmt| stmt.as_function_def_stmt())
|
.filter_map(|stmt| stmt.as_function_def_stmt())
|
||||||
{
|
{
|
||||||
let Type::Function(ty) = function.ty(semantic) else {
|
let Type::FunctionLiteral(ty) = function.ty(semantic) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue