[ty] Extract ty_site_packages crate (#22622)

This commit is contained in:
Micha Reiser
2026-01-16 15:02:47 +01:00
committed by GitHub
parent 3c323559ed
commit eb99f80a02
10 changed files with 165 additions and 90 deletions

24
Cargo.lock generated
View File

@@ -3176,6 +3176,7 @@ dependencies = [
"serde",
"ty_module_resolver",
"ty_python_semantic",
"ty_site_packages",
"zip",
]
@@ -4562,7 +4563,6 @@ dependencies = [
"bitflags 2.10.0",
"bitvec",
"camino",
"colored 3.0.0",
"compact_str",
"datatest-stable",
"drop_bomb",
@@ -4578,7 +4578,6 @@ dependencies = [
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"ruff_annotate_snippets",
"ruff_db",
"ruff_diagnostics",
"ruff_index",
@@ -4606,6 +4605,7 @@ dependencies = [
"tracing",
"ty_combine",
"ty_module_resolver",
"ty_site_packages",
"ty_static",
"ty_test",
"ty_vendored",
@@ -4649,6 +4649,26 @@ dependencies = [
"ty_python_semantic",
]
[[package]]
name = "ty_site_packages"
version = "0.0.0"
dependencies = [
"camino",
"colored 3.0.0",
"get-size2",
"indexmap",
"ruff_annotate_snippets",
"ruff_db",
"ruff_python_ast",
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"strum",
"strum_macros",
"tracing",
"ty_static",
]
[[package]]
name = "ty_static"
version = "0.0.1"

View File

@@ -49,6 +49,7 @@ ty_module_resolver = { path = "crates/ty_module_resolver" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" }
ty_site_packages = { path = "crates/ty_site_packages" }
ty_static = { path = "crates/ty_static" }
ty_test = { path = "crates/ty_test" }
ty_vendored = { path = "crates/ty_vendored" }

View File

@@ -22,6 +22,7 @@ ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ty_module_resolver = { workspace = true }
ty_python_semantic = { workspace = true }
ty_site_packages = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, optional = true }

View File

@@ -10,9 +10,10 @@ use ruff_python_ast::PythonVersion;
use ty_module_resolver::{SearchPathSettings, SearchPaths};
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
AnalysisSettings, Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin, default_lint_registry,
AnalysisSettings, Db, Program, ProgramSettings, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, default_lint_registry,
};
use ty_site_packages::{PythonEnvironment, SysPrefixPathOrigin};
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
let mut builder = VendoredFileSystemBuilder::new(CompressionMethod::Stored);

View File

@@ -11,7 +11,6 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_db = { workspace = true }
ruff_diagnostics = { workspace = true }
ruff_index = { workspace = true, features = ["salsa"] }
@@ -26,13 +25,11 @@ ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ty_combine = { workspace = true }
ty_module_resolver = { workspace = true }
ty_static = { workspace = true }
ty_site_packages = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
bitvec = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
compact_str = { workspace = true }
drop_bomb = { workspace = true }
get-size2 = { workspace = true, features = ["indexmap", "ordermap"] }
@@ -63,6 +60,7 @@ ty_test = { workspace = true }
ty_vendored = { workspace = true }
anyhow = { workspace = true }
camino = { workspace = true }
datatest-stable = { workspace = true }
glob = { workspace = true }
indoc = { workspace = true }

View File

@@ -8,17 +8,18 @@ use crate::lint::{LintRegistry, LintRegistryBuilder};
use crate::suppression::{IGNORE_COMMENT_UNKNOWN_RULE, INVALID_IGNORE_COMMENT};
pub use db::Db;
pub use diagnostic::add_inferred_python_version_hint_to_diagnostic;
pub use program::{
Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource,
};
pub use program::{Program, ProgramSettings};
pub use python_platform::PythonPlatform;
use rustc_hash::FxHasher;
pub use semantic_model::{
Completion, HasDefinition, HasType, MemberDefinition, NameKind, SemanticModel,
};
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
pub use suppression::{UNUSED_IGNORE_COMMENT, suppress_all, suppress_single};
pub use ty_module_resolver::MisconfigurationMode;
pub use ty_site_packages::{
PythonEnvironment, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource,
SitePackagesPaths, SysPrefixPathOrigin,
};
pub use types::DisplaySettings;
pub use types::ide_support::{
ImportAliasResolution, ResolvedDefinition, definitions_for_attribute, definitions_for_bin_op,
@@ -38,7 +39,6 @@ mod python_platform;
mod rank;
pub mod semantic_index;
mod semantic_model;
pub(crate) mod site_packages;
mod subscript;
mod suppression;
pub mod types;

View File

@@ -1,16 +1,12 @@
use std::sync::Arc;
use crate::Db;
use crate::python_platform::PythonPlatform;
use ruff_db::diagnostic::Span;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_db::system::SystemPath;
use ruff_python_ast::PythonVersion;
use ruff_text_size::TextRange;
use salsa::Durability;
use salsa::Setter;
use ty_module_resolver::SearchPaths;
use ty_site_packages::PythonVersionWithSource;
#[salsa::input(singleton, heap_size=ruff_memory_usage::heap_size)]
pub struct Program {
@@ -91,73 +87,3 @@ pub struct ProgramSettings {
pub python_platform: PythonPlatform,
pub search_paths: SearchPaths,
}
#[derive(Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
pub enum PythonVersionSource {
/// Value loaded from a project's configuration file.
ConfigFile(PythonVersionFileSource),
/// Value loaded from the `pyvenv.cfg` file of the virtual environment.
/// The virtual environment might have been configured, activated or inferred.
PyvenvCfgFile(PythonVersionFileSource),
/// Value inferred from the layout of the Python installation.
///
/// This only ever applies on Unix. On Unix, the `site-packages` directory
/// will always be at `sys.prefix/lib/pythonX.Y/site-packages`,
/// so we can infer the Python version from the parent directory of `site-packages`.
InstallationDirectoryLayout { site_packages_parent_dir: Box<str> },
/// The value comes from a CLI argument, while it's left open if specified using a short argument,
/// long argument (`--extra-paths`) or `--config key=value`.
Cli,
/// The value comes from the user's editor,
/// while it's left open if specified as a setting
/// or if the value was auto-discovered by the editor
/// (e.g., the Python environment)
Editor,
/// We fell back to a default value because the value was not specified via the CLI or a config file.
#[default]
Default,
}
/// Information regarding the file and [`TextRange`] of the configuration
/// from which we inferred the Python version.
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
pub struct PythonVersionFileSource {
path: Arc<SystemPathBuf>,
range: Option<TextRange>,
}
impl PythonVersionFileSource {
pub fn new(path: Arc<SystemPathBuf>, range: Option<TextRange>) -> Self {
Self { path, range }
}
/// Attempt to resolve a [`Span`] that corresponds to the location of
/// the configuration setting that specified the Python version.
///
/// Useful for subdiagnostics when informing the user
/// what the inferred Python version of their project is.
pub(crate) fn span(&self, db: &dyn Db) -> Option<Span> {
let file = system_path_to_file(db, &*self.path).ok()?;
Some(Span::from(file).with_optional_range(self.range))
}
}
#[derive(Eq, PartialEq, Debug, Clone, get_size2::GetSize)]
pub struct PythonVersionWithSource {
pub version: PythonVersion,
pub source: PythonVersionSource,
}
impl Default for PythonVersionWithSource {
fn default() -> Self {
Self {
version: PythonVersion::latest_ty(),
source: PythonVersionSource::Default,
}
}
}

View File

@@ -0,0 +1,34 @@
[package]
name = "ty_site_packages"
version = "0.0.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_db = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ty_static = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
get-size2 = { workspace = true }
indexmap = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
ruff_db = { workspace = true, features = ["testing", "os"] }
[lints]
workspace = true

View File

@@ -8,13 +8,14 @@
//! reasonably ask us to type-check code assuming that the code runs
//! on Linux.)
mod version;
use std::io;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::str::FromStr;
use std::{fmt, sync::Arc};
use crate::{PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource};
use camino::Utf8Component;
use indexmap::IndexSet;
use ruff_annotate_snippets::{Level, Renderer, Snippet};
@@ -25,6 +26,7 @@ use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextLen, TextRange};
use strum::IntoEnumIterator;
use ty_static::EnvVars;
pub use version::{PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource};
type SitePackagesDiscoveryResult<T> = Result<T, SitePackagesDiscoveryError>;
type StdlibDiscoveryResult<T> = Result<T, StdlibDiscoveryError>;

View File

@@ -0,0 +1,92 @@
//! Types for representing the Python version and its source.
use std::sync::Arc;
use ruff_db::Db;
use ruff_db::diagnostic::Span;
use ruff_db::files::system_path_to_file;
use ruff_db::system::SystemPathBuf;
use ruff_python_ast::PythonVersion;
use ruff_text_size::TextRange;
/// The source of the Python version.
#[derive(Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
pub enum PythonVersionSource {
/// Value loaded from a project's configuration file.
ConfigFile(PythonVersionFileSource),
/// Value loaded from the `pyvenv.cfg` file of the virtual environment.
/// The virtual environment might have been configured, activated or inferred.
PyvenvCfgFile(PythonVersionFileSource),
/// Value inferred from the layout of the Python installation.
///
/// This only ever applies on Unix. On Unix, the `site-packages` directory
/// will always be at `sys.prefix/lib/pythonX.Y/site-packages`,
/// so we can infer the Python version from the parent directory of `site-packages`.
InstallationDirectoryLayout { site_packages_parent_dir: Box<str> },
/// The value comes from a CLI argument, while it's left open if specified using a short argument,
/// long argument (`--extra-paths`) or `--config key=value`.
Cli,
/// The value comes from the user's editor,
/// while it's left open if specified as a setting
/// or if the value was auto-discovered by the editor
/// (e.g., the Python environment)
Editor,
/// We fell back to a default value because the value was not specified via the CLI or a config file.
#[default]
Default,
}
/// Information regarding the file and [`TextRange`] of the configuration
/// from which we inferred the Python version.
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
pub struct PythonVersionFileSource {
path: Arc<SystemPathBuf>,
range: Option<TextRange>,
}
impl PythonVersionFileSource {
pub fn new(path: Arc<SystemPathBuf>, range: Option<TextRange>) -> Self {
Self { path, range }
}
/// Returns the path to the configuration file.
pub fn path(&self) -> &SystemPathBuf {
&self.path
}
/// Returns the range of the configuration setting.
pub fn range(&self) -> Option<TextRange> {
self.range
}
/// Attempt to resolve a [`Span`] that corresponds to the location of
/// the configuration setting that specified the Python version.
///
/// Useful for subdiagnostics when informing the user
/// what the inferred Python version of their project is.
pub fn span(&self, db: &dyn Db) -> Option<Span> {
let file = system_path_to_file(db, &*self.path).ok()?;
Some(Span::from(file).with_optional_range(self.range))
}
}
/// A Python version with its source.
#[derive(Eq, PartialEq, Debug, Clone, get_size2::GetSize)]
pub struct PythonVersionWithSource {
pub version: PythonVersion,
pub source: PythonVersionSource,
}
impl Default for PythonVersionWithSource {
fn default() -> Self {
Self {
version: PythonVersion::latest_ty(),
source: PythonVersionSource::Default,
}
}
}