[red-knot] Use a distinct type for module search paths in the module resolver (#12379)

This commit is contained in:
Alex Waygood 2024-07-22 20:44:27 +01:00 committed by GitHub
parent ea2d51c2bb
commit 2a8f95c437
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 334 additions and 316 deletions

View File

@ -5,7 +5,7 @@ use ruff_db::files::File;
use crate::db::Db; use crate::db::Db;
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::path::{ModuleResolutionPathBuf, ModuleResolutionPathRef}; use crate::path::ModuleSearchPath;
/// Representation of a Python module. /// Representation of a Python module.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
@ -17,7 +17,7 @@ impl Module {
pub(crate) fn new( pub(crate) fn new(
name: ModuleName, name: ModuleName,
kind: ModuleKind, kind: ModuleKind,
search_path: Arc<ModuleResolutionPathBuf>, search_path: ModuleSearchPath,
file: File, file: File,
) -> Self { ) -> Self {
Self { Self {
@ -41,8 +41,8 @@ impl Module {
} }
/// The search path from which the module was resolved. /// The search path from which the module was resolved.
pub(crate) fn search_path(&self) -> ModuleResolutionPathRef { pub(crate) fn search_path(&self) -> &ModuleSearchPath {
ModuleResolutionPathRef::from(&*self.inner.search_path) &self.inner.search_path
} }
/// Determine whether this module is a single-file module or a package /// Determine whether this module is a single-file module or a package
@ -77,7 +77,7 @@ impl salsa::DebugWithDb<dyn Db> for Module {
struct ModuleInner { struct ModuleInner {
name: ModuleName, name: ModuleName,
kind: ModuleKind, kind: ModuleKind,
search_path: Arc<ModuleResolutionPathBuf>, search_path: ModuleSearchPath,
file: File, file: File,
} }

View File

@ -4,6 +4,8 @@
//! <https://github.com/astral-sh/ruff/pull/12141#discussion_r1667010245> //! <https://github.com/astral-sh/ruff/pull/12141#discussion_r1667010245>
use std::fmt; use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
use ruff_db::files::{system_path_to_file, vendored_path_to_file, File, FilePath}; use ruff_db::files::{system_path_to_file, vendored_path_to_file, File, FilePath};
use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_db::system::{System, SystemPath, SystemPathBuf};
@ -68,7 +70,7 @@ impl<'a> From<&'a FilePath> for FilePathRef<'a> {
/// ///
/// [the order given in the typing spec]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering /// [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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ModuleResolutionPathBufInner { enum ModulePathBufInner {
Extra(SystemPathBuf), Extra(SystemPathBuf),
FirstParty(SystemPathBuf), FirstParty(SystemPathBuf),
StandardLibrary(FilePath), StandardLibrary(FilePath),
@ -76,7 +78,7 @@ enum ModuleResolutionPathBufInner {
EditableInstall(SystemPathBuf), EditableInstall(SystemPathBuf),
} }
impl ModuleResolutionPathBufInner { impl ModulePathBufInner {
fn push(&mut self, component: &str) { fn push(&mut self, component: &str) {
let extension = camino::Utf8Path::new(component).extension(); let extension = camino::Utf8Path::new(component).extension();
match self { match self {
@ -153,9 +155,9 @@ impl ModuleResolutionPathBufInner {
} }
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) struct ModuleResolutionPathBuf(ModuleResolutionPathBufInner); pub(crate) struct ModulePathBuf(ModulePathBufInner);
impl ModuleResolutionPathBuf { impl ModulePathBuf {
/// Push a new part to the path, /// 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. /// For the stdlib variant specifically, it may only have a `.pyi` extension.
@ -171,7 +173,7 @@ impl ModuleResolutionPathBuf {
let path = path.into(); let path = path.into();
path.extension() path.extension()
.map_or(true, |ext| matches!(ext, "py" | "pyi")) .map_or(true, |ext| matches!(ext, "py" | "pyi"))
.then_some(Self(ModuleResolutionPathBufInner::Extra(path))) .then_some(Self(ModulePathBufInner::Extra(path)))
} }
#[must_use] #[must_use]
@ -179,28 +181,14 @@ impl ModuleResolutionPathBuf {
let path = path.into(); let path = path.into();
path.extension() path.extension()
.map_or(true, |ext| matches!(ext, "pyi" | "py")) .map_or(true, |ext| matches!(ext, "pyi" | "py"))
.then_some(Self(ModuleResolutionPathBufInner::FirstParty(path))) .then_some(Self(ModulePathBufInner::FirstParty(path)))
} }
#[must_use] #[must_use]
pub(crate) fn standard_library(path: FilePath) -> Option<Self> { pub(crate) fn standard_library(path: FilePath) -> Option<Self> {
path.extension() path.extension()
.map_or(true, |ext| ext == "pyi") .map_or(true, |ext| ext == "pyi")
.then_some(Self(ModuleResolutionPathBufInner::StandardLibrary(path))) .then_some(Self(ModulePathBufInner::StandardLibrary(path)))
}
#[must_use]
pub(crate) fn stdlib_from_custom_typeshed_root(typeshed_root: &SystemPath) -> Option<Self> {
Self::standard_library(FilePath::System(
typeshed_root.join(SystemPath::new("stdlib")),
))
}
#[must_use]
pub(crate) fn vendored_stdlib() -> Self {
Self(ModuleResolutionPathBufInner::StandardLibrary(
FilePath::Vendored(VendoredPathBuf::from("stdlib")),
))
} }
#[must_use] #[must_use]
@ -208,7 +196,7 @@ impl ModuleResolutionPathBuf {
let path = path.into(); let path = path.into();
path.extension() path.extension()
.map_or(true, |ext| matches!(ext, "pyi" | "py")) .map_or(true, |ext| matches!(ext, "pyi" | "py"))
.then_some(Self(ModuleResolutionPathBufInner::SitePackages(path))) .then_some(Self(ModulePathBufInner::SitePackages(path)))
} }
#[must_use] #[must_use]
@ -220,104 +208,115 @@ impl ModuleResolutionPathBuf {
// TODO: Add Salsa invalidation to this system call: // TODO: Add Salsa invalidation to this system call:
system system
.is_directory(&path) .is_directory(&path)
.then_some(Self(ModuleResolutionPathBufInner::EditableInstall(path))) .then_some(Self(ModulePathBufInner::EditableInstall(path)))
} }
#[must_use] #[must_use]
pub(crate) fn is_regular_package(&self, search_path: &Self, resolver: &ResolverState) -> bool { pub(crate) fn is_regular_package(&self, search_path: &Self, resolver: &ResolverState) -> bool {
ModuleResolutionPathRef::from(self).is_regular_package(search_path, resolver) ModulePathRef::from(self).is_regular_package(search_path, resolver)
} }
#[must_use] #[must_use]
pub(crate) fn is_directory(&self, search_path: &Self, resolver: &ResolverState) -> bool { pub(crate) fn is_directory(&self, search_path: &Self, resolver: &ResolverState) -> bool {
ModuleResolutionPathRef::from(self).is_directory(search_path, resolver) ModulePathRef::from(self).is_directory(search_path, resolver)
} }
#[must_use] #[must_use]
pub(crate) const fn is_site_packages(&self) -> bool { pub(crate) const fn is_site_packages(&self) -> bool {
matches!(self.0, ModuleResolutionPathBufInner::SitePackages(_)) matches!(self.0, ModulePathBufInner::SitePackages(_))
} }
#[must_use] #[must_use]
pub(crate) const fn is_standard_library(&self) -> bool { pub(crate) const fn is_standard_library(&self) -> bool {
matches!(self.0, ModuleResolutionPathBufInner::StandardLibrary(_)) matches!(self.0, ModulePathBufInner::StandardLibrary(_))
} }
#[must_use] #[must_use]
pub(crate) fn with_pyi_extension(&self) -> Self { pub(crate) fn with_pyi_extension(&self) -> Self {
ModuleResolutionPathRef::from(self).with_pyi_extension() ModulePathRef::from(self).with_pyi_extension()
} }
#[must_use] #[must_use]
pub(crate) fn with_py_extension(&self) -> Option<Self> { pub(crate) fn with_py_extension(&self) -> Option<Self> {
ModuleResolutionPathRef::from(self).with_py_extension() ModulePathRef::from(self).with_py_extension()
} }
#[must_use] #[must_use]
pub(crate) fn relativize_path<'a>( pub(crate) fn relativize_path<'a>(
&'a self, &'a self,
absolute_path: &'a FilePath, absolute_path: &'a FilePath,
) -> Option<ModuleResolutionPathRef<'a>> { ) -> Option<ModulePathRef<'a>> {
ModuleResolutionPathRef::from(self).relativize_path(&FilePathRef::from(absolute_path)) ModulePathRef::from(self).relativize_path(&FilePathRef::from(absolute_path))
} }
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory. /// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory.
pub(crate) fn to_file(&self, search_path: &Self, resolver: &ResolverState) -> Option<File> { pub(crate) fn to_file(&self, search_path: &Self, resolver: &ResolverState) -> Option<File> {
ModuleResolutionPathRef::from(self).to_file(search_path, resolver) ModulePathRef::from(self).to_file(search_path, resolver)
} }
pub(crate) fn as_system_path(&self) -> Option<&SystemPathBuf> { pub(crate) fn as_system_path(&self) -> Option<&SystemPathBuf> {
match &self.0 { match &self.0 {
ModuleResolutionPathBufInner::Extra(path) => Some(path), ModulePathBufInner::Extra(path) => Some(path),
ModuleResolutionPathBufInner::FirstParty(path) => Some(path), ModulePathBufInner::FirstParty(path) => Some(path),
ModuleResolutionPathBufInner::StandardLibrary(_) => None, ModulePathBufInner::StandardLibrary(_) => None,
ModuleResolutionPathBufInner::SitePackages(path) => Some(path), ModulePathBufInner::SitePackages(path) => Some(path),
ModuleResolutionPathBufInner::EditableInstall(path) => Some(path), ModulePathBufInner::EditableInstall(path) => Some(path),
} }
} }
} }
impl fmt::Debug for ModuleResolutionPathBuf { impl fmt::Debug for ModulePathBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
ModuleResolutionPathBufInner::Extra(path) => f ModulePathBufInner::Extra(path) => {
.debug_tuple("ModuleResolutionPathBuf::Extra") f.debug_tuple("ModulePathBuf::Extra").field(path).finish()
}
ModulePathBufInner::FirstParty(path) => f
.debug_tuple("ModulePathBuf::FirstParty")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathBufInner::FirstParty(path) => f ModulePathBufInner::SitePackages(path) => f
.debug_tuple("ModuleResolutionPathBuf::FirstParty") .debug_tuple("ModulePathBuf::SitePackages")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathBufInner::SitePackages(path) => f ModulePathBufInner::StandardLibrary(path) => f
.debug_tuple("ModuleResolutionPathBuf::SitePackages") .debug_tuple("ModulePathBuf::StandardLibrary")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathBufInner::StandardLibrary(path) => f ModulePathBufInner::EditableInstall(path) => f
.debug_tuple("ModuleResolutionPathBuf::StandardLibrary") .debug_tuple("ModulePathBuf::EditableInstall")
.field(path)
.finish(),
ModuleResolutionPathBufInner::EditableInstall(path) => f
.debug_tuple("ModuleResolutionPathBuf::EditableInstall")
.field(path) .field(path)
.finish(), .finish(),
} }
} }
} }
impl PartialEq<SystemPathBuf> for ModuleResolutionPathBuf { impl PartialEq<SystemPathBuf> for ModulePathBuf {
fn eq(&self, other: &SystemPathBuf) -> bool { fn eq(&self, other: &SystemPathBuf) -> bool {
ModuleResolutionPathRef::from(self) == **other ModulePathRef::from(self) == **other
} }
} }
impl PartialEq<ModuleResolutionPathBuf> for SystemPathBuf { impl PartialEq<ModulePathBuf> for SystemPathBuf {
fn eq(&self, other: &ModuleResolutionPathBuf) -> bool { fn eq(&self, other: &ModulePathBuf) -> bool {
other.eq(self)
}
}
impl PartialEq<VendoredPathBuf> for ModulePathBuf {
fn eq(&self, other: &VendoredPathBuf) -> bool {
ModulePathRef::from(self) == **other
}
}
impl PartialEq<ModulePathBuf> for VendoredPathBuf {
fn eq(&self, other: &ModulePathBuf) -> bool {
other.eq(self) other.eq(self)
} }
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
enum ModuleResolutionPathRefInner<'a> { enum ModulePathRefInner<'a> {
Extra(&'a SystemPath), Extra(&'a SystemPath),
FirstParty(&'a SystemPath), FirstParty(&'a SystemPath),
StandardLibrary(FilePathRef<'a>), StandardLibrary(FilePathRef<'a>),
@ -325,7 +324,7 @@ enum ModuleResolutionPathRefInner<'a> {
EditableInstall(&'a SystemPath), EditableInstall(&'a SystemPath),
} }
impl<'a> ModuleResolutionPathRefInner<'a> { impl<'a> ModulePathRefInner<'a> {
#[must_use] #[must_use]
fn query_stdlib_version<'db>( fn query_stdlib_version<'db>(
module_path: &FilePathRef<'a>, module_path: &FilePathRef<'a>,
@ -463,45 +462,37 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
} }
#[must_use] #[must_use]
fn with_pyi_extension(&self) -> ModuleResolutionPathBufInner { fn with_pyi_extension(&self) -> ModulePathBufInner {
match self { match self {
Self::Extra(path) => ModuleResolutionPathBufInner::Extra(path.with_extension("pyi")), Self::Extra(path) => ModulePathBufInner::Extra(path.with_extension("pyi")),
Self::FirstParty(path) => { Self::FirstParty(path) => ModulePathBufInner::FirstParty(path.with_extension("pyi")),
ModuleResolutionPathBufInner::FirstParty(path.with_extension("pyi"))
}
Self::StandardLibrary(FilePathRef::System(path)) => { Self::StandardLibrary(FilePathRef::System(path)) => {
ModuleResolutionPathBufInner::StandardLibrary(FilePath::System( ModulePathBufInner::StandardLibrary(FilePath::System(path.with_extension("pyi")))
path.with_extension("pyi"),
))
} }
Self::StandardLibrary(FilePathRef::Vendored(path)) => { Self::StandardLibrary(FilePathRef::Vendored(path)) => {
ModuleResolutionPathBufInner::StandardLibrary(FilePath::Vendored( ModulePathBufInner::StandardLibrary(FilePath::Vendored(path.with_pyi_extension()))
path.with_pyi_extension(),
))
} }
Self::SitePackages(path) => { Self::SitePackages(path) => {
ModuleResolutionPathBufInner::SitePackages(path.with_extension("pyi")) ModulePathBufInner::SitePackages(path.with_extension("pyi"))
} }
Self::EditableInstall(path) => { Self::EditableInstall(path) => {
ModuleResolutionPathBufInner::EditableInstall(path.with_extension("pyi")) ModulePathBufInner::EditableInstall(path.with_extension("pyi"))
} }
} }
} }
#[must_use] #[must_use]
fn with_py_extension(&self) -> Option<ModuleResolutionPathBufInner> { fn with_py_extension(&self) -> Option<ModulePathBufInner> {
match self { match self {
Self::Extra(path) => Some(ModuleResolutionPathBufInner::Extra( Self::Extra(path) => Some(ModulePathBufInner::Extra(path.with_extension("py"))),
path.with_extension("py"), Self::FirstParty(path) => {
)), Some(ModulePathBufInner::FirstParty(path.with_extension("py")))
Self::FirstParty(path) => Some(ModuleResolutionPathBufInner::FirstParty( }
path.with_extension("py"),
)),
Self::StandardLibrary(_) => None, Self::StandardLibrary(_) => None,
Self::SitePackages(path) => Some(ModuleResolutionPathBufInner::SitePackages( Self::SitePackages(path) => {
path.with_extension("py"), Some(ModulePathBufInner::SitePackages(path.with_extension("py")))
)), }
Self::EditableInstall(path) => Some(ModuleResolutionPathBufInner::EditableInstall( Self::EditableInstall(path) => Some(ModulePathBufInner::EditableInstall(
path.with_extension("py"), path.with_extension("py"),
)), )),
} }
@ -567,9 +558,9 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) struct ModuleResolutionPathRef<'a>(ModuleResolutionPathRefInner<'a>); pub(crate) struct ModulePathRef<'a>(ModulePathRefInner<'a>);
impl<'a> ModuleResolutionPathRef<'a> { impl<'a> ModulePathRef<'a> {
#[must_use] #[must_use]
pub(crate) fn is_directory( pub(crate) fn is_directory(
&self, &self,
@ -603,13 +594,13 @@ impl<'a> ModuleResolutionPathRef<'a> {
} }
#[must_use] #[must_use]
pub(crate) fn with_pyi_extension(&self) -> ModuleResolutionPathBuf { pub(crate) fn with_pyi_extension(&self) -> ModulePathBuf {
ModuleResolutionPathBuf(self.0.with_pyi_extension()) ModulePathBuf(self.0.with_pyi_extension())
} }
#[must_use] #[must_use]
pub(crate) fn with_py_extension(self) -> Option<ModuleResolutionPathBuf> { pub(crate) fn with_py_extension(self) -> Option<ModulePathBuf> {
self.0.with_py_extension().map(ModuleResolutionPathBuf) self.0.with_py_extension().map(ModulePathBuf)
} }
#[must_use] #[must_use]
@ -618,123 +609,188 @@ impl<'a> ModuleResolutionPathRef<'a> {
} }
} }
impl fmt::Debug for ModuleResolutionPathRef<'_> { impl fmt::Debug for ModulePathRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
ModuleResolutionPathRefInner::Extra(path) => f ModulePathRefInner::Extra(path) => {
.debug_tuple("ModuleResolutionPathRef::Extra") f.debug_tuple("ModulePathRef::Extra").field(path).finish()
}
ModulePathRefInner::FirstParty(path) => f
.debug_tuple("ModulePathRef::FirstParty")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathRefInner::FirstParty(path) => f ModulePathRefInner::SitePackages(path) => f
.debug_tuple("ModuleResolutionPathRef::FirstParty") .debug_tuple("ModulePathRef::SitePackages")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathRefInner::SitePackages(path) => f ModulePathRefInner::StandardLibrary(path) => f
.debug_tuple("ModuleResolutionPathRef::SitePackages") .debug_tuple("ModulePathRef::StandardLibrary")
.field(path) .field(path)
.finish(), .finish(),
ModuleResolutionPathRefInner::StandardLibrary(path) => f ModulePathRefInner::EditableInstall(path) => f
.debug_tuple("ModuleResolutionPathRef::StandardLibrary") .debug_tuple("ModulePathRef::EditableInstall")
.field(path)
.finish(),
ModuleResolutionPathRefInner::EditableInstall(path) => f
.debug_tuple("ModuleResolutionPathRef::EditableInstall")
.field(path) .field(path)
.finish(), .finish(),
} }
} }
} }
impl<'a> From<&'a ModuleResolutionPathBuf> for ModuleResolutionPathRef<'a> { impl<'a> From<&'a ModulePathBuf> for ModulePathRef<'a> {
fn from(value: &'a ModuleResolutionPathBuf) -> Self { fn from(value: &'a ModulePathBuf) -> Self {
let inner = match &value.0 { let inner = match &value.0 {
ModuleResolutionPathBufInner::Extra(path) => ModuleResolutionPathRefInner::Extra(path), ModulePathBufInner::Extra(path) => ModulePathRefInner::Extra(path),
ModuleResolutionPathBufInner::FirstParty(path) => { ModulePathBufInner::FirstParty(path) => ModulePathRefInner::FirstParty(path),
ModuleResolutionPathRefInner::FirstParty(path) ModulePathBufInner::StandardLibrary(FilePath::System(path)) => {
ModulePathRefInner::StandardLibrary(FilePathRef::System(path))
} }
ModuleResolutionPathBufInner::StandardLibrary(FilePath::System(path)) => { ModulePathBufInner::StandardLibrary(FilePath::Vendored(path)) => {
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::System(path)) ModulePathRefInner::StandardLibrary(FilePathRef::Vendored(path))
}
ModuleResolutionPathBufInner::StandardLibrary(FilePath::Vendored(path)) => {
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::Vendored(path))
}
ModuleResolutionPathBufInner::SitePackages(path) => {
ModuleResolutionPathRefInner::SitePackages(path)
}
ModuleResolutionPathBufInner::EditableInstall(path) => {
ModuleResolutionPathRefInner::EditableInstall(path)
} }
ModulePathBufInner::SitePackages(path) => ModulePathRefInner::SitePackages(path),
ModulePathBufInner::EditableInstall(path) => ModulePathRefInner::EditableInstall(path),
}; };
ModuleResolutionPathRef(inner) ModulePathRef(inner)
} }
} }
impl PartialEq<SystemPath> for ModuleResolutionPathRef<'_> { impl PartialEq<SystemPath> for ModulePathRef<'_> {
fn eq(&self, other: &SystemPath) -> bool { fn eq(&self, other: &SystemPath) -> bool {
match self.0 { match self.0 {
ModuleResolutionPathRefInner::Extra(path) => path == other, ModulePathRefInner::Extra(path) => path == other,
ModuleResolutionPathRefInner::FirstParty(path) => path == other, ModulePathRefInner::FirstParty(path) => path == other,
ModuleResolutionPathRefInner::SitePackages(path) => path == other, ModulePathRefInner::SitePackages(path) => path == other,
ModuleResolutionPathRefInner::EditableInstall(path) => path == other, ModulePathRefInner::EditableInstall(path) => path == other,
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::System(path)) => { ModulePathRefInner::StandardLibrary(FilePathRef::System(path)) => path == other,
path == other ModulePathRefInner::StandardLibrary(FilePathRef::Vendored(_)) => false,
}
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::Vendored(_)) => false,
} }
} }
} }
impl PartialEq<ModuleResolutionPathRef<'_>> for SystemPath { impl PartialEq<ModulePathRef<'_>> for SystemPath {
fn eq(&self, other: &ModuleResolutionPathRef) -> bool { fn eq(&self, other: &ModulePathRef) -> bool {
other == self other == self
} }
} }
impl PartialEq<SystemPathBuf> for ModuleResolutionPathRef<'_> { impl PartialEq<SystemPathBuf> for ModulePathRef<'_> {
fn eq(&self, other: &SystemPathBuf) -> bool { fn eq(&self, other: &SystemPathBuf) -> bool {
self == &**other self == &**other
} }
} }
impl PartialEq<ModuleResolutionPathRef<'_>> for SystemPathBuf { impl PartialEq<ModulePathRef<'_>> for SystemPathBuf {
fn eq(&self, other: &ModuleResolutionPathRef<'_>) -> bool { fn eq(&self, other: &ModulePathRef<'_>) -> bool {
&**self == other &**self == other
} }
} }
impl PartialEq<VendoredPath> for ModuleResolutionPathRef<'_> { impl PartialEq<VendoredPath> for ModulePathRef<'_> {
fn eq(&self, other: &VendoredPath) -> bool { fn eq(&self, other: &VendoredPath) -> bool {
match self.0 { match self.0 {
ModuleResolutionPathRefInner::Extra(_) => false, ModulePathRefInner::Extra(_) => false,
ModuleResolutionPathRefInner::FirstParty(_) => false, ModulePathRefInner::FirstParty(_) => false,
ModuleResolutionPathRefInner::SitePackages(_) => false, ModulePathRefInner::SitePackages(_) => false,
ModuleResolutionPathRefInner::EditableInstall(_) => false, ModulePathRefInner::EditableInstall(_) => false,
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::System(_)) => false, ModulePathRefInner::StandardLibrary(FilePathRef::System(_)) => false,
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::Vendored(path)) => { ModulePathRefInner::StandardLibrary(FilePathRef::Vendored(path)) => path == other,
path == other
}
} }
} }
} }
impl PartialEq<ModuleResolutionPathRef<'_>> for VendoredPath { impl PartialEq<ModulePathRef<'_>> for VendoredPath {
fn eq(&self, other: &ModuleResolutionPathRef) -> bool { fn eq(&self, other: &ModulePathRef) -> bool {
other == self other == self
} }
} }
impl PartialEq<VendoredPathBuf> for ModuleResolutionPathRef<'_> { impl PartialEq<VendoredPathBuf> for ModulePathRef<'_> {
fn eq(&self, other: &VendoredPathBuf) -> bool { fn eq(&self, other: &VendoredPathBuf) -> bool {
self == &**other self == &**other
} }
} }
impl PartialEq<ModuleResolutionPathRef<'_>> for VendoredPathBuf { impl PartialEq<ModulePathRef<'_>> for VendoredPathBuf {
fn eq(&self, other: &ModuleResolutionPathRef<'_>) -> bool { fn eq(&self, other: &ModulePathRef<'_>) -> bool {
&**self == other &**self == other
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ModuleSearchPath(Arc<ModulePathBuf>);
impl ModuleSearchPath {
pub(crate) fn extra(path: SystemPathBuf) -> Option<Self> {
Some(Self(Arc::new(ModulePathBuf::extra(path)?)))
}
pub(crate) fn first_party(path: SystemPathBuf) -> Option<Self> {
Some(Self(Arc::new(ModulePathBuf::first_party(path)?)))
}
pub(crate) fn custom_stdlib(path: &SystemPath) -> Option<Self> {
Some(Self(Arc::new(ModulePathBuf::standard_library(
FilePath::System(path.join("stdlib")),
)?)))
}
pub(crate) fn vendored_stdlib() -> Self {
Self(Arc::new(ModulePathBuf(
ModulePathBufInner::StandardLibrary(FilePath::Vendored(VendoredPathBuf::from(
"stdlib",
))),
)))
}
pub(crate) fn site_packages(path: SystemPathBuf) -> Option<Self> {
Some(Self(Arc::new(ModulePathBuf::site_packages(path)?)))
}
pub(crate) fn editable(system: &dyn System, path: SystemPathBuf) -> Option<Self> {
Some(Self(Arc::new(ModulePathBuf::editable_installation_root(
system, path,
)?)))
}
pub(crate) fn as_module_path(&self) -> &ModulePathBuf {
&self.0
}
}
impl PartialEq<SystemPathBuf> for ModuleSearchPath {
fn eq(&self, other: &SystemPathBuf) -> bool {
&*self.0 == other
}
}
impl PartialEq<ModuleSearchPath> for SystemPathBuf {
fn eq(&self, other: &ModuleSearchPath) -> bool {
other.eq(self)
}
}
impl PartialEq<VendoredPathBuf> for ModuleSearchPath {
fn eq(&self, other: &VendoredPathBuf) -> bool {
&*self.0 == other
}
}
impl PartialEq<ModuleSearchPath> for VendoredPathBuf {
fn eq(&self, other: &ModuleSearchPath) -> bool {
other.eq(self)
}
}
// TODO: this is unprincipled.
// We should instead just implement the methods we need on ModuleSearchPath,
// and adjust the signatures/implementations of methods that receive ModuleSearchPaths.
impl Deref for ModuleSearchPath {
type Target = ModulePathBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use insta::assert_debug_snapshot; use insta::assert_debug_snapshot;
@ -751,67 +807,60 @@ mod tests {
} }
} }
impl ModuleResolutionPathBuf { impl ModulePathBuf {
#[must_use] #[must_use]
pub(crate) fn join(&self, component: &str) -> Self { pub(crate) fn join(&self, component: &str) -> Self {
ModuleResolutionPathRef::from(self).join(component) ModulePathRef::from(self).join(component)
} }
} }
impl<'a> ModuleResolutionPathRef<'a> { impl<'a> ModulePathRef<'a> {
#[must_use] #[must_use]
fn join( fn join(&self, component: &'a (impl AsRef<SystemPath> + ?Sized)) -> ModulePathBuf {
&self,
component: &'a (impl AsRef<SystemPath> + ?Sized),
) -> ModuleResolutionPathBuf {
let mut result = self.to_path_buf(); let mut result = self.to_path_buf();
result.push(component.as_ref().as_str()); result.push(component.as_ref().as_str());
result result
} }
#[must_use] #[must_use]
pub(crate) fn to_path_buf(self) -> ModuleResolutionPathBuf { pub(crate) fn to_path_buf(self) -> ModulePathBuf {
let inner = match self.0 { let inner = match self.0 {
ModuleResolutionPathRefInner::Extra(path) => { ModulePathRefInner::Extra(path) => ModulePathBufInner::Extra(path.to_path_buf()),
ModuleResolutionPathBufInner::Extra(path.to_path_buf()) ModulePathRefInner::FirstParty(path) => {
ModulePathBufInner::FirstParty(path.to_path_buf())
} }
ModuleResolutionPathRefInner::FirstParty(path) => { ModulePathRefInner::StandardLibrary(FilePathRef::System(path)) => {
ModuleResolutionPathBufInner::FirstParty(path.to_path_buf()) ModulePathBufInner::StandardLibrary(FilePath::System(path.to_path_buf()))
} }
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::System(path)) => { ModulePathRefInner::StandardLibrary(FilePathRef::Vendored(path)) => {
ModuleResolutionPathBufInner::StandardLibrary(FilePath::System( ModulePathBufInner::StandardLibrary(FilePath::Vendored(path.to_path_buf()))
path.to_path_buf(),
))
} }
ModuleResolutionPathRefInner::StandardLibrary(FilePathRef::Vendored(path)) => { ModulePathRefInner::SitePackages(path) => {
ModuleResolutionPathBufInner::StandardLibrary(FilePath::Vendored( ModulePathBufInner::SitePackages(path.to_path_buf())
path.to_path_buf(),
))
} }
ModuleResolutionPathRefInner::SitePackages(path) => { ModulePathRefInner::EditableInstall(path) => {
ModuleResolutionPathBufInner::SitePackages(path.to_path_buf()) ModulePathBufInner::EditableInstall(path.to_path_buf())
}
ModuleResolutionPathRefInner::EditableInstall(path) => {
ModuleResolutionPathBufInner::EditableInstall(path.to_path_buf())
} }
}; };
ModuleResolutionPathBuf(inner) ModulePathBuf(inner)
}
} }
impl ModuleSearchPath {
#[must_use] #[must_use]
pub(crate) const fn is_stdlib_search_path(&self) -> bool { pub(crate) fn is_stdlib_search_path(&self) -> bool {
matches!(&self.0, ModuleResolutionPathRefInner::StandardLibrary(_)) matches!(&self.0 .0, ModulePathBufInner::StandardLibrary(_))
} }
} }
#[test] #[test]
fn constructor_rejects_non_pyi_stdlib_paths() { fn constructor_rejects_non_pyi_stdlib_paths() {
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo.py")), ModulePathBuf::standard_library(FilePath::system("foo.py")),
None None
); );
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo/__init__.py")), ModulePathBuf::standard_library(FilePath::system("foo/__init__.py")),
None None
); );
} }
@ -819,9 +868,9 @@ mod tests {
#[test] #[test]
fn path_buf_debug_impl() { fn path_buf_debug_impl() {
assert_debug_snapshot!( assert_debug_snapshot!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo/bar.pyi")).unwrap(), ModulePathBuf::standard_library(FilePath::system("foo/bar.pyi")).unwrap(),
@r###" @r###"
ModuleResolutionPathBuf::StandardLibrary( ModulePathBuf::StandardLibrary(
System( System(
"foo/bar.pyi", "foo/bar.pyi",
), ),
@ -833,9 +882,9 @@ mod tests {
#[test] #[test]
fn path_ref_debug_impl() { fn path_ref_debug_impl() {
assert_debug_snapshot!( assert_debug_snapshot!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new("foo/bar.py"))), ModulePathRef(ModulePathRefInner::Extra(SystemPath::new("foo/bar.py"))),
@r###" @r###"
ModuleResolutionPathRef::Extra( ModulePathRef::Extra(
"foo/bar.py", "foo/bar.py",
) )
"### "###
@ -845,50 +894,49 @@ mod tests {
#[test] #[test]
fn with_extension_methods() { fn with_extension_methods() {
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.with_py_extension(), .with_py_extension(),
None None
); );
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.with_pyi_extension(), .with_pyi_extension(),
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary( ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::System(
FilePath::System(SystemPathBuf::from("foo.pyi")) SystemPathBuf::from("foo.pyi")
)) )))
); );
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::first_party("foo/bar") ModulePathBuf::first_party("foo/bar")
.unwrap() .unwrap()
.with_py_extension() .with_py_extension()
.unwrap(), .unwrap(),
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::FirstParty( ModulePathBuf(ModulePathBufInner::FirstParty(SystemPathBuf::from(
SystemPathBuf::from("foo/bar.py") "foo/bar.py"
)) )))
); );
} }
#[test] #[test]
fn module_name_1_part() { fn module_name_1_part() {
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new("foo"))) ModulePathRef(ModulePathRefInner::Extra(SystemPath::new("foo"))).to_module_name(),
ModuleName::new_static("foo")
);
assert_eq!(
ModulePathRef(ModulePathRefInner::StandardLibrary(FilePathRef::system(
"foo.pyi"
)))
.to_module_name(), .to_module_name(),
ModuleName::new_static("foo") ModuleName::new_static("foo")
); );
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary( ModulePathRef(ModulePathRefInner::FirstParty(SystemPath::new(
FilePathRef::system("foo.pyi")
))
.to_module_name(),
ModuleName::new_static("foo")
);
assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::FirstParty(SystemPath::new(
"foo/__init__.py" "foo/__init__.py"
))) )))
.to_module_name(), .to_module_name(),
@ -899,23 +947,21 @@ mod tests {
#[test] #[test]
fn module_name_2_parts() { fn module_name_2_parts() {
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary( ModulePathRef(ModulePathRefInner::StandardLibrary(FilePathRef::system(
FilePathRef::system("foo/bar") "foo/bar"
))
.to_module_name(),
ModuleName::new_static("foo.bar")
);
assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new(
"foo/bar.pyi"
))) )))
.to_module_name(), .to_module_name(),
ModuleName::new_static("foo.bar") ModuleName::new_static("foo.bar")
); );
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new( ModulePathRef(ModulePathRefInner::Extra(SystemPath::new("foo/bar.pyi")))
.to_module_name(),
ModuleName::new_static("foo.bar")
);
assert_eq!(
ModulePathRef(ModulePathRefInner::SitePackages(SystemPath::new(
"foo/bar/__init__.pyi" "foo/bar/__init__.pyi"
))) )))
.to_module_name(), .to_module_name(),
@ -926,7 +972,7 @@ mod tests {
#[test] #[test]
fn module_name_3_parts() { fn module_name_3_parts() {
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new( ModulePathRef(ModulePathRefInner::SitePackages(SystemPath::new(
"foo/bar/__init__.pyi" "foo/bar/__init__.pyi"
))) )))
.to_module_name(), .to_module_name(),
@ -934,7 +980,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new( ModulePathRef(ModulePathRefInner::SitePackages(SystemPath::new(
"foo/bar/baz" "foo/bar/baz"
))) )))
.to_module_name(), .to_module_name(),
@ -945,35 +991,31 @@ mod tests {
#[test] #[test]
fn join() { fn join() {
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.join("bar"), .join("bar"),
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary( ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
FilePath::system("foo/bar") "foo/bar"
)) )))
); );
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.join("bar.pyi"), .join("bar.pyi"),
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary( ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
FilePath::system("foo/bar.pyi") "foo/bar.pyi"
)) )))
); );
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::extra("foo") ModulePathBuf::extra("foo").unwrap().join("bar.py"),
.unwrap() ModulePathBuf(ModulePathBufInner::Extra(SystemPathBuf::from("foo/bar.py")))
.join("bar.py"),
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::Extra(SystemPathBuf::from(
"foo/bar.py"
)))
); );
} }
#[test] #[test]
#[should_panic(expected = "Extension must be `pyi`; got `py`")] #[should_panic(expected = "Extension must be `pyi`; got `py`")]
fn stdlib_path_invalid_join_py() { fn stdlib_path_invalid_join_py() {
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.push("bar.py"); .push("bar.py");
} }
@ -981,7 +1023,7 @@ mod tests {
#[test] #[test]
#[should_panic(expected = "Extension must be `pyi`; got `rs`")] #[should_panic(expected = "Extension must be `pyi`; got `rs`")]
fn stdlib_path_invalid_join_rs() { fn stdlib_path_invalid_join_rs() {
ModuleResolutionPathBuf::standard_library(FilePath::system("foo")) ModulePathBuf::standard_library(FilePath::system("foo"))
.unwrap() .unwrap()
.push("bar.rs"); .push("bar.rs");
} }
@ -989,23 +1031,20 @@ mod tests {
#[test] #[test]
#[should_panic(expected = "Extension must be `py` or `pyi`; got `rs`")] #[should_panic(expected = "Extension must be `py` or `pyi`; got `rs`")]
fn non_stdlib_path_invalid_join_rs() { fn non_stdlib_path_invalid_join_rs() {
ModuleResolutionPathBuf::site_packages("foo") ModulePathBuf::site_packages("foo").unwrap().push("bar.rs");
.unwrap()
.push("bar.rs");
} }
#[test] #[test]
#[should_panic(expected = "already has an extension")] #[should_panic(expected = "already has an extension")]
fn invalid_stdlib_join_too_many_extensions() { fn invalid_stdlib_join_too_many_extensions() {
ModuleResolutionPathBuf::standard_library(FilePath::system("foo.pyi")) ModulePathBuf::standard_library(FilePath::system("foo.pyi"))
.unwrap() .unwrap()
.push("bar.pyi"); .push("bar.pyi");
} }
#[test] #[test]
fn relativize_stdlib_path_errors() { fn relativize_stdlib_path_errors() {
let root = let root = ModulePathBuf::standard_library(FilePath::system("foo/stdlib")).unwrap();
ModuleResolutionPathBuf::standard_library(FilePath::system("foo/stdlib")).unwrap();
// Must have a `.pyi` extension or no extension: // Must have a `.pyi` extension or no extension:
let bad_absolute_path = FilePath::system("foo/stdlib/x.py"); let bad_absolute_path = FilePath::system("foo/stdlib/x.py");
@ -1020,7 +1059,7 @@ mod tests {
#[test] #[test]
fn relativize_non_stdlib_path_errors() { fn relativize_non_stdlib_path_errors() {
let root = ModuleResolutionPathBuf::extra("foo/stdlib").unwrap(); let root = ModulePathBuf::extra("foo/stdlib").unwrap();
// Must have a `.py` extension, a `.pyi` extension, or no extension: // Must have a `.py` extension, a `.pyi` extension, or no extension:
let bad_absolute_path = FilePath::system("foo/stdlib/x.rs"); let bad_absolute_path = FilePath::system("foo/stdlib/x.rs");
assert_eq!(root.relativize_path(&bad_absolute_path), None); assert_eq!(root.relativize_path(&bad_absolute_path), None);
@ -1032,33 +1071,33 @@ mod tests {
#[test] #[test]
fn relativize_path() { fn relativize_path() {
assert_eq!( assert_eq!(
ModuleResolutionPathBuf::standard_library(FilePath::system("foo/baz")) ModulePathBuf::standard_library(FilePath::system("foo/baz"))
.unwrap() .unwrap()
.relativize_path(&FilePath::system("foo/baz/eggs/__init__.pyi")) .relativize_path(&FilePath::system("foo/baz/eggs/__init__.pyi"))
.unwrap(), .unwrap(),
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary( ModulePathRef(ModulePathRefInner::StandardLibrary(FilePathRef::system(
FilePathRef::system("eggs/__init__.pyi") "eggs/__init__.pyi"
)) )))
); );
} }
fn typeshed_test_case( fn typeshed_test_case(
typeshed: MockedTypeshed, typeshed: MockedTypeshed,
target_version: TargetVersion, target_version: TargetVersion,
) -> (TestDb, ModuleResolutionPathBuf) { ) -> (TestDb, ModulePathBuf) {
let TestCase { db, stdlib, .. } = TestCaseBuilder::new() let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
.with_custom_typeshed(typeshed) .with_custom_typeshed(typeshed)
.with_target_version(target_version) .with_target_version(target_version)
.build(); .build();
let stdlib = ModuleResolutionPathBuf::standard_library(FilePath::System(stdlib)).unwrap(); let stdlib = ModulePathBuf::standard_library(FilePath::System(stdlib)).unwrap();
(db, stdlib) (db, stdlib)
} }
fn py38_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModuleResolutionPathBuf) { fn py38_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModulePathBuf) {
typeshed_test_case(typeshed, TargetVersion::Py38) typeshed_test_case(typeshed, TargetVersion::Py38)
} }
fn py39_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModuleResolutionPathBuf) { fn py39_typeshed_test_case(typeshed: MockedTypeshed) -> (TestDb, ModulePathBuf) {
typeshed_test_case(typeshed, TargetVersion::Py39) typeshed_test_case(typeshed, TargetVersion::Py39)
} }

View File

@ -1,6 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::sync::Arc;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::{FxBuildHasher, FxHashSet}; use rustc_hash::{FxBuildHasher, FxHashSet};
@ -12,11 +11,9 @@ use ruff_db::system::{DirectoryEntry, System, SystemPath, SystemPathBuf};
use crate::db::Db; use crate::db::Db;
use crate::module::{Module, ModuleKind}; use crate::module::{Module, ModuleKind};
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::path::ModuleResolutionPathBuf; use crate::path::{ModulePathBuf, ModuleSearchPath};
use crate::state::ResolverState; use crate::state::ResolverState;
type SearchPathRoot = Arc<ModuleResolutionPathBuf>;
/// Resolves a module name to a module. /// Resolves a module name to a module.
pub fn resolve_module(db: &dyn Db, module_name: ModuleName) -> Option<Module> { pub fn resolve_module(db: &dyn Db, module_name: ModuleName) -> Option<Module> {
let interned_name = internal::ModuleNameIngredient::new(db, module_name); let interned_name = internal::ModuleNameIngredient::new(db, module_name);
@ -137,38 +134,25 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
let mut static_search_paths: Vec<_> = extra_paths let mut static_search_paths: Vec<_> = extra_paths
.iter() .iter()
.map(|fs_path| { .map(|path| ModuleSearchPath::extra(SystemPath::absolute(path, current_directory)).unwrap())
Arc::new(
ModuleResolutionPathBuf::extra(SystemPath::absolute(fs_path, current_directory))
.unwrap(),
)
})
.collect(); .collect();
static_search_paths.push(Arc::new( static_search_paths.push(
ModuleResolutionPathBuf::first_party(SystemPath::absolute( ModuleSearchPath::first_party(SystemPath::absolute(workspace_root, current_directory))
workspace_root,
current_directory,
))
.unwrap(),
));
static_search_paths.push(Arc::new(custom_typeshed.as_ref().map_or_else(
ModuleResolutionPathBuf::vendored_stdlib,
|custom| {
ModuleResolutionPathBuf::stdlib_from_custom_typeshed_root(&SystemPath::absolute(
custom,
current_directory,
))
.unwrap()
},
)));
if let Some(path) = site_packages {
let site_packages_root = Arc::new(
ModuleResolutionPathBuf::site_packages(SystemPath::absolute(path, current_directory))
.unwrap(), .unwrap(),
); );
static_search_paths.push(custom_typeshed.as_ref().map_or_else(
ModuleSearchPath::vendored_stdlib,
|custom| {
ModuleSearchPath::custom_stdlib(&SystemPath::absolute(custom, current_directory))
.unwrap()
},
));
if let Some(path) = site_packages {
let site_packages_root =
ModuleSearchPath::site_packages(SystemPath::absolute(path, current_directory)).unwrap();
static_search_paths.push(site_packages_root); static_search_paths.push(site_packages_root);
} }
@ -204,7 +188,7 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
/// search paths listed in `.pth` files in the `site-packages` directory /// search paths listed in `.pth` files in the `site-packages` directory
/// due to editable installations of third-party packages. /// due to editable installations of third-party packages.
#[salsa::tracked(return_ref)] #[salsa::tracked(return_ref)]
pub(crate) fn editable_install_resolution_paths(db: &dyn Db) -> Vec<Arc<ModuleResolutionPathBuf>> { pub(crate) fn editable_install_resolution_paths(db: &dyn Db) -> Vec<ModuleSearchPath> {
// This query needs to be re-executed each time a `.pth` file // This query needs to be re-executed each time a `.pth` file
// is added, modified or removed from the `site-packages` directory. // is added, modified or removed from the `site-packages` directory.
// However, we don't use Salsa queries to read the source text of `.pth` files; // However, we don't use Salsa queries to read the source text of `.pth` files;
@ -259,7 +243,7 @@ pub(crate) fn editable_install_resolution_paths(db: &dyn Db) -> Vec<Arc<ModuleRe
if existing_paths.insert(Cow::Owned( if existing_paths.insert(Cow::Owned(
installation.as_system_path().unwrap().to_path_buf(), installation.as_system_path().unwrap().to_path_buf(),
)) { )) {
dynamic_paths.push(Arc::new(installation)); dynamic_paths.push(installation);
} }
} }
} }
@ -277,12 +261,12 @@ pub(crate) fn editable_install_resolution_paths(db: &dyn Db) -> Vec<Arc<ModuleRe
/// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site /// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site
struct SearchPathIterator<'db> { struct SearchPathIterator<'db> {
db: &'db dyn Db, db: &'db dyn Db,
static_paths: std::slice::Iter<'db, SearchPathRoot>, static_paths: std::slice::Iter<'db, ModuleSearchPath>,
dynamic_paths: Option<std::slice::Iter<'db, SearchPathRoot>>, dynamic_paths: Option<std::slice::Iter<'db, ModuleSearchPath>>,
} }
impl<'db> Iterator for SearchPathIterator<'db> { impl<'db> Iterator for SearchPathIterator<'db> {
type Item = &'db SearchPathRoot; type Item = &'db ModuleSearchPath;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let SearchPathIterator { let SearchPathIterator {
@ -314,7 +298,7 @@ struct PthFile<'db> {
impl<'db> PthFile<'db> { impl<'db> PthFile<'db> {
/// Yield paths in this `.pth` file that appear to represent editable installations, /// Yield paths in this `.pth` file that appear to represent editable installations,
/// and should therefore be added as module-resolution search paths. /// and should therefore be added as module-resolution search paths.
fn editable_installations(&'db self) -> impl Iterator<Item = ModuleResolutionPathBuf> + 'db { fn editable_installations(&'db self) -> impl Iterator<Item = ModuleSearchPath> + 'db {
let PthFile { let PthFile {
system, system,
path: _, path: _,
@ -336,7 +320,7 @@ impl<'db> PthFile<'db> {
return None; return None;
} }
let possible_editable_install = SystemPath::absolute(line, site_packages); let possible_editable_install = SystemPath::absolute(line, site_packages);
ModuleResolutionPathBuf::editable_installation_root(*system, possible_editable_install) ModuleSearchPath::editable(*system, possible_editable_install)
}) })
} }
} }
@ -408,7 +392,7 @@ pub(crate) struct ModuleResolutionSettings {
/// ///
/// Note that `site-packages` *is included* as a search path in this sequence, /// Note that `site-packages` *is included* as a search path in this sequence,
/// but it is also stored separately so that we're able to find editable installs later. /// but it is also stored separately so that we're able to find editable installs later.
static_search_paths: Vec<SearchPathRoot>, static_search_paths: Vec<ModuleSearchPath>,
} }
impl ModuleResolutionSettings { impl ModuleResolutionSettings {
@ -491,10 +475,7 @@ static BUILTIN_MODULES: Lazy<FxHashSet<&str>> = Lazy::new(|| {
/// Given a module name and a list of search paths in which to lookup modules, /// Given a module name and a list of search paths in which to lookup modules,
/// attempt to resolve the module name /// attempt to resolve the module name
fn resolve_name( fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(ModuleSearchPath, File, ModuleKind)> {
db: &dyn Db,
name: &ModuleName,
) -> Option<(Arc<ModuleResolutionPathBuf>, File, ModuleKind)> {
let resolver_settings = module_resolution_settings(db); let resolver_settings = module_resolution_settings(db);
let resolver_state = ResolverState::new(db, resolver_settings.target_version()); let resolver_state = ResolverState::new(db, resolver_settings.target_version());
let is_builtin_module = BUILTIN_MODULES.contains(&name.as_str()); let is_builtin_module = BUILTIN_MODULES.contains(&name.as_str());
@ -554,14 +535,14 @@ fn resolve_name(
} }
fn resolve_package<'a, 'db, I>( fn resolve_package<'a, 'db, I>(
module_search_path: &ModuleResolutionPathBuf, module_search_path: &ModuleSearchPath,
components: I, components: I,
resolver_state: &ResolverState<'db>, resolver_state: &ResolverState<'db>,
) -> Result<ResolvedPackage, PackageKind> ) -> Result<ResolvedPackage, PackageKind>
where where
I: Iterator<Item = &'a str>, I: Iterator<Item = &'a str>,
{ {
let mut package_path = module_search_path.clone(); let mut package_path = module_search_path.as_module_path().clone();
// `true` if inside a folder that is a namespace package (has no `__init__.py`). // `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. // Namespace packages are special because they can be spread across multiple search paths.
@ -613,7 +594,7 @@ where
#[derive(Debug)] #[derive(Debug)]
struct ResolvedPackage { struct ResolvedPackage {
path: ModuleResolutionPathBuf, path: ModulePathBuf,
kind: PackageKind, kind: PackageKind,
} }
@ -669,7 +650,7 @@ mod tests {
); );
assert_eq!("foo", foo_module.name()); assert_eq!("foo", foo_module.name());
assert_eq!(&src, &foo_module.search_path()); assert_eq!(&src, foo_module.search_path());
assert_eq!(ModuleKind::Module, foo_module.kind()); assert_eq!(ModuleKind::Module, foo_module.kind());
let expected_foo_path = src.join("foo.py"); let expected_foo_path = src.join("foo.py");
@ -734,7 +715,7 @@ mod tests {
resolve_module(&db, functools_module_name).as_ref() resolve_module(&db, functools_module_name).as_ref()
); );
assert_eq!(&stdlib, &functools_module.search_path().to_path_buf()); assert_eq!(&stdlib, functools_module.search_path());
assert_eq!(ModuleKind::Module, functools_module.kind()); assert_eq!(ModuleKind::Module, functools_module.kind());
let expected_functools_path = stdlib.join("functools.pyi"); let expected_functools_path = stdlib.join("functools.pyi");
@ -786,7 +767,7 @@ mod tests {
}); });
let search_path = resolved_module.search_path(); let search_path = resolved_module.search_path();
assert_eq!( assert_eq!(
&stdlib, &search_path, &stdlib, search_path,
"Search path for {module_name} was unexpectedly {search_path:?}" "Search path for {module_name} was unexpectedly {search_path:?}"
); );
assert!( assert!(
@ -882,7 +863,7 @@ mod tests {
}); });
let search_path = resolved_module.search_path(); let search_path = resolved_module.search_path();
assert_eq!( assert_eq!(
&stdlib, &search_path, &stdlib, search_path,
"Search path for {module_name} was unexpectedly {search_path:?}" "Search path for {module_name} was unexpectedly {search_path:?}"
); );
assert!( assert!(
@ -941,7 +922,7 @@ mod tests {
Some(&functools_module), Some(&functools_module),
resolve_module(&db, functools_module_name).as_ref() resolve_module(&db, functools_module_name).as_ref()
); );
assert_eq!(&src, &functools_module.search_path()); assert_eq!(&src, functools_module.search_path());
assert_eq!(ModuleKind::Module, functools_module.kind()); assert_eq!(ModuleKind::Module, functools_module.kind());
assert_eq!(&src.join("functools.py"), functools_module.file().path(&db)); assert_eq!(&src.join("functools.py"), functools_module.file().path(&db));
@ -962,7 +943,7 @@ mod tests {
let pydoc_data_topics = resolve_module(&db, pydoc_data_topics_name).unwrap(); let pydoc_data_topics = resolve_module(&db, pydoc_data_topics_name).unwrap();
assert_eq!("pydoc_data.topics", pydoc_data_topics.name()); assert_eq!("pydoc_data.topics", pydoc_data_topics.name());
assert_eq!(pydoc_data_topics.search_path(), stdlib); assert_eq!(pydoc_data_topics.search_path(), &stdlib);
assert_eq!( assert_eq!(
pydoc_data_topics.file().path(&db), pydoc_data_topics.file().path(&db),
&stdlib.join("pydoc_data/topics.pyi") &stdlib.join("pydoc_data/topics.pyi")
@ -979,7 +960,7 @@ mod tests {
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
assert_eq!("foo", foo_module.name()); assert_eq!("foo", foo_module.name());
assert_eq!(&src, &foo_module.search_path()); assert_eq!(&src, foo_module.search_path());
assert_eq!(&foo_path, foo_module.file().path(&db)); assert_eq!(&foo_path, foo_module.file().path(&db));
assert_eq!( assert_eq!(
@ -1006,7 +987,7 @@ mod tests {
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_init_path = src.join("foo/__init__.py"); let foo_init_path = src.join("foo/__init__.py");
assert_eq!(&src, &foo_module.search_path()); assert_eq!(&src, foo_module.search_path());
assert_eq!(&foo_init_path, foo_module.file().path(&db)); assert_eq!(&foo_init_path, foo_module.file().path(&db));
assert_eq!(ModuleKind::Package, foo_module.kind()); assert_eq!(ModuleKind::Package, foo_module.kind());
@ -1029,7 +1010,6 @@ mod tests {
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_stub = src.join("foo.pyi"); let foo_stub = src.join("foo.pyi");
assert_eq!(&src, &foo.search_path());
assert_eq!(&foo_stub, foo.file().path(&db)); assert_eq!(&foo_stub, foo.file().path(&db));
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub))); assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
@ -1053,7 +1033,7 @@ mod tests {
resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap(); resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
let baz_path = src.join("foo/bar/baz.py"); let baz_path = src.join("foo/bar/baz.py");
assert_eq!(&src, &baz_module.search_path()); assert_eq!(&src, baz_module.search_path());
assert_eq!(&baz_path, baz_module.file().path(&db)); assert_eq!(&baz_path, baz_module.file().path(&db));
assert_eq!( assert_eq!(
@ -1153,7 +1133,7 @@ mod tests {
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_src_path = src.join("foo.py"); let foo_src_path = src.join("foo.py");
assert_eq!(&src, &foo_module.search_path()); assert_eq!(&src, foo_module.search_path());
assert_eq!(&foo_src_path, foo_module.file().path(&db)); assert_eq!(&foo_src_path, foo_module.file().path(&db));
assert_eq!( assert_eq!(
Some(foo_module), Some(foo_module),
@ -1205,12 +1185,12 @@ mod tests {
assert_ne!(foo_module, bar_module); assert_ne!(foo_module, bar_module);
assert_eq!(&src, &foo_module.search_path()); assert_eq!(&src, foo_module.search_path());
assert_eq!(&foo, foo_module.file().path(&db)); assert_eq!(&foo, foo_module.file().path(&db));
// `foo` and `bar` shouldn't resolve to the same file // `foo` and `bar` shouldn't resolve to the same file
assert_eq!(&src, &bar_module.search_path()); assert_eq!(&src, bar_module.search_path());
assert_eq!(&bar, bar_module.file().path(&db)); assert_eq!(&bar, bar_module.file().path(&db));
assert_eq!(&foo, foo_module.file().path(&db)); assert_eq!(&foo, foo_module.file().path(&db));
@ -1326,7 +1306,7 @@ mod tests {
let stdlib_functools_path = stdlib.join("functools.pyi"); let stdlib_functools_path = stdlib.join("functools.pyi");
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
@ -1346,7 +1326,7 @@ mod tests {
&ModuleNameIngredient::new(&db, functools_module_name.clone()), &ModuleNameIngredient::new(&db, functools_module_name.clone()),
&events, &events,
); );
assert_eq!(functools_module.search_path(), stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
@ -1372,7 +1352,7 @@ mod tests {
let functools_module_name = ModuleName::new_static("functools").unwrap(); let functools_module_name = ModuleName::new_static("functools").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
@ -1383,7 +1363,7 @@ mod tests {
let src_functools_path = src.join("functools.py"); let src_functools_path = src.join("functools.py");
db.write_file(&src_functools_path, "FOO: int").unwrap(); db.write_file(&src_functools_path, "FOO: int").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), src); assert_eq!(functools_module.search_path(), &src);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
@ -1414,7 +1394,7 @@ mod tests {
let src_functools_path = src.join("functools.py"); let src_functools_path = src.join("functools.py");
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), src); assert_eq!(functools_module.search_path(), &src);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
@ -1427,7 +1407,7 @@ mod tests {
.unwrap(); .unwrap();
File::touch_path(&mut db, &src_functools_path); File::touch_path(&mut db, &src_functools_path);
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Some(functools_module.file()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
@ -1677,15 +1657,14 @@ not_a_directory
.with_site_packages_files(&[("_foo.pth", "/src")]) .with_site_packages_files(&[("_foo.pth", "/src")])
.build(); .build();
let search_paths: Vec<&SearchPathRoot> = let search_paths: Vec<&ModuleSearchPath> =
module_resolution_settings(&db).search_paths(&db).collect(); module_resolution_settings(&db).search_paths(&db).collect();
assert!(search_paths.contains(&&Arc::new( assert!(search_paths
ModuleResolutionPathBuf::first_party("/src").unwrap() .contains(&&ModuleSearchPath::first_party(SystemPathBuf::from("/src")).unwrap()));
)));
assert!(!search_paths.contains(&&Arc::new( assert!(!search_paths.contains(
ModuleResolutionPathBuf::editable_installation_root(db.system(), "/src").unwrap() &&ModuleSearchPath::editable(db.system(), SystemPathBuf::from("/src")).unwrap()
))); ));
} }
} }

View File

@ -75,8 +75,8 @@ pub struct SearchPathSettings {
/// The root of the workspace, used for finding first-party modules. /// The root of the workspace, used for finding first-party modules.
pub workspace_root: SystemPathBuf, pub workspace_root: SystemPathBuf,
/// Optional (already validated) path to standard-library typeshed stubs. /// Optional path to a "custom typeshed" directory on disk for us to use for standard-library types.
/// If this is not provided, we will fallback to our vendored typeshed stubs /// If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
/// bundled as a zip file in the binary /// bundled as a zip file in the binary
pub custom_typeshed: Option<SystemPathBuf>, pub custom_typeshed: Option<SystemPathBuf>,