[red-knot] Cleanup some KnownClass APIs (#14269)

This commit is contained in:
Alex Waygood
2024-11-11 11:54:42 +00:00
committed by GitHub
parent 3ef4b3bf32
commit 9180635171
4 changed files with 76 additions and 83 deletions

View File

@@ -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.
///

View File

@@ -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<Self> {
let candidate = Self::from_name(class_name)?;
if candidate.check_module(module) {
Some(candidate)
} else {
None
}
}
fn from_name(name: &str) -> Option<Self> {
pub fn try_from_module(module: &Module, class_name: &str) -> Option<Self> {
// 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;

View File

@@ -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);

View File

@@ -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)