Compare commits

...

3 Commits

Author SHA1 Message Date
Ibraheem Ahmed
992e77e4d0 suggestions from code review 2025-08-22 19:36:40 -04:00
Ibraheem Ahmed
8d05367d60 reload Project after deserialization 2025-08-22 19:09:22 -04:00
Ibraheem Ahmed
2402831223 experimental persistent caching 2025-08-22 19:09:21 -04:00
66 changed files with 1504 additions and 270 deletions

35
Cargo.lock generated
View File

@@ -260,6 +260,9 @@ name = "bitflags"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
dependencies = [
"serde",
]
[[package]]
name = "bitvec"
@@ -1028,6 +1031,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.13"
@@ -3451,12 +3464,13 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
dependencies = [
"boxcar",
"compact_str",
"crossbeam-queue",
"crossbeam-utils",
"erased-serde",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
@@ -3467,6 +3481,7 @@ dependencies = [
"rustc-hash",
"salsa-macro-rules",
"salsa-macros",
"serde",
"smallvec",
"thin-vec",
"tracing",
@@ -3475,12 +3490,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
[[package]]
name = "salsa-macros"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
dependencies = [
"proc-macro2",
"quote",
@@ -3700,6 +3715,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "snapbox"
@@ -3904,6 +3922,9 @@ name = "thin-vec"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
dependencies = [
"serde",
]
[[package]]
name = "thiserror"
@@ -4261,6 +4282,7 @@ name = "ty_project"
version = "0.0.0"
dependencies = [
"anyhow",
"bincode 2.0.1",
"camino",
"colored 3.0.0",
"crossbeam",
@@ -4290,6 +4312,7 @@ dependencies = [
"tracing",
"ty_combine",
"ty_python_semantic",
"ty_static",
"ty_vendored",
]
@@ -4461,6 +4484,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"

View File

@@ -57,8 +57,8 @@ anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" }
bincode = { version = "2.0.0", features = ["serde"] }
bitflags = { version = "2.5.0", features = ["serde"] }
bitvec = { version = "1.0.1", default-features = false, features = [
"alloc",
] }
@@ -126,7 +126,7 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "8.0.0" }
ordermap = { version = "0.5.0" }
ordermap = { version = "0.5.0", features = ["serde"] }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@@ -143,24 +143,25 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a3ffa22cb26756473d56f867aedec3fd907c4dd9", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a0e7a06", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
"persistence",
] }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
serde = { version = "1.0.197", features = ["derive", "rc"] }
serde-wasm-bindgen = { version = "0.6.4" }
serde_json = { version = "1.0.113" }
serde_json = { version = "1.0.142" }
serde_test = { version = "1.0.152" }
serde_with = { version = "3.6.0", default-features = false, features = [
"macros",
] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new", "serde"] }
snapbox = { version = "0.6.0", features = [
"diff",
"term-svg",

View File

@@ -31,7 +31,7 @@ ruff_workspace = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
bincode = { workspace = true, features = ["serde"] }
bincode = { workspace = true }
bitflags = { workspace = true }
cachedir = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }

View File

@@ -22,6 +22,7 @@ mod stylesheet;
/// a characteristic is a deficiency. An example of a characteristic that is
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Diagnostic {
/// The actual diagnostic.
///
@@ -500,6 +501,7 @@ impl Diagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct DiagnosticInner {
id: DiagnosticId,
severity: Severity,
@@ -576,6 +578,7 @@ impl Eq for RenderingSortKey<'_> {}
/// another (for a single parent diagnostic) is the order in which they were
/// attached to the diagnostic.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SubDiagnostic {
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
/// pointer-sized.
@@ -685,6 +688,7 @@ impl SubDiagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct SubDiagnosticInner {
severity: SubDiagnosticSeverity,
message: DiagnosticMessage,
@@ -713,6 +717,7 @@ struct SubDiagnosticInner {
/// Messages attached to annotations should also be as brief and specific as
/// possible. Long messages could negative impact the quality of rendering.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Annotation {
/// The span of this annotation, corresponding to some subsequence of the
/// user's input that we want to highlight.
@@ -855,6 +860,7 @@ impl Annotation {
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
@@ -869,6 +875,7 @@ pub enum DiagnosticTag {
///
/// Rules use kebab case, e.g. `no-foo`.
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct LintName(&'static str);
impl LintName {
@@ -881,6 +888,66 @@ impl LintName {
}
}
#[cfg(feature = "serde")]
pub use lint_name_serde::LintRegistryGuard;
#[cfg(feature = "serde")]
mod lint_name_serde {
use super::LintName;
use std::cell::RefCell;
thread_local! {
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
/// but we need a way to retrieve static [`LintName`]s from the lint registry when deserializing.
///
/// Use the [`LintRegistryGuard`] to initialize the thread local before calling into any
/// deserialization code. It ensures that the thread local variable gets cleaned up
/// once deserialization is done (once the guard gets dropped).
static LINT_REGISTRY: RefCell<Option<LintRegistry>> = const { RefCell::new(None) };
}
type LintRegistry = fn(&str) -> Option<LintName>;
/// Guard to safely change the lint registry for the current thread.
#[must_use]
pub struct LintRegistryGuard {
prev_value: Option<LintRegistry>,
}
impl LintRegistryGuard {
pub fn new(registry: LintRegistry) -> Self {
let prev = LINT_REGISTRY.replace(Some(registry));
Self { prev_value: prev }
}
}
impl Drop for LintRegistryGuard {
fn drop(&mut self) {
LINT_REGISTRY.set(self.prev_value.take());
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LintName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name: &str = serde::Deserialize::deserialize(deserializer)?;
LINT_REGISTRY.with_borrow(|registry| {
let registry = registry
.expect("must set the `LintRegistryGuard` when deserializing a `LintName`");
registry(name).ok_or(serde::de::Error::custom(format!(
"invalid `LintName` {name}"
)))
})
}
}
}
impl std::ops::Deref for LintName {
type Target = str;
@@ -909,6 +976,7 @@ impl PartialEq<&str> for LintName {
/// Uniquely identifies the kind of a diagnostic.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticId {
Panic,
@@ -1097,6 +1165,30 @@ impl UnifiedFile {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for UnifiedFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
UnifiedFile::Ty(file) => serde::Serialize::serialize(file, serializer),
// Persistent caching is only used in ty.
UnifiedFile::Ruff(..) => panic!("Ruff files are not persistable"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for UnifiedFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
serde::Deserialize::deserialize(deserializer).map(UnifiedFile::Ty)
}
}
/// A unified wrapper for types that can be converted to a [`SourceCode`].
///
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
@@ -1128,6 +1220,7 @@ impl DiagnosticSource {
/// range isn't present, it semantically implies that the diagnostic refers to
/// the entire file. For example, when the file should be executable but isn't.
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Span {
file: UnifiedFile,
range: Option<TextRange>,
@@ -1206,6 +1299,7 @@ impl From<crate::files::FileRange> for Span {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Severity {
Info,
Warning,
@@ -1241,6 +1335,7 @@ impl Severity {
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
/// deleted and the two combined again.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SubDiagnosticSeverity {
Help,
Info,
@@ -1489,6 +1584,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
/// a blanket trait implementation for `IntoDiagnosticMessage` for
/// anything that implements `std::fmt::Display`.
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
@@ -1552,7 +1648,11 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
///
/// For Ruff rules this means the noqa code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct SecondaryCode(String);
impl SecondaryCode {

View File

@@ -9,7 +9,17 @@ use crate::system::file_time_now;
/// * The last modification time of the file.
/// * The hash of the file's content.
/// * The revision as it comes from an external system, for example the LSP.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct FileRevision(u128);
impl FileRevision {

View File

@@ -14,7 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
use crate::file_revision::FileRevision;
use crate::files::file_root::FileRoots;
use crate::files::private::FileStatus;
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::system::{FileType, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::vendored::{VendoredPath, VendoredPathBuf};
use crate::{Db, FxDashMap, vendored};
@@ -139,6 +139,7 @@ impl Files {
};
tracing::trace!("Adding vendored file `{}`", path);
let file = File::builder(FilePath::Vendored(path.to_path_buf()))
.permissions(Some(0o444))
.revision(metadata.revision())
@@ -200,7 +201,15 @@ impl Files {
let mut roots = self.inner.roots.write().unwrap();
let absolute = SystemPath::absolute(path, db.system().current_directory());
roots.try_add(db, absolute, kind)
let (Ok(root) | Err(root)) = roots.try_add(db, absolute, |absolute| {
FileRoot::builder(absolute, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db)
});
root
}
/// Updates the revision of the root for `path`.
@@ -259,6 +268,51 @@ impl Files {
root.set_revision(db).to(FileRevision::now());
}
}
/// Seed the files with an existing [`File`] instance.
pub fn seed(&self, file: File, db: &dyn Db) {
let seeded = match file.path(db) {
FilePath::System(path) => self
.inner
.system_by_path
.insert(path.clone(), file)
.is_none(),
FilePath::SystemVirtual(path) => self
.inner
.system_virtual_by_path
.insert(path.clone(), VirtualFile(file))
.is_none(),
FilePath::Vendored(path) => self
.inner
.vendored_by_path
.insert(path.clone(), file)
.is_none(),
};
// Recreating a `File` input means the persisted queries depending on that file
// will be invalidated.
assert!(
seeded,
"unexpected `File` input recreated for path `{}`",
file.path(db)
);
}
/// Seed the files with an existing [`FileRoot`] instance.
pub fn seed_root(&self, root: FileRoot, db: &dyn Db) {
let mut roots = self.inner.roots.write().unwrap();
let seeded = roots
.try_add(db, root.path(db).to_path_buf(), |_| root)
.is_ok();
// Recreating a `FileRoot` input means the persisted queries depending on that file
// root will be invalidated.
assert!(
seeded,
"unexpected `FileRoot` input recreated for path `{}`",
root.path(db)
);
}
}
impl fmt::Debug for Files {
@@ -290,7 +344,7 @@ impl std::panic::RefUnwindSafe for Files {}
/// # Ordering
/// Ordering is based on the file's salsa-assigned id and not on its values.
/// The id may change between runs.
#[salsa::input(heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(persist, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct File {
/// The path of the file (immutable).
@@ -414,6 +468,15 @@ impl File {
}
}
/// Loads all existing [`File`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<File> {
// TODO: Prune deleted paths.
File::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
/// Private method providing the implementation for [`Self::sync_path`] and [`Self::sync`] for
/// system paths.
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
@@ -522,7 +585,17 @@ impl VirtualFile {
// The types in here need to be public because they're salsa ingredients but we
// don't want them to be publicly accessible. That's why we put them into a private module.
mod private {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum FileStatus {
/// The file exists.
#[default]
@@ -536,6 +609,16 @@ mod private {
}
}
impl From<FileType> for FileStatus {
fn from(value: FileType) -> Self {
match value {
FileType::File => FileStatus::Exists,
FileType::Symlink => FileStatus::Exists,
FileType::Directory => FileStatus::IsADirectory,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FileError {
IsADirectory,

View File

@@ -16,7 +16,7 @@ use crate::system::{SystemPath, SystemPathBuf};
/// The main usage of file roots is to determine a file's durability. But it can also be used
/// to make a salsa query dependent on whether a file in a root has changed without writing any
/// manual invalidation logic.
#[salsa::input(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct FileRoot {
/// The path of a root is guaranteed to never change.
#[returns(deref)]
@@ -35,9 +35,20 @@ impl FileRoot {
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
self.kind_at_time_of_creation(db).durability()
}
/// Loads all existing [`FileRoot`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<FileRoot> {
// TODO: Prune deleted paths.
FileRoot::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
#[derive(
Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum FileRootKind {
/// The root of a project.
Project,
@@ -47,7 +58,7 @@ pub enum FileRootKind {
}
impl FileRootKind {
const fn durability(self) -> Durability {
pub const fn durability(self) -> Durability {
match self {
FileRootKind::Project => Durability::LOW,
FileRootKind::LibrarySearchPath => Durability::HIGH,
@@ -62,34 +73,34 @@ pub(super) struct FileRoots {
}
impl FileRoots {
/// Tries to add a new root for `path` and returns the root.
/// Tries to add a new root for `path`.
///
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
///
/// Returns `Ok(root)` if the `FileRoot` was successfully added, and returns `Err(root)` with
/// the previous root if one already existed at that path.
pub(super) fn try_add(
&mut self,
db: &dyn Db,
path: SystemPathBuf,
kind: FileRootKind,
) -> FileRoot {
create_root: impl FnOnce(SystemPathBuf) -> FileRoot,
) -> Result<FileRoot, FileRoot> {
// SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters.
let normalized_path = path.as_std_path().to_slash().unwrap();
if let Ok(existing) = self.by_path.at(&normalized_path) {
// Only if it is an exact match
if existing.value.path(db) == &*path {
return *existing.value;
return Err(*existing.value);
}
}
// normalize the path to use `/` separators and escape the '{' and '}' characters,
// which matchit uses for routing parameters
// Normalize the path to use `/` separators and escape the '{' and '}' characters,
// which `matchit` uses for routing parameters.
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
// Insert a new source root
let root = FileRoot::builder(path, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db);
let root = create_root(path);
// Insert a path that matches the root itself
self.by_path.insert(route.clone(), root).unwrap();
@@ -100,7 +111,7 @@ impl FileRoots {
self.by_path.insert(route, root).unwrap();
self.roots.push(root);
root
Ok(root)
}
/// Returns the closest root for `path` or `None` if no root contains `path`.

View File

@@ -11,7 +11,9 @@ use std::fmt::{Display, Formatter};
/// * a file stored on the [host system](crate::system::System).
/// * a virtual file stored on the [host system](crate::system::System).
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum FilePath {
/// Path to a file on the [host system](crate::system::System).
System(SystemPathBuf),

View File

@@ -148,7 +148,16 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
#[derive(
Debug,
thiserror::Error,
PartialEq,
Eq,
Clone,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),

View File

@@ -66,6 +66,9 @@ pub trait System: Debug + Sync + Send {
/// See [dunce::canonicalize] for more information.
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
/// Reads the content of the file at `path` into a bytes buffer.
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>>;
/// Reads the content of the file at `path` into a [`String`].
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
@@ -242,7 +245,7 @@ pub trait WritableSystem: System {
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
/// Writes the given content to the file at the given path.
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
/// Creates a directory at `path` as well as any intermediate directories.
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
@@ -278,7 +281,7 @@ pub trait WritableSystem: System {
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
// the cache.
self.create_new_file(&cache_path)?;
self.write_file(&cache_path, &contents)?;
self.write_file(&cache_path, contents.as_bytes())?;
Ok(Some(cache_path))
}

View File

@@ -114,8 +114,8 @@ impl MemoryFileSystem {
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
}
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
fn read_to_string(fs: &MemoryFileSystem, path: &SystemPath) -> Result<String> {
pub fn read_to_end(&self, path: impl AsRef<SystemPath>) -> Result<Vec<u8>> {
fn read_to_end(fs: &MemoryFileSystem, path: &SystemPath) -> Result<Vec<u8>> {
let by_path = fs.inner.by_path.read().unwrap();
let normalized = fs.normalize_path(path);
@@ -127,13 +127,18 @@ impl MemoryFileSystem {
}
}
read_to_string(self, path.as_ref())
read_to_end(self, path.as_ref())
}
pub(crate) fn read_virtual_path_to_string(
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
self.read_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
}
pub(crate) fn read_virtual_path_to_end(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<String> {
) -> Result<Vec<u8>> {
let virtual_files = self.inner.virtual_files.read().unwrap();
let file = virtual_files
.get(&path.as_ref().to_path_buf())
@@ -142,6 +147,14 @@ impl MemoryFileSystem {
Ok(file.content.clone())
}
pub(crate) fn read_virtual_path_to_string(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<String> {
self.read_virtual_path_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
}
pub fn exists(&self, path: &SystemPath) -> bool {
let by_path = self.inner.by_path.read().unwrap();
let normalized = self.normalize_path(path);
@@ -161,7 +174,7 @@ impl MemoryFileSystem {
match by_path.entry(normalized) {
btree_map::Entry::Vacant(entry) => {
entry.insert(Entry::File(File {
content: String::new(),
content: Vec::new(),
last_modified: file_time_now(),
}));
@@ -177,13 +190,17 @@ impl MemoryFileSystem {
/// Stores a new file in the file system.
///
/// The operation overrides the content for an existing file with the same normalized `path`.
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
pub fn write_file(
&self,
path: impl AsRef<SystemPath>,
content: impl Into<Vec<u8>>,
) -> Result<()> {
let mut by_path = self.inner.by_path.write().unwrap();
let normalized = self.normalize_path(path.as_ref());
let file = get_or_create_file(&mut by_path, &normalized)?;
file.content = content.to_string();
file.content = content.into();
file.last_modified = file_time_now();
Ok(())
@@ -214,7 +231,7 @@ impl MemoryFileSystem {
pub fn write_file_all(
&self,
path: impl AsRef<SystemPath>,
content: impl ToString,
content: impl Into<Vec<u8>>,
) -> Result<()> {
let path = path.as_ref();
@@ -228,19 +245,23 @@ impl MemoryFileSystem {
/// Stores a new virtual file in the file system.
///
/// The operation overrides the content for an existing virtual file with the same `path`.
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
pub fn write_virtual_file(
&self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
let path = path.as_ref();
let mut virtual_files = self.inner.virtual_files.write().unwrap();
match virtual_files.entry(path.to_path_buf()) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(File {
content: content.to_string(),
content: content.into(),
last_modified: file_time_now(),
});
}
std::collections::hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().content = content.to_string();
entry.get_mut().content = content.into();
}
}
}
@@ -468,7 +489,7 @@ impl Entry {
#[derive(Debug)]
struct File {
content: String,
content: Vec<u8>,
last_modified: FileTime,
}
@@ -533,7 +554,7 @@ fn get_or_create_file<'a>(
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
Entry::File(File {
content: String::new(),
content: Vec::new(),
last_modified: file_time_now(),
})
});

View File

@@ -93,6 +93,10 @@ impl System for OsSystem {
})
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
std::fs::read(path.as_std_path())
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
std::fs::read_to_string(path.as_std_path())
}
@@ -357,7 +361,7 @@ impl WritableSystem for OsSystem {
std::fs::File::create_new(path).map(drop)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
std::fs::write(path.as_std_path(), content)
}

View File

@@ -762,7 +762,17 @@ impl SystemVirtualPath {
}
/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord, get_size2::GetSize)]
#[derive(
Eq,
PartialEq,
Clone,
Hash,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct SystemVirtualPathBuf(String);
impl SystemVirtualPathBuf {

View File

@@ -75,6 +75,10 @@ impl System for TestSystem {
self.system().canonicalize_path(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.system().read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.system().read_to_string(path)
}
@@ -165,7 +169,7 @@ impl WritableSystem for TestSystem {
self.system().create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.system().write_file(path, content)
}
@@ -185,7 +189,9 @@ pub trait DbWithWritableSystem: Db + Sized {
/// Writes the content of the given file and notifies the Db about the change.
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
let path = path.as_ref();
match self.writable_system().write_file(path, content.as_ref()) {
let content = content.as_ref();
match self.writable_system().write_file(path, content.as_bytes()) {
Ok(()) => {
File::sync_path(self, path);
Ok(())
@@ -198,7 +204,8 @@ pub trait DbWithWritableSystem: Db + Sized {
File::sync_path(self, ancestor);
}
self.writable_system().write_file(path, content.as_ref())?;
self.writable_system()
.write_file(path, content.as_bytes())?;
File::sync_path(self, path);
Ok(())
@@ -243,8 +250,14 @@ pub trait DbWithTestSystem: Db + Sized {
///
/// ## Panics
/// If the db isn't using the [`InMemorySystem`].
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
fn write_virtual_file(
&mut self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
let path = path.as_ref();
let content = content.into();
self.test_system()
.memory_file_system()
.write_virtual_file(path, content);
@@ -322,6 +335,10 @@ impl System for InMemorySystem {
self.memory_fs.canonicalize(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.memory_fs.read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.memory_fs.read_to_string(path)
}
@@ -412,7 +429,7 @@ impl WritableSystem for InMemorySystem {
self.memory_fs.create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.memory_fs.write_file(path, content)
}

View File

@@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
}
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, serde::Serialize, serde::Deserialize)]
pub struct VendoredPathBuf(Utf8PathBuf);
impl get_size2::GetSize for VendoredPathBuf {

View File

@@ -19,6 +19,7 @@ where
/// A unique index for a node within an AST.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeIndex(NonZeroU32);
impl NodeIndex {

View File

@@ -23,8 +23,8 @@ use crossbeam::channel as crossbeam_channel;
use rayon::ThreadPoolBuilder;
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
use ruff_db::files::File;
use ruff_db::max_parallelism;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
use ruff_db::{Db as _, max_parallelism};
use salsa::Database;
use ty_project::metadata::options::ProjectOptionsOverrides;
use ty_project::watch::ProjectWatcher;
@@ -171,6 +171,13 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
Err(_) => {}
}
// Write the database to the persistent cache.
if let Ok(path) = db.system().env_var(EnvVars::TY_PERSIST) {
if let Err(err) = db.persist(SystemPath::new(&path)) {
tracing::warn!("failed to write to persistent cache: {err:?}");
}
}
std::mem::forget(db);
if exit_zero {

View File

@@ -21,10 +21,12 @@ ruff_python_ast = { workspace = true, features = ["serde"] }
ruff_python_formatter = { workspace = true, optional = true }
ruff_text_size = { workspace = true }
ty_combine = { workspace = true }
ty_python_semantic = { workspace = true, features = ["serde"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
ty_vendored = { workspace = true }
anyhow = { workspace = true }
bincode = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
crossbeam = { workspace = true }
@@ -32,7 +34,7 @@ get-size2 = { workspace = true }
globset = { workspace = true }
notify = { workspace = true }
pep440_rs = { workspace = true, features = ["version-ranges"] }
ordermap = { workspace = true, features = ["serde"] }
ordermap = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
regex-automata = { workspace = true }

View File

@@ -1,20 +1,22 @@
use std::fmt::Formatter;
use std::panic::RefUnwindSafe;
use std::sync::Arc;
use std::{cmp, fmt};
use std::{cmp, fmt, io};
pub use self::changes::ChangeResult;
use crate::metadata::settings::file_settings;
use crate::metadata::value::{ValueSource, ValueSourceGuard};
use crate::{CollectReporter, DEFAULT_LINT_REGISTRY};
use crate::{ProgressReporter, Project, ProjectMetadata};
use ruff_db::Db as SourceDb;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{File, Files};
use ruff_db::system::System;
use ruff_db::diagnostic::{Diagnostic, LintRegistryGuard};
use ruff_db::files::{File, FileRoot, Files};
use ruff_db::system::{System, SystemPath};
use ruff_db::vendored::VendoredFileSystem;
use salsa::{Database, Event, Setter};
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{Db as SemanticDb, Program};
use ty_static::EnvVars;
mod changes;
@@ -66,18 +68,27 @@ impl ProjectDatabase {
system: Arc::new(system),
};
// Load the database from the persistent cache.
//
// TODO: Use the `program_settings` to compute the key for the database's persistent
// cache and load the cache if it exists.
// we may want to have a dedicated method for this?
// cache, to support multiple persistent caches for different configurations.
if let Ok(path) = db.system().env_var(EnvVars::TY_PERSIST) {
if let Err(err) = db.deserialize(SystemPath::new(&path), &project_metadata) {
tracing::warn!("failed to read from persistent cache: {err:?}");
}
}
// Initialize the `Program` singleton
// Initialize the `Program` singleton.
let program_settings = project_metadata.to_program_settings(db.system(), db.vendored())?;
Program::from_settings(&db, program_settings);
Program::init_or_update(&mut db, program_settings);
db.project = Some(
Project::from_metadata(&db, project_metadata)
.map_err(|error| anyhow::anyhow!("{}", error.pretty(&db)))?,
);
// Initialize the `Project`, unless it was persisted.
if db.project.is_none() {
db.project = Some(
Project::from_metadata(&db, project_metadata)
.map_err(|error| anyhow::anyhow!("{}", error.pretty(&db)))?,
);
}
Ok(db)
}
@@ -126,6 +137,77 @@ impl ProjectDatabase {
)
}
/// Deserialize the database from the persistent cache.
fn deserialize(&mut self, path: &SystemPath, metadata: &ProjectMetadata) -> anyhow::Result<()> {
// Read from the persistent cache.
let contents = match self.system.read_to_end(path) {
Ok(contents) => contents,
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(err) => return Err(err.into()),
};
let _lint_registry = LintRegistryGuard::new(|name| {
ty_python_semantic::default_lint_registry()
.get(name)
.map(|lint| lint.name())
.ok()
});
// We have to a `ValueSource` to deserialize the `ProjectMetadata`, but we end up using
// the new `metadata` value to resolve the settings, so this value doesn't really matter.
let _value_source = ValueSourceGuard::new(ValueSource::Cli, false);
// Deserialize the database.
let mut decoder = bincode::serde::BorrowedSerdeDecoder::from_slice(
&contents,
bincode::config::standard(),
(),
);
<dyn salsa::Database>::deserialize(self, decoder.as_deserializer())?;
// Sync any deserialized inputs.
//
// TODO: Consider parallelizing the work here similar to `ProjectFilesFilter::collect_vec`,
// as the stat(2) calls can be expensive on large projects.
for file in File::load_all(self) {
file.sync(self);
self.files.seed(file, self);
}
for root in FileRoot::load_all(self) {
self.files.seed_root(root, self);
}
for project in Project::load_all(self) {
if project.resolve_deserialized_settings(metadata, self)? {
self.project = Some(project);
break;
}
}
Ok(())
}
/// Persist the current state of the database to disk.
pub fn persist(&mut self, path: &SystemPath) -> anyhow::Result<()> {
if self.system.as_writable().is_none() {
return Ok(());
}
let contents = bincode::serde::encode_to_vec(
<dyn salsa::Database>::as_serialize(self),
bincode::config::standard(),
)?;
self.system
.as_writable()
.unwrap()
.write_file(path, &contents)?;
Ok(())
}
/// Returns a [`SalsaMemoryDump`] that can be use to dump Salsa memory usage information
/// to the CLI after a typechecker run.
pub fn salsa_memory_dump(&self) -> SalsaMemoryDump {
@@ -204,8 +286,17 @@ impl std::fmt::Debug for ProjectDatabase {
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, get_size2::GetSize)]
#[cfg_attr(test, derive(serde::Serialize))]
#[derive(
Default,
Debug,
Clone,
Copy,
PartialEq,
Eq,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum CheckMode {
/// Checks the open files in the project.
OpenFiles,

View File

@@ -287,8 +287,7 @@ impl ProjectDatabase {
);
project
.set_settings_diagnostics(self)
.to(vec![error.into_diagnostic()]);
.set_settings_diagnostics(self, vec![error.into_diagnostic()]);
}
}

View File

@@ -102,7 +102,7 @@ impl Default for IndexedFiles {
}
}
#[derive(Debug, get_size2::GetSize)]
#[derive(Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
enum State {
/// The files of a package haven't been indexed yet.
Lazy,
@@ -150,7 +150,7 @@ pub struct Indexed<'db> {
_lifetime: PhantomData<&'db ()>,
}
#[derive(Debug, get_size2::GetSize)]
#[derive(Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
struct IndexedInner {
files: FxHashSet<File>,
diagnostics: Vec<IOErrorDiagnostic>,

View File

@@ -21,11 +21,12 @@ use ruff_db::system::{SystemPath, SystemPathBuf};
use rustc_hash::FxHashSet;
use salsa::Durability;
use salsa::Setter;
use serde::{Deserialize, Serialize};
use std::backtrace::BacktraceStatus;
use std::collections::hash_set;
use std::iter::FusedIterator;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use thiserror::Error;
use tracing::error;
use ty_python_semantic::lint::{LintRegistry, LintRegistryBuilder, RuleSelection};
@@ -59,7 +60,7 @@ pub fn default_lints_registry() -> LintRegistry {
/// 2. Running `ruff check` with different target versions results in different programs (settings) but
/// it remains the same project. That's why program is a narrowed view of the project only
/// holding on to the most fundamental settings required for checking.
#[salsa::input(heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(persist(serialize = Project::serialize, deserialize = Project::deserialize), heap_size=ruff_memory_usage::heap_size)]
#[derive(Debug)]
pub struct Project {
/// The files that are open in the project, [`None`] if there are no open files.
@@ -85,8 +86,13 @@ pub struct Project {
/// We box the metadata here because it's a fairly large type and
/// reducing the size of `Project` helps reduce the size of the
/// salsa allocated table for `Project`.
#[returns(deref)]
pub settings: Box<Settings>,
///
/// This field is uninitialized temporarily after deserialization,
/// until `Project::resolve_settings` is called. We use a `OnceLock`
/// to initialize it without bumping the Salsa revision, as we know
/// the settings will remain the same if the metadata has not changed.
#[returns(ref)]
raw_settings: OnceLock<Box<Settings>>,
/// The paths that should be included when checking this project.
///
@@ -113,8 +119,14 @@ pub struct Project {
included_paths_list: Vec<SystemPathBuf>,
/// Diagnostics that were generated when resolving the project settings.
#[returns(deref)]
settings_diagnostics: Vec<OptionDiagnostic>,
///
/// This field is uninitialized temporarily after deserialization,
/// until `Project::resolve_settings` is called. We use a `OnceLock`
/// to initialize it without bumping the Salsa revision, as we know
/// the settings diagnostics will remain the same if the metadata has
/// not changed.
#[returns(ref)]
raw_settings_diagnostics: OnceLock<Vec<OptionDiagnostic>>,
/// The mode in which the project should be checked.
///
@@ -124,6 +136,39 @@ pub struct Project {
check_mode: CheckMode,
}
type ProjectFields = (
FxHashSet<File>,
IndexedFiles,
Box<ProjectMetadata>,
OnceLock<Box<Settings>>,
Vec<SystemPathBuf>,
OnceLock<Vec<OptionDiagnostic>>,
CheckMode,
);
/// The serialized representation of a `Project`.
///
/// Notably, this type does not contain the `settings` or `settings_diagnostics` fields, as the
/// resolved settings contain types that are not easily serializable.
///
/// It also doesn't include the `IndexedFiles`, as those always need to be reloaded anyways.
#[derive(serde::Deserialize)]
struct ProjectWire {
open_fileset: FxHashSet<File>,
metadata: Box<ProjectMetadata>,
included_paths_list: Vec<SystemPathBuf>,
check_mode: CheckMode,
}
/// A reference to the serialized representation of a `Project`.
#[derive(serde::Serialize)]
struct ProjectWireRef<'a> {
open_fileset: &'a FxHashSet<File>,
metadata: &'a ProjectMetadata,
included_paths_list: &'a Vec<SystemPathBuf>,
check_mode: &'a CheckMode,
}
/// A progress reporter.
pub trait ProgressReporter: Send + Sync {
/// Initialize the reporter with the number of files.
@@ -183,11 +228,15 @@ impl Project {
db.files()
.try_add_root(db, metadata.root(), FileRootKind::Project);
let project = Project::builder(Box::new(metadata), Box::new(settings), diagnostics)
.durability(Durability::MEDIUM)
.open_fileset_durability(Durability::LOW)
.file_set_durability(Durability::LOW)
.new(db);
let project = Project::builder(
Box::new(metadata),
Box::new(settings).into(),
diagnostics.into(),
)
.durability(Durability::MEDIUM)
.open_fileset_durability(Durability::LOW)
.file_set_durability(Durability::LOW)
.new(db);
Ok(project)
}
@@ -232,20 +281,21 @@ impl Project {
tracing::debug!("Reloading project");
assert_eq!(self.root(db), metadata.root());
if &metadata != self.metadata(db) {
if metadata != *self.metadata(db) {
match metadata.options().to_settings(db, metadata.root()) {
Ok((settings, settings_diagnostics)) => {
if self.settings(db) != &settings {
self.set_settings(db).to(Box::new(settings));
if *self.settings(db) != settings {
self.set_raw_settings(db).to(Box::new(settings).into());
}
if self.settings_diagnostics(db) != settings_diagnostics {
self.set_settings_diagnostics(db).to(settings_diagnostics);
self.set_raw_settings_diagnostics(db)
.to(settings_diagnostics.into());
}
}
Err(error) => {
self.set_settings_diagnostics(db)
.to(vec![error.into_diagnostic()]);
self.set_raw_settings_diagnostics(db)
.to(vec![error.into_diagnostic()].into());
}
}
@@ -373,8 +423,10 @@ impl Project {
pub fn set_included_paths(self, db: &mut dyn Db, paths: Vec<SystemPathBuf>) {
tracing::debug!("Setting included paths: {paths}", paths = paths.len());
self.set_included_paths_list(db).to(paths);
self.reload_files(db);
if self.included_paths_list(db) != paths {
self.set_included_paths_list(db).to(paths);
self.reload_files(db);
}
}
/// Returns the paths that should be checked.
@@ -526,6 +578,124 @@ impl Project {
.map(OptionDiagnostic::to_diagnostic)
.collect()
}
/// Returns the resolved project settings.
pub fn settings<'db>(&self, db: &'db dyn Db) -> &'db Settings {
match self.raw_settings(db).get() {
Some(settings) => settings,
None => panic!("`Project::resolve_settings` must be called after deserialization"),
}
}
/// Returns any diagnostics that were generated when resolving the project settings.
pub fn settings_diagnostics<'db>(&self, db: &'db dyn Db) -> &'db [OptionDiagnostic] {
match self.raw_settings_diagnostics(db).get() {
Some(diagnostics) => diagnostics.as_slice(),
None => panic!("`Project::resolve_settings` must be called after deserialization"),
}
}
/// Set diagnostics that were generated when resolving the project settings.
pub fn set_settings_diagnostics(self, db: &mut dyn Db, paths: Vec<OptionDiagnostic>) {
self.set_raw_settings_diagnostics(db).to(paths.into());
}
/// Resolve the project settings after deserialization.
///
/// Returns `Ok(true)` if the settings were resolved and the `Project` was updated,
/// or returns `Ok(false)` if a new `Project` should be created.
pub fn resolve_deserialized_settings(
&self,
metadata: &ProjectMetadata,
db: &mut dyn Db,
) -> Result<bool, ToSettingsError> {
// Incompatible project metadata.
if self.root(db) != metadata.root() {
return Ok(false);
}
// Reload the project if the metadata has changed.
if self.metadata(db) != metadata {
self.reload(db, metadata.clone());
}
// Otherwise, resolve the settings and diagnostics, which are not persisted.
//
// Note that we use the new `ProjectMetadata` here instead of the one that was
// persisted to ensure the `ValueSource` fields are updated even if the metadata
// values compare equal (but may come from new sources).
let (settings, diagnostics) = metadata.options().to_settings(db, metadata.root())?;
// Note that we use interior mutability here to avoid bumping the Salsa revision
// for these fields because we know that the settings and settings diagnostics will
// not change if the metadata compared equal.
self.raw_settings(db)
.set(Box::new(settings))
.expect("`Project::resolve_settings` should only be called once");
self.raw_settings_diagnostics(db)
.set(diagnostics)
.expect("`Project::resolve_settings` should only be called once");
// Note that `IndexedFiles` are not deserialized, so this is already set to
// `IndexedFiles::lazy`. However, we need to make sure any queries that depend
// on the file set are invalidated.
//
// TODO: This currently causes the persisted `infer_scope_types` query to be
// re-executed, because `semantic_index` has a dependency on the file set. See
// if we can work around this with a intermediate query.
self.set_file_set(db).to(IndexedFiles::lazy());
Ok(true)
}
/// Loads all existing [`Project`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<Project> {
Project::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
fn serialize<S>(fields: &ProjectFields, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let (
open_fileset,
_file_set,
metadata,
_settings,
included_paths_list,
_settings_diagnostics,
check_mode,
) = fields;
ProjectWireRef {
open_fileset,
metadata,
included_paths_list,
check_mode,
}
.serialize(serializer)
}
fn deserialize<'de, D>(deserializer: D) -> Result<ProjectFields, D::Error>
where
D: serde::Deserializer<'de>,
{
let wire = ProjectWire::deserialize(deserializer)?;
Ok((
wire.open_fileset,
IndexedFiles::lazy(),
wire.metadata,
OnceLock::new(),
wire.included_paths_list,
OnceLock::new(),
wire.check_mode,
))
}
}
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
@@ -640,7 +810,7 @@ impl Iterator for ProjectFilesIter<'_> {
impl FusedIterator for ProjectFilesIter<'_> {}
#[derive(Debug, Clone, get_size2::GetSize)]
#[derive(Debug, Clone, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct IOErrorDiagnostic {
file: Option<File>,
error: IOErrorKind,
@@ -656,7 +826,7 @@ impl IOErrorDiagnostic {
}
}
#[derive(Error, Debug, Clone, get_size2::GetSize)]
#[derive(Error, Debug, Clone, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
enum IOErrorKind {
#[error(transparent)]
Walk(#[from] walk::WalkError),

View File

@@ -19,8 +19,7 @@ pub mod pyproject;
pub mod settings;
pub mod value;
#[derive(Debug, PartialEq, Eq, get_size2::GetSize)]
#[cfg_attr(test, derive(serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ProjectMetadata {
pub(super) name: Name,
@@ -35,7 +34,6 @@ pub struct ProjectMetadata {
/// knowing which files to watch for changes.
///
/// The path ordering doesn't imply precedence.
#[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))]
pub(super) extra_configuration_paths: Vec<SystemPathBuf>,
}
@@ -375,7 +373,14 @@ mod tests {
ProjectMetadata(
name: Name("app"),
root: "/app",
options: Options(),
options: Options(
environment: None,
src: None,
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -413,7 +418,14 @@ mod tests {
ProjectMetadata(
name: Name("backend"),
root: "/app",
options: Options(),
options: Options(
environment: None,
src: None,
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -506,10 +518,18 @@ unclosed table, expected `]`
name: Name("nested-project"),
root: "/app/packages/a",
options: Options(
environment: None,
src: Some(SrcOptions(
root: Some("src"),
r#respect-ignore-files: None,
include: None,
exclude: None,
)),
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -556,10 +576,18 @@ unclosed table, expected `]`
name: Name("project-root"),
root: "/app",
options: Options(
environment: None,
src: Some(SrcOptions(
root: Some("src"),
r#respect-ignore-files: None,
include: None,
exclude: None,
)),
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -599,7 +627,14 @@ unclosed table, expected `]`
ProjectMetadata(
name: Name("nested-project"),
root: "/app/packages/a",
options: Options(),
options: Options(
environment: None,
src: None,
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -644,9 +679,19 @@ unclosed table, expected `]`
root: "/app",
options: Options(
environment: Some(EnvironmentOptions(
root: None,
r#python-version: Some("3.10"),
r#python-platform: None,
r#extra-paths: None,
typeshed: None,
python: None,
)),
src: None,
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});
@@ -696,12 +741,24 @@ unclosed table, expected `]`
root: "/app",
options: Options(
environment: Some(EnvironmentOptions(
root: None,
r#python-version: Some("3.12"),
r#python-platform: None,
r#extra-paths: None,
typeshed: None,
python: None,
)),
src: Some(SrcOptions(
root: Some("src"),
r#respect-ignore-files: None,
include: None,
exclude: None,
)),
rules: None,
terminal: None,
overrides: None,
),
extra_configuration_paths: [],
)
"#);
});

View File

@@ -52,10 +52,8 @@ use ty_python_semantic::{
pub struct Options {
/// Configures the type checking environment.
#[option_group]
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<EnvironmentOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
#[option_group]
pub src: Option<SrcOptions>,
@@ -69,7 +67,6 @@ pub struct Options {
/// * `warn`: Enable the rule and create a warning diagnostic.
/// * `error`: Enable the rule and create an error diagnostic.
/// ty will exit with a non-zero code if any error diagnostics are emitted.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"{...}"#,
value_type = r#"dict[RuleName, "ignore" | "warn" | "error"]"#,
@@ -81,7 +78,6 @@ pub struct Options {
)]
pub rules: Option<Rules>,
#[serde(skip_serializing_if = "Option::is_none")]
#[option_group]
pub terminal: Option<TerminalOptions>,
@@ -90,7 +86,6 @@ pub struct Options {
/// Each override specifies include/exclude patterns and rule configurations
/// that apply to matching files. Multiple overrides can match the same file,
/// with later overrides taking precedence.
#[serde(skip_serializing_if = "Option::is_none")]
#[option_group]
pub overrides: Option<OverridesOptions>,
}
@@ -430,7 +425,6 @@ pub struct EnvironmentOptions {
///
/// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
/// it will also be included in the first party search path.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = "list[str]",
@@ -458,7 +452,6 @@ pub struct EnvironmentOptions {
/// For some language features, ty can also understand conditionals based on comparisons
/// with `sys.version_info`. These are commonly found in typeshed, for example,
/// to reflect the differing contents of the standard library across Python versions.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#""3.13""#,
value_type = r#""3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | <major>.<minor>"#,
@@ -479,7 +472,6 @@ pub struct EnvironmentOptions {
/// - `android` for Android
/// - `ios` for iOS
/// - `linux` for everything else
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"<current-platform>"#,
value_type = r#""win32" | "darwin" | "android" | "ios" | "linux" | "all" | str"#,
@@ -493,7 +485,6 @@ pub struct EnvironmentOptions {
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"[]"#,
value_type = "list[str]",
@@ -506,7 +497,6 @@ pub struct EnvironmentOptions {
/// Optional path to a "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 for the stdlib,
/// bundled as a zip file in the binary
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = "str",
@@ -522,7 +512,6 @@ pub struct EnvironmentOptions {
/// third-party imports.
///
/// This option is commonly used to specify the path to a virtual environment.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = "str",
@@ -558,7 +547,6 @@ pub struct SrcOptions {
///
/// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
/// it will also be included in the first party search path.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = "str",
@@ -579,7 +567,6 @@ pub struct SrcOptions {
respect-ignore-files = false
"#
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub respect_ignore_files: Option<bool>,
/// A list of files and directories to check. The `include` option
@@ -602,7 +589,6 @@ pub struct SrcOptions {
/// matches `<project_root>/src` and not `<project_root>/test/src`).
///
/// `exclude` takes precedence over `include`.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = r#"list[str]"#,
@@ -672,7 +658,6 @@ pub struct SrcOptions {
]
"#
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<RangedValue<Vec<RelativeGlobPattern>>>,
}
@@ -1084,7 +1069,6 @@ pub struct TerminalOptions {
/// The format to use for printing diagnostic messages.
///
/// Defaults to `full`.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"full"#,
value_type = "full | concise",
@@ -1192,7 +1176,6 @@ pub struct OverrideOptions {
/// are affected by this override.
///
/// If not specified, defaults to `["**"]` (matches all files).
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = r#"list[str]"#,
@@ -1212,7 +1195,6 @@ pub struct OverrideOptions {
/// Exclude patterns take precedence over include patterns within the same override.
///
/// If not specified, defaults to `[]` (excludes no files).
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"null"#,
value_type = r#"list[str]"#,
@@ -1233,7 +1215,6 @@ pub struct OverrideOptions {
/// These rules will be merged with the global rules, with override rules
/// taking precedence for matching files. You can set rules to different
/// severity levels or disable them entirely.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(
default = r#"{...}"#,
value_type = r#"dict[RuleName, "ignore" | "warn" | "error"]"#,
@@ -1400,7 +1381,17 @@ impl RangedValue<OverrideOptions> {
}
/// The options for an override but without the include/exclude patterns.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Combine, get_size2::GetSize)]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Combine,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(super) struct InnerOverrideOptions {
/// Raw rule options as specified in the configuration.
/// Used when multiple overrides match a file and need to be merged.

View File

@@ -57,14 +57,14 @@ thread_local! {
static VALUE_SOURCE: RefCell<Option<(ValueSource, bool)>> = const { RefCell::new(None) };
}
/// Guard to safely change the [`VALUE_SOURCE`] for the current thread.
/// Guard to safely change the [`ValueSource`] for the current thread.
#[must_use]
pub(super) struct ValueSourceGuard {
pub struct ValueSourceGuard {
prev_value: Option<(ValueSource, bool)>,
}
impl ValueSourceGuard {
pub(super) fn new(source: ValueSource, is_toml: bool) -> Self {
pub fn new(source: ValueSource, is_toml: bool) -> Self {
let prev = VALUE_SOURCE.replace(Some((source, is_toml)));
Self { prev_value: prev }
}

View File

@@ -291,7 +291,7 @@ impl<'a> ProjectFilesWalker<'a> {
}
}
#[derive(Error, Debug, Clone, get_size2::GetSize)]
#[derive(Error, Debug, Clone, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub(crate) enum WalkError {
#[error("`{path}`: {error}")]
IOPathError { path: SystemPathBuf, error: String },

View File

@@ -11,12 +11,12 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff_db = { workspace = true }
ruff_db = { workspace = true, features = ["serde"] }
ruff_annotate_snippets = { workspace = true }
ruff_index = { workspace = true, features = ["salsa"] }
ruff_macros = { workspace = true }
ruff_memory_usage = { workspace = true }
ruff_python_ast = { workspace = true, features = ["salsa"] }
ruff_python_ast = { workspace = true, features = ["salsa", "serde"] }
ruff_python_parser = { workspace = true }
ruff_python_stdlib = { workspace = true }
ruff_source_file = { workspace = true }
@@ -42,7 +42,7 @@ tracing = { workspace = true }
rustc-hash = { workspace = true }
hashbrown = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde = { workspace = true }
smallvec = { workspace = true }
static_assertions = { workspace = true }
test-case = { workspace = true }
@@ -68,7 +68,6 @@ quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = { version = "1.0.0" }
[features]
serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"]
testing = []
[lints]

View File

@@ -37,7 +37,7 @@ pub struct AstNodeRef<T> {
/// Debug information.
#[cfg(debug_assertions)]
kind: ruff_python_ast::NodeKind,
kind: Option<ruff_python_ast::NodeKind>,
#[cfg(debug_assertions)]
range: ruff_text_size::TextRange,
// Note that because the module address is not stored in release builds, `AstNodeRef`
@@ -68,7 +68,7 @@ where
#[cfg(debug_assertions)]
module_addr: module_ref.module().addr(),
#[cfg(debug_assertions)]
kind: AnyNodeRef::from(node).kind(),
kind: Some(AnyNodeRef::from(node).kind()),
#[cfg(debug_assertions)]
range: node.range(),
_node: PhantomData,
@@ -81,7 +81,7 @@ where
/// different file or Salsa revision than the module to which the node belongs.
pub fn node<'ast>(&self, module_ref: &'ast ParsedModuleRef) -> &'ast T {
#[cfg(debug_assertions)]
assert_eq!(module_ref.module().addr(), self.module_addr);
assert!(self.module_addr == 0 || module_ref.module().addr() == self.module_addr);
// The user guarantees that the module is from the same file and Salsa
// revision, so the file contents cannot have changed.
@@ -109,6 +109,33 @@ unsafe impl<T> salsa::Update for AstNodeRef<T> {
impl<T> get_size2::GetSize for AstNodeRef<T> {}
impl<T> serde::Serialize for AstNodeRef<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.index.serialize(serializer)
}
}
impl<'de, T> serde::Deserialize<'de> for AstNodeRef<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(AstNodeRef {
#[cfg(debug_assertions)]
module_addr: 0,
#[cfg(debug_assertions)]
kind: None,
#[cfg(debug_assertions)]
range: ruff_text_size::TextRange::default(),
index: NodeIndex::deserialize(deserializer)?,
_node: PhantomData,
})
}
}
#[allow(clippy::missing_fields_in_debug)]
impl<T> Debug for AstNodeRef<T>
where

View File

@@ -32,12 +32,10 @@ pub struct LintMetadata {
pub line: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
#[derive(
Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Level {
/// # Ignore

View File

@@ -13,7 +13,18 @@ use crate::{db::Db, module_resolver::file_to_module};
/// A module name, e.g. `foo.bar`.
///
/// Always normalized to the absolute form (never a relative module name, i.e., never `.foo`).
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, get_size2::GetSize)]
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Hash,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct ModuleName(compact_str::CompactString);
impl ModuleName {

View File

@@ -13,7 +13,17 @@ use crate::module_name::ModuleName;
use crate::module_resolver::path::SystemOrVendoredPathRef;
/// Representation of a Python module.
#[derive(Clone, Copy, Eq, Hash, PartialEq, salsa::Supertype, salsa::Update)]
#[derive(
Clone,
Copy,
Eq,
Hash,
PartialEq,
salsa::Supertype,
salsa::Update,
serde::Serialize,
serde::Deserialize,
)]
pub enum Module<'db> {
File(FileModule<'db>),
Namespace(NamespacePackage<'db>),
@@ -214,7 +224,7 @@ fn all_submodule_names_for_package(db: &dyn Db, file: File) -> Option<Vec<Name>>
}
/// A module that resolves to a file (`lib.py` or `package/__init__.py`)
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct FileModule<'db> {
#[returns(ref)]
pub(super) name: ModuleName,
@@ -229,13 +239,23 @@ pub struct FileModule<'db> {
///
/// Namespace packages are special because there are
/// multiple possible paths and they have no corresponding code file.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct NamespacePackage<'db> {
#[returns(ref)]
pub(super) name: ModuleName,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum ModuleKind {
/// A single-file module (e.g. `foo.py` or `foo.pyi`)
Module,
@@ -254,7 +274,18 @@ impl ModuleKind {
}
/// Enumeration of various core stdlib modules in which important types are located
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum_macros::EnumString, get_size2::GetSize)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
strum_macros::EnumString,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
#[strum(serialize_all = "snake_case")]
pub enum KnownModule {

View File

@@ -449,7 +449,9 @@ impl From<SitePackagesDiscoveryError> for SearchPathValidationError {
type SearchPathResult<T> = Result<T, SearchPathValidationError>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(
Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
enum SearchPathInner {
Extra(SystemPathBuf),
FirstParty(SystemPathBuf),
@@ -487,7 +489,9 @@ enum SearchPathInner {
/// or the "Editable" category. For the "First-party", "Site-packages"
/// and "Standard-library" categories, however, there will always be exactly
/// one search path from that category in any given list of search paths.
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(
Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub struct SearchPath(Arc<SearchPathInner>);
impl SearchPath {

View File

@@ -172,7 +172,7 @@ pub(crate) fn search_paths(db: &dyn Db, resolve_mode: ModuleResolveMode) -> Sear
Program::get(db).search_paths(db).iter(db, resolve_mode)
}
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct SearchPaths {
/// Search paths that have been statically determined purely from reading
/// ty's configuration settings. These shouldn't ever change unless the

View File

@@ -74,7 +74,7 @@ pub(crate) enum TypeshedVersionsParseErrorKind {
VersionParseError(#[from] PythonVersionDeserializationError),
}
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub(crate) struct TypeshedVersions(FxHashMap<ModuleName, PyVersionRange>);
impl TypeshedVersions {
@@ -230,7 +230,9 @@ impl fmt::Display for TypeshedVersions {
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub(crate) enum PyVersionRange {
AvailableFrom(RangeFrom<PythonVersion>),
AvailableWithin(RangeInclusive<PythonVersion>),

View File

@@ -1,7 +1,17 @@
use ruff_python_ast::{HasNodeIndex, NodeIndex};
/// Compact key for a node for use in a hash map.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(super) struct NodeKey(NodeIndex);
impl NodeKey {

View File

@@ -13,7 +13,7 @@ use ruff_text_size::TextRange;
use salsa::Durability;
use salsa::Setter;
#[salsa::input(singleton, heap_size=ruff_memory_usage::heap_size)]
#[salsa::input(persist, singleton, heap_size=ruff_memory_usage::heap_size)]
pub struct Program {
#[returns(ref)]
pub python_version_with_source: PythonVersionWithSource,
@@ -93,7 +93,9 @@ pub struct ProgramSettings {
pub search_paths: SearchPaths,
}
#[derive(Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum PythonVersionSource {
/// Value loaded from a project's configuration file.
ConfigFile(PythonVersionFileSource),
@@ -123,7 +125,7 @@ pub enum PythonVersionSource {
/// Information regarding the file and [`TextRange`] of the configuration
/// from which we inferred the Python version.
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct PythonVersionFileSource {
path: Arc<SystemPathBuf>,
range: Option<TextRange>,
@@ -145,7 +147,7 @@ impl PythonVersionFileSource {
}
}
#[derive(Eq, PartialEq, Debug, Clone, get_size2::GetSize)]
#[derive(Eq, PartialEq, Debug, Clone, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct PythonVersionWithSource {
pub version: PythonVersion,
pub source: PythonVersionSource,

View File

@@ -1,12 +1,7 @@
use std::fmt::{Display, Formatter};
/// The target platform to assume when resolving types.
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize, ruff_macros::RustDoc),
serde(rename_all = "kebab-case")
)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, ruff_macros::RustDoc)]
pub enum PythonPlatform {
/// Do not make any assumptions about the target platform.
All,
@@ -16,25 +11,52 @@ pub enum PythonPlatform {
/// We use a string (instead of individual enum variants), as the set of possible platforms
/// may change over time. See <https://docs.python.org/3/library/sys.html#sys.platform> for
/// some known platform identifiers.
#[cfg_attr(feature = "serde", serde(untagged))]
Identifier(String),
}
impl serde::Serialize for PythonPlatform {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serialize::serialize(&self.as_ref(), serializer)
}
}
impl<'de> serde::de::Deserialize<'de> for PythonPlatform {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let platform = String::deserialize(deserializer)?;
match platform.as_str() {
"all" => Ok(PythonPlatform::All),
_ => Ok(PythonPlatform::Identifier(platform)),
}
}
}
impl From<String> for PythonPlatform {
fn from(platform: String) -> Self {
match platform.as_str() {
"all" => PythonPlatform::All,
_ => PythonPlatform::Identifier(platform.to_string()),
_ => PythonPlatform::Identifier(platform),
}
}
}
impl AsRef<str> for PythonPlatform {
fn as_ref(&self) -> &str {
match self {
PythonPlatform::All => "all",
PythonPlatform::Identifier(platform) => platform,
}
}
}
impl Display for PythonPlatform {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PythonPlatform::All => f.write_str("all"),
PythonPlatform::Identifier(name) => f.write_str(name),
}
f.write_str(self.as_ref())
}
}

View File

@@ -115,7 +115,18 @@ pub(crate) mod node_key {
use crate::node_key::NodeKey;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, salsa::Update, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) struct ExpressionNodeKey(NodeKey);
impl From<ast::ExprRef<'_>> for ExpressionNodeKey {

View File

@@ -22,7 +22,7 @@ use crate::unpack::{Unpack, UnpackPosition};
/// because a new scope gets inserted before the `Definition` or a new place is inserted
/// before this `Definition`. However, the ID can be considered stable and it is okay to use
/// `Definition` in cross-module` salsa queries or as a field on other salsa tracked structs.
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::tracked(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct Definition<'db> {
/// The file in which the definition occurs.
pub file: File,
@@ -652,7 +652,7 @@ impl DefinitionCategory {
/// [`DefinitionKind`] fields in salsa tracked structs should be tracked (attributed with `#[tracked]`)
/// because the kind is a thin wrapper around [`AstNodeRef`]. See the [`AstNodeRef`] documentation
/// for an in-depth explanation of why this is necessary.
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub enum DefinitionKind<'db> {
Import(ImportDefinitionKind),
ImportFrom(ImportFromDefinitionKind),
@@ -852,7 +852,9 @@ impl DefinitionKind<'_> {
}
}
#[derive(Copy, Clone, Debug, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Copy, Clone, Debug, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub(crate) enum TargetKind<'db> {
Sequence(UnpackPosition, Unpack<'db>),
/// Name, attribute, or subscript.
@@ -868,7 +870,7 @@ impl<'db> From<Option<(UnpackPosition, Unpack<'db>)>> for TargetKind<'db> {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct StarImportDefinitionKind {
node: AstNodeRef<ast::StmtImportFrom>,
symbol_id: ScopedSymbolId,
@@ -898,7 +900,7 @@ impl StarImportDefinitionKind {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct MatchPatternDefinitionKind {
pattern: AstNodeRef<ast::Pattern>,
identifier: AstNodeRef<ast::Identifier>,
@@ -920,7 +922,7 @@ impl MatchPatternDefinitionKind {
/// But if the target is an attribute or subscript, its definition is not in the comprehension's scope;
/// it is in the scope in which the root variable is bound.
/// TODO: currently we don't model this correctly and simply assume that it is in a scope outside the comprehension.
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ComprehensionDefinitionKind<'db> {
target_kind: TargetKind<'db>,
iterable: AstNodeRef<ast::Expr>,
@@ -951,7 +953,7 @@ impl<'db> ComprehensionDefinitionKind<'db> {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ImportDefinitionKind {
node: AstNodeRef<ast::StmtImport>,
alias_index: usize,
@@ -972,7 +974,7 @@ impl ImportDefinitionKind {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ImportFromDefinitionKind {
node: AstNodeRef<ast::StmtImportFrom>,
alias_index: usize,
@@ -993,7 +995,7 @@ impl ImportFromDefinitionKind {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct AssignmentDefinitionKind<'db> {
target_kind: TargetKind<'db>,
value: AstNodeRef<ast::Expr>,
@@ -1014,7 +1016,7 @@ impl<'db> AssignmentDefinitionKind<'db> {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct AnnotatedAssignmentDefinitionKind {
annotation: AstNodeRef<ast::Expr>,
value: Option<AstNodeRef<ast::Expr>>,
@@ -1035,7 +1037,7 @@ impl AnnotatedAssignmentDefinitionKind {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct WithItemDefinitionKind<'db> {
target_kind: TargetKind<'db>,
context_expr: AstNodeRef<ast::Expr>,
@@ -1061,7 +1063,7 @@ impl<'db> WithItemDefinitionKind<'db> {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ForStmtDefinitionKind<'db> {
target_kind: TargetKind<'db>,
iterable: AstNodeRef<ast::Expr>,
@@ -1087,7 +1089,7 @@ impl<'db> ForStmtDefinitionKind<'db> {
}
}
#[derive(Clone, Debug, get_size2::GetSize)]
#[derive(Clone, Debug, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ExceptHandlerDefinitionKind {
handler: AstNodeRef<ast::ExceptHandlerExceptHandler>,
is_star: bool,

View File

@@ -10,7 +10,17 @@ use salsa;
/// a type expression. For example, in `self.x: <annotation> = <value>`, the
/// `<annotation>` is inferred as a type expression, while `<value>` is inferred
/// as a normal expression.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum ExpressionKind {
Normal,
TypeExpression,
@@ -31,7 +41,7 @@ pub(crate) enum ExpressionKind {
/// * a return type of a cross-module query
/// * a field of a type that is a return type of a cross-module query
/// * an argument of a cross-module query
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::tracked(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub(crate) struct Expression<'db> {
/// The file in which the expression occurs.
pub(crate) file: File,

View File

@@ -347,7 +347,8 @@ impl Hash for MemberExprRef<'_> {
/// Uniquely identifies a member in a scope.
#[newtype_index]
#[derive(get_size2::GetSize, salsa::Update)]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(get_size2::GetSize, salsa::Update, serde::Serialize, serde::Deserialize)]
pub struct ScopedMemberId;
/// The members of a scope. Allows lookup by member path and [`ScopedMemberId`].

View File

@@ -133,7 +133,18 @@ impl std::fmt::Display for PlaceExprRef<'_> {
}
/// ID that uniquely identifies a place inside a [`Scope`](super::FileScopeId).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, get_size2::GetSize, salsa::Update)]
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Hash,
get_size2::GetSize,
salsa::Update,
serde::Serialize,
serde::Deserialize,
)]
pub enum ScopedPlaceId {
Symbol(ScopedSymbolId),
Member(ScopedMemberId),

View File

@@ -14,7 +14,13 @@ use crate::{
};
/// A cross-module identifier of a scope that can be used as a salsa query parameter.
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
///
/// This type is an interned struct as opposed to a tracked struct as it is the input of
/// the `infer_scope_types` query, which is the root query of the persistent cache.
///
/// Additionally, this type disables garbage collection, as garbage collectable interned values are volatile,
/// and would cause `infer_scope_types` to have a dependency on every single `ScopeId` in the file.
#[salsa::interned(persist, debug, revisions=usize::MAX, heap_size=ruff_memory_usage::heap_size)]
pub struct ScopeId<'db> {
pub file: File,
@@ -70,7 +76,8 @@ impl<'db> ScopeId<'db> {
/// ID that uniquely identifies a scope inside of a module.
#[newtype_index]
#[derive(salsa::Update, get_size2::GetSize)]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(salsa::Update, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct FileScopeId;
impl FileScopeId {

View File

@@ -8,7 +8,8 @@ use std::ops::{Deref, DerefMut};
/// Uniquely identifies a symbol in a given scope.
#[newtype_index]
#[derive(get_size2::GetSize)]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct ScopedSymbolId;
/// A symbol in a given scope.

View File

@@ -466,7 +466,17 @@ impl Suppression {
/// The wrapped `TextRange` is the suppression's range.
/// This is unique enough because it is its exact
/// location in the source.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) struct FileSuppressionId(TextRange);
#[derive(Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize)]

View File

@@ -363,8 +363,41 @@ impl std::fmt::Display for TodoType {
}
}
#[cfg(debug_assertions)]
impl serde::Serialize for TodoType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serialize::serialize(&(), serializer)
}
}
#[cfg(debug_assertions)]
impl<'de> serde::Deserialize<'de> for TodoType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<()>::deserialize(deserializer)?;
// TODO: Use an enum for `TodoType` instead of a static string to enable deserialization.
Ok(TodoType("<deserialized>"))
}
}
#[cfg(not(debug_assertions))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct TodoType;
#[cfg(not(debug_assertions))]
@@ -400,6 +433,7 @@ macro_rules! todo_type {
i += 1;
}
};
$crate::types::Type::Dynamic($crate::types::DynamicType::Todo($crate::types::TodoType(
$message,
)))
@@ -432,7 +466,7 @@ pub(crate) use todo_type;
/// # Ordering
/// Ordering is based on the property instance's salsa-assigned id and not on its values.
/// The id may change between runs, or when the property instance was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct PropertyInstanceType<'db> {
getter: Option<Type<'db>>,
@@ -502,7 +536,7 @@ bitflags! {
/// that were passed in. For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/dataclasses.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct DataclassParams: u16 {
const INIT = 0b0000_0000_0001;
const REPR = 0b0000_0000_0010;
@@ -561,7 +595,19 @@ impl From<DataclassTransformerParams> for DataclassParams {
/// Representation of a type: a set of possible values at runtime.
///
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum Type<'db> {
/// The dynamic type: a statically unknown set of values
Dynamic(DynamicType),
@@ -6577,7 +6623,9 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
/// This is represented as an enum (with some variants using `Cow`), and not an `FnMut` trait,
/// since we sometimes have to apply type mappings lazily (e.g., to the signature of a function
/// literal).
#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum TypeMapping<'a, 'db> {
/// Applies a specialization to the type
Specialization(Specialization<'db>),
@@ -6676,7 +6724,18 @@ impl<'db> TypeMapping<'_, 'db> {
/// Ordering within variants is based on the wrapped data's salsa-assigned id and not on its values.
/// The id may change between runs, or when e.g. a `TypeVarInstance` was garbage-collected and recreated.
#[derive(
Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, Ord, PartialOrd, get_size2::GetSize,
Copy,
Clone,
Debug,
Eq,
Hash,
PartialEq,
salsa::Update,
Ord,
PartialOrd,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum KnownInstanceType<'db> {
/// The type of `Protocol[T]`, `Protocol[U, S]`, etc -- usually only found in a class's bases list.
@@ -6816,7 +6875,17 @@ impl<'db> KnownInstanceType<'db> {
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
PartialEq,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum DynamicType {
/// An explicitly annotated `typing.Any`
Any,
@@ -6884,7 +6953,7 @@ impl std::fmt::Display for DynamicType {
bitflags! {
/// Type qualifiers that appear in an annotation expression.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, salsa::Update, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, salsa::Update, Hash, serde::Serialize, serde::Deserialize)]
pub(crate) struct TypeQualifiers: u8 {
/// `typing.ClassVar`
const CLASS_VAR = 1 << 0;
@@ -7125,7 +7194,7 @@ impl<'db> InvalidTypeExpression<'db> {
}
/// Data regarding a `warnings.deprecated` or `typing_extensions.deprecated` decorator.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DeprecatedInstance<'db> {
/// The message for the deprecation
@@ -7137,7 +7206,7 @@ impl get_size2::GetSize for DeprecatedInstance<'_> {}
/// Contains information about instances of `dataclasses.Field`, typically created using
/// `dataclasses.field()`.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct FieldInstance<'db> {
/// The type of the default value for this field. This is derived from the `default` or
@@ -7167,7 +7236,17 @@ impl<'db> FieldInstance<'db> {
/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
/// or an implicit typevar like `Self` was used.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
PartialEq,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeVarKind {
/// `T = TypeVar("T")`
Legacy,
@@ -7213,7 +7292,7 @@ pub enum TypeVarKind {
/// # Ordering
/// Ordering is based on the type var instance's salsa-assigned id and not on its values.
/// The id may change between runs, or when the type var instance was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct TypeVarInstance<'db> {
/// The name of this TypeVar (e.g. `T`)
@@ -7413,7 +7492,18 @@ impl<'db> TypeVarInstance<'db> {
}
/// Where a type variable is bound and usable.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum BindingContext<'db> {
/// The definition of the generic class, function, or type alias that binds this typevar.
Definition(Definition<'db>),
@@ -7433,7 +7523,7 @@ impl<'db> BindingContext<'db> {
/// A type variable that has been bound to a generic context, and which can be specialized to a
/// concrete type.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct BoundTypeVarInstance<'db> {
pub typevar: TypeVarInstance<'db>,
@@ -7534,7 +7624,18 @@ impl<'db> BoundTypeVarInstance<'db> {
}
/// Whether a typevar default is eagerly specified or lazily evaluated.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeVarDefaultEvaluation<'db> {
/// The default type is lazily evaluated.
Lazy,
@@ -7549,7 +7650,18 @@ impl<'db> From<Type<'db>> for TypeVarDefaultEvaluation<'db> {
}
/// Whether a typevar bound/constraints is eagerly specified or lazily evaluated.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeVarBoundOrConstraintsEvaluation<'db> {
/// There is a lazily-evaluated upper bound.
LazyUpperBound,
@@ -7565,7 +7677,18 @@ impl<'db> From<TypeVarBoundOrConstraints<'db>> for TypeVarBoundOrConstraintsEval
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeVarBoundOrConstraints<'db> {
UpperBound(Type<'db>),
Constraints(UnionType<'db>),
@@ -8676,7 +8799,7 @@ impl From<bool> for Truthiness {
/// # Ordering
/// Ordering is based on the bounded method's salsa-assigned id and not on its values.
/// The id may change between runs, or when the bounded method was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct BoundMethodType<'db> {
/// The function that is being bound. Corresponds to the `__func__` attribute on a
@@ -8790,7 +8913,7 @@ impl<'db> BoundMethodType<'db> {
/// # Ordering
/// Ordering is based on the callable type's salsa-assigned id and not on its values.
/// The id may change between runs, or when the callable type was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct CallableType<'db> {
#[returns(ref)]
@@ -8934,7 +9057,18 @@ impl<'db> CallableType<'db> {
/// Represents a specific instance of `types.MethodWrapperType`
#[derive(
Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, salsa::Update, get_size2::GetSize,
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum MethodWrapperKind<'db> {
/// Method wrapper for `some_function.__get__`
@@ -9076,7 +9210,18 @@ impl<'db> MethodWrapperKind<'db> {
/// Represents a specific instance of `types.WrapperDescriptorType`
#[derive(
Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, salsa::Update, get_size2::GetSize,
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum WrapperDescriptorKind {
/// `FunctionType.__get__`
@@ -9090,7 +9235,7 @@ pub enum WrapperDescriptorKind {
/// # Ordering
/// Ordering is based on the module literal's salsa-assigned id and not on its values.
/// The id may change between runs, or when the module literal was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct ModuleLiteralType<'db> {
/// The imported module.
@@ -9200,7 +9345,7 @@ impl<'db> ModuleLiteralType<'db> {
/// # Ordering
/// Ordering is based on the type alias's salsa-assigned id and not on its values.
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct PEP695TypeAliasType<'db> {
#[returns(ref)]
@@ -9262,7 +9407,7 @@ fn value_type_cycle_initial<'db>(_db: &'db dyn Db, _self: PEP695TypeAliasType<'d
/// # Ordering
/// Ordering is based on the type alias's salsa-assigned id and not on its values.
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct ManualPEP695TypeAliasType<'db> {
#[returns(ref)]
@@ -9294,7 +9439,18 @@ impl<'db> ManualPEP695TypeAliasType<'db> {
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, salsa::Update, get_size2::GetSize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeAliasType<'db> {
/// A type alias defined using the PEP 695 `type` statement.
@@ -9359,7 +9515,7 @@ pub(super) struct MetaclassCandidate<'db> {
explicit_metaclass_of: ClassLiteral<'db>,
}
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct UnionType<'db> {
/// The union type includes values in any of these types.
#[returns(deref)]
@@ -9581,7 +9737,7 @@ impl<'db> UnionType<'db> {
}
}
#[salsa::interned(debug, heap_size=IntersectionType::heap_size)]
#[salsa::interned(persist, debug, heap_size=IntersectionType::heap_size)]
pub struct IntersectionType<'db> {
/// The intersection type includes only values in all of these types.
#[returns(ref)]
@@ -9790,7 +9946,7 @@ impl<'db> IntersectionType<'db> {
/// # Ordering
/// Ordering is based on the string literal's salsa-assigned id and not on its value.
/// The id may change between runs, or when the string literal was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct StringLiteralType<'db> {
#[returns(deref)]
@@ -9818,7 +9974,7 @@ impl<'db> StringLiteralType<'db> {
/// # Ordering
/// Ordering is based on the byte literal's salsa-assigned id and not on its value.
/// The id may change between runs, or when the byte literal was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct BytesLiteralType<'db> {
#[returns(deref)]
@@ -9843,7 +9999,7 @@ impl<'db> BytesLiteralType<'db> {
/// NO = 0
/// YES = 1
/// ```
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct EnumLiteralType<'db> {
/// A reference to the enum class this literal belongs to
@@ -9864,7 +10020,18 @@ impl<'db> EnumLiteralType<'db> {
/// Type that represents the set of all inhabitants (`dict` instances) that conform to
/// a given `TypedDict` schema.
#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash, get_size2::GetSize)]
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
salsa::Update,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct TypedDictType<'db> {
/// A reference to the class (inheriting from `typing.TypedDict`) that specifies the
/// schema of this `TypedDict`.
@@ -9945,7 +10112,17 @@ impl BoundSuperError<'_> {
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, get_size2::GetSize)]
#[derive(
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum SuperOwnerKind<'db> {
Dynamic(DynamicType),
Class(ClassType<'db>),
@@ -10034,7 +10211,7 @@ impl<'db> From<SuperOwnerKind<'db>> for Type<'db> {
}
/// Represent a bound super object like `super(PivotClass, owner)`
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct BoundSuperType<'db> {
pub pivot_class: ClassBase<'db>,
pub owner: SuperOwnerKind<'db>,
@@ -10243,7 +10420,7 @@ impl<'db> BoundSuperType<'db> {
}
}
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct TypeIsType<'db> {
return_type: Type<'db>,
/// The ID of the scope to which the place belongs

View File

@@ -244,7 +244,7 @@ impl CodeGeneratorKind {
/// # Ordering
/// Ordering is based on the generic aliases's salsa-assigned id and not on its values.
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct GenericAlias<'db> {
pub(crate) origin: ClassLiteral<'db>,
@@ -365,6 +365,7 @@ impl<'db> VarianceInferable<'db> for GenericAlias<'db> {
/// Represents a class type, which might be a non-generic class, or a specialization of a generic
/// class.
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(
Clone,
Copy,
@@ -377,6 +378,8 @@ impl<'db> VarianceInferable<'db> for GenericAlias<'db> {
salsa::Supertype,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum ClassType<'db> {
NonGeneric(ClassLiteral<'db>),
@@ -1280,7 +1283,7 @@ impl<'db> Field<'db> {
/// # Ordering
/// Ordering is based on the class's id assigned by salsa and not on the class literal's values.
/// The id may change between runs, or when the class literal was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct ClassLiteral<'db> {
/// Name of the class at definition
@@ -3357,7 +3360,17 @@ pub(super) enum SolidBaseKind {
/// Feel free to expand this enum if you ever find yourself using the same class in multiple
/// places.
/// Note: good candidates are any classes in `[crate::module_resolver::module::KnownModule]`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum KnownClass {
// To figure out where an stdlib symbol is defined, you can go into `crates/ty_vendored`

View File

@@ -14,7 +14,18 @@ use crate::types::{
/// Note that a non-specialized generic class _cannot_ be a class base. When we see a
/// non-specialized generic class in any type expression (including the list of base classes), we
/// automatically construct the default specialization for that class.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Debug,
Copy,
Clone,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum ClassBase<'db> {
Dynamic(DynamicType),
Class(ClassType<'db>),

View File

@@ -1759,7 +1759,7 @@ declare_lint! {
}
/// A collection of type check diagnostics.
#[derive(Default, Eq, PartialEq, get_size2::GetSize)]
#[derive(Default, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize)]
pub struct TypeCheckDiagnostics {
diagnostics: Vec<Diagnostic>,
used_suppressions: FxHashSet<FileSuppressionId>,

View File

@@ -102,7 +102,7 @@ pub(crate) struct FunctionSpans {
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash, serde::Serialize, serde::Deserialize)]
pub struct FunctionDecorators: u8 {
/// `@classmethod`
const CLASSMETHOD = 1 << 0;
@@ -160,7 +160,7 @@ bitflags! {
/// arguments that were passed in. For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, serde::Serialize, serde::Deserialize)]
pub struct DataclassTransformerParams: u8 {
const EQ_DEFAULT = 1 << 0;
const ORDER_DEFAULT = 1 << 1;
@@ -188,7 +188,7 @@ impl Default for DataclassTransformerParams {
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct OverloadLiteral<'db> {
/// Name of the function at definition.
@@ -410,7 +410,7 @@ impl<'db> OverloadLiteral<'db> {
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct FunctionLiteral<'db> {
pub(crate) last_definition: OverloadLiteral<'db>,
@@ -616,7 +616,7 @@ impl<'db> FunctionLiteral<'db> {
/// Represents a function type, which might be a non-generic function, or a specialization of a
/// generic function.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct FunctionType<'db> {
pub(crate) literal: FunctionLiteral<'db>,
@@ -1082,6 +1082,8 @@ fn last_definition_signature_cycle_initial<'db>(
strum_macros::EnumString,
strum_macros::IntoStaticStr,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(test, derive(strum_macros::EnumIter))]

View File

@@ -91,7 +91,7 @@ pub(crate) fn bind_typevar<'db>(
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug, heap_size=GenericContext::heap_size)]
#[salsa::interned(persist, debug, heap_size=GenericContext::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct GenericContext<'db> {
#[returns(ref)]
@@ -402,7 +402,7 @@ impl std::fmt::Display for LegacyGenericBase {
///
/// TODO: Handle nested specializations better, with actual parent links to the specialization of
/// the lexically containing context.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct Specialization<'db> {
pub(crate) generic_context: GenericContext<'db>,
#[returns(deref)]
@@ -688,7 +688,9 @@ impl<'db> Specialization<'db> {
///
/// You will usually use [`Specialization`] instead of this type. This type is used when we need to
/// substitute types for type variables before we have fully constructed a [`Specialization`].
#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub struct PartialSpecialization<'a, 'db> {
generic_context: GenericContext<'db>,
types: Cow<'a, [Type<'db>]>,

View File

@@ -135,7 +135,7 @@ use crate::{Db, FxOrderSet, Program};
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
/// scope.
#[salsa::tracked(returns(ref), cycle_fn=scope_cycle_recover, cycle_initial=scope_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
#[salsa::tracked(persist, returns(ref), cycle_fn=scope_cycle_recover, cycle_initial=scope_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> ScopeInference<'db> {
let file = scope.file(db);
let _span = tracing::trace_span!("infer_scope_types", scope=?scope.as_id(), ?file).entered();
@@ -421,7 +421,9 @@ struct TypeAndRange<'db> {
}
/// The inferred types for a scope region.
#[derive(Debug, Eq, PartialEq, salsa::Update, get_size2::GetSize)]
#[derive(
Debug, Eq, PartialEq, salsa::Update, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub(crate) struct ScopeInference<'db> {
/// The types of every expression in this region.
expressions: FxHashMap<ExpressionNodeKey, Type<'db>>,
@@ -430,7 +432,16 @@ pub(crate) struct ScopeInference<'db> {
extra: Option<Box<ScopeInferenceExtra>>,
}
#[derive(Debug, Eq, PartialEq, get_size2::GetSize, salsa::Update, Default)]
#[derive(
Debug,
Eq,
PartialEq,
get_size2::GetSize,
salsa::Update,
Default,
serde::Serialize,
serde::Deserialize,
)]
struct ScopeInferenceExtra {
/// The fallback type for missing expressions/bindings/declarations.
///

View File

@@ -117,7 +117,18 @@ impl<'db> Type<'db> {
}
/// A type representing the set of runtime objects which are instances of a certain nominal class.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct NominalInstanceType<'db>(
// Keep this field private, so that the only way of constructing `NominalInstanceType` instances
// is through the `Type::instance` constructor function.
@@ -399,7 +410,18 @@ impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
/// [`NominalInstanceType`] is split into two variants internally as a pure
/// optimization to avoid having to materialize the [`ClassType`] for tuple
/// instances where it would be unnecessary (this is somewhat expensive!).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
enum NominalInstanceInner<'db> {
/// A tuple type, e.g. `tuple[int, str]`.
///
@@ -429,7 +451,18 @@ impl<'db> VarianceInferable<'db> for NominalInstanceType<'db> {
/// A `ProtocolInstanceType` represents the set of all possible runtime objects
/// that conform to the interface described by a certain protocol.
#[derive(
Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord, get_size2::GetSize,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
salsa::Update,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct ProtocolInstanceType<'db> {
pub(super) inner: Protocol<'db>,
@@ -633,7 +666,18 @@ impl<'db> VarianceInferable<'db> for ProtocolInstanceType<'db> {
/// An enumeration of the two kinds of protocol types: those that originate from a class
/// definition in source code, and those that are synthesized from a set of members.
#[derive(
Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord, get_size2::GetSize,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
salsa::Update,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(super) enum Protocol<'db> {
FromClass(ClassType<'db>),
@@ -685,7 +729,18 @@ mod synthesized_protocol {
/// The constructor method of this type maintains the invariant that a synthesized protocol type
/// is always constructed from a *normalized* protocol interface.
#[derive(
Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord, get_size2::GetSize,
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
salsa::Update,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(in crate::types) struct SynthesizedProtocolType<'db>(ProtocolInterface<'db>);

View File

@@ -127,7 +127,7 @@ impl<'db> Deref for ProtocolClassLiteral<'db> {
/// # Ordering
/// Ordering is based on the protocol interface member's salsa-assigned id and not on its members.
/// The id may change between runs, or when the protocol instance members was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub(super) struct ProtocolInterface<'db> {
#[returns(ref)]
@@ -318,7 +318,17 @@ impl<'db> VarianceInferable<'db> for ProtocolInterface<'db> {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Debug,
PartialEq,
Eq,
Clone,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(super) struct ProtocolMemberData<'db> {
kind: ProtocolMemberKind<'db>,
qualifiers: TypeQualifiers,
@@ -403,7 +413,18 @@ impl<'db> ProtocolMemberData<'db> {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash, get_size2::GetSize)]
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
salsa::Update,
Hash,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
enum ProtocolMemberKind<'db> {
Method(CallableType<'db>),
Property(PropertyInstanceType<'db>),

View File

@@ -28,7 +28,17 @@ use ruff_python_ast::{self as ast, name::Name};
/// The signature of a single callable. If the callable is overloaded, there is a separate
/// [`Signature`] for each overload.
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct CallableSignature<'db> {
/// The signatures of each overload of this callable. Will be empty if the type is not
/// callable.
@@ -247,7 +257,17 @@ impl<'db> VarianceInferable<'db> for &CallableSignature<'db> {
}
/// The signature of one of the overloads of a callable.
#[derive(Clone, Debug, salsa::Update, get_size2::GetSize, PartialEq, Eq, Hash)]
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct Signature<'db> {
/// The generic context for this overload, if it is generic.
pub(crate) generic_context: Option<GenericContext<'db>>,
@@ -1015,7 +1035,17 @@ impl<'db> VarianceInferable<'db> for &Signature<'db> {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) struct Parameters<'db> {
// TODO: use SmallVec here once invariance bug is fixed
value: Vec<Parameter<'db>>,
@@ -1314,7 +1344,17 @@ impl<'db> std::ops::Index<usize> for Parameters<'db> {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) struct Parameter<'db> {
/// Annotated type of the parameter.
annotated_type: Option<Type<'db>>,
@@ -1576,7 +1616,17 @@ impl<'db> Parameter<'db> {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum ParameterKind<'db> {
/// Positional-only parameter, e.g. `def f(x, /): ...`
PositionalOnly {
@@ -1642,7 +1692,17 @@ impl<'db> ParameterKind<'db> {
}
/// Whether a parameter is used as a value or a type form.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
PartialEq,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum ParameterForm {
Value,
Type,

View File

@@ -25,6 +25,8 @@ use std::str::FromStr;
Ord,
strum_macros::EnumString,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum SpecialFormType {
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)

View File

@@ -14,7 +14,18 @@ use crate::{Db, FxOrderSet};
use super::{TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance};
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct SubclassOfType<'db> {
// Keep this field private, so that the only way of constructing the struct is through the `from` method.
subclass_of: SubclassOfInner<'db>,
@@ -249,7 +260,18 @@ impl<'db> VarianceInferable<'db> for SubclassOfType<'db> {
/// Note that this enum is similar to the [`super::ClassBase`] enum,
/// but does not include the `ClassBase::Protocol` and `ClassBase::Generic` variants
/// (`type[Protocol]` and `type[Generic]` are not valid types).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum SubclassOfInner<'db> {
Class(ClassType<'db>),
Dynamic(DynamicType),

View File

@@ -126,7 +126,7 @@ impl TupleLength {
/// # Ordering
/// Ordering is based on the tuple's salsa-assigned id and not on its elements.
/// The id may change between runs, or when the tuple was garbage collected and recreated.
#[salsa::interned(debug, constructor=new_internal, heap_size=ruff_memory_usage::heap_size)]
#[salsa::interned(persist, debug, constructor=new_internal, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct TupleType<'db> {
#[returns(ref)]
@@ -316,7 +316,9 @@ pub(crate) type TupleSpec<'db> = Tuple<Type<'db>>;
///
/// Our tuple representation can hold instances of any Rust type. For tuples containing Python
/// types, use [`TupleSpec`], which defines some additional type-specific methods.
#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub struct FixedLengthTuple<T>(Box<[T]>);
impl<T> FixedLengthTuple<T> {
@@ -523,7 +525,9 @@ impl<'db> PySlice<'db> for FixedLengthTuple<Type<'db>> {
///
/// Our tuple representation can hold instances of any Rust type. For tuples containing Python
/// types, use [`TupleSpec`], which defines some additional type-specific methods.
#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub struct VariableLengthTuple<T> {
pub(crate) prefix: Box<[T]>,
pub(crate) variable: T,
@@ -964,7 +968,9 @@ impl<'db> PyIndex<'db> for &VariableLengthTuple<Type<'db>> {
///
/// Our tuple representation can hold instances of any Rust type. For tuples containing Python
/// types, use [`TupleSpec`], which defines some additional type-specific methods.
#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
#[derive(
Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum Tuple<T> {
Fixed(FixedLengthTuple<T>),
Variable(VariableLengthTuple<T>),

View File

@@ -1,6 +1,17 @@
use crate::{Db, types::BoundTypeVarInstance};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
Eq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum TypeVarVariance {
Invariant,
Covariant,

View File

@@ -26,7 +26,7 @@ use crate::semantic_index::scope::{FileScopeId, ScopeId};
/// * a return type of a cross-module query
/// * a field of a type that is a return type of a cross-module query
/// * an argument of a cross-module query
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
#[salsa::tracked(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub(crate) struct Unpack<'db> {
pub(crate) file: File,
@@ -70,7 +70,16 @@ impl<'db> Unpack<'db> {
}
/// The expression that is being unpacked.
#[derive(Clone, Copy, Debug, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) struct UnpackValue<'db> {
/// The kind of unpack expression
kind: UnpackKind,
@@ -102,7 +111,16 @@ impl<'db> UnpackValue<'db> {
}
}
#[derive(Clone, Copy, Debug, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum EvaluationMode {
Sync,
Async,
@@ -122,7 +140,16 @@ impl EvaluationMode {
}
}
#[derive(Clone, Copy, Debug, Hash, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum UnpackKind {
/// An iterable expression like the one in a `for` loop or a comprehension.
Iterable { mode: EvaluationMode },
@@ -133,7 +160,17 @@ pub(crate) enum UnpackKind {
}
/// The position of the target element in an unpacking.
#[derive(Clone, Copy, Debug, Hash, PartialEq, salsa::Update, get_size2::GetSize)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
salsa::Update,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub(crate) enum UnpackPosition {
/// The target element is in the first position of the unpacking.
First,

View File

@@ -199,6 +199,15 @@ impl System for LSPSystem {
self.native_system.path_exists_case_sensitive(path, prefix)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
let document = self.system_path_to_document_ref(path);
match document {
Some(DocumentQuery::Text { document, .. }) => Ok(document.contents().into()),
_ => self.native_system.read_to_end(path),
}
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
let document = self.system_path_to_document_ref(path);

View File

@@ -18,6 +18,7 @@ impl EnvVars {
pub const TY_LOG: &'static str = "TY_LOG";
/// If set to `"1"` or `"true"`, ty will enable flamegraph profiling.
///
/// This creates a `tracing.folded` file that can be used to generate flame graphs
/// for performance analysis.
pub const TY_LOG_PROFILE: &'static str = "TY_LOG_PROFILE";
@@ -39,6 +40,14 @@ impl EnvVars {
/// when necessary, e.g. to watch for file system changes or a dedicated UI thread.
pub const TY_MAX_PARALLELISM: &'static str = "TY_MAX_PARALLELISM";
/// Used to enable persistent caching at the specified path.
///
/// This improves performance and memory usage by caching to disk across ty runs.
/// Note that this feature is currently experimental and may result in decreased
/// performance.
#[attr_hidden]
pub const TY_PERSIST: &'static str = "TY_PERSIST";
/// Used to detect an activated virtual environment.
pub const VIRTUAL_ENV: &'static str = "VIRTUAL_ENV";

View File

@@ -18,7 +18,7 @@ ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ruff_python_ast = { workspace = true }
ty_python_semantic = { workspace = true, features = ["serde", "testing"] }
ty_python_semantic = { workspace = true, features = ["testing"] }
ty_static = { workspace = true }
ty_vendored = { workspace = true }

View File

@@ -186,6 +186,10 @@ impl System for MdtestSystem {
}
}
fn read_to_end(&self, path: &SystemPath) -> ruff_db::system::Result<Vec<u8>> {
self.as_system().read_to_end(&self.normalize_path(path))
}
fn read_to_string(&self, path: &SystemPath) -> ruff_db::system::Result<String> {
self.as_system().read_to_string(&self.normalize_path(path))
}
@@ -279,7 +283,7 @@ impl WritableSystem for MdtestSystem {
self.as_system().create_new_file(&self.normalize_path(path))
}
fn write_file(&self, path: &SystemPath, content: &str) -> ruff_db::system::Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> ruff_db::system::Result<()> {
self.as_system()
.write_file(&self.normalize_path(path), content)
}

View File

@@ -1154,6 +1154,10 @@ impl System for WasmSystem {
self.fs.canonicalize(path)
}
fn read_to_end(&self, path: &SystemPath) -> ruff_db::system::Result<Vec<u8>> {
self.fs.read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> ruff_db::system::Result<String> {
self.fs.read_to_string(path)
}

View File

@@ -30,11 +30,12 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" }
ty_vendored = { path = "../crates/ty_vendored" }
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a3ffa22cb26756473d56f867aedec3fd907c4dd9", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a0e7a06", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
"persistence",
] }
similar = { version = "2.5.0" }
tracing = { version = "0.1.40" }