From d91626c5301ffb9e79653cf5afb6592c5f0ce8c2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 2 Jul 2024 12:03:26 +0100 Subject: [PATCH] Get rid of a lot of boilerplate --- crates/red_knot_module_resolver/src/lib.rs | 4 - crates/red_knot_module_resolver/src/module.rs | 53 +- crates/red_knot_module_resolver/src/path.rs | 1354 +++++------------ .../red_knot_module_resolver/src/resolver.rs | 205 ++- crates/red_knot_python_semantic/src/types.rs | 6 +- .../src/types/infer.rs | 6 +- 6 files changed, 517 insertions(+), 1111 deletions(-) diff --git a/crates/red_knot_module_resolver/src/lib.rs b/crates/red_knot_module_resolver/src/lib.rs index d8f62fc03d979..c1f5993974370 100644 --- a/crates/red_knot_module_resolver/src/lib.rs +++ b/crates/red_knot_module_resolver/src/lib.rs @@ -9,10 +9,6 @@ mod typeshed; pub use db::{Db, Jar}; pub use module::{Module, ModuleKind}; pub use module_name::ModuleName; -pub use path::{ - ExtraPath, ExtraPathBuf, FirstPartyPath, FirstPartyPathBuf, SitePackagesPath, - SitePackagesPathBuf, StandardLibraryPath, StandardLibraryPathBuf, -}; pub use resolver::{resolve_module, set_module_resolution_settings, ModuleResolutionSettings}; pub use supported_py_version::SupportedPyVersion; pub use typeshed::TypeshedVersions; diff --git a/crates/red_knot_module_resolver/src/module.rs b/crates/red_knot_module_resolver/src/module.rs index c14a328ca0f66..024d104a9f2b2 100644 --- a/crates/red_knot_module_resolver/src/module.rs +++ b/crates/red_knot_module_resolver/src/module.rs @@ -4,10 +4,7 @@ use std::sync::Arc; use ruff_db::vfs::VfsFile; use crate::module_name::ModuleName; -use crate::path::{ - ExtraPathBuf, FirstPartyPathBuf, ModuleResolutionPathRef, SitePackagesPathBuf, - StandardLibraryPath, StandardLibraryPathBuf, -}; +use crate::path::{ModuleResolutionPath, ModuleResolutionPathRef}; use crate::Db; /// Representation of a Python module. @@ -20,7 +17,7 @@ impl Module { pub(crate) fn new( name: ModuleName, kind: ModuleKind, - search_path: Arc, + search_path: Arc, file: VfsFile, ) -> Self { Self { @@ -44,8 +41,8 @@ impl Module { } /// The search path from which the module was resolved. - pub(crate) fn search_path(&self) -> &ModuleSearchPathEntry { - &self.inner.search_path + pub(crate) fn search_path(&self) -> ModuleResolutionPathRef { + ModuleResolutionPathRef::from(&*self.inner.search_path) } /// Determine whether this module is a single-file module or a package @@ -80,7 +77,7 @@ impl salsa::DebugWithDb for Module { struct ModuleInner { name: ModuleName, kind: ModuleKind, - search_path: Arc, + search_path: Arc, file: VfsFile, } @@ -92,43 +89,3 @@ pub enum ModuleKind { /// A python package (`foo/__init__.py` or `foo/__init__.pyi`) Package, } - -/// Enumeration of the different kinds of search paths type checkers are expected to support. -/// -/// N.B. Although we don't implement `Ord` for this enum, they are ordered in terms of the -/// priority that we want to give these modules when resolving them, -/// as per [the order given in the typing spec] -/// -/// [the order given in the typing spec]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering -#[derive(Debug, Eq, PartialEq, Hash)] -pub(crate) enum ModuleSearchPathEntry { - /// "Extra" paths provided by the user in a config file, env var or CLI flag. - /// E.g. mypy's `MYPYPATH` env var, or pyright's `stubPath` configuration setting - Extra(ExtraPathBuf), - - /// Files in the project we're directly being invoked on - FirstParty(FirstPartyPathBuf), - - /// The `stdlib` directory of typeshed (either vendored or custom) - StandardLibrary(StandardLibraryPathBuf), - - /// Stubs or runtime modules installed in site-packages - SitePackagesThirdParty(SitePackagesPathBuf), - // TODO(Alex): vendor third-party stubs from typeshed as well? - // VendoredThirdParty(VendoredPathBuf), -} - -impl ModuleSearchPathEntry { - pub(crate) fn stdlib_from_typeshed_root(typeshed: &StandardLibraryPath) -> Self { - Self::StandardLibrary(StandardLibraryPath::stdlib_from_typeshed_root(typeshed)) - } - - pub(crate) fn path(&self) -> ModuleResolutionPathRef { - match self { - Self::Extra(path) => ModuleResolutionPathRef::Extra(path), - Self::FirstParty(path) => ModuleResolutionPathRef::FirstParty(path), - Self::StandardLibrary(path) => ModuleResolutionPathRef::StandardLibrary(path), - Self::SitePackagesThirdParty(path) => ModuleResolutionPathRef::SitePackages(path), - } - } -} diff --git a/crates/red_knot_module_resolver/src/path.rs b/crates/red_knot_module_resolver/src/path.rs index d8e2532ed2e93..26a17c9aae1b9 100644 --- a/crates/red_knot_module_resolver/src/path.rs +++ b/crates/red_knot_module_resolver/src/path.rs @@ -1,9 +1,8 @@ #![allow(unsafe_code)] use std::iter::FusedIterator; use std::ops::Deref; -use std::path; -use ruff_db::file_system::{FileSystem, FileSystemPath, FileSystemPathBuf}; +use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf}; use ruff_db::vfs::VfsPath; use crate::module_name::ModuleName; @@ -13,842 +12,213 @@ use crate::Db; #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash)] -pub struct ExtraPath(FileSystemPath); +pub(crate) struct ExtraPath(FileSystemPath); impl ExtraPath { - #[must_use] - pub fn new(path: &(impl AsRef + ?Sized)) -> Option<&Self> { - let path = path.as_ref(); - if path - .extension() - .is_some_and(|extension| !matches!(extension, "pyi" | "py")) - { - return None; - } - Some(Self::new_unchecked(path)) - } - #[must_use] fn new_unchecked(path: &FileSystemPath) -> &Self { // SAFETY: ExtraPath is marked as #[repr(transparent)] so the conversion from a // *const FileSystemPath to a *const ExtraPath is valid. - unsafe { &*(path as *const FileSystemPath as *const ExtraPath) } - } - - #[must_use] - pub(crate) fn parent(&self) -> Option<&Self> { - Some(Self::new_unchecked(self.0.parent()?)) - } - - #[must_use] - pub(crate) fn sans_dunder_init(&self) -> &Self { - if self.0.ends_with("__init__.py") || self.0.ends_with("__init__.pyi") { - self.parent() - .unwrap_or_else(|| Self::new_unchecked(FileSystemPath::new(""))) - } else { - self - } - } - - #[must_use] - pub(crate) fn module_name_parts(&self) -> ModulePartIterator { - ModulePartIterator::from_fs_path(&self.0) - } - - #[must_use] - pub(crate) fn relative_to_search_path(&self, search_path: &ExtraPath) -> Option<&Self> { - self.0 - .strip_prefix(search_path) - .map(Self::new_unchecked) - .ok() - } - - #[must_use] - pub fn to_path_buf(&self) -> ExtraPathBuf { - ExtraPathBuf(self.0.to_path_buf()) - } - - #[must_use] - fn is_regular_package(&self, file_system: &dyn FileSystem) -> bool { - file_system.exists(&self.0.join("__init__.py")) - || file_system.exists(&self.0.join("__init__.pyi")) - } - - #[must_use] - fn is_directory(&self, file_system: &dyn FileSystem) -> bool { - file_system.is_directory(&self.0) - } - - #[must_use] - pub(crate) fn with_pyi_extension(&self) -> ExtraPathBuf { - ExtraPathBuf(self.0.with_extension("pyi")) - } - - #[must_use] - pub(crate) fn with_py_extension(&self) -> ExtraPathBuf { - ExtraPathBuf(self.0.with_extension("py")) - } - - #[must_use] - #[inline] - pub(crate) fn as_file_system_path(&self) -> &FileSystemPath { - &self.0 - } -} - -impl PartialEq for ExtraPath { - fn eq(&self, other: &FileSystemPath) -> bool { - self.0 == *other - } -} - -impl PartialEq for ExtraPath { - fn eq(&self, other: &VfsPath) -> bool { - match other { - VfsPath::FileSystem(path) => **path == self.0, - VfsPath::Vendored(_) => false, - } - } -} - -impl AsRef for ExtraPath { - #[inline] - fn as_ref(&self) -> &ExtraPath { - self - } -} - -impl AsRef for ExtraPath { - #[inline] - fn as_ref(&self) -> &FileSystemPath { - self.as_file_system_path() - } -} - -impl AsRef for ExtraPath { - #[inline] - fn as_ref(&self) -> &path::Path { - self.0.as_ref() - } -} - -impl AsRef for ExtraPath { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_str() + unsafe { &*(path as *const FileSystemPath as *const Self) } } } #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct ExtraPathBuf(FileSystemPathBuf); - -impl ExtraPathBuf { - #[must_use] - #[inline] - fn as_path(&self) -> &ExtraPath { - ExtraPath::new(&self.0).unwrap() - } - - /// Push a new part to the path, - /// while maintaining the invariant that the path can only have `.py` or `.pyi` extensions - /// - /// ## Panics: - /// If a component with an invalid extension is passed - fn push(&mut self, component: &str) { - debug_assert!(matches!(component.matches('.').count(), 0 | 1)); - if cfg!(debug) { - if let Some(extension) = std::path::Path::new(component).extension() { - assert!( - matches!(extension.to_str().unwrap(), "pyi" | "py"), - "Extension must be `py` or `pyi`; got {extension:?}" - ); - } - } - self.0.push(component); - } - - #[inline] - pub(crate) fn as_file_system_path_buf(&self) -> &FileSystemPathBuf { - &self.0 - } -} - -impl AsRef for ExtraPathBuf { - #[inline] - fn as_ref(&self) -> &FileSystemPathBuf { - self.as_file_system_path_buf() - } -} - -impl AsRef for ExtraPathBuf { - #[inline] - fn as_ref(&self) -> &ExtraPath { - self.as_path() - } -} +pub(crate) struct ExtraPathBuf(FileSystemPathBuf); impl Deref for ExtraPathBuf { type Target = ExtraPath; - #[inline] fn deref(&self) -> &Self::Target { - self.as_path() + ExtraPath::new_unchecked(&self.0) } } #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash)] -pub struct FirstPartyPath(FileSystemPath); +pub(crate) struct FirstPartyPath(FileSystemPath); impl FirstPartyPath { - #[must_use] - pub fn new(path: &(impl AsRef + ?Sized)) -> Option<&Self> { - let path = path.as_ref(); - if path - .extension() - .is_some_and(|extension| !matches!(extension, "pyi" | "py")) - { - return None; - } - Some(Self::new_unchecked(path)) - } - #[must_use] fn new_unchecked(path: &FileSystemPath) -> &Self { // SAFETY: FirstPartyPath is marked as #[repr(transparent)] so the conversion from a // *const FileSystemPath to a *const FirstPartyPath is valid. - unsafe { &*(path as *const FileSystemPath as *const FirstPartyPath) } - } - - #[must_use] - pub(crate) fn parent(&self) -> Option<&Self> { - Some(Self::new_unchecked(self.0.parent()?)) - } - - #[must_use] - pub(crate) fn sans_dunder_init(&self) -> &Self { - if self.0.ends_with("__init__.py") || self.0.ends_with("__init__.pyi") { - self.parent() - .unwrap_or_else(|| Self::new_unchecked(FileSystemPath::new(""))) - } else { - self - } - } - - #[must_use] - pub(crate) fn module_name_parts(&self) -> ModulePartIterator { - ModulePartIterator::from_fs_path(&self.0) - } - - #[must_use] - pub(crate) fn relative_to_search_path(&self, search_path: &FirstPartyPath) -> Option<&Self> { - self.0 - .strip_prefix(search_path) - .map(Self::new_unchecked) - .ok() - } - - #[must_use] - pub fn to_path_buf(&self) -> FirstPartyPathBuf { - FirstPartyPathBuf(self.0.to_path_buf()) - } - - #[must_use] - fn is_regular_package(&self, file_system: &dyn FileSystem) -> bool { - file_system.exists(&self.0.join("__init__.py")) - || file_system.exists(&self.0.join("__init__.pyi")) - } - - #[must_use] - fn is_directory(&self, file_system: &dyn FileSystem) -> bool { - file_system.is_directory(&self.0) - } - - #[must_use] - pub(crate) fn with_pyi_extension(&self) -> FirstPartyPathBuf { - FirstPartyPathBuf(self.0.with_extension("pyi")) - } - - #[must_use] - pub(crate) fn with_py_extension(&self) -> FirstPartyPathBuf { - FirstPartyPathBuf(self.0.with_extension("py")) - } - - #[must_use] - #[inline] - pub(crate) fn as_file_system_path(&self) -> &FileSystemPath { - &self.0 - } - - #[cfg(test)] - #[must_use] - pub(crate) fn join(&self, path: &str) -> FirstPartyPathBuf { - let mut result = self.to_path_buf(); - result.push(path); - result - } -} - -impl PartialEq for FirstPartyPath { - fn eq(&self, other: &FileSystemPath) -> bool { - self.0 == *other - } -} - -impl PartialEq for FirstPartyPath { - fn eq(&self, other: &VfsPath) -> bool { - match other { - VfsPath::FileSystem(path) => **path == self.0, - VfsPath::Vendored(_) => false, - } - } -} - -impl AsRef for FirstPartyPath { - #[inline] - fn as_ref(&self) -> &FirstPartyPath { - self - } -} - -impl AsRef for FirstPartyPath { - #[inline] - fn as_ref(&self) -> &FileSystemPath { - self.as_file_system_path() - } -} - -impl AsRef for FirstPartyPath { - #[inline] - fn as_ref(&self) -> &path::Path { - self.0.as_ref() - } -} - -impl AsRef for FirstPartyPath { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_str() + unsafe { &*(path as *const FileSystemPath as *const Self) } } } #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct FirstPartyPathBuf(FileSystemPathBuf); - -impl FirstPartyPathBuf { - #[must_use] - #[inline] - fn as_path(&self) -> &FirstPartyPath { - FirstPartyPath::new(&self.0).unwrap() - } - - /// Push a new part to the path, - /// while maintaining the invariant that the path can only have `.py` or `.pyi` extensions - /// - /// ## Panics: - /// If a component with an invalid extension is passed - fn push(&mut self, component: &str) { - debug_assert!(matches!(component.matches('.').count(), 0 | 1)); - if cfg!(debug) { - if let Some(extension) = std::path::Path::new(component).extension() { - assert!( - matches!(extension.to_str().unwrap(), "pyi" | "py"), - "Extension must be `py` or `pyi`; got {extension:?}" - ); - } - } - self.0.push(component); - } - - #[cfg(test)] - pub(crate) fn into_vfs_path(self) -> VfsPath { - VfsPath::FileSystem(self.0) - } - - #[inline] - pub(crate) fn as_file_system_path_buf(&self) -> &FileSystemPathBuf { - &self.0 - } -} - -impl AsRef for FirstPartyPathBuf { - #[inline] - fn as_ref(&self) -> &FileSystemPathBuf { - self.as_file_system_path_buf() - } -} - -impl AsRef for FirstPartyPathBuf { - #[inline] - fn as_ref(&self) -> &FirstPartyPath { - self.as_path() - } -} +pub(crate) struct FirstPartyPathBuf(FileSystemPathBuf); impl Deref for FirstPartyPathBuf { type Target = FirstPartyPath; - #[inline] fn deref(&self) -> &Self::Target { - self.as_path() + FirstPartyPath::new_unchecked(&self.0) } } -// TODO(Alex): Standard-library paths could be vendored paths #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash)] -pub struct StandardLibraryPath(FileSystemPath); +pub(crate) struct StandardLibraryPath(FileSystemPath); impl StandardLibraryPath { #[must_use] - pub fn new(path: &(impl AsRef + ?Sized)) -> Option<&Self> { - let path = path.as_ref(); - // Only allow pyi extensions, unlike other paths - if path.extension().is_some_and(|extension| extension != "pyi") { - return None; - } - Some(Self::new_unchecked(path)) - } - - #[must_use] - fn new_unchecked(path: &(impl AsRef + ?Sized)) -> &Self { - // SAFETY: FirstPartyPath is marked as #[repr(transparent)] so the conversion from a - // *const FileSystemPath to a *const FirstPartyPath is valid. - let path = path.as_ref(); - unsafe { &*(path as *const FileSystemPath as *const StandardLibraryPath) } - } - - #[must_use] - #[inline] - pub(crate) fn stdlib_dir() -> &'static Self { - Self::new_unchecked("stdlib") - } - - pub(crate) fn stdlib_from_typeshed_root( - typeshed: &StandardLibraryPath, - ) -> StandardLibraryPathBuf { - StandardLibraryPathBuf(typeshed.0.join(Self::stdlib_dir())) - } - - #[must_use] - pub(crate) fn relative_to_search_path( - &self, - search_path: &StandardLibraryPath, - ) -> Option<&Self> { - self.0 - .strip_prefix(search_path) - .map(Self::new_unchecked) - .ok() - } - - #[must_use] - pub(crate) fn parent(&self) -> Option<&Self> { - Some(Self::new_unchecked(self.0.parent()?)) - } - - #[must_use] - pub(crate) fn sans_dunder_init(&self) -> &Self { - // Only try to strip `__init__.pyi` from the end, unlike other paths - if self.0.ends_with("__init__.pyi") { - self.parent() - .unwrap_or_else(|| Self::new_unchecked(FileSystemPath::new(""))) - } else { - self - } - } - - #[must_use] - pub(crate) fn module_name_parts(&self) -> ModulePartIterator { - ModulePartIterator::from_fs_path(&self.0) - } - - #[must_use] - pub fn to_path_buf(&self) -> StandardLibraryPathBuf { - StandardLibraryPathBuf(self.0.to_path_buf()) - } - - pub(crate) fn as_module_name(&self) -> Option { - ModuleResolutionPathRef::StandardLibrary(self).as_module_name() - } - - #[must_use] - fn is_regular_package(&self, db: &dyn Db) -> bool { - let Some(module_name) = self.as_module_name() else { - return false; - }; - match db - .typeshed_versions() - .query_module(&module_name, get_target_py_version(db)) - { - TypeshedVersionsQueryResult::Exists | TypeshedVersionsQueryResult::MaybeExists => { - db.file_system().exists(&self.0.join("__init__.pyi")) - } - TypeshedVersionsQueryResult::DoesNotExist => false, - } - } - - #[must_use] - fn is_directory(&self, db: &dyn Db) -> bool { - let Some(module_name) = self.as_module_name() else { - return false; - }; - match db - .typeshed_versions() - .query_module(&module_name, get_target_py_version(db)) - { - TypeshedVersionsQueryResult::Exists | TypeshedVersionsQueryResult::MaybeExists => { - db.file_system().is_directory(&self.0) - } - TypeshedVersionsQueryResult::DoesNotExist => false, - } - } - - #[must_use] - pub(crate) fn with_pyi_extension(&self) -> StandardLibraryPathBuf { - StandardLibraryPathBuf(self.0.with_extension("pyi")) - } - - #[must_use] - #[inline] - pub(crate) fn as_file_system_path(&self) -> &FileSystemPath { - &self.0 - } - - #[cfg(test)] - #[must_use] - pub(crate) fn join(&self, path: &str) -> StandardLibraryPathBuf { - let mut result = self.to_path_buf(); - result.push(path); - result - } -} - -impl PartialEq for StandardLibraryPath { - fn eq(&self, other: &FileSystemPath) -> bool { - self.0 == *other - } -} - -impl PartialEq for StandardLibraryPath { - fn eq(&self, other: &VfsPath) -> bool { - match other { - VfsPath::FileSystem(path) => **path == self.0, - VfsPath::Vendored(_) => false, - } - } -} - -impl AsRef for StandardLibraryPath { - fn as_ref(&self) -> &StandardLibraryPath { - self - } -} - -impl AsRef for StandardLibraryPath { - #[inline] - fn as_ref(&self) -> &FileSystemPath { - self.as_file_system_path() - } -} - -impl AsRef for StandardLibraryPath { - #[inline] - fn as_ref(&self) -> &path::Path { - self.0.as_ref() - } -} - -impl AsRef for StandardLibraryPath { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_str() + fn new_unchecked(path: &FileSystemPath) -> &Self { + // SAFETY: StandardLibraryPath is marked as #[repr(transparent)] so the conversion from a + // *const FileSystemPath to a *const StandardLibraryPath is valid. + unsafe { &*(path as *const FileSystemPath as *const Self) } } } -// TODO(Alex): Standard-library paths could also be vendored paths #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct StandardLibraryPathBuf(FileSystemPathBuf); - -impl StandardLibraryPathBuf { - #[must_use] - #[inline] - fn as_path(&self) -> &StandardLibraryPath { - StandardLibraryPath::new(&self.0).unwrap() - } - - /// Push a new part to the path, - /// while maintaining the invariant that the path can only have `.pyi` extensions - /// - /// ## Panics: - /// If a component with an invalid extension is passed - fn push(&mut self, component: &str) { - debug_assert!(matches!(component.matches('.').count(), 0 | 1)); - if cfg!(debug) { - if let Some(extension) = std::path::Path::new(component).extension() { - assert_eq!( - extension.to_str().unwrap(), - "pyi", - "Extension must be `pyi`; got {extension:?}" - ); - } - } - self.0.push(component); - } - - #[cfg(test)] - pub(crate) fn into_vfs_path(self) -> VfsPath { - VfsPath::FileSystem(self.0) - } - - #[inline] - pub(crate) fn as_file_system_path_buf(&self) -> &FileSystemPathBuf { - &self.0 - } -} - -impl AsRef for StandardLibraryPathBuf { - #[inline] - fn as_ref(&self) -> &StandardLibraryPath { - self.as_path() - } -} - -impl AsRef for StandardLibraryPathBuf { - #[inline] - fn as_ref(&self) -> &FileSystemPathBuf { - self.as_file_system_path_buf() - } -} - -impl Deref for StandardLibraryPathBuf { - type Target = StandardLibraryPath; - - #[inline] - fn deref(&self) -> &Self::Target { - self.as_path() - } -} - -#[repr(transparent)] -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct SitePackagesPath(FileSystemPath); - -impl SitePackagesPath { - #[must_use] - pub fn new(path: &(impl AsRef + ?Sized)) -> Option<&Self> { - let path = path.as_ref(); - if path - .extension() - .is_some_and(|extension| !matches!(extension, "pyi" | "py")) - { - return None; - } - Some(Self::new_unchecked(path)) - } - - #[must_use] - fn new_unchecked(path: &FileSystemPath) -> &Self { - // SAFETY: SitePackagesPath is marked as #[repr(transparent)] so the conversion from a - // *const FileSystemPath to a *const SitePackagesPath is valid. - unsafe { &*(path as *const FileSystemPath as *const SitePackagesPath) } - } - - #[must_use] - pub(crate) fn parent(&self) -> Option<&Self> { - Some(Self::new_unchecked(self.0.parent()?)) - } - - #[must_use] - pub(crate) fn sans_dunder_init(&self) -> &Self { - // Only try to strip `__init__.pyi` from the end, unlike other paths - if self.0.ends_with("__init__.pyi") || self.0.ends_with("__init__.py") { - self.parent() - .unwrap_or_else(|| Self::new_unchecked(FileSystemPath::new(""))) - } else { - self - } - } - - #[must_use] - pub(crate) fn module_name_parts(&self) -> ModulePartIterator { - ModulePartIterator::from_fs_path(&self.0) - } - - #[must_use] - pub(crate) fn relative_to_search_path(&self, search_path: &SitePackagesPath) -> Option<&Self> { - self.0 - .strip_prefix(search_path) - .map(Self::new_unchecked) - .ok() - } +pub(crate) struct StandardLibraryPathBuf(FileSystemPathBuf); - #[must_use] - pub fn to_path_buf(&self) -> SitePackagesPathBuf { - SitePackagesPathBuf(self.0.to_path_buf()) - } - - #[must_use] - fn is_regular_package(&self, file_system: &dyn FileSystem) -> bool { - file_system.exists(&self.0.join("__init__.py")) - || file_system.exists(&self.0.join("__init__.pyi")) - } - - #[must_use] - fn is_directory(&self, file_system: &dyn FileSystem) -> bool { - file_system.is_directory(&self.0) - } - - #[must_use] - pub(crate) fn with_pyi_extension(&self) -> SitePackagesPathBuf { - SitePackagesPathBuf(self.0.with_extension("pyi")) - } - - #[must_use] - pub(crate) fn with_py_extension(&self) -> SitePackagesPathBuf { - SitePackagesPathBuf(self.0.with_extension("py")) - } - - #[must_use] - #[inline] - pub(crate) fn as_file_system_path(&self) -> &FileSystemPath { - &self.0 - } - - #[cfg(test)] - #[must_use] - pub(crate) fn join(&self, path: &str) -> SitePackagesPathBuf { - let mut result = self.to_path_buf(); - result.push(path); - result - } -} - -impl PartialEq for SitePackagesPath { - fn eq(&self, other: &FileSystemPath) -> bool { - self.0 == *other - } -} - -impl PartialEq for SitePackagesPath { - fn eq(&self, other: &VfsPath) -> bool { - match other { - VfsPath::FileSystem(path) => **path == self.0, - VfsPath::Vendored(_) => false, - } - } -} - -impl AsRef for SitePackagesPath { - fn as_ref(&self) -> &SitePackagesPath { - self - } -} - -impl AsRef for SitePackagesPath { - #[inline] - fn as_ref(&self) -> &FileSystemPath { - self.as_file_system_path() - } -} +impl Deref for StandardLibraryPathBuf { + type Target = StandardLibraryPath; -impl AsRef for SitePackagesPath { - #[inline] - fn as_ref(&self) -> &path::Path { - self.0.as_ref() + fn deref(&self) -> &Self::Target { + StandardLibraryPath::new_unchecked(&self.0) } } -impl AsRef for SitePackagesPath { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_str() +#[repr(transparent)] +#[derive(Debug, PartialEq, Eq, Hash)] +pub(crate) struct SitePackagesPath(FileSystemPath); + +impl SitePackagesPath { + #[must_use] + fn new_unchecked(path: &FileSystemPath) -> &Self { + // SAFETY: SitePackagesPath is marked as #[repr(transparent)] so the conversion from a + // *const FileSystemPath to a *const SitePackagesPath is valid. + unsafe { &*(path as *const FileSystemPath as *const Self) } } } #[repr(transparent)] #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct SitePackagesPathBuf(FileSystemPathBuf); +pub(crate) struct SitePackagesPathBuf(FileSystemPathBuf); -impl SitePackagesPathBuf { - #[must_use] - #[inline] - fn as_path(&self) -> &SitePackagesPath { - SitePackagesPath::new(&self.0).unwrap() +impl Deref for SitePackagesPathBuf { + type Target = SitePackagesPath; + + fn deref(&self) -> &Self::Target { + SitePackagesPath::new_unchecked(&self.0) } +} + +/// Enumeration of the different kinds of search paths type checkers are expected to support. +/// +/// N.B. Although we don't implement `Ord` for this enum, they are ordered in terms of the +/// priority that we want to give these modules when resolving them, +/// as per [the order given in the typing spec] +/// +/// [the order given in the typing spec]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum ModuleResolutionPath { + Extra(ExtraPathBuf), + FirstParty(FirstPartyPathBuf), + StandardLibrary(StandardLibraryPathBuf), + SitePackages(SitePackagesPathBuf), +} +impl ModuleResolutionPath { /// Push a new part to the path, - /// while maintaining the invariant that the path can only have `.py` or `.pyi` extensions + /// while maintaining the invariant that the path can only have `.py` or `.pyi` extensions. + /// For the stdlib variant specifically, it may only have a `.pyi` extension. /// /// ## Panics: /// If a component with an invalid extension is passed - fn push(&mut self, component: &str) { + pub(crate) fn push(&mut self, component: &str) { debug_assert!(matches!(component.matches('.').count(), 0 | 1)); if cfg!(debug) { if let Some(extension) = std::path::Path::new(component).extension() { - assert!( - matches!(extension.to_str().unwrap(), "pyi" | "py"), - "Extension must be `py` or `pyi`; got {extension:?}" - ); + match self { + Self::Extra(_) | Self::FirstParty(_) | Self::SitePackages(_) => assert!( + matches!(extension.to_str().unwrap(), "pyi" | "py"), + "Extension must be `py` or `pyi`; got {extension:?}" + ), + Self::StandardLibrary(_) => assert_eq!( + extension.to_str().unwrap(), + "pyi", + "Extension must be `py` or `pyi`; got {extension:?}" + ), + }; } } - self.0.push(component); + let inner = match self { + Self::Extra(ExtraPathBuf(ref mut path)) => path, + Self::FirstParty(FirstPartyPathBuf(ref mut path)) => path, + Self::StandardLibrary(StandardLibraryPathBuf(ref mut path)) => path, + Self::SitePackages(SitePackagesPathBuf(ref mut path)) => path, + }; + inner.push(component); } - #[cfg(test)] - pub(crate) fn into_vfs_path(self) -> VfsPath { - VfsPath::FileSystem(self.0) + pub(crate) fn extra(path: FileSystemPathBuf) -> Option { + if path + .extension() + .map_or(true, |ext| matches!(ext, "py" | "pyi")) + { + Some(Self::extra_unchecked(path)) + } else { + None + } } - #[inline] - pub(crate) fn as_file_system_path_buf(&self) -> &FileSystemPathBuf { - &self.0 + fn extra_unchecked(path: FileSystemPathBuf) -> Self { + Self::Extra(ExtraPathBuf(path)) } -} -impl AsRef for SitePackagesPathBuf { - #[inline] - fn as_ref(&self) -> &FileSystemPathBuf { - self.as_file_system_path_buf() + pub(crate) fn first_party(path: FileSystemPathBuf) -> Option { + if path + .extension() + .map_or(true, |ext| matches!(ext, "pyi" | "py")) + { + Some(Self::first_party_unchecked(path)) + } else { + None + } } -} -impl AsRef for SitePackagesPathBuf { - #[inline] - fn as_ref(&self) -> &SitePackagesPath { - self.as_path() + fn first_party_unchecked(path: FileSystemPathBuf) -> Self { + Self::FirstParty(FirstPartyPathBuf(path)) } -} -impl Deref for SitePackagesPathBuf { - type Target = SitePackagesPath; + pub(crate) fn standard_library(path: FileSystemPathBuf) -> Option { + if path.extension().map_or(true, |ext| ext == "pyi") { + Some(Self::standard_library_unchecked(path)) + } else { + None + } + } - #[inline] - fn deref(&self) -> &Self::Target { - self.as_path() + pub(crate) fn stdlib_from_typeshed_root(typeshed_root: &FileSystemPath) -> Option { + Self::standard_library(typeshed_root.join(FileSystemPath::new("stdlib"))) } -} -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum ModuleResolutionPath { - Extra(ExtraPathBuf), - FirstParty(FirstPartyPathBuf), - StandardLibrary(StandardLibraryPathBuf), - SitePackages(SitePackagesPathBuf), -} + fn standard_library_unchecked(path: FileSystemPathBuf) -> Self { + Self::StandardLibrary(StandardLibraryPathBuf(path)) + } -impl ModuleResolutionPath { - pub(crate) fn push(&mut self, component: &str) { - match self { - Self::Extra(ref mut path) => path.push(component), - Self::FirstParty(ref mut path) => path.push(component), - Self::StandardLibrary(ref mut path) => path.push(component), - Self::SitePackages(ref mut path) => path.push(component), + pub(crate) fn site_packages(path: FileSystemPathBuf) -> Option { + if path + .extension() + .map_or(true, |ext| matches!(ext, "pyi" | "py")) + { + Some(Self::site_packages_unchecked(path)) + } else { + None } } + fn site_packages_unchecked(path: FileSystemPathBuf) -> Self { + Self::SitePackages(SitePackagesPathBuf(path)) + } + pub(crate) fn is_regular_package(&self, db: &dyn Db) -> bool { ModuleResolutionPathRef::from(self).is_regular_package(db) } @@ -864,244 +234,345 @@ impl ModuleResolutionPath { pub(crate) fn with_py_extension(&self) -> Option { ModuleResolutionPathRef::from(self).with_py_extension() } -} -impl AsRef for ModuleResolutionPath { - fn as_ref(&self) -> &FileSystemPath { + #[cfg(test)] + pub(crate) fn join(&self, component: &(impl AsRef + ?Sized)) -> Self { + ModuleResolutionPathRef::from(self).join(component) + } + + pub(crate) fn as_file_system_path_buf(&self) -> &FileSystemPathBuf { match self { - Self::Extra(path) => path.as_file_system_path(), - Self::FirstParty(path) => path.as_file_system_path(), - Self::StandardLibrary(path) => path.as_file_system_path(), - Self::SitePackages(path) => path.as_file_system_path(), + Self::Extra(ExtraPathBuf(path)) => path, + Self::FirstParty(FirstPartyPathBuf(path)) => path, + Self::StandardLibrary(StandardLibraryPathBuf(path)) => path, + Self::SitePackages(SitePackagesPathBuf(path)) => path, } } -} -impl AsRef for ModuleResolutionPath { - fn as_ref(&self) -> &FileSystemPathBuf { + #[cfg(test)] + #[must_use] + #[inline] + fn into_file_system_path_buf(self) -> FileSystemPathBuf { match self { - Self::Extra(path) => path.as_file_system_path_buf(), - Self::FirstParty(path) => path.as_file_system_path_buf(), - Self::StandardLibrary(path) => path.as_file_system_path_buf(), - Self::SitePackages(path) => path.as_file_system_path_buf(), + Self::Extra(ExtraPathBuf(path)) => path, + Self::FirstParty(FirstPartyPathBuf(path)) => path, + Self::StandardLibrary(StandardLibraryPathBuf(path)) => path, + Self::SitePackages(SitePackagesPathBuf(path)) => path, } } + + #[cfg(test)] + #[must_use] + pub(crate) fn into_vfs_path(self) -> VfsPath { + VfsPath::FileSystem(self.into_file_system_path_buf()) + } } -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &ExtraPath) -> bool { - if let ModuleResolutionPath::Extra(path) = self { - **path == *other - } else { - false +impl PartialEq for ModuleResolutionPath { + fn eq(&self, other: &VfsPath) -> bool { + match other { + VfsPath::FileSystem(path) => self.as_file_system_path_buf() == path, + VfsPath::Vendored(_) => false, } } } -impl PartialEq for ExtraPath { +impl PartialEq for VfsPath { fn eq(&self, other: &ModuleResolutionPath) -> bool { other.eq(self) } } -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &ExtraPathBuf) -> bool { - self.eq(&**other) +impl PartialEq for ModuleResolutionPath { + fn eq(&self, other: &FileSystemPathBuf) -> bool { + self.as_file_system_path_buf() == other } } -impl PartialEq for ExtraPathBuf { +impl PartialEq for FileSystemPathBuf { fn eq(&self, other: &ModuleResolutionPath) -> bool { other.eq(self) } } -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &FirstPartyPath) -> bool { - if let ModuleResolutionPath::FirstParty(path) = self { - **path == *other - } else { - false - } +impl PartialEq for ModuleResolutionPath { + fn eq(&self, other: &FileSystemPath) -> bool { + ModuleResolutionPathRef::from(self) == *other } } -impl PartialEq for FirstPartyPath { +impl PartialEq for FileSystemPath { fn eq(&self, other: &ModuleResolutionPath) -> bool { other.eq(self) } } -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &FirstPartyPathBuf) -> bool { - self.eq(&**other) +impl AsRef for ModuleResolutionPath { + fn as_ref(&self) -> &FileSystemPathBuf { + self.as_file_system_path_buf() } } -impl PartialEq for FirstPartyPathBuf { - fn eq(&self, other: &ModuleResolutionPath) -> bool { - other.eq(self) +impl AsRef for ModuleResolutionPath { + fn as_ref(&self) -> &FileSystemPath { + ModuleResolutionPathRef::from(self).as_file_system_path() } } -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &StandardLibraryPath) -> bool { - if let ModuleResolutionPath::StandardLibrary(path) = self { - **path == *other +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) enum ModuleResolutionPathRef<'a> { + Extra(&'a ExtraPath), + FirstParty(&'a FirstPartyPath), + StandardLibrary(&'a StandardLibraryPath), + SitePackages(&'a SitePackagesPath), +} + +impl<'a> ModuleResolutionPathRef<'a> { + pub(crate) fn extra(path: &'a (impl AsRef + ?Sized)) -> Option { + let path = path.as_ref(); + if path + .extension() + .map_or(true, |ext| matches!(ext, "pyi" | "py")) + { + Some(Self::extra_unchecked(path)) } else { - false + None } } -} -impl PartialEq for StandardLibraryPath { - fn eq(&self, other: &ModuleResolutionPath) -> bool { - other.eq(self) + fn extra_unchecked(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::Extra(ExtraPath::new_unchecked(path.as_ref())) } -} -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &StandardLibraryPathBuf) -> bool { - self.eq(&**other) + pub(crate) fn first_party(path: &'a (impl AsRef + ?Sized)) -> Option { + let path = path.as_ref(); + if path + .extension() + .map_or(true, |ext| matches!(ext, "pyi" | "py")) + { + Some(Self::first_party_unchecked(path)) + } else { + None + } } -} -impl PartialEq for StandardLibraryPathBuf { - fn eq(&self, other: &ModuleResolutionPath) -> bool { - other.eq(self) + fn first_party_unchecked(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::FirstParty(FirstPartyPath::new_unchecked(path.as_ref())) } -} -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &SitePackagesPath) -> bool { - if let ModuleResolutionPath::SitePackages(path) = self { - **path == *other + pub(crate) fn standard_library( + path: &'a (impl AsRef + ?Sized), + ) -> Option { + let path = path.as_ref(); + if path.extension().map_or(true, |ext| ext == "pyi") { + Some(Self::standard_library_unchecked(path)) } else { - false + None } } -} -impl PartialEq for SitePackagesPath { - fn eq(&self, other: &ModuleResolutionPath) -> bool { - other.eq(self) + fn standard_library_unchecked(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::StandardLibrary(StandardLibraryPath::new_unchecked(path.as_ref())) } -} -impl PartialEq for ModuleResolutionPath { - fn eq(&self, other: &SitePackagesPathBuf) -> bool { - self.eq(&**other) + pub(crate) fn site_packages(path: &'a (impl AsRef + ?Sized)) -> Option { + let path = path.as_ref(); + if path + .extension() + .map_or(true, |ext| matches!(ext, "pyi" | "py")) + { + Some(Self::site_packages_unchecked(path)) + } else { + None + } } -} -impl PartialEq for SitePackagesPathBuf { - fn eq(&self, other: &ModuleResolutionPath) -> bool { - other.eq(self) + fn site_packages_unchecked(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::SitePackages(SitePackagesPath::new_unchecked(path.as_ref())) } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) enum ModuleResolutionPathRef<'a> { - Extra(&'a ExtraPath), - FirstParty(&'a FirstPartyPath), - StandardLibrary(&'a StandardLibraryPath), - SitePackages(&'a SitePackagesPath), -} -impl<'a> ModuleResolutionPathRef<'a> { - #[must_use] - pub(crate) fn sans_dunder_init(self) -> Self { + pub(crate) fn is_directory(&self, db: &dyn Db) -> bool { match self { - Self::Extra(path) => Self::Extra(path.sans_dunder_init()), - Self::FirstParty(path) => Self::FirstParty(path.sans_dunder_init()), - Self::StandardLibrary(path) => Self::StandardLibrary(path.sans_dunder_init()), - Self::SitePackages(path) => Self::SitePackages(path.sans_dunder_init()), + Self::Extra(ExtraPath(path)) => db.file_system().is_directory(path), + Self::FirstParty(FirstPartyPath(path)) => db.file_system().is_directory(path), + Self::SitePackages(SitePackagesPath(path)) => db.file_system().is_directory(path), + Self::StandardLibrary(StandardLibraryPath(path)) => { + let Some(module_name) = self.as_module_name() else { + return false; + }; + match db + .typeshed_versions() + .query_module(&module_name, get_target_py_version(db)) + { + TypeshedVersionsQueryResult::Exists + | TypeshedVersionsQueryResult::MaybeExists => { + db.file_system().is_directory(path) + } + TypeshedVersionsQueryResult::DoesNotExist => false, + } + } } } - #[must_use] - pub(crate) fn module_name_parts(self) -> ModulePartIterator<'a> { + pub(crate) fn is_regular_package(&self, db: &dyn Db) -> bool { match self { - Self::Extra(path) => path.module_name_parts(), - Self::FirstParty(path) => path.module_name_parts(), - Self::StandardLibrary(path) => path.module_name_parts(), - Self::SitePackages(path) => path.module_name_parts(), + Self::Extra(ExtraPath(fs_path)) + | Self::FirstParty(FirstPartyPath(fs_path)) + | Self::SitePackages(SitePackagesPath(fs_path)) => { + let file_system = db.file_system(); + file_system.exists(&fs_path.join("__init__.py")) + || file_system.exists(&fs_path.join("__init__.pyi")) + } + // Unlike the other variants: + // (1) Account for VERSIONS + // (2) Only test for `__init__.pyi`, not `__init__.py` + Self::StandardLibrary(StandardLibraryPath(fs_path)) => { + let Some(module_name) = self.as_module_name() else { + return false; + }; + match db + .typeshed_versions() + .query_module(&module_name, get_target_py_version(db)) + { + TypeshedVersionsQueryResult::Exists + | TypeshedVersionsQueryResult::MaybeExists => { + db.file_system().exists(&fs_path.join("__init__.pyi")) + } + TypeshedVersionsQueryResult::DoesNotExist => false, + } + } } } - #[must_use] - pub(crate) fn to_owned(self) -> ModuleResolutionPath { + pub(crate) fn parent(&self) -> Option { + Some(match self { + Self::Extra(ExtraPath(path)) => Self::extra_unchecked(path.parent()?), + Self::FirstParty(FirstPartyPath(path)) => Self::first_party_unchecked(path.parent()?), + Self::StandardLibrary(StandardLibraryPath(path)) => { + Self::standard_library_unchecked(path.parent()?) + } + Self::SitePackages(SitePackagesPath(path)) => { + Self::site_packages_unchecked(path.parent()?) + } + }) + } + + fn ends_with_dunder_init(&self) -> bool { match self { - Self::Extra(path) => ModuleResolutionPath::Extra(path.to_path_buf()), - Self::FirstParty(path) => ModuleResolutionPath::FirstParty(path.to_path_buf()), - Self::StandardLibrary(path) => { - ModuleResolutionPath::StandardLibrary(path.to_path_buf()) + Self::Extra(ExtraPath(path)) + | Self::FirstParty(FirstPartyPath(path)) + | Self::SitePackages(SitePackagesPath(path)) => { + path.ends_with("__init__.py") || path.ends_with("__init__.pyi") } - Self::SitePackages(path) => ModuleResolutionPath::SitePackages(path.to_path_buf()), + Self::StandardLibrary(StandardLibraryPath(path)) => path.ends_with("__init__.pyi"), } } - #[must_use] - pub(crate) fn is_regular_package(self, db: &dyn Db) -> bool { - match self { - Self::Extra(path) => path.is_regular_package(db.file_system()), - Self::FirstParty(path) => path.is_regular_package(db.file_system()), - Self::StandardLibrary(path) => path.is_regular_package(db), - Self::SitePackages(path) => path.is_regular_package(db.file_system()), + fn sans_dunder_init(self) -> Self { + if self.ends_with_dunder_init() { + self.parent().unwrap_or_else(|| match self { + Self::Extra(_) => Self::extra_unchecked(""), + Self::FirstParty(_) => Self::first_party_unchecked(""), + Self::StandardLibrary(_) => Self::standard_library_unchecked(""), + Self::SitePackages(_) => Self::site_packages_unchecked(""), + }) + } else { + self } } - #[must_use] - pub(crate) fn is_directory(self, db: &dyn Db) -> bool { - match self { - Self::Extra(path) => path.is_directory(db.file_system()), - Self::FirstParty(path) => path.is_directory(db.file_system()), - Self::StandardLibrary(path) => path.is_directory(db), - Self::SitePackages(path) => path.is_directory(db.file_system()), + pub(crate) fn as_module_name(&self) -> Option { + let mut parts_iter = match self.sans_dunder_init() { + Self::Extra(ExtraPath(path)) => ModulePartIterator::from_fs_path(path), + Self::FirstParty(FirstPartyPath(path)) => ModulePartIterator::from_fs_path(path), + Self::StandardLibrary(StandardLibraryPath(path)) => { + ModulePartIterator::from_fs_path(path) + } + Self::SitePackages(SitePackagesPath(path)) => ModulePartIterator::from_fs_path(path), + }; + let first_part = parts_iter.next()?; + if let Some(second_part) = parts_iter.next() { + let mut name = format!("{first_part}.{second_part}"); + for part in parts_iter { + name.push('.'); + name.push_str(part); + } + ModuleName::new(&name) + } else { + ModuleName::new(first_part) } } - #[must_use] - pub(crate) fn with_pyi_extension(self) -> ModuleResolutionPath { + pub(crate) fn with_pyi_extension(&self) -> ModuleResolutionPath { match self { - Self::Extra(path) => ModuleResolutionPath::Extra(path.with_pyi_extension()), - Self::FirstParty(path) => ModuleResolutionPath::FirstParty(path.with_pyi_extension()), - Self::StandardLibrary(path) => { - ModuleResolutionPath::StandardLibrary(path.with_pyi_extension()) + Self::Extra(ExtraPath(path)) => { + ModuleResolutionPath::extra_unchecked(path.with_extension("pyi")) + } + Self::FirstParty(FirstPartyPath(path)) => { + ModuleResolutionPath::first_party_unchecked(path.with_extension("pyi")) } - Self::SitePackages(path) => { - ModuleResolutionPath::SitePackages(path.with_pyi_extension()) + Self::StandardLibrary(StandardLibraryPath(path)) => { + ModuleResolutionPath::standard_library_unchecked(path.with_extension("pyi")) } + Self::SitePackages(SitePackagesPath(path)) => { + ModuleResolutionPath::site_packages_unchecked(path.with_extension("pyi")) + } + } + } + + pub(crate) fn with_py_extension(&self) -> Option { + match self { + Self::Extra(ExtraPath(path)) => Some(ModuleResolutionPath::extra_unchecked( + path.with_extension("py"), + )), + Self::FirstParty(FirstPartyPath(path)) => Some( + ModuleResolutionPath::first_party_unchecked(path.with_extension("py")), + ), + Self::StandardLibrary(_) => None, + Self::SitePackages(SitePackagesPath(path)) => Some( + ModuleResolutionPath::site_packages_unchecked(path.with_extension("py")), + ), } } + #[cfg(test)] #[must_use] - pub(crate) fn with_py_extension(self) -> Option { + pub(crate) fn to_module_resolution_path(self) -> ModuleResolutionPath { match self { - Self::Extra(path) => Some(ModuleResolutionPath::Extra(path.with_py_extension())), - Self::FirstParty(path) => { - Some(ModuleResolutionPath::FirstParty(path.with_py_extension())) + Self::Extra(ExtraPath(path)) => { + ModuleResolutionPath::extra_unchecked(path.to_path_buf()) } - Self::StandardLibrary(_) => None, - Self::SitePackages(path) => { - Some(ModuleResolutionPath::SitePackages(path.with_py_extension())) + Self::FirstParty(FirstPartyPath(path)) => { + ModuleResolutionPath::first_party_unchecked(path.to_path_buf()) + } + Self::StandardLibrary(StandardLibraryPath(path)) => { + ModuleResolutionPath::standard_library_unchecked(path.to_path_buf()) + } + Self::SitePackages(SitePackagesPath(path)) => { + ModuleResolutionPath::site_packages_unchecked(path.to_path_buf()) } } } - pub(crate) fn as_module_name(&self) -> Option { - let path = self.sans_dunder_init(); - let mut parts_iter = path.module_name_parts(); - let first_part = parts_iter.next()?; - if let Some(second_part) = parts_iter.next() { - let mut name = format!("{first_part}.{second_part}"); - for part in parts_iter { - name.push('.'); - name.push_str(part); - } - ModuleName::new(&name) - } else { - ModuleName::new(first_part) + #[cfg(test)] + #[must_use] + pub(crate) fn join( + &self, + component: &'a (impl AsRef + ?Sized), + ) -> ModuleResolutionPath { + let mut result = self.to_module_resolution_path(); + result.push(component.as_ref().as_str()); + result + } + + #[must_use] + #[inline] + fn as_file_system_path(self) -> &'a FileSystemPath { + match self { + Self::Extra(ExtraPath(path)) => path, + Self::FirstParty(FirstPartyPath(path)) => path, + Self::StandardLibrary(StandardLibraryPath(path)) => path, + Self::SitePackages(SitePackagesPath(path)) => path, } } } @@ -1121,76 +592,65 @@ impl<'a> From<&'a ModuleResolutionPath> for ModuleResolutionPathRef<'a> { } impl<'a> AsRef for ModuleResolutionPathRef<'a> { + #[inline] fn as_ref(&self) -> &FileSystemPath { - match self { - Self::Extra(path) => path.as_file_system_path(), - Self::FirstParty(path) => path.as_file_system_path(), - Self::StandardLibrary(path) => path.as_file_system_path(), - Self::SitePackages(path) => path.as_file_system_path(), - } - } -} - -impl<'a> PartialEq for ModuleResolutionPathRef<'a> { - fn eq(&self, other: &ExtraPath) -> bool { - if let ModuleResolutionPathRef::Extra(path) = self { - *path == other - } else { - false - } - } -} - -impl<'a> PartialEq> for ExtraPath { - fn eq(&self, other: &ModuleResolutionPathRef) -> bool { - other.eq(self) - } -} - -impl<'a> PartialEq for ModuleResolutionPathRef<'a> { - fn eq(&self, other: &FirstPartyPath) -> bool { - if let ModuleResolutionPathRef::FirstParty(path) = self { - *path == other - } else { - false - } + self.as_file_system_path() } } -impl<'a> PartialEq> for FirstPartyPath { - fn eq(&self, other: &ModuleResolutionPathRef) -> bool { +impl<'a> PartialEq for ModuleResolutionPathRef<'a> { + fn eq(&self, other: &ModuleResolutionPath) -> bool { + match (self, other) { + ( + ModuleResolutionPathRef::Extra(ExtraPath(self_path)), + ModuleResolutionPath::Extra(ExtraPathBuf(other_path)), + ) + | ( + ModuleResolutionPathRef::FirstParty(FirstPartyPath(self_path)), + ModuleResolutionPath::FirstParty(FirstPartyPathBuf(other_path)), + ) + | ( + ModuleResolutionPathRef::StandardLibrary(StandardLibraryPath(self_path)), + ModuleResolutionPath::StandardLibrary(StandardLibraryPathBuf(other_path)), + ) + | ( + ModuleResolutionPathRef::SitePackages(SitePackagesPath(self_path)), + ModuleResolutionPath::SitePackages(SitePackagesPathBuf(other_path)), + ) => *self_path == **other_path, + _ => false, + } + } +} + +impl<'a> PartialEq> for ModuleResolutionPath { + fn eq(&self, other: &ModuleResolutionPathRef<'a>) -> bool { other.eq(self) } } -impl<'a> PartialEq for ModuleResolutionPathRef<'a> { - fn eq(&self, other: &StandardLibraryPath) -> bool { - if let ModuleResolutionPathRef::StandardLibrary(path) = self { - *path == other - } else { - false - } +impl<'a> PartialEq for ModuleResolutionPathRef<'a> { + fn eq(&self, other: &FileSystemPath) -> bool { + self.as_file_system_path() == other } } -impl<'a> PartialEq> for StandardLibraryPath { - fn eq(&self, other: &ModuleResolutionPathRef) -> bool { - other.eq(self) +impl<'a> PartialEq> for FileSystemPath { + fn eq(&self, other: &ModuleResolutionPathRef<'a>) -> bool { + self == other.as_file_system_path() } } -impl<'a> PartialEq for ModuleResolutionPathRef<'a> { - fn eq(&self, other: &SitePackagesPath) -> bool { - if let ModuleResolutionPathRef::SitePackages(path) = self { - *path == other - } else { - false - } +impl<'a> PartialEq for ModuleResolutionPathRef<'a> { + fn eq(&self, other: &VfsPath) -> bool { + let VfsPath::FileSystem(other) = other else { + return false; + }; + self.as_file_system_path() == &**other } } -impl<'a> PartialEq> for SitePackagesPath { - fn eq(&self, other: &ModuleResolutionPathRef) -> bool { +impl<'a> PartialEq> for VfsPath { + fn eq(&self, other: &ModuleResolutionPathRef<'a>) -> bool { other.eq(self) } } diff --git a/crates/red_knot_module_resolver/src/resolver.rs b/crates/red_knot_module_resolver/src/resolver.rs index 40a8543112046..ae91b60acc04a 100644 --- a/crates/red_knot_module_resolver/src/resolver.rs +++ b/crates/red_knot_module_resolver/src/resolver.rs @@ -1,15 +1,12 @@ use std::ops::Deref; use std::sync::Arc; +use ruff_db::file_system::FileSystemPathBuf; use ruff_db::vfs::{system_path_to_file, vfs_path_to_file, VfsFile, VfsPath}; -use crate::module::{Module, ModuleKind, ModuleSearchPathEntry}; +use crate::module::{Module, ModuleKind}; use crate::module_name::ModuleName; -use crate::path::{ - ExtraPath, ExtraPathBuf, FirstPartyPath, FirstPartyPathBuf, ModuleResolutionPath, - ModuleResolutionPathRef, SitePackagesPath, SitePackagesPathBuf, StandardLibraryPath, - StandardLibraryPathBuf, -}; +use crate::path::{ModuleResolutionPath, ModuleResolutionPathRef}; use crate::resolver::internal::ModuleResolverSearchPaths; use crate::supported_py_version::set_target_py_version; use crate::{Db, SupportedPyVersion}; @@ -88,25 +85,23 @@ pub(crate) fn file_to_module(db: &dyn Db, file: VfsFile) -> Option { let relative_path = search_paths.iter().find_map(|root| match (&**root, path) { (_, VfsPath::Vendored(_)) => todo!("VendoredPaths are not yet supported"), - (ModuleSearchPathEntry::Extra(root_path), VfsPath::FileSystem(path)) => { - Some(ModuleResolutionPathRef::Extra( - ExtraPath::new(path)?.relative_to_search_path(root_path)?, - )) + (ModuleResolutionPath::Extra(_), VfsPath::FileSystem(path)) => { + ModuleResolutionPathRef::extra(path.strip_prefix(root.as_file_system_path_buf()).ok()?) } - (ModuleSearchPathEntry::FirstParty(root_path), VfsPath::FileSystem(path)) => { - Some(ModuleResolutionPathRef::FirstParty( - FirstPartyPath::new(path)?.relative_to_search_path(root_path)?, - )) + (ModuleResolutionPath::FirstParty(_), VfsPath::FileSystem(path)) => { + ModuleResolutionPathRef::first_party( + path.strip_prefix(root.as_file_system_path_buf()).ok()?, + ) } - (ModuleSearchPathEntry::StandardLibrary(root_path), VfsPath::FileSystem(path)) => { - Some(ModuleResolutionPathRef::StandardLibrary( - StandardLibraryPath::new(path)?.relative_to_search_path(root_path)?, - )) + (ModuleResolutionPath::StandardLibrary(_), VfsPath::FileSystem(path)) => { + ModuleResolutionPathRef::standard_library( + path.strip_prefix(root.as_file_system_path_buf()).ok()?, + ) } - (ModuleSearchPathEntry::SitePackagesThirdParty(root_path), VfsPath::FileSystem(path)) => { - Some(ModuleResolutionPathRef::SitePackages( - SitePackagesPath::new(path)?.relative_to_search_path(root_path)?, - )) + (ModuleResolutionPath::SitePackages(_), VfsPath::FileSystem(path)) => { + ModuleResolutionPathRef::site_packages( + path.strip_prefix(root.as_file_system_path_buf()).ok()?, + ) } })?; @@ -141,18 +136,18 @@ pub struct ModuleResolutionSettings { /// List of user-provided paths that should take first priority in the module resolution. /// Examples in other type checkers are mypy's MYPYPATH environment variable, /// or pyright's stubPath configuration setting. - pub extra_paths: Vec, + pub extra_paths: Vec, /// The root of the workspace, used for finding first-party modules. - pub workspace_root: FirstPartyPathBuf, + pub workspace_root: FileSystemPathBuf, /// Optional (already validated) path to standard-library typeshed stubs. /// If this is not provided, we will fallback to our vendored typeshed stubs /// bundled as a zip file in the binary - pub custom_typeshed: Option, + pub custom_typeshed: Option, /// The path to the user's `site-packages` directory, where third-party packages from ``PyPI`` are installed. - pub site_packages: Option, + pub site_packages: Option, } impl ModuleResolutionSettings { @@ -167,22 +162,21 @@ impl ModuleResolutionSettings { custom_typeshed, } = self; - let mut paths: Vec<_> = extra_paths + let mut paths = extra_paths .into_iter() - .map(ModuleSearchPathEntry::Extra) - .collect(); + .map(ModuleResolutionPath::extra) + .collect::>>() + .unwrap(); - paths.push(ModuleSearchPathEntry::FirstParty(workspace_root)); + paths.push(ModuleResolutionPath::first_party(workspace_root).unwrap()); if let Some(custom_typeshed) = custom_typeshed { - paths.push(ModuleSearchPathEntry::stdlib_from_typeshed_root( - &custom_typeshed, - )); + paths.push(ModuleResolutionPath::stdlib_from_typeshed_root(&custom_typeshed).unwrap()); } // TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step if let Some(site_packages) = site_packages { - paths.push(ModuleSearchPathEntry::SitePackagesThirdParty(site_packages)); + paths.push(ModuleResolutionPath::site_packages(site_packages).unwrap()); } ( @@ -195,10 +189,10 @@ impl ModuleResolutionSettings { /// A resolved module resolution order, implementing PEP 561 /// (with some small, deliberate differences) #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub(crate) struct OrderedSearchPaths(Vec>); +pub(crate) struct OrderedSearchPaths(Vec>); impl Deref for OrderedSearchPaths { - type Target = [Arc]; + type Target = [Arc]; fn deref(&self) -> &Self::Target { &self.0 @@ -230,7 +224,7 @@ pub(crate) mod internal { } } -fn module_search_paths(db: &dyn Db) -> &[Arc] { +fn module_search_paths(db: &dyn Db) -> &[Arc] { ModuleResolverSearchPaths::get(db).search_paths(db) } @@ -239,14 +233,14 @@ fn module_search_paths(db: &dyn Db) -> &[Arc] { fn resolve_name( db: &dyn Db, name: &ModuleName, -) -> Option<(Arc, VfsFile, ModuleKind)> { +) -> Option<(Arc, VfsFile, ModuleKind)> { let search_paths = module_search_paths(db); for search_path in search_paths { let mut components = name.components(); let module_name = components.next_back()?; - match resolve_package(db, search_path.path(), components) { + match resolve_package(db, search_path, components) { Ok(resolved_package) => { let mut package_path = resolved_package.path; @@ -293,13 +287,13 @@ fn resolve_name( fn resolve_package<'a, I>( db: &dyn Db, - module_search_path: ModuleResolutionPathRef, + module_search_path: &ModuleResolutionPath, components: I, ) -> Result where I: Iterator, { - let mut package_path = module_search_path.to_owned(); + let mut package_path = module_search_path.clone(); // `true` if inside a folder that is a namespace package (has no `__init__.py`). // Namespace packages are special because they can be spread across multiple search paths. @@ -385,26 +379,23 @@ mod tests { use crate::db::tests::TestDb; use crate::module::ModuleKind; use crate::module_name::ModuleName; - use crate::path::{FirstPartyPath, SitePackagesPath}; use super::*; struct TestCase { db: TestDb, - src: FirstPartyPathBuf, - custom_typeshed: StandardLibraryPathBuf, - site_packages: SitePackagesPathBuf, + src: FileSystemPathBuf, + custom_typeshed: FileSystemPathBuf, + site_packages: FileSystemPathBuf, } fn create_resolver() -> std::io::Result { let mut db = TestDb::new(); - let src = FirstPartyPath::new("src").unwrap().to_path_buf(); - let site_packages = SitePackagesPath::new("site_packages") - .unwrap() - .to_path_buf(); - let custom_typeshed = StandardLibraryPath::new("typeshed").unwrap().to_path_buf(); + let src = FileSystemPath::new("src").to_path_buf(); + let site_packages = FileSystemPath::new("site_packages").to_path_buf(); + let custom_typeshed = FileSystemPath::new("typeshed").to_path_buf(); let fs = db.memory_file_system(); @@ -447,13 +438,13 @@ mod tests { ); assert_eq!("foo", foo_module.name()); - assert_eq!(*src, foo_module.search_path().path()); + assert_eq!(*src, foo_module.search_path()); assert_eq!(ModuleKind::Module, foo_module.kind()); assert_eq!(*foo_path, *foo_module.file().path(&db)); assert_eq!( Some(foo_module), - path_to_module(&db, &foo_path.into_vfs_path()) + path_to_module(&db, &VfsPath::FileSystem(foo_path)) ); Ok(()) @@ -467,10 +458,10 @@ mod tests { .. } = create_resolver()?; - let stdlib_dir = StandardLibraryPath::stdlib_from_typeshed_root(&custom_typeshed); + let stdlib_dir = ModuleResolutionPath::stdlib_from_typeshed_root(&custom_typeshed).unwrap(); let functools_path = stdlib_dir.join("functools.pyi"); db.memory_file_system() - .write_file(&*functools_path, "def update_wrapper(): ...")?; + .write_file(&functools_path, "def update_wrapper(): ...")?; let functools_module_name = ModuleName::new_static("functools").unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); @@ -480,10 +471,10 @@ mod tests { resolve_module(&db, functools_module_name).as_ref() ); - assert_eq!(*stdlib_dir, functools_module.search_path().path()); + assert_eq!(stdlib_dir, functools_module.search_path()); assert_eq!(ModuleKind::Module, functools_module.kind()); - assert_eq!(*functools_path, *functools_module.file().path(&db)); + assert_eq!(functools_path, *functools_module.file().path(&db)); assert_eq!( Some(functools_module), @@ -502,19 +493,13 @@ mod tests { .. } = create_resolver()?; - let stdlib_dir = StandardLibraryPath::stdlib_from_typeshed_root(&custom_typeshed); + let stdlib_dir = custom_typeshed.join("stdlib"); let stdlib_functools_path = stdlib_dir.join("functools.pyi"); let first_party_functools_path = src.join("functools.py"); db.memory_file_system().write_files([ - ( - stdlib_functools_path.as_file_system_path(), - "def update_wrapper(): ...", - ), - ( - first_party_functools_path.as_file_system_path(), - "def update_wrapper(): ...", - ), + (&stdlib_functools_path, "def update_wrapper(): ..."), + (&first_party_functools_path, "def update_wrapper(): ..."), ])?; let functools_module_name = ModuleName::new_static("functools").unwrap(); @@ -524,7 +509,7 @@ mod tests { Some(&functools_module), resolve_module(&db, functools_module_name).as_ref() ); - assert_eq!(*src, functools_module.search_path().path()); + assert_eq!(*src, functools_module.search_path()); assert_eq!(ModuleKind::Module, functools_module.kind()); assert_eq!( *first_party_functools_path, @@ -533,7 +518,7 @@ mod tests { assert_eq!( Some(functools_module), - path_to_module(&db, &first_party_functools_path.into_vfs_path()) + path_to_module(&db, &VfsPath::FileSystem(first_party_functools_path)) ); Ok(()) @@ -576,16 +561,16 @@ mod tests { let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); assert_eq!("foo", foo_module.name()); - assert_eq!(*src, foo_module.search_path().path()); + assert_eq!(*src, foo_module.search_path()); assert_eq!(*foo_path, *foo_module.file().path(&db)); assert_eq!( Some(&foo_module), - path_to_module(&db, &foo_path.into_vfs_path()).as_ref() + path_to_module(&db, &VfsPath::FileSystem(foo_path)).as_ref() ); // Resolving by directory doesn't resolve to the init file. - assert_eq!(None, path_to_module(&db, &foo_dir.into_vfs_path())); + assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_dir))); Ok(()) } @@ -606,15 +591,15 @@ mod tests { let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); - assert_eq!(*src, foo_module.search_path().path()); + assert_eq!(*src, foo_module.search_path()); assert_eq!(*foo_init, *foo_module.file().path(&db)); assert_eq!(ModuleKind::Package, foo_module.kind()); assert_eq!( Some(foo_module), - path_to_module(&db, &foo_init.into_vfs_path()) + path_to_module(&db, &VfsPath::FileSystem(foo_init)) ); - assert_eq!(None, path_to_module(&db, &foo_py.into_vfs_path())); + assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_py))); Ok(()) } @@ -630,11 +615,14 @@ mod tests { let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); - assert_eq!(*src, foo.search_path().path()); + assert_eq!(*src, foo.search_path()); assert_eq!(*foo_stub, *foo.file().path(&db)); - assert_eq!(Some(foo), path_to_module(&db, &foo_stub.into_vfs_path())); - assert_eq!(None, path_to_module(&db, &foo_py.into_vfs_path())); + assert_eq!( + Some(foo), + path_to_module(&db, &VfsPath::FileSystem(foo_stub)) + ); + assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_py))); Ok(()) } @@ -656,10 +644,13 @@ mod tests { let baz_module = resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap(); - assert_eq!(*src, baz_module.search_path().path()); + assert_eq!(*src, baz_module.search_path()); assert_eq!(*baz, *baz_module.file().path(&db)); - assert_eq!(Some(baz_module), path_to_module(&db, &baz.into_vfs_path())); + assert_eq!( + Some(baz_module), + path_to_module(&db, &VfsPath::FileSystem(baz)) + ); Ok(()) } @@ -695,18 +686,24 @@ mod tests { let two = child2.join("two.py"); db.memory_file_system().write_files([ - (one.as_file_system_path(), "print('Hello, world!')"), - (two.as_file_system_path(), "print('Hello, world!')"), + (&one, "print('Hello, world!')"), + (&two, "print('Hello, world!')"), ])?; let one_module = resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap()).unwrap(); - assert_eq!(Some(one_module), path_to_module(&db, &one.into_vfs_path())); + assert_eq!( + Some(one_module), + path_to_module(&db, &VfsPath::FileSystem(one)) + ); let two_module = resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap()).unwrap(); - assert_eq!(Some(two_module), path_to_module(&db, &two.into_vfs_path())); + assert_eq!( + Some(two_module), + path_to_module(&db, &VfsPath::FileSystem(two)) + ); Ok(()) } @@ -742,18 +739,18 @@ mod tests { let two = child2.join("two.py"); db.memory_file_system().write_files([ - ( - child1.join("__init__.py").as_file_system_path(), - "print('Hello, world!')", - ), - (one.as_file_system_path(), "print('Hello, world!')"), - (two.as_file_system_path(), "print('Hello, world!')"), + (&child1.join("__init__.py"), "print('Hello, world!')"), + (&one, "print('Hello, world!')"), + (&two, "print('Hello, world!')"), ])?; let one_module = resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap()).unwrap(); - assert_eq!(Some(one_module), path_to_module(&db, &one.into_vfs_path())); + assert_eq!( + Some(one_module), + path_to_module(&db, &VfsPath::FileSystem(one)) + ); assert_eq!( None, @@ -774,23 +771,21 @@ mod tests { let foo_src = src.join("foo.py"); let foo_site_packages = site_packages.join("foo.py"); - db.memory_file_system().write_files([ - (foo_src.as_file_system_path(), ""), - (foo_site_packages.as_file_system_path(), ""), - ])?; + db.memory_file_system() + .write_files([(&foo_src, ""), (&foo_site_packages, "")])?; let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); - assert_eq!(*src, foo_module.search_path().path()); + assert_eq!(*src, foo_module.search_path()); assert_eq!(*foo_src, *foo_module.file().path(&db)); assert_eq!( Some(foo_module), - path_to_module(&db, &foo_src.into_vfs_path()) + path_to_module(&db, &VfsPath::FileSystem(foo_src)) ); assert_eq!( None, - path_to_module(&db, &foo_site_packages.into_vfs_path()) + path_to_module(&db, &VfsPath::FileSystem(foo_site_packages)) ); Ok(()) @@ -825,11 +820,9 @@ mod tests { std::fs::write(foo.as_std_path(), "")?; std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?; - let src = FirstPartyPath::new(&src).unwrap().to_path_buf(); - let site_packages = SitePackagesPath::new(&site_packages).unwrap().to_path_buf(); - let custom_typeshed = StandardLibraryPath::new(&custom_typeshed) - .unwrap() - .to_path_buf(); + let src = src.to_path_buf(); + let site_packages = site_packages.to_path_buf(); + let custom_typeshed = custom_typeshed.to_path_buf(); let settings = ModuleResolutionSettings { target_version: SupportedPyVersion::Py38, @@ -846,12 +839,12 @@ mod tests { assert_ne!(foo_module, bar_module); - assert_eq!(*src, foo_module.search_path().path()); + assert_eq!(*src, foo_module.search_path()); assert_eq!(&foo, foo_module.file().path(&db)); // `foo` and `bar` shouldn't resolve to the same file - assert_eq!(*src, bar_module.search_path().path()); + assert_eq!(*src, bar_module.search_path()); assert_eq!(&bar, bar_module.file().path(&db)); assert_eq!(&foo, foo_module.file().path(&db)); @@ -874,7 +867,7 @@ mod tests { let TestCase { mut db, src, .. } = create_resolver()?; let foo_path = src.join("foo.py"); - let bar_path: FirstPartyPathBuf = src.join("bar.py"); + let bar_path = src.join("bar.py"); db.memory_file_system() .write_files([(&*foo_path, "x = 1"), (&*bar_path, "y = 2")])?; @@ -916,7 +909,7 @@ mod tests { // Now write the foo file db.memory_file_system().write_file(&*foo_path, "x = 1")?; - VfsFile::touch_path(&mut db, &foo_path.clone().into_vfs_path()); + VfsFile::touch_path(&mut db, &VfsPath::FileSystem(foo_path.clone())); let foo_file = system_path_to_file(&db, &*foo_path).expect("foo.py to exist"); let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve"); @@ -944,7 +937,7 @@ mod tests { db.memory_file_system().remove_file(&*foo_init_path)?; db.memory_file_system() .remove_directory(foo_init_path.parent().unwrap())?; - VfsFile::touch_path(&mut db, &foo_init_path.into_vfs_path()); + VfsFile::touch_path(&mut db, &VfsPath::FileSystem(foo_init_path)); let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve"); assert_eq!(*foo_path, *foo_module.file().path(&db)); diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6174aede48887..6bbbb9f59a8a8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -511,9 +511,9 @@ impl<'db, 'inference> TypingContext<'db, 'inference> { #[cfg(test)] mod tests { use red_knot_module_resolver::{ - set_module_resolution_settings, FirstPartyPath, ModuleResolutionSettings, - SupportedPyVersion, + set_module_resolution_settings, ModuleResolutionSettings, SupportedPyVersion, }; + use ruff_db::file_system::FileSystemPath; use ruff_db::parsed::parsed_module; use ruff_db::vfs::system_path_to_file; @@ -531,7 +531,7 @@ mod tests { ModuleResolutionSettings { target_version: SupportedPyVersion::Py38, extra_paths: vec![], - workspace_root: FirstPartyPath::new("/src").unwrap().to_path_buf(), + workspace_root: FileSystemPath::new("/src").to_path_buf(), site_packages: None, custom_typeshed: None, }, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index e1ebca8c4acef..c8449682bb6fa 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -700,13 +700,13 @@ impl<'db> TypeInferenceBuilder<'db> { #[cfg(test)] mod tests { + use ruff_db::file_system::FileSystemPath; use ruff_db::vfs::system_path_to_file; use crate::db::tests::TestDb; use crate::types::{public_symbol_ty_by_name, Type, TypingContext}; use red_knot_module_resolver::{ - set_module_resolution_settings, FirstPartyPath, ModuleResolutionSettings, - SupportedPyVersion, + set_module_resolution_settings, ModuleResolutionSettings, SupportedPyVersion, }; use ruff_python_ast::name::Name; @@ -718,7 +718,7 @@ mod tests { ModuleResolutionSettings { target_version: SupportedPyVersion::Py38, extra_paths: Vec::new(), - workspace_root: FirstPartyPath::new("/src").unwrap().to_path_buf(), + workspace_root: FileSystemPath::new("/src").to_path_buf(), site_packages: None, custom_typeshed: None, },