From 6e4e09ea14369551378949862a002caf4c886c71 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 12 Dec 2024 15:05:49 +0000 Subject: [PATCH] [red-knot] Move the `ClassBase` enum to its own submodule --- crates/red_knot_python_semantic/src/types.rs | 4 +- .../src/types/class_base.rs | 173 ++++++++++++++++++ .../src/types/display.rs | 2 +- .../src/types/infer.rs | 3 +- .../red_knot_python_semantic/src/types/mro.rs | 172 +---------------- 5 files changed, 181 insertions(+), 173 deletions(-) create mode 100644 crates/red_knot_python_semantic/src/types/class_base.rs diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 83b02eddb6cf5..150370ec8355a 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -27,13 +27,15 @@ use crate::stdlib::{ }; use crate::symbol::{Boundness, Symbol}; use crate::types::call::{CallDunderResult, CallOutcome}; +use crate::types::class_base::ClassBase; use crate::types::diagnostic::TypeCheckDiagnosticsBuilder; -use crate::types::mro::{ClassBase, Mro, MroError, MroIterator}; +use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::narrow::narrowing_constraint; use crate::{Db, FxOrderSet, Module, Program, PythonVersion}; mod builder; mod call; +mod class_base; mod diagnostic; mod display; mod infer; diff --git a/crates/red_knot_python_semantic/src/types/class_base.rs b/crates/red_knot_python_semantic/src/types/class_base.rs new file mode 100644 index 0000000000000..2a3c5b63403bf --- /dev/null +++ b/crates/red_knot_python_semantic/src/types/class_base.rs @@ -0,0 +1,173 @@ +use crate::types::{ + todo_type, Class, ClassLiteralType, KnownClass, KnownInstanceType, TodoType, Type, +}; +use crate::Db; +use itertools::Either; + +/// Enumeration of the possible kinds of types we allow in class bases. +/// +/// This is much more limited than the [`Type`] enum: +/// all types that would be invalid to have as a class base are +/// transformed into [`ClassBase::Unknown`] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update)] +pub enum ClassBase<'db> { + Any, + Unknown, + Todo(TodoType), + Class(Class<'db>), +} + +impl<'db> ClassBase<'db> { + pub fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { + struct Display<'db> { + base: ClassBase<'db>, + db: &'db dyn Db, + } + + impl std::fmt::Display for Display<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.base { + ClassBase::Any => f.write_str("Any"), + ClassBase::Todo(todo) => todo.fmt(f), + ClassBase::Unknown => f.write_str("Unknown"), + ClassBase::Class(class) => write!(f, "", class.name(self.db)), + } + } + } + + Display { base: self, db } + } + + /// Return a `ClassBase` representing the class `builtins.object` + pub(super) fn object(db: &'db dyn Db) -> Self { + KnownClass::Object + .to_class_literal(db) + .into_class_literal() + .map_or(Self::Unknown, |ClassLiteralType { class }| { + Self::Class(class) + }) + } + + /// Attempt to resolve `ty` into a `ClassBase`. + /// + /// Return `None` if `ty` is not an acceptable type for a class base. + pub(super) fn try_from_ty(db: &'db dyn Db, ty: Type<'db>) -> Option { + match ty { + Type::Any => Some(Self::Any), + Type::Unknown => Some(Self::Unknown), + Type::Todo(todo) => Some(Self::Todo(todo)), + Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)), + Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? + Type::Intersection(_) => None, // TODO -- probably incorrect? + Type::Instance(_) => None, // TODO -- handle `__mro_entries__`? + Type::Never + | Type::BooleanLiteral(_) + | Type::FunctionLiteral(_) + | Type::BytesLiteral(_) + | Type::IntLiteral(_) + | Type::StringLiteral(_) + | Type::LiteralString + | Type::Tuple(_) + | Type::SliceLiteral(_) + | Type::ModuleLiteral(_) + | Type::SubclassOf(_) => None, + Type::KnownInstance(known_instance) => match known_instance { + KnownInstanceType::TypeVar(_) + | KnownInstanceType::TypeAliasType(_) + | KnownInstanceType::Literal + | KnownInstanceType::LiteralString + | KnownInstanceType::Union + | KnownInstanceType::NoReturn + | KnownInstanceType::Never + | KnownInstanceType::Final + | KnownInstanceType::NotRequired + | KnownInstanceType::TypeGuard + | KnownInstanceType::TypeIs + | KnownInstanceType::TypingSelf + | KnownInstanceType::Unpack + | KnownInstanceType::ClassVar + | KnownInstanceType::Concatenate + | KnownInstanceType::Required + | KnownInstanceType::TypeAlias + | KnownInstanceType::ReadOnly + | KnownInstanceType::Optional => None, + KnownInstanceType::Any => Some(Self::Any), + // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO + KnownInstanceType::Dict => { + Self::try_from_ty(db, KnownClass::Dict.to_class_literal(db)) + } + KnownInstanceType::List => { + Self::try_from_ty(db, KnownClass::List.to_class_literal(db)) + } + KnownInstanceType::Type => { + Self::try_from_ty(db, KnownClass::Type.to_class_literal(db)) + } + KnownInstanceType::Tuple => { + Self::try_from_ty(db, KnownClass::Tuple.to_class_literal(db)) + } + KnownInstanceType::Set => { + Self::try_from_ty(db, KnownClass::Set.to_class_literal(db)) + } + KnownInstanceType::FrozenSet => { + Self::try_from_ty(db, KnownClass::FrozenSet.to_class_literal(db)) + } + KnownInstanceType::Callable + | KnownInstanceType::ChainMap + | KnownInstanceType::Counter + | KnownInstanceType::DefaultDict + | KnownInstanceType::Deque + | KnownInstanceType::OrderedDict => Self::try_from_ty( + db, + todo_type!("Support for more typing aliases as base classes"), + ), + }, + } + } + + pub(super) fn into_class_literal_type(self) -> Option> { + match self { + Self::Class(class) => Some(class), + _ => None, + } + } + + /// Iterate over the MRO of this base + pub(super) fn mro( + self, + db: &'db dyn Db, + ) -> Either>, impl Iterator>> { + match self { + ClassBase::Any => Either::Left([ClassBase::Any, ClassBase::object(db)].into_iter()), + ClassBase::Unknown => { + Either::Left([ClassBase::Unknown, ClassBase::object(db)].into_iter()) + } + ClassBase::Todo(todo) => { + Either::Left([ClassBase::Todo(todo), ClassBase::object(db)].into_iter()) + } + ClassBase::Class(class) => Either::Right(class.iter_mro(db)), + } + } +} + +impl<'db> From> for ClassBase<'db> { + fn from(value: Class<'db>) -> Self { + ClassBase::Class(value) + } +} + +impl<'db> From> for Type<'db> { + fn from(value: ClassBase<'db>) -> Self { + match value { + ClassBase::Any => Type::Any, + ClassBase::Todo(todo) => Type::Todo(todo), + ClassBase::Unknown => Type::Unknown, + ClassBase::Class(class) => Type::class_literal(class), + } + } +} + +impl<'db> From<&ClassBase<'db>> for Type<'db> { + fn from(value: &ClassBase<'db>) -> Self { + Self::from(*value) + } +} diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 600fde8b9e7ff..49a629aa9eef5 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -6,7 +6,7 @@ use ruff_db::display::FormatterJoinExtension; use ruff_python_ast::str::Quote; use ruff_python_literal::escape::AsciiEscape; -use crate::types::mro::ClassBase; +use crate::types::class_base::ClassBase; use crate::types::{ ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, SubclassOfType, Type, UnionType, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 1b732ce5311b8..cccef4ece49e3 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -48,6 +48,7 @@ use crate::semantic_index::semantic_index; use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId}; use crate::semantic_index::SemanticIndex; use crate::stdlib::builtins_module_scope; +use crate::types::class_base::ClassBase; use crate::types::diagnostic::{ TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, @@ -57,7 +58,7 @@ use crate::types::diagnostic::{ INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR, }; -use crate::types::mro::{ClassBase, MroErrorKind}; +use crate::types::mro::MroErrorKind; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type, diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index e8ca1caf1e953..908e259670fb2 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -1,10 +1,10 @@ use std::collections::VecDeque; use std::ops::Deref; -use itertools::Either; use rustc_hash::FxHashSet; -use super::{todo_type, Class, ClassLiteralType, KnownClass, KnownInstanceType, TodoType, Type}; +use crate::types::class_base::ClassBase; +use crate::types::{Class, KnownClass, Type}; use crate::Db; /// The inferred method resolution order of a given class. @@ -287,174 +287,6 @@ pub(super) enum MroErrorKind<'db> { UnresolvableMro { bases_list: Box<[ClassBase<'db>]> }, } -/// Enumeration of the possible kinds of types we allow in class bases. -/// -/// This is much more limited than the [`Type`] enum: -/// all types that would be invalid to have as a class base are -/// transformed into [`ClassBase::Unknown`] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update)] -pub enum ClassBase<'db> { - Any, - Unknown, - Todo(TodoType), - Class(Class<'db>), -} - -impl<'db> ClassBase<'db> { - pub fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { - struct Display<'db> { - base: ClassBase<'db>, - db: &'db dyn Db, - } - - impl std::fmt::Display for Display<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.base { - ClassBase::Any => f.write_str("Any"), - ClassBase::Todo(todo) => todo.fmt(f), - ClassBase::Unknown => f.write_str("Unknown"), - ClassBase::Class(class) => write!(f, "", class.name(self.db)), - } - } - } - - Display { base: self, db } - } - - /// Return a `ClassBase` representing the class `builtins.object` - fn object(db: &'db dyn Db) -> Self { - KnownClass::Object - .to_class_literal(db) - .into_class_literal() - .map_or(Self::Unknown, |ClassLiteralType { class }| { - Self::Class(class) - }) - } - - /// Attempt to resolve `ty` into a `ClassBase`. - /// - /// Return `None` if `ty` is not an acceptable type for a class base. - pub(super) fn try_from_ty(db: &'db dyn Db, ty: Type<'db>) -> Option { - match ty { - Type::Any => Some(Self::Any), - Type::Unknown => Some(Self::Unknown), - Type::Todo(todo) => Some(Self::Todo(todo)), - Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)), - Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? - Type::Intersection(_) => None, // TODO -- probably incorrect? - Type::Instance(_) => None, // TODO -- handle `__mro_entries__`? - Type::Never - | Type::BooleanLiteral(_) - | Type::FunctionLiteral(_) - | Type::BytesLiteral(_) - | Type::IntLiteral(_) - | Type::StringLiteral(_) - | Type::LiteralString - | Type::Tuple(_) - | Type::SliceLiteral(_) - | Type::ModuleLiteral(_) - | Type::SubclassOf(_) => None, - Type::KnownInstance(known_instance) => match known_instance { - KnownInstanceType::TypeVar(_) - | KnownInstanceType::TypeAliasType(_) - | KnownInstanceType::Literal - | KnownInstanceType::LiteralString - | KnownInstanceType::Union - | KnownInstanceType::NoReturn - | KnownInstanceType::Never - | KnownInstanceType::Final - | KnownInstanceType::NotRequired - | KnownInstanceType::TypeGuard - | KnownInstanceType::TypeIs - | KnownInstanceType::TypingSelf - | KnownInstanceType::Unpack - | KnownInstanceType::ClassVar - | KnownInstanceType::Concatenate - | KnownInstanceType::Required - | KnownInstanceType::TypeAlias - | KnownInstanceType::ReadOnly - | KnownInstanceType::Optional => None, - KnownInstanceType::Any => Some(Self::Any), - // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO - KnownInstanceType::Dict => { - ClassBase::try_from_ty(db, KnownClass::Dict.to_class_literal(db)) - } - KnownInstanceType::List => { - ClassBase::try_from_ty(db, KnownClass::List.to_class_literal(db)) - } - KnownInstanceType::Type => { - ClassBase::try_from_ty(db, KnownClass::Type.to_class_literal(db)) - } - KnownInstanceType::Tuple => { - ClassBase::try_from_ty(db, KnownClass::Tuple.to_class_literal(db)) - } - KnownInstanceType::Set => { - ClassBase::try_from_ty(db, KnownClass::Set.to_class_literal(db)) - } - KnownInstanceType::FrozenSet => { - ClassBase::try_from_ty(db, KnownClass::FrozenSet.to_class_literal(db)) - } - KnownInstanceType::Callable - | KnownInstanceType::ChainMap - | KnownInstanceType::Counter - | KnownInstanceType::DefaultDict - | KnownInstanceType::Deque - | KnownInstanceType::OrderedDict => Self::try_from_ty( - db, - todo_type!("Support for more typing aliases as base classes"), - ), - }, - } - } - - fn into_class_literal_type(self) -> Option> { - match self { - Self::Class(class) => Some(class), - _ => None, - } - } - - /// Iterate over the MRO of this base - fn mro( - self, - db: &'db dyn Db, - ) -> Either>, impl Iterator>> { - match self { - ClassBase::Any => Either::Left([ClassBase::Any, ClassBase::object(db)].into_iter()), - ClassBase::Unknown => { - Either::Left([ClassBase::Unknown, ClassBase::object(db)].into_iter()) - } - ClassBase::Todo(todo) => { - Either::Left([ClassBase::Todo(todo), ClassBase::object(db)].into_iter()) - } - ClassBase::Class(class) => Either::Right(class.iter_mro(db)), - } - } -} - -impl<'db> From> for ClassBase<'db> { - fn from(value: Class<'db>) -> Self { - ClassBase::Class(value) - } -} - -impl<'db> From> for Type<'db> { - fn from(value: ClassBase<'db>) -> Self { - match value { - ClassBase::Any => Type::Any, - ClassBase::Todo(todo) => Type::Todo(todo), - ClassBase::Unknown => Type::Unknown, - ClassBase::Class(class) => Type::class_literal(class), - } - } -} - -impl<'db> From<&ClassBase<'db>> for Type<'db> { - fn from(value: &ClassBase<'db>) -> Self { - Self::from(*value) - } -} - /// Implementation of the [C3-merge algorithm] for calculating a Python class's /// [method resolution order]. ///