From 9180635171dcb61bf2b96ab7a489fe0f549d78d7 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 11 Nov 2024 11:54:42 +0000 Subject: [PATCH] [red-knot] Cleanup some `KnownClass` APIs (#14269) --- crates/red_knot_python_semantic/src/stdlib.rs | 35 ++--- crates/red_knot_python_semantic/src/types.rs | 120 +++++++++--------- .../src/types/infer.rs | 2 +- .../red_knot_python_semantic/src/types/mro.rs | 2 +- 4 files changed, 76 insertions(+), 83 deletions(-) diff --git a/crates/red_knot_python_semantic/src/stdlib.rs b/crates/red_knot_python_semantic/src/stdlib.rs index a212c17b34..f752b8155c 100644 --- a/crates/red_knot_python_semantic/src/stdlib.rs +++ b/crates/red_knot_python_semantic/src/stdlib.rs @@ -8,7 +8,7 @@ use crate::Db; /// Enumeration of various core stdlib modules, for which we have dedicated Salsa queries. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum CoreStdlibModule { +pub(crate) enum CoreStdlibModule { Builtins, Types, Typeshed, @@ -17,23 +17,27 @@ enum CoreStdlibModule { } impl CoreStdlibModule { - fn name(self) -> ModuleName { - let module_name = match self { + pub(crate) const fn as_str(self) -> &'static str { + match self { Self::Builtins => "builtins", Self::Types => "types", Self::Typing => "typing", Self::Typeshed => "_typeshed", Self::TypingExtensions => "typing_extensions", - }; - ModuleName::new_static(module_name) - .unwrap_or_else(|| panic!("{module_name} should be a valid module name!")) + } + } + + pub(crate) fn name(self) -> ModuleName { + let self_as_str = self.as_str(); + ModuleName::new_static(self_as_str) + .unwrap_or_else(|| panic!("{self_as_str} should be a valid module name!")) } } /// Lookup the type of `symbol` in a given core module /// /// Returns `Symbol::Unbound` if the given core module cannot be resolved for some reason -fn core_module_symbol<'db>( +pub(crate) fn core_module_symbol<'db>( db: &'db dyn Db, core_module: CoreStdlibModule, symbol: &str, @@ -51,29 +55,14 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> core_module_symbol(db, CoreStdlibModule::Builtins, symbol) } -/// Lookup the type of `symbol` in the `types` module namespace. -/// -/// Returns `Symbol::Unbound` if the `types` module isn't available for some reason. -#[inline] -pub(crate) fn types_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> { - core_module_symbol(db, CoreStdlibModule::Types, symbol) -} - /// Lookup the type of `symbol` in the `typing` module namespace. /// /// Returns `Symbol::Unbound` if the `typing` module isn't available for some reason. #[inline] -#[allow(dead_code)] // currently only used in tests +#[cfg(test)] pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> { core_module_symbol(db, CoreStdlibModule::Typing, symbol) } -/// Lookup the type of `symbol` in the `_typeshed` module namespace. -/// -/// Returns `Symbol::Unbound` if the `_typeshed` module isn't available for some reason. -#[inline] -pub(crate) fn typeshed_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> { - core_module_symbol(db, CoreStdlibModule::Typeshed, symbol) -} /// Lookup the type of `symbol` in the `typing_extensions` module namespace. /// diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4f895b3fa3..ecd266ea6f 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -14,7 +14,7 @@ use crate::semantic_index::{ BindingWithConstraintsIterator, DeclarationsIterator, }; use crate::stdlib::{ - builtins_symbol, types_symbol, typeshed_symbol, typing_extensions_symbol, typing_symbol, + builtins_symbol, core_module_symbol, typing_extensions_symbol, CoreStdlibModule, }; use crate::symbol::{Boundness, Symbol}; use crate::types::diagnostic::TypeCheckDiagnosticsBuilder; @@ -126,7 +126,10 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> /// so the cost of hashing the names is likely to be more expensive than it's worth. #[salsa::tracked(return_ref)] fn module_type_symbols<'db>(db: &'db dyn Db) -> smallvec::SmallVec<[ast::name::Name; 8]> { - let Some(module_type) = KnownClass::ModuleType.to_class(db).into_class_literal() else { + let Some(module_type) = KnownClass::ModuleType + .to_class_literal(db) + .into_class_literal() + else { // The most likely way we get here is if a user specified a `--custom-typeshed-dir` // without a `types.pyi` stub in the `stdlib/` directory return smallvec::SmallVec::default(); @@ -167,7 +170,7 @@ pub(crate) fn global_symbol<'db>(db: &'db dyn Db, file: File, name: &str) -> Sym { // TODO: this should use `.to_instance(db)`. but we don't understand attribute access // on instance types yet. - let module_type_member = KnownClass::ModuleType.to_class(db).member(db, name); + let module_type_member = KnownClass::ModuleType.to_class_literal(db).member(db, name); return explicit_symbol.replace_unbound_with(db, &module_type_member); } @@ -1069,7 +1072,7 @@ impl<'db> Type<'db> { if name != "__getattr__" && global_lookup.may_be_unbound() { // TODO: this should use `.to_instance()`, but we don't understand instance attribute yet let module_type_instance_member = - KnownClass::ModuleType.to_class(db).member(db, name); + KnownClass::ModuleType.to_class_literal(db).member(db, name); global_lookup.replace_unbound_with(db, &module_type_instance_member) } else { global_lookup @@ -1424,25 +1427,25 @@ impl<'db> Type<'db> { Type::Instance(InstanceType { class }) => { Type::SubclassOf(SubclassOfType { class: *class }) } - Type::KnownInstance(known_instance) => known_instance.class().to_class(db), + Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db), Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)), - Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db), - Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db), - Type::SliceLiteral(_) => KnownClass::Slice.to_class(db), - Type::IntLiteral(_) => KnownClass::Int.to_class(db), - Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class(db), - Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db), - Type::Tuple(_) => KnownClass::Tuple.to_class(db), + Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db), + Type::BytesLiteral(_) => KnownClass::Bytes.to_class_literal(db), + Type::SliceLiteral(_) => KnownClass::Slice.to_class_literal(db), + Type::IntLiteral(_) => KnownClass::Int.to_class_literal(db), + Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class_literal(db), + Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), + Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db), Type::SubclassOf(SubclassOfType { class }) => Type::subclass_of( class .try_metaclass(db) .ok() .and_then(Type::into_class_literal) - .unwrap_or_else(|| KnownClass::Type.to_class(db).expect_class_literal()) + .unwrap_or_else(|| KnownClass::Type.to_class_literal(db).expect_class_literal()) .class, ), - Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db), + Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db), // TODO: `type[Any]`? Type::Any => Type::Any, // TODO: `type[Unknown]`? @@ -1566,10 +1569,15 @@ impl<'db> KnownClass { } pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> { - self.to_class(db).to_instance(db) + self.to_class_literal(db).to_instance(db) } - pub fn to_class(&self, db: &'db dyn Db) -> Type<'db> { + pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> { + core_module_symbol(db, self.canonical_module(), self.as_str()).unwrap_or_unknown() + } + + /// Return the module in which we should look up the definition for this class + pub(crate) const fn canonical_module(self) -> CoreStdlibModule { match self { Self::Bool | Self::Object @@ -1582,20 +1590,20 @@ impl<'db> KnownClass { | Self::Tuple | Self::Set | Self::Dict - | Self::Slice => builtins_symbol(db, self.as_str()).unwrap_or_unknown(), - Self::GenericAlias | Self::ModuleType | Self::FunctionType => { - types_symbol(db, self.as_str()).unwrap_or_unknown() - } - Self::NoneType => typeshed_symbol(db, self.as_str()).unwrap_or_unknown(), - Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(), - Self::TypeVar => typing_symbol(db, self.as_str()).unwrap_or_unknown(), + | Self::Slice => CoreStdlibModule::Builtins, + Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types, + Self::NoneType => CoreStdlibModule::Typeshed, + Self::SpecialForm | Self::TypeVar => CoreStdlibModule::Typing, // TODO when we understand sys.version_info, we will need an explicit fallback here, // because typing_extensions has a 3.13+ re-export for the `typing.NoDefault` // singleton, but not for `typing._NoDefaultType` - Self::NoDefaultType => typing_extensions_symbol(db, self.as_str()).unwrap_or_unknown(), + Self::NoDefaultType => CoreStdlibModule::TypingExtensions, } } + /// Is this class a singleton class? + /// + /// 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) match self { @@ -1620,40 +1628,33 @@ impl<'db> KnownClass { } } - pub fn maybe_from_module(module: &Module, class_name: &str) -> Option { - let candidate = Self::from_name(class_name)?; - if candidate.check_module(module) { - Some(candidate) - } else { - None - } - } - - fn from_name(name: &str) -> Option { + pub fn try_from_module(module: &Module, class_name: &str) -> Option { // Note: if this becomes hard to maintain (as rust can't ensure at compile time that all // variants of `Self` are covered), we might use a macro (in-house or dependency) // See: https://stackoverflow.com/q/39070244 - match name { - "bool" => Some(Self::Bool), - "object" => Some(Self::Object), - "bytes" => Some(Self::Bytes), - "tuple" => Some(Self::Tuple), - "type" => Some(Self::Type), - "int" => Some(Self::Int), - "float" => Some(Self::Float), - "str" => Some(Self::Str), - "set" => Some(Self::Set), - "dict" => Some(Self::Dict), - "list" => Some(Self::List), - "slice" => Some(Self::Slice), - "GenericAlias" => Some(Self::GenericAlias), - "NoneType" => Some(Self::NoneType), - "ModuleType" => Some(Self::ModuleType), - "FunctionType" => Some(Self::FunctionType), - "_SpecialForm" => Some(Self::SpecialForm), - "_NoDefaultType" => Some(Self::NoDefaultType), - _ => None, - } + let candidate = match class_name { + "bool" => Self::Bool, + "object" => Self::Object, + "bytes" => Self::Bytes, + "tuple" => Self::Tuple, + "type" => Self::Type, + "int" => Self::Int, + "float" => Self::Float, + "str" => Self::Str, + "set" => Self::Set, + "dict" => Self::Dict, + "list" => Self::List, + "slice" => Self::Slice, + "GenericAlias" => Self::GenericAlias, + "NoneType" => Self::NoneType, + "ModuleType" => Self::ModuleType, + "FunctionType" => Self::FunctionType, + "_SpecialForm" => Self::SpecialForm, + "_NoDefaultType" => Self::NoDefaultType, + _ => return None, + }; + + candidate.check_module(module).then_some(candidate) } /// Private method checking if known class can be defined in the given module. @@ -1673,8 +1674,10 @@ impl<'db> KnownClass { | Self::Tuple | Self::Set | Self::Dict - | Self::Slice => module.name() == "builtins", - Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types", + | Self::Slice + | Self::GenericAlias + | Self::ModuleType + | Self::FunctionType => module.name() == self.canonical_module().as_str(), Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"), Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => { matches!(module.name().as_str(), "typing" | "typing_extensions") @@ -2482,7 +2485,7 @@ impl<'db> Class<'db> { } else if let Some(base_class) = base_classes.next() { (base_class.metaclass(db), base_class) } else { - (KnownClass::Type.to_class(db), self) + (KnownClass::Type.to_class_literal(db), self) }; let mut candidate = if let Type::ClassLiteral(metaclass_ty) = metaclass { @@ -2800,6 +2803,7 @@ mod tests { use crate::db::tests::TestDb; use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; + use crate::stdlib::typing_symbol; use crate::ProgramSettings; use ruff_db::files::system_path_to_file; use ruff_db::parsed::parsed_module; diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index b36f160cd8..d066f328b2 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1038,7 +1038,7 @@ impl<'db> TypeInferenceBuilder<'db> { let maybe_known_class = file_to_module(self.db, self.file) .as_ref() - .and_then(|module| KnownClass::maybe_from_module(module, name.as_str())); + .and_then(|module| KnownClass::try_from_module(module, name.as_str())); let class = Class::new(self.db, &*name.id, body_scope, maybe_known_class); let class_ty = Type::class_literal(class); diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index 0182db2994..68e092d9d8 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -340,7 +340,7 @@ impl<'db> ClassBase<'db> { /// Return a `ClassBase` representing the class `builtins.object` fn object(db: &'db dyn Db) -> Self { KnownClass::Object - .to_class(db) + .to_class_literal(db) .into_class_literal() .map_or(Self::Unknown, |ClassLiteralType { class }| { Self::Class(class)