mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Make module resolution environment-aware (#27128)
## Summary This PR continues the refactor to make type inference and module resolution support scripts that may have different settings than the main project. This PR introduces: * `ResolverEnvironment(SearchPaths, PythonVersion)`: Uniquely identifies the settings used to resolve a module * `ResolverFile(File, ResolverEnvironment)`: A file with its `ResolverEnvironment`. Used for desperate module resolution and `module_to_file` (and all other places where the operation doesn't depend on type-inference specific settings). This PR also `ProgramFile` and updates the `Program` alias to point to `ResolverEnvironment`. These changes are in preparation to reduce the diff of the third PR (we don't want to rewrite all `python_file` call sites to `resolver_file` to then rename them to `program_file`). ## Memory regression This PR changes `Program` from `PythonVersion` (u16) to `ResolverEnvironment` (u64). This increases the size of interned structs and interned query arguments that contain `Program`. Memory usage is still lower than before we made db `Program` aware (the base PR reduced memory usage by about 1%). ## Test Plan Testing: Ran workspace checks, resolver/core/semantic/IDE tests, and repository hooks, including cross-version inference coverage.
This commit is contained in:
1 parent
7fd30bb81c
commit
95734a549e
150 files changed
+2293
-1684
No files matched your search
Generated
+2
@@ -3163,6 +3163,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4722,6 +4723,7 @@ dependencies = [
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"ordermap",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"ruff_db",
|
||||
|
||||
@@ -6,12 +6,13 @@ use indexmap::IndexSet;
|
||||
use log::{debug, warn};
|
||||
use path_absolutize::CWD;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
||||
use ruff_graph::{
|
||||
Direction, ImportMap, ModuleDb, ModuleImports, ResolverEnvironment, resolve_search_paths,
|
||||
};
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{warn_user, warn_user_once};
|
||||
use ruff_python_ast::SourceType;
|
||||
use ruff_python_parser::ParseOptions;
|
||||
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, project_files_in_path};
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use std::io::Write;
|
||||
@@ -97,12 +98,14 @@ pub(crate) fn analyze_graph(
|
||||
);
|
||||
|
||||
let system = OsSystem::default();
|
||||
let db = ModuleDb::from_src_roots(
|
||||
system,
|
||||
let search_paths = resolve_search_paths(
|
||||
&system,
|
||||
src_roots.into_iter().collect(),
|
||||
args.python
|
||||
.and_then(|python| SystemPathBuf::from_path_buf(python).ok()),
|
||||
)?;
|
||||
let db = ModuleDb::new(system);
|
||||
search_paths.try_register_static_roots(&db);
|
||||
|
||||
let imports = {
|
||||
// Create a cache for resolved globs.
|
||||
@@ -112,6 +115,7 @@ pub(crate) fn analyze_graph(
|
||||
let result = Arc::new(Mutex::new(Vec::new()));
|
||||
let inner_result = Arc::clone(&result);
|
||||
let db = db.clone();
|
||||
let search_paths = &search_paths;
|
||||
|
||||
rayon::scope(move |scope| {
|
||||
for resolved_file in paths {
|
||||
@@ -172,14 +176,13 @@ pub(crate) fn analyze_graph(
|
||||
}
|
||||
};
|
||||
|
||||
let source_code = source_kind.source_code();
|
||||
let environment = ResolverEnvironment::new(&db, python_version, search_paths);
|
||||
|
||||
// Identify any imports via static analysis.
|
||||
let mut imports = ModuleImports::detect(
|
||||
&db,
|
||||
source_code,
|
||||
ParseOptions::from(source_type.expect_python())
|
||||
.with_target_version(python_version),
|
||||
environment,
|
||||
&source_kind,
|
||||
&path,
|
||||
package.as_deref(),
|
||||
string_imports,
|
||||
|
||||
@@ -23,6 +23,7 @@ ruff_python_formatter = { workspace = true, optional = true }
|
||||
ruff_python_parser = { workspace = true, optional = true }
|
||||
ruff_python_trivia = { workspace = true, optional = true }
|
||||
ty_module_resolver = { workspace = true, optional = true }
|
||||
ty_python_core = { workspace = true, optional = true }
|
||||
ty_project = { workspace = true, optional = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
@@ -60,7 +61,7 @@ ruff_instrumented = [
|
||||
# Enables the ty instrumented benchmarks
|
||||
ty_instrumented = ["criterion", "ty_project", "ruff_python_trivia"]
|
||||
# Enables the module-resolution benchmark
|
||||
module_resolution = ["divan", "ty_module_resolver", "ty_project"]
|
||||
module_resolution = ["divan", "ty_module_resolver", "ty_project", "ty_python_core"]
|
||||
codspeed = ["codspeed-criterion-compat"]
|
||||
# Enables the ty_walltime benchmarks
|
||||
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
@@ -113,6 +114,7 @@ ignored = [
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ty_module_resolver",
|
||||
"ty_python_core",
|
||||
"mimalloc",
|
||||
"tikv-jemallocator"
|
||||
]
|
||||
@@ -5,15 +5,15 @@ use std::hint::black_box;
|
||||
|
||||
use divan::{Bencher, bench};
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{File, system_path_to_file};
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf, TestSystem};
|
||||
use ruff_ranged_value::RangedValue;
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, resolve_module};
|
||||
use ty_project::metadata::options::{EnvironmentOptions, Options};
|
||||
use ty_project::metadata::python_version::SupportedPythonVersion;
|
||||
use ty_project::metadata::value::RelativePathBuf;
|
||||
use ty_project::{Db as _, ProjectDatabase, ProjectMetadata};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_core::program::Program;
|
||||
|
||||
const SEEDED_TARGETS: &[&str] = &["target_0", "target_1", "target_2", "target_3", "target_4"];
|
||||
// Exercise stub-overlay discovery followed by normal fallback.
|
||||
@@ -73,6 +73,8 @@ fn setup_case(n: usize) -> Case {
|
||||
});
|
||||
|
||||
let db = ProjectDatabase::fallible(metadata, system).unwrap();
|
||||
// Intern the resolver environment before timing so its initial allocation is not benchmarked.
|
||||
let _ = Program::get(&db).resolver_environment(&db);
|
||||
let importing_file = system_path_to_file(&db, &importing_path).unwrap();
|
||||
|
||||
let resolves = SEEDED_TARGETS
|
||||
@@ -94,10 +96,13 @@ fn ty_module_resolver<const PATHS: usize>(bencher: Bencher) {
|
||||
bencher
|
||||
.with_inputs(|| setup_case(PATHS))
|
||||
.bench_local_refs(|case| {
|
||||
let importing_file =
|
||||
PythonFile::new(&case.db, case.importing_file, case.db.python_version());
|
||||
let environment = Program::get(&case.db).resolver_environment(&case.db);
|
||||
for name in &case.resolves {
|
||||
black_box(resolve_module(&case.db, importing_file, name));
|
||||
black_box(resolve_module(
|
||||
&case.db,
|
||||
ImportingFile::File(case.importing_file, environment),
|
||||
name,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+27
-34
@@ -22,47 +22,44 @@ pub struct ModuleDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
system: Arc<dyn System + Send + Sync + RefUnwindSafe>,
|
||||
search_paths: Arc<SearchPaths>,
|
||||
}
|
||||
|
||||
impl ModuleDb {
|
||||
/// Initialize a [`ModuleDb`] from the given source root.
|
||||
pub fn from_src_roots<S>(
|
||||
system: S,
|
||||
src_roots: Vec<SystemPathBuf>,
|
||||
venv_path: Option<SystemPathBuf>,
|
||||
) -> Result<Self>
|
||||
/// Initialize a [`ModuleDb`] for the given system.
|
||||
pub fn new<S>(system: S) -> Self
|
||||
where
|
||||
S: System + 'static + Send + Sync + RefUnwindSafe,
|
||||
{
|
||||
let mut search_path_settings = SearchPathSettings::new(src_roots);
|
||||
// TODO: Consider calling `PythonEnvironment::discover` if the `venv_path` is not provided.
|
||||
if let Some(venv_path) = venv_path {
|
||||
let environment =
|
||||
PythonEnvironment::new(venv_path, SysPrefixPathOrigin::PythonCliFlag, &system)?;
|
||||
search_path_settings.site_packages_paths = environment
|
||||
.site_packages_paths(&system)
|
||||
.context("Failed to discover the site-packages directory")?
|
||||
.into_vec();
|
||||
}
|
||||
let search_paths = search_path_settings
|
||||
.to_search_paths(&system, &EMPTY_VENDORED, &FallibleStrategy)
|
||||
.context("Invalid search path settings")?;
|
||||
|
||||
let db = Self {
|
||||
Self {
|
||||
storage: salsa::Storage::new(None),
|
||||
files: Files::default(),
|
||||
system: Arc::new(system),
|
||||
search_paths: Arc::new(search_paths),
|
||||
};
|
||||
|
||||
// Register the static roots for salsa durability
|
||||
db.search_paths.try_register_static_roots(&db);
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve module search paths for the given source roots and Python environment.
|
||||
pub fn resolve_search_paths(
|
||||
system: &dyn System,
|
||||
src_roots: Vec<SystemPathBuf>,
|
||||
venv_path: Option<SystemPathBuf>,
|
||||
) -> Result<SearchPaths> {
|
||||
let mut search_path_settings = SearchPathSettings::new(src_roots);
|
||||
// TODO: Consider calling `PythonEnvironment::discover` if the `venv_path` is not provided.
|
||||
if let Some(venv_path) = venv_path {
|
||||
let environment =
|
||||
PythonEnvironment::new(venv_path, SysPrefixPathOrigin::PythonCliFlag, system)?;
|
||||
search_path_settings.site_packages_paths = environment
|
||||
.site_packages_paths(system)
|
||||
.context("Failed to discover the site-packages directory")?
|
||||
.into_vec();
|
||||
}
|
||||
|
||||
search_path_settings
|
||||
.to_search_paths(system, &EMPTY_VENDORED, &FallibleStrategy)
|
||||
.context("Invalid search path settings")
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl SourceDb for ModuleDb {
|
||||
fn vendored(&self) -> &VendoredFileSystem {
|
||||
@@ -79,11 +76,7 @@ impl SourceDb for ModuleDb {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for ModuleDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
&self.search_paths
|
||||
}
|
||||
}
|
||||
impl ty_module_resolver::Db for ModuleDb {}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for ModuleDb {}
|
||||
@@ -3,11 +3,13 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
use ruff_python_parser::{ParseOptions, parse};
|
||||
pub use ty_module_resolver::ResolverEnvironment;
|
||||
|
||||
use crate::collector::Collector;
|
||||
pub use crate::db::ModuleDb;
|
||||
pub use crate::db::{ModuleDb, resolve_search_paths};
|
||||
use crate::resolver::Resolver;
|
||||
pub use crate::settings::{AnalyzeSettings, Direction, StringImports};
|
||||
|
||||
@@ -22,18 +24,19 @@ pub struct ModuleImports(BTreeSet<SystemPathBuf>);
|
||||
|
||||
impl ModuleImports {
|
||||
/// Detect the [`ModuleImports`] for a given Python file.
|
||||
pub fn detect(
|
||||
db: &ModuleDb,
|
||||
source: &str,
|
||||
parse_options: ParseOptions,
|
||||
pub fn detect<'db>(
|
||||
db: &'db ModuleDb,
|
||||
environment: ResolverEnvironment<'db>,
|
||||
source: &SourceKind,
|
||||
path: &SystemPath,
|
||||
package: Option<&SystemPath>,
|
||||
string_imports: StringImports,
|
||||
type_checking_imports: bool,
|
||||
) -> Result<Self> {
|
||||
// Parse the source code.
|
||||
let python_version = parse_options.target_version();
|
||||
let parsed = parse(source, parse_options)?;
|
||||
let parse_options = ParseOptions::from(source.py_source_type())
|
||||
.with_target_version(environment.python_version(db));
|
||||
let parsed = parse(source.source_code(), parse_options)?;
|
||||
|
||||
let module_path =
|
||||
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));
|
||||
@@ -48,7 +51,7 @@ impl ModuleImports {
|
||||
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
let resolver = Resolver::new(db, path, python_version);
|
||||
let resolver = Resolver::new(db, path, environment);
|
||||
for import in imports {
|
||||
for resolved in resolver.resolve(import) {
|
||||
if let Some(path) = resolved.as_system_path() {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{FilePath, system_path_to_file};
|
||||
use ruff_db::files::{File, FilePath, system_path_to_file};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{
|
||||
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident,
|
||||
ImportingFile, ModuleName, ResolverEnvironment, resolve_module, resolve_module_confident,
|
||||
resolve_real_module, resolve_real_module_confident,
|
||||
};
|
||||
|
||||
use crate::ModuleDb;
|
||||
@@ -13,21 +11,23 @@ use crate::collector::CollectedImport;
|
||||
/// Collect all imports for a given Python file.
|
||||
pub(crate) struct Resolver<'a> {
|
||||
db: &'a ModuleDb,
|
||||
file: Option<PythonFile<'a>>,
|
||||
python_version: PythonVersion,
|
||||
file: Option<File>,
|
||||
environment: ResolverEnvironment<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Resolver<'a> {
|
||||
/// Initialize a [`Resolver`] with a given [`ModuleDb`].
|
||||
pub(crate) fn new(db: &'a ModuleDb, path: &SystemPath, python_version: PythonVersion) -> Self {
|
||||
pub(crate) fn new(
|
||||
db: &'a ModuleDb,
|
||||
path: &SystemPath,
|
||||
environment: ResolverEnvironment<'a>,
|
||||
) -> Self {
|
||||
// If we know the importing file we can potentially resolve more imports
|
||||
let file = system_path_to_file(db, path)
|
||||
.ok()
|
||||
.map(|file| PythonFile::new(db, file, python_version));
|
||||
let file = system_path_to_file(db, path).ok();
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
python_version,
|
||||
environment,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +110,13 @@ impl<'a> Resolver<'a> {
|
||||
/// Resolves a module name to a module.
|
||||
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = if let Some(file) = self.file {
|
||||
resolve_module(self.db, file, module_name)?
|
||||
resolve_module(
|
||||
self.db,
|
||||
ImportingFile::File(file, self.environment),
|
||||
module_name,
|
||||
)?
|
||||
} else {
|
||||
resolve_module_confident(self.db, self.python_version, module_name)?
|
||||
resolve_module_confident(self.db, self.environment, module_name)?
|
||||
};
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
@@ -120,9 +124,13 @@ impl<'a> Resolver<'a> {
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = if let Some(file) = self.file {
|
||||
resolve_real_module(self.db, file, module_name)?
|
||||
resolve_real_module(
|
||||
self.db,
|
||||
ImportingFile::File(file, self.environment),
|
||||
module_name,
|
||||
)?
|
||||
} else {
|
||||
resolve_real_module_confident(self.db, self.python_version, module_name)?
|
||||
resolve_real_module_confident(self.db, self.environment, module_name)?
|
||||
};
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
|
||||
@@ -409,17 +409,12 @@ impl MainLoop {
|
||||
}
|
||||
}
|
||||
MainLoopMode::Fix(mode) => {
|
||||
let python_version = db.python_version();
|
||||
let result = match mode {
|
||||
FixMode::AddIgnore => suppress_all_diagnostics(
|
||||
db,
|
||||
python_version,
|
||||
result,
|
||||
&self.cancellation_token,
|
||||
),
|
||||
FixMode::AddIgnore => {
|
||||
suppress_all_diagnostics(db, result, &self.cancellation_token)
|
||||
}
|
||||
FixMode::ApplyFixes => fix_all_diagnostics(
|
||||
db,
|
||||
python_version,
|
||||
result,
|
||||
Applicability::Safe,
|
||||
&self.cancellation_token,
|
||||
|
||||
@@ -39,7 +39,7 @@ fn resolve_module_confident<'db>(
|
||||
) -> Option<Module<'db>> {
|
||||
ty_module_resolver::resolve_module_confident(
|
||||
db,
|
||||
Program::get(db).python_version(db),
|
||||
Program::get(db).resolver_environment(db),
|
||||
module_name,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,15 +11,13 @@ use std::process::ExitCode;
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap::Parser;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ty_ide::{Completion, CompletionCapabilities};
|
||||
use ty_project::Db as _;
|
||||
use ty_project::metadata::Options;
|
||||
use ty_project::metadata::options::EnvironmentOptions;
|
||||
use ty_project::metadata::value::RelativePathBuf;
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata, SemanticDb as _};
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[command(
|
||||
@@ -144,7 +142,7 @@ fn get_completions<'db>(
|
||||
db,
|
||||
&settings,
|
||||
CompletionCapabilities::default(),
|
||||
PythonFile::new(db, file, db.python_version()),
|
||||
db.program_file(file),
|
||||
offset,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -10,16 +10,14 @@ use anyhow::{Context, anyhow};
|
||||
use clap::Parser;
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ty_ide::{Completion, CompletionCapabilities};
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_project::Db as _;
|
||||
use ty_project::metadata::Options;
|
||||
use ty_project::metadata::options::EnvironmentOptions;
|
||||
use ty_project::metadata::value::RelativePathBuf;
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata, SemanticDb as _};
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[command(
|
||||
@@ -332,7 +330,7 @@ impl Task {
|
||||
&self.db,
|
||||
&self.settings,
|
||||
CompletionCapabilities::default(),
|
||||
PythonFile::new(&self.db, file, self.db.python_version()),
|
||||
self.db.program_file(file),
|
||||
offset,
|
||||
);
|
||||
Ok(completions)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use compact_str::CompactString;
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::{PythonFile, files::File};
|
||||
use ty_module_resolver::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
|
||||
use ruff_db::files::File;
|
||||
use ty_module_resolver::{
|
||||
ImportingFile, Module, ModuleName, all_modules, resolve_real_shadowable_module,
|
||||
};
|
||||
use ty_project::{Db, parallel::ParallelIteratorExt};
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
use crate::{
|
||||
SymbolKind,
|
||||
@@ -15,7 +18,7 @@ use crate::{
|
||||
/// by the query.
|
||||
pub fn all_symbols<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_from: PythonFile<'db>,
|
||||
importing_from: ProgramFile<'db>,
|
||||
query: &QueryPattern,
|
||||
) -> Vec<AllSymbolInfo<'db>> {
|
||||
// If the query is empty, return immediately to avoid expensive file scanning
|
||||
@@ -27,10 +30,13 @@ pub fn all_symbols<'db>(
|
||||
let _span = all_symbols_span.enter();
|
||||
|
||||
let typing_extensions = ModuleName::new_static("typing_extensions").unwrap();
|
||||
let program = importing_from.program(db);
|
||||
let resolver_environment = importing_from.resolver_environment(db);
|
||||
let importing_file = ImportingFile::File(importing_from.file(db), resolver_environment);
|
||||
let is_typing_extensions_available = importing_from.file(db).is_stub(db)
|
||||
|| resolve_real_shadowable_module(db, importing_from, &typing_extensions).is_some();
|
||||
|| resolve_real_shadowable_module(db, importing_file, &typing_extensions).is_some();
|
||||
|
||||
let results = all_modules(db, importing_from.python_version(db))
|
||||
let results = all_modules(db, resolver_environment)
|
||||
.into_par_iter()
|
||||
.map_with_db(db, |db, module| {
|
||||
let name = module.name(db);
|
||||
@@ -55,10 +61,10 @@ pub fn all_symbols<'db>(
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let Some(python_file) = module.python_file(db) else {
|
||||
let Some(file) = module.file(db) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let file = python_file.file(db);
|
||||
let program_file = ProgramFile::new(db, file, program);
|
||||
|
||||
let symbols_for_file_span = tracing::debug_span!(
|
||||
parent: &all_symbols_span,
|
||||
@@ -71,7 +77,7 @@ pub fn all_symbols<'db>(
|
||||
if query.is_match_symbol_name(module.name(db)) {
|
||||
symbols.push(AllSymbolInfo::from_module(db, module, file));
|
||||
}
|
||||
for (_, symbol) in symbols_for_file_global_only(db, python_file).search(query) {
|
||||
for (_, symbol) in symbols_for_file_global_only(db, program_file).search(query) {
|
||||
// Test functions (starting with `test_`) in third-party
|
||||
// packages are almost never useful to import.
|
||||
if is_non_first_party && symbol.name.starts_with("test_") {
|
||||
@@ -710,7 +716,7 @@ def zqzqzq():
|
||||
|
||||
let symbols = all_symbols(
|
||||
&test.db,
|
||||
test.python_file(test.cursor.file),
|
||||
test.program_file(test.cursor.file),
|
||||
&QueryPattern::fuzzy("zqzqzq"),
|
||||
);
|
||||
let symbol = symbols
|
||||
@@ -1112,7 +1118,7 @@ def test_helper_xyzxyzxyz():
|
||||
fn all_symbols(&self, query: &str) -> String {
|
||||
let symbols = all_symbols(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
&QueryPattern::fuzzy(query),
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ pub(crate) mod outgoing_calls;
|
||||
|
||||
use crate::goto::{GotoTarget, find_goto_target};
|
||||
use crate::{Db, SymbolKind};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::find_node::CoveringNode;
|
||||
@@ -21,6 +20,8 @@ use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::token::Tokens;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_module_resolver::ResolverFile;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::DefinitionKind;
|
||||
use ty_python_semantic::{ImportAliasResolution, ResolvedDefinition, SemanticModel};
|
||||
|
||||
@@ -33,10 +34,10 @@ use ty_python_semantic::{ImportAliasResolution, ResolvedDefinition, SemanticMode
|
||||
/// cursor on a specific `@overload def` yields just that one.
|
||||
pub fn prepare_call_hierarchy(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<Vec<CallHierarchyItem>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let definitions = goto_target
|
||||
@@ -109,7 +110,7 @@ impl CallHierarchyItem {
|
||||
Some(CallHierarchyItem {
|
||||
name: Name::new(name),
|
||||
kind,
|
||||
detail: module_detail(db, def.python_file(db)),
|
||||
detail: module_detail(db, def.program_file(db).resolver_file(db)),
|
||||
file: def_file,
|
||||
full_range: def.full_range(db, module).range(),
|
||||
selection_range: def.focus_range(db, module).range(),
|
||||
@@ -117,7 +118,7 @@ impl CallHierarchyItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn module_detail(db: &dyn Db, file: PythonFile<'_>) -> Option<String> {
|
||||
fn module_detail(db: &dyn Db, file: ResolverFile<'_>) -> Option<String> {
|
||||
ty_module_resolver::file_to_module(db, file).map(|module| module.name(db).to_string())
|
||||
}
|
||||
|
||||
@@ -199,7 +200,7 @@ mod tests {
|
||||
pub(super) fn prepare_calls(&self) -> Option<Vec<CallHierarchyItem>> {
|
||||
prepare_call_hierarchy(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::goto::{Definitions, GotoTarget, find_goto_target};
|
||||
use crate::references::has_any_external_visible_definitions;
|
||||
use crate::{CallHierarchyItem, Db, SymbolKind};
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_python_ast::helpers::is_dunder;
|
||||
@@ -13,7 +12,9 @@ use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_module_resolver::ResolverFile;
|
||||
use ty_project::parallel::{ParallelIteratorExt, minimum_parallel_job_len};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::scope::{NodeWithScopeKind, ScopeKind};
|
||||
use ty_python_semantic::types::ide_support::static_member_type_for_attribute;
|
||||
use ty_python_semantic::types::{PropertyAccessorRole, Type};
|
||||
@@ -29,8 +30,8 @@ const MAX_MIN_FILES_PER_PARALLEL_JOB: usize = 16;
|
||||
|
||||
/// Find every place in the project that calls the symbol at `offset`, grouped
|
||||
/// by enclosing function/method/class/module.
|
||||
pub fn incoming_calls(db: &dyn Db, file: PythonFile<'_>, offset: TextSize) -> Vec<IncomingCall> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
pub fn incoming_calls(db: &dyn Db, file: ProgramFile<'_>, offset: TextSize) -> Vec<IncomingCall> {
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let source_file = file.file(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let Some(goto_target) = find_goto_target(&model, &module, offset) else {
|
||||
@@ -75,8 +76,8 @@ pub fn incoming_calls(db: &dyn Db, file: PythonFile<'_>, offset: TextSize) -> Ve
|
||||
let mut raw = call_sites_for_file(db, file, &target_definitions, target_role, needle);
|
||||
|
||||
if is_externally_visible {
|
||||
let program = model.program();
|
||||
let files = db.project().files(db);
|
||||
let python_version = file.python_version(db);
|
||||
let files: Vec<_> = files
|
||||
.iter()
|
||||
.copied()
|
||||
@@ -103,7 +104,7 @@ pub fn incoming_calls(db: &dyn Db, file: PythonFile<'_>, offset: TextSize) -> Ve
|
||||
|
||||
call_sites_for_file(
|
||||
db,
|
||||
PythonFile::new(db, other_file, python_version),
|
||||
ProgramFile::new(db, other_file, program),
|
||||
&target_definitions,
|
||||
target_role,
|
||||
needle,
|
||||
@@ -170,12 +171,12 @@ struct EnclosingKey {
|
||||
/// `target_definitions`.
|
||||
fn call_sites_for_file(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
target_definitions: &Definitions<'_>,
|
||||
target_role: Option<PropertyAccessorRole>,
|
||||
needle: Option<&str>,
|
||||
) -> Vec<RawCallSite> {
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let mut sites = Vec::new();
|
||||
@@ -390,8 +391,9 @@ impl<'a> CallSitesFinder<'a, '_> {
|
||||
/// method's AST node. Comprehension and annotation scopes have no callable
|
||||
/// hierarchy item of their own, so walk outward until reaching one that does.
|
||||
fn enclosing_scope_item(&self, scope_node: AnyNodeRef<'_>) -> CallHierarchyItem {
|
||||
let python_file = self.model.python_file();
|
||||
let file = python_file.file(self.db);
|
||||
let program_file = self.model.program_file();
|
||||
let resolver_file = program_file.resolver_file(self.db);
|
||||
let file = program_file.file(self.db);
|
||||
let mut ancestors = self.model.ancestor_scopes(scope_node);
|
||||
let Some((_, enclosing)) = ancestors.find(|(_, ancestor)| {
|
||||
matches!(
|
||||
@@ -399,11 +401,11 @@ impl<'a> CallSitesFinder<'a, '_> {
|
||||
ScopeKind::Module | ScopeKind::Function | ScopeKind::Class | ScopeKind::Lambda
|
||||
)
|
||||
}) else {
|
||||
return module_item(self.db, python_file);
|
||||
return module_item(self.db, resolver_file);
|
||||
};
|
||||
|
||||
match enclosing.node() {
|
||||
NodeWithScopeKind::Module => module_item(self.db, python_file),
|
||||
NodeWithScopeKind::Module => module_item(self.db, resolver_file),
|
||||
NodeWithScopeKind::Function(func) => {
|
||||
let func = func.node(self.module);
|
||||
let is_method = ancestors
|
||||
@@ -422,7 +424,7 @@ impl<'a> CallSitesFinder<'a, '_> {
|
||||
} else {
|
||||
SymbolKind::Function
|
||||
},
|
||||
detail: module_detail(self.db, python_file),
|
||||
detail: module_detail(self.db, resolver_file),
|
||||
file,
|
||||
full_range: func.range(),
|
||||
selection_range: func.name.range(),
|
||||
@@ -433,7 +435,7 @@ impl<'a> CallSitesFinder<'a, '_> {
|
||||
CallHierarchyItem {
|
||||
name: class.name.id.clone(),
|
||||
kind: SymbolKind::Class,
|
||||
detail: module_detail(self.db, python_file),
|
||||
detail: module_detail(self.db, resolver_file),
|
||||
file,
|
||||
full_range: class.range(),
|
||||
selection_range: class.name.range(),
|
||||
@@ -450,13 +452,13 @@ impl<'a> CallSitesFinder<'a, '_> {
|
||||
CallHierarchyItem {
|
||||
name: Name::new_static("(lambda)"),
|
||||
kind: SymbolKind::Function,
|
||||
detail: module_detail(self.db, python_file),
|
||||
detail: module_detail(self.db, resolver_file),
|
||||
file,
|
||||
full_range: lambda.range(),
|
||||
selection_range: TextRange::new(lambda.start(), end),
|
||||
}
|
||||
}
|
||||
_ => module_item(self.db, python_file),
|
||||
_ => module_item(self.db, resolver_file),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,7 +469,7 @@ struct RawCallSite {
|
||||
}
|
||||
|
||||
/// Build an item for the module-level enclosing scope (no enclosing function).
|
||||
fn module_item(db: &dyn Db, file: PythonFile<'_>) -> CallHierarchyItem {
|
||||
fn module_item(db: &dyn Db, file: ResolverFile<'_>) -> CallHierarchyItem {
|
||||
let name = ty_module_resolver::file_to_module(db, file)
|
||||
.map(|module| Name::new(module.name(db).last_component()))
|
||||
.unwrap_or_else(|| Name::new_static("<module>"));
|
||||
@@ -527,7 +529,7 @@ mod tests {
|
||||
};
|
||||
let calls = incoming_calls(
|
||||
&self.db,
|
||||
self.python_file(target.file),
|
||||
self.program_file(target.file),
|
||||
target.selection_range.start(),
|
||||
);
|
||||
if calls.is_empty() {
|
||||
@@ -1147,7 +1149,7 @@ def make() -> C:
|
||||
};
|
||||
let incoming = incoming_calls(
|
||||
&test.db,
|
||||
test.python_file(target.file),
|
||||
test.program_file(target.file),
|
||||
target.selection_range.start(),
|
||||
);
|
||||
// The selection identifies the anonymous callable header.
|
||||
@@ -1280,7 +1282,7 @@ def make() -> C:
|
||||
};
|
||||
let incoming = incoming_calls(
|
||||
&test.db,
|
||||
test.python_file(target.file),
|
||||
test.program_file(target.file),
|
||||
target.selection_range.start(),
|
||||
);
|
||||
assert_eq!(incoming.len(), 1, "got {incoming:?}");
|
||||
@@ -1289,7 +1291,7 @@ def make() -> C:
|
||||
|
||||
let follow_up_incoming = incoming_calls(
|
||||
&test.db,
|
||||
test.python_file(lambda_item.file),
|
||||
test.program_file(lambda_item.file),
|
||||
lambda_item.selection_range.start(),
|
||||
);
|
||||
assert!(
|
||||
@@ -1299,7 +1301,7 @@ def make() -> C:
|
||||
|
||||
let follow_up_outgoing = outgoing_calls(
|
||||
&test.db,
|
||||
test.python_file(lambda_item.file),
|
||||
test.program_file(lambda_item.file),
|
||||
lambda_item.selection_range.start(),
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::hash_map::Entry;
|
||||
use crate::call_hierarchy::CalleeLeaf;
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::{CallHierarchyItem, Db};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::token::Tokens;
|
||||
@@ -16,6 +15,7 @@ use ruff_python_ast::{
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::DefinitionKind;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
@@ -30,8 +30,8 @@ use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
/// are reported when the nested callable is expanded separately. Declaration
|
||||
/// expressions attached to a nested callable are still included while
|
||||
/// traversing the containing item's body.
|
||||
pub fn outgoing_calls(db: &dyn Db, file: PythonFile<'_>, offset: TextSize) -> Vec<OutgoingCall> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
pub fn outgoing_calls(db: &dyn Db, file: ProgramFile<'_>, offset: TextSize) -> Vec<OutgoingCall> {
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let Some(goto_target) = find_goto_target(&model, &module, offset) else {
|
||||
return Vec::new();
|
||||
@@ -54,7 +54,7 @@ pub fn outgoing_calls(db: &dyn Db, file: PythonFile<'_>, offset: TextSize) -> Ve
|
||||
};
|
||||
let parsed = parsed_module(db, def.python_file(db)).load(db);
|
||||
|
||||
let model = SemanticModel::new(db, def.python_file(db));
|
||||
let model = SemanticModel::new(db, def.program_file(db));
|
||||
let mut finder = OutgoingCallsFinder {
|
||||
db,
|
||||
model: &model,
|
||||
@@ -306,7 +306,7 @@ mod tests {
|
||||
};
|
||||
let calls = outgoing_calls(
|
||||
&self.db,
|
||||
self.python_file(target.file),
|
||||
self.program_file(target.file),
|
||||
target.selection_range.start(),
|
||||
);
|
||||
if calls.is_empty() {
|
||||
|
||||
@@ -2,11 +2,11 @@ use crate::completion;
|
||||
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::find_node::covering_node;
|
||||
use ruff_text_size::TextRange;
|
||||
use ty_project::Db;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::lint::LintId;
|
||||
use ty_python_semantic::suppress_single;
|
||||
use ty_python_semantic::types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
@@ -21,7 +21,7 @@ pub struct QuickFix {
|
||||
|
||||
pub fn code_actions(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
diagnostic_range: TextRange,
|
||||
diagnostic_id: &str,
|
||||
) -> Vec<QuickFix> {
|
||||
@@ -44,7 +44,7 @@ pub fn code_actions(
|
||||
// Suggest just suppressing the lint (always a valid option, but never ideal)
|
||||
actions.push(QuickFix {
|
||||
title: format!("Ignore '{}' for this line", lint_id.name()),
|
||||
edits: suppress_single(db, file, lint_id, diagnostic_range).into_edits(),
|
||||
edits: suppress_single(db, file.python_file(db), lint_id, diagnostic_range).into_edits(),
|
||||
preferred: false,
|
||||
});
|
||||
|
||||
@@ -53,10 +53,10 @@ pub fn code_actions(
|
||||
|
||||
fn unresolved_fixes(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
diagnostic_range: TextRange,
|
||||
) -> Option<impl Iterator<Item = QuickFix>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let node = covering_node(parsed.syntax().into(), diagnostic_range).node();
|
||||
let symbol = &node.expr_name()?.id;
|
||||
Some(
|
||||
@@ -77,7 +77,6 @@ mod tests {
|
||||
|
||||
use insta::assert_snapshot;
|
||||
use ruff_db::{
|
||||
PythonFile,
|
||||
diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
|
||||
LintName, Span, SubDiagnostic,
|
||||
@@ -89,6 +88,7 @@ mod tests {
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ty_project::ProjectMetadata;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::{
|
||||
default_lint_registry,
|
||||
lint::LintMetadata,
|
||||
@@ -936,7 +936,11 @@ mod tests {
|
||||
|
||||
for mut action in code_actions(
|
||||
&self.db,
|
||||
PythonFile::new(&self.db, self.file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
self.file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
self.diagnostic_range,
|
||||
&lint.name,
|
||||
) {
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::{BinaryHeap, binary_heap};
|
||||
use ty_python_semantic::ProgramEnvironment;
|
||||
|
||||
use compact_str::{CompactString, CompactStringExt};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_db::source::{SourceText, source_text};
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -16,7 +15,8 @@ use ruff_python_codegen::Stylist;
|
||||
use ruff_python_literal::escape::{Escape, UnicodeEscape};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_module_resolver::{KnownModule, Module, ModuleName};
|
||||
use ty_module_resolver::{ImportingFile, KnownModule, Module, ModuleName};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::HasType;
|
||||
use ty_python_semantic::types::{SpecialFormType, UnionType};
|
||||
use ty_python_semantic::{
|
||||
@@ -34,18 +34,18 @@ pub fn completion<'db>(
|
||||
db: &'db dyn Db,
|
||||
settings: &CompletionSettings,
|
||||
capabilities: CompletionCapabilities,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
offset: TextSize,
|
||||
) -> Vec<Completion<'db>> {
|
||||
let python_file = file;
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let program_file = file;
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let file = file.file(db);
|
||||
let source = source_text(db, file);
|
||||
|
||||
let Some(context) = Context::new(db, python_file, &parsed, &source, offset) else {
|
||||
let Some(context) = Context::new(db, program_file, &parsed, &source, offset) else {
|
||||
return vec![];
|
||||
};
|
||||
let model = SemanticModel::new(db, python_file);
|
||||
let model = SemanticModel::new(db, program_file);
|
||||
|
||||
if !matches!(context.kind, ContextKind::Keywords(_)) && context.cursor.is_in_string() {
|
||||
let Some(string_expr) = context.cursor.enclosing_string_literal_expr() else {
|
||||
@@ -54,7 +54,7 @@ pub fn completion<'db>(
|
||||
|
||||
let mut completions = Completions::new(
|
||||
db,
|
||||
python_file,
|
||||
program_file,
|
||||
CollectionContext::none(),
|
||||
UserQuery::fuzzy(None),
|
||||
);
|
||||
@@ -72,7 +72,7 @@ pub fn completion<'db>(
|
||||
let query = UserQuery::fuzzy(context.cursor.typed);
|
||||
let mut completions = Completions::new(
|
||||
db,
|
||||
python_file,
|
||||
program_file,
|
||||
context.collection_context(db, &model, settings, capabilities),
|
||||
query,
|
||||
);
|
||||
@@ -84,7 +84,7 @@ pub fn completion<'db>(
|
||||
}
|
||||
}
|
||||
ContextKind::Import(ref import) => {
|
||||
import.add_completions(db, python_file, &mut completions);
|
||||
import.add_completions(db, program_file, &mut completions);
|
||||
}
|
||||
ContextKind::NonImport(ref non_import) => match non_import.target {
|
||||
CompletionTargetAst::ObjectDot { expr } => {
|
||||
@@ -106,7 +106,7 @@ pub fn completion<'db>(
|
||||
add_keyword_completions(db, &env, &mut completions);
|
||||
add_argument_completions(
|
||||
db,
|
||||
python_file,
|
||||
program_file,
|
||||
&model,
|
||||
&context.cursor,
|
||||
&mut completions,
|
||||
@@ -114,7 +114,7 @@ pub fn completion<'db>(
|
||||
if settings.auto_import {
|
||||
add_unimported_completions(
|
||||
db,
|
||||
python_file,
|
||||
program_file,
|
||||
&parsed,
|
||||
scoped,
|
||||
|module_name: &ModuleName, symbol: &str| {
|
||||
@@ -146,7 +146,7 @@ impl CompletionCapabilities {
|
||||
/// A collection of completions built up from various sources.
|
||||
struct Completions<'db> {
|
||||
db: &'db dyn Db,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
context: CollectionContext<'db>,
|
||||
items: BinaryHeap<CompletionRanker<'db>>,
|
||||
/// The query used to match against candidate completions.
|
||||
@@ -172,13 +172,13 @@ impl<'db> Completions<'db> {
|
||||
/// add completions that match it.
|
||||
fn new(
|
||||
db: &'db dyn Db,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
context: CollectionContext<'db>,
|
||||
query: UserQuery,
|
||||
) -> Completions<'db> {
|
||||
Completions {
|
||||
db,
|
||||
python_file,
|
||||
program_file,
|
||||
context,
|
||||
items: BinaryHeap::new(),
|
||||
query,
|
||||
@@ -274,7 +274,7 @@ impl<'db> Completions<'db> {
|
||||
return false;
|
||||
}
|
||||
let completion =
|
||||
CompletionRanker(builder.build(self.db, self.python_file, &self.context, &self.query));
|
||||
CompletionRanker(builder.build(self.db, self.program_file, &self.context, &self.query));
|
||||
if self.items.len() >= Completions::LIMIT {
|
||||
// OK because `self.items` is guaranteed to be non-empty here.
|
||||
let worst = self.items.peek_mut().unwrap();
|
||||
@@ -294,7 +294,7 @@ impl<'db> Extend<SemanticCompletion<'db>> for Completions<'db> {
|
||||
T: IntoIterator<Item = SemanticCompletion<'db>>,
|
||||
{
|
||||
let db = self.db;
|
||||
let env = ProgramEnvironment::from_file(self.python_file);
|
||||
let env = ProgramEnvironment::from_file(self.program_file);
|
||||
for c in it {
|
||||
self.add_semantic(db, &env, c);
|
||||
}
|
||||
@@ -487,7 +487,7 @@ impl<'db> CompletionBuilder<'db> {
|
||||
fn build(
|
||||
mut self,
|
||||
db: &'db dyn Db,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
collection_context: &CollectionContext<'db>,
|
||||
query: &UserQuery,
|
||||
) -> Completion<'db> {
|
||||
@@ -501,7 +501,7 @@ impl<'db> CompletionBuilder<'db> {
|
||||
// but aren't marked here. That is, false negatives are
|
||||
// possible but false positives are not.
|
||||
if let Some(exception_ty) = collection_context.exception_ty {
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
self.is_context_specific |= ty.is_assignable_to(db, &env, exception_ty);
|
||||
}
|
||||
if collection_context.is_in_class_def() {
|
||||
@@ -788,7 +788,7 @@ impl<'m> Context<'m> {
|
||||
/// Create a new context for finding completions.
|
||||
fn new(
|
||||
db: &'_ dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
parsed: &'m ParsedModuleRef,
|
||||
source: &'m SourceText,
|
||||
offset: TextSize,
|
||||
@@ -802,7 +802,11 @@ impl<'m> Context<'m> {
|
||||
ContextKind::Keywords(keywords)
|
||||
} else if cursor.is_in_definition_place() {
|
||||
return None;
|
||||
} else if let Some(import) = ImportStatement::detect(db, file, &cursor) {
|
||||
} else if let Some(import) = ImportStatement::detect(
|
||||
db,
|
||||
ImportingFile::File(file.file(db), file.resolver_environment(db)),
|
||||
&cursor,
|
||||
) {
|
||||
ContextKind::Import(import)
|
||||
} else {
|
||||
let target_token = CompletionTargetTokens::find(&cursor)?;
|
||||
@@ -1964,7 +1968,7 @@ enum Sort {
|
||||
/// Detect and add completions for unset arguments.
|
||||
fn add_argument_completions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
model: &SemanticModel<'db>,
|
||||
cursor: &ContextCursor<'_>,
|
||||
completions: &mut Completions<'db>,
|
||||
@@ -2052,7 +2056,7 @@ fn add_class_arg_completions<'db>(
|
||||
/// set and 2) been defined as positional-only.
|
||||
fn add_function_arg_completions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
cursor: &ContextCursor<'_>,
|
||||
completions: &mut Completions<'db>,
|
||||
) {
|
||||
@@ -2133,7 +2137,7 @@ pub(crate) struct ImportEdit {
|
||||
/// Get fixes that would resolve an unresolved reference
|
||||
pub(crate) fn unresolved_fixes<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
parsed: &ParsedModuleRef,
|
||||
symbol: &str,
|
||||
node: AnyNodeRef,
|
||||
@@ -2276,7 +2280,7 @@ fn add_string_literal_completions<'db>(
|
||||
/// when selected into `File`.
|
||||
fn add_unimported_completions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
parsed: &ParsedModuleRef,
|
||||
scoped: ScopedTarget<'_>,
|
||||
create_import_request: impl for<'a> Fn(&'a ModuleName, &'a str) -> ImportRequest<'a>,
|
||||
@@ -2295,6 +2299,7 @@ fn add_unimported_completions<'db>(
|
||||
let stylist = Stylist::from_tokens(parsed.tokens(), source.as_str());
|
||||
let importer = Importer::new(db, &stylist, file, source.as_str(), parsed);
|
||||
let members = importer.members_in_scope_at(scoped.node, scoped.node.start());
|
||||
let importing_file = ImportingFile::File(source_file, file.resolver_environment(db));
|
||||
|
||||
for symbol in all_symbols(db, file, &completions.query.pattern) {
|
||||
if symbol.file() == source_file || symbol.module().is_known(db, KnownModule::Builtins) {
|
||||
@@ -2311,7 +2316,7 @@ fn add_unimported_completions<'db>(
|
||||
});
|
||||
|
||||
// Don't suggest symbols that are already imported.
|
||||
if members.satisfies(db, file, &request) {
|
||||
if members.satisfies(db, importing_file, &request) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2576,7 +2581,7 @@ impl<'a> ImportStatement<'a> {
|
||||
/// `tokens`.
|
||||
fn detect(
|
||||
db: &'_ dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ImportingFile<'_>,
|
||||
cursor: &ContextCursor<'a>,
|
||||
) -> Option<ImportStatement<'a>> {
|
||||
use TokenKind as TK;
|
||||
@@ -2974,7 +2979,7 @@ impl<'a> ImportStatement<'a> {
|
||||
fn add_completions<'db>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
completions: &mut Completions<'db>,
|
||||
) {
|
||||
let model = SemanticModel::new(db, file);
|
||||
@@ -3071,7 +3076,7 @@ fn add_import_completions_impl<'db>(
|
||||
semantic_completions: impl IntoIterator<Item = SemanticCompletion<'db>>,
|
||||
module_dependency_kind: impl Fn(&SemanticCompletion<'db>) -> Option<ModuleDependencyKind>,
|
||||
) {
|
||||
let env = ProgramEnvironment::from_file(completions.python_file);
|
||||
let env = ProgramEnvironment::from_file(completions.program_file);
|
||||
for semantic in semantic_completions {
|
||||
let module_dependency_kind = module_dependency_kind(&semantic);
|
||||
let mut builder = CompletionBuilder::from_semantic_completion(db, &env, semantic);
|
||||
@@ -10877,7 +10882,7 @@ raise <CURSOR>
|
||||
&self.cursor_test.db,
|
||||
&self.settings,
|
||||
self.capabilities,
|
||||
self.cursor_test.python_file(self.cursor_test.cursor.file),
|
||||
self.cursor_test.program_file(self.cursor_test.cursor.file),
|
||||
self.cursor_test.cursor.offset,
|
||||
);
|
||||
let filtered = original
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
/// Find all document highlights for a symbol at the given position.
|
||||
/// Document highlights are limited to the current file only.
|
||||
pub fn document_highlights(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
@@ -36,7 +36,7 @@ mod tests {
|
||||
fn document_highlights(&self) -> String {
|
||||
let Some(highlight_results) = document_highlights(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
) else {
|
||||
return "No highlights found".to_string();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::symbols::{FlatSymbols, symbols_for_file};
|
||||
use ruff_db::PythonFile;
|
||||
use ty_project::Db;
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
/// Get all document symbols for a file with the given options.
|
||||
pub fn document_symbols<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> &'db FlatSymbols {
|
||||
pub fn document_symbols<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> &'db FlatSymbols {
|
||||
symbols_for_file(db, file)
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ def function():
|
||||
<CURSOR>",
|
||||
);
|
||||
|
||||
let symbols = document_symbols(&test.db, test.python_file(test.cursor.file))
|
||||
let symbols = document_symbols(&test.db, test.program_file(test.cursor.file))
|
||||
.iter()
|
||||
.map(|(_, symbol)| (symbol.name.into_owned(), symbol.kind))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -442,7 +442,7 @@ lambda_value = lambda: (lambda_local := 1)
|
||||
<CURSOR>",
|
||||
);
|
||||
|
||||
let names = document_symbols(&test.db, test.python_file(test.cursor.file))
|
||||
let names = document_symbols(&test.db, test.program_file(test.cursor.file))
|
||||
.iter()
|
||||
.map(|(_, symbol)| symbol.name.into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -464,7 +464,7 @@ class Example((class_base := Base)):
|
||||
<CURSOR>",
|
||||
);
|
||||
|
||||
let symbols = document_symbols(&test.db, test.python_file(test.cursor.file))
|
||||
let symbols = document_symbols(&test.db, test.program_file(test.cursor.file))
|
||||
.iter()
|
||||
.map(|(_, symbol)| (symbol.name.into_owned(), symbol.kind))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -487,7 +487,7 @@ class Example((class_base := Base)):
|
||||
impl CursorTest {
|
||||
fn document_symbols(&self) -> String {
|
||||
let symbols =
|
||||
document_symbols(&self.db, self.python_file(self.cursor.file)).to_hierarchical();
|
||||
document_symbols(&self.db, self.program_file(self.cursor.file)).to_hierarchical();
|
||||
|
||||
if symbols.is_empty() {
|
||||
return "No symbols found".to_string();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
/// Find all references to a symbol at the given position.
|
||||
/// Search for references across all files in the project.
|
||||
pub fn find_references(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
include_declaration: bool,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
@@ -48,7 +48,7 @@ mod tests {
|
||||
fn references_with_include_declaration(&self, include_declaration: bool) -> String {
|
||||
let Some(mut reference_results) = find_references(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
include_declaration,
|
||||
) else {
|
||||
|
||||
@@ -2579,7 +2579,11 @@ with open("file.txt") as f:
|
||||
|
||||
impl CursorTest {
|
||||
fn folding_ranges(&self) -> String {
|
||||
let ranges = folding_ranges(&self.db, self.python_file(self.cursor.file), None);
|
||||
let ranges = folding_ranges(
|
||||
&self.db,
|
||||
self.program_file(self.cursor.file).python_file(&self.db),
|
||||
None,
|
||||
);
|
||||
|
||||
if ranges.is_empty() {
|
||||
return "No folding ranges found".to_string();
|
||||
|
||||
@@ -14,6 +14,7 @@ use ruff_python_ast::token::{Token, TokenAt, TokenKind, Tokens};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, ExprRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::{Definition, DefinitionKind};
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
@@ -265,7 +266,7 @@ impl<'db> Definitions<'db> {
|
||||
let ty_def = ty.definition(db, env)?;
|
||||
let resolved = match ty_def {
|
||||
ty_python_semantic::types::TypeDefinition::Module(module) => {
|
||||
ResolvedDefinition::Module(module.python_file(db)?)
|
||||
ResolvedDefinition::Module(ProgramFile::new(db, module.file(db)?, env.program(db)))
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::StaticClass(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::DynamicClass(definition)
|
||||
@@ -1451,7 +1452,7 @@ fn definitions_for_module<'db>(
|
||||
level: u32,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
let module = model.resolve_module(module, level)?;
|
||||
let file = module.python_file(model.db())?;
|
||||
let file = ProgramFile::new(model.db(), module.file(model.db())?, model.program());
|
||||
Some(vec![ResolvedDefinition::Module(file)])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the declaration of a symbol.
|
||||
@@ -13,10 +13,10 @@ use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
/// is needed because Python doesn't require formal declarations of variables like most languages do.
|
||||
pub fn goto_declaration(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
|
||||
@@ -2768,7 +2768,7 @@ def ab(a: int, *, c: int): ...
|
||||
let Some(targets) = salsa::attach(&self.db, || {
|
||||
goto_declaration(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}) else {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the definition of a symbol.
|
||||
@@ -14,10 +14,10 @@ use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
/// source file implementations using the `StubMapper`.
|
||||
pub fn goto_definition(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let definition_targets = goto_target
|
||||
@@ -2651,7 +2651,7 @@ class GenericFoo[T](Base):
|
||||
let Some(targets) = salsa::attach(&self.db, || {
|
||||
goto_definition(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}) else {
|
||||
|
||||
@@ -58,11 +58,11 @@
|
||||
use crate::goto::{Definitions, GotoTarget, find_goto_target};
|
||||
use crate::{Db, NavigationTarget, NavigationTargets, RangedValue};
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_project::parallel::ParallelIteratorExt;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::{
|
||||
ImplementationsFinder, ImportAliasResolution, ResolvedDefinition, SemanticModel,
|
||||
};
|
||||
@@ -73,15 +73,15 @@ use ty_python_semantic::{
|
||||
/// identified.
|
||||
pub fn goto_implementation(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let finder = prepare_implementations_finder_for_goto_target(&model, &goto_target)?;
|
||||
let source_file = file.file(db);
|
||||
let python_version = file.python_version(db);
|
||||
let program = file.program(db);
|
||||
|
||||
let mut candidate_files: Vec<File> = db
|
||||
.project()
|
||||
@@ -95,7 +95,7 @@ pub fn goto_implementation(
|
||||
let batches = candidate_files
|
||||
.into_par_iter()
|
||||
.map_with_db(db, |db, file| {
|
||||
let file = PythonFile::new(db, file, python_version);
|
||||
let file = ProgramFile::new(db, file, program);
|
||||
let definitions = finder.implementations_for_file(db, file);
|
||||
definitions_to_implementation_targets(db, definitions)
|
||||
})
|
||||
@@ -838,7 +838,7 @@ mod tests {
|
||||
let targets = salsa::attach(&test.db, || {
|
||||
goto_implementation(
|
||||
&test.db,
|
||||
test.python_file(test.cursor.file),
|
||||
test.program_file(test.cursor.file),
|
||||
test.cursor.offset,
|
||||
)
|
||||
.expect("implementation targets")
|
||||
@@ -2144,7 +2144,7 @@ class MyClass:
|
||||
let Some(targets) = salsa::attach(&self.db, || {
|
||||
goto_implementation(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}) else {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
pub fn goto_type_definition(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
|
||||
@@ -2055,7 +2055,7 @@ def function():
|
||||
let Some(targets) = salsa::attach(&self.db, || {
|
||||
goto_type_definition(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}) else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::TextRange;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
UnreachableKind, unreachable_ranges, unused_bindings,
|
||||
};
|
||||
@@ -40,7 +40,7 @@ impl HintKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hints(db: &dyn Db, file: PythonFile<'_>) -> Vec<Hint> {
|
||||
pub fn hints(db: &dyn Db, file: ProgramFile<'_>) -> Vec<Hint> {
|
||||
let source_file = file.file(db);
|
||||
if !db.should_check_file(source_file) {
|
||||
return Vec::new();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::docstring::{Docstring, DocstringFragment};
|
||||
use crate::goto::{Definitions, GotoTarget, docstring_for_call_definition, find_goto_target};
|
||||
use crate::{Db, MarkupKind, RangedValue};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::ProgramEnvironment;
|
||||
use ty_python_semantic::types::ide_support::{resolved_call_signature, typed_dict_key_hover};
|
||||
use ty_python_semantic::types::{KnownInstanceType, Type, TypeAliasType, TypeVarVariance};
|
||||
@@ -16,10 +16,10 @@ use ty_python_semantic::{DisplaySettings, SemanticModel, TypeQualifiers};
|
||||
|
||||
pub fn hover<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<Hover<'db>>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &parsed, offset)?;
|
||||
|
||||
@@ -142,7 +142,7 @@ pub fn hover<'db>(
|
||||
Some(RangedValue {
|
||||
range: FileRange::new(file.file(db), goto_target.range()),
|
||||
value: Hover {
|
||||
python_file: file,
|
||||
program_file: file,
|
||||
contents,
|
||||
},
|
||||
})
|
||||
@@ -224,7 +224,7 @@ fn documentation_for_parameter(docstring: &Docstring, name: &str) -> Option<Docs
|
||||
}
|
||||
|
||||
pub struct Hover<'db> {
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
contents: Vec<HoverContent<'db>>,
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ impl fmt::Display for DisplayHover<'_, '_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let db = self.db;
|
||||
let mut first = true;
|
||||
let env = ProgramEnvironment::from_file(self.hover.python_file);
|
||||
let env = ProgramEnvironment::from_file(self.hover.program_file);
|
||||
for content in &self.hover.contents {
|
||||
if !first {
|
||||
self.kind.horizontal_line().fmt(f)?;
|
||||
@@ -6900,7 +6900,7 @@ type U<CURSOR> = MyType
|
||||
|
||||
let Some(hover) = hover(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
) else {
|
||||
return "Hover provided no content".to_string();
|
||||
|
||||
@@ -20,7 +20,6 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast as ast;
|
||||
@@ -30,8 +29,9 @@ use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_importer::Insertion;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_module_resolver::{ImportingFile, ModuleName};
|
||||
use ty_project::Db;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::DefinitionKind;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::{MemberDefinition, SemanticModel};
|
||||
@@ -41,7 +41,7 @@ pub(crate) struct Importer<'a> {
|
||||
db: &'a dyn Db,
|
||||
/// The file corresponding to the module that
|
||||
/// we want to insert an import statement into.
|
||||
file: PythonFile<'a>,
|
||||
file: ProgramFile<'a>,
|
||||
/// The parsed module ref.
|
||||
parsed: &'a ParsedModuleRef,
|
||||
/// The tokens representing the Python AST.
|
||||
@@ -74,7 +74,7 @@ impl<'a> Importer<'a> {
|
||||
pub(crate) fn new(
|
||||
db: &'a dyn Db,
|
||||
stylist: &'a Stylist<'a>,
|
||||
file: PythonFile<'a>,
|
||||
file: ProgramFile<'a>,
|
||||
source: &'a str,
|
||||
parsed: &'a ParsedModuleRef,
|
||||
) -> Self {
|
||||
@@ -146,9 +146,13 @@ impl<'a> Importer<'a> {
|
||||
request: ImportRequest<'_>,
|
||||
members: &MembersInScope,
|
||||
) -> ImportAction {
|
||||
let request = request.avoid_conflicts(self.db, self.file, members);
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file.file(self.db),
|
||||
self.file.resolver_environment(self.db),
|
||||
);
|
||||
let request = request.avoid_conflicts(self.db, importing_file, members);
|
||||
let mut symbol_text: Box<str> = request.member.unwrap_or(request.module).into();
|
||||
let Some(response) = self.find(&request, members.at) else {
|
||||
let Some(response) = self.find(importing_file, &request, members.at) else {
|
||||
let insertion = if let Some(future) = self.find_last_future_import(members.at) {
|
||||
Insertion::end_of_statement(future.stmt, self.source, self.stylist)
|
||||
} else {
|
||||
@@ -223,6 +227,7 @@ impl<'a> Importer<'a> {
|
||||
/// satisfies the request.
|
||||
fn find<'importer>(
|
||||
&'importer self,
|
||||
importing_file: ImportingFile<'_>,
|
||||
request: &ImportRequest<'_>,
|
||||
available_at: TextSize,
|
||||
) -> Option<ImportResponse<'importer, 'a>> {
|
||||
@@ -248,7 +253,7 @@ impl<'a> Importer<'a> {
|
||||
return choice;
|
||||
}
|
||||
|
||||
if let Some(response) = import.satisfies(self.db, self.file, request) {
|
||||
if let Some(response) = import.satisfies(self.db, importing_file, request) {
|
||||
let partial = matches!(response.kind, ImportResponseKind::Partial { .. });
|
||||
|
||||
// The LSP doesn't support edits across cell boundaries.
|
||||
@@ -331,7 +336,7 @@ pub struct MembersInScope<'ast> {
|
||||
impl<'ast> MembersInScope<'ast> {
|
||||
fn new(
|
||||
db: &'ast dyn Db,
|
||||
file: PythonFile<'ast>,
|
||||
file: ProgramFile<'ast>,
|
||||
parsed: &'ast ParsedModuleRef,
|
||||
node: ast::AnyNodeRef<'_>,
|
||||
at: TextSize,
|
||||
@@ -373,7 +378,7 @@ impl<'ast> MembersInScope<'ast> {
|
||||
pub(crate) fn satisfies(
|
||||
&self,
|
||||
db: &dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
request: &ImportRequest<'_>,
|
||||
) -> bool {
|
||||
let symbol_text = request.member.unwrap_or(request.module);
|
||||
@@ -409,7 +414,7 @@ impl<'ast> MemberInScope<'ast> {
|
||||
fn satisfies_anywhere(
|
||||
&self,
|
||||
db: &dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
request: &ImportRequest<'_>,
|
||||
) -> bool {
|
||||
let MemberImportKind::Imported(ref ast_import) = self.kind else {
|
||||
@@ -481,7 +486,7 @@ impl<'ast> AstImport<'ast> {
|
||||
fn satisfies<'importer>(
|
||||
&'importer self,
|
||||
db: &'_ dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
request: &ImportRequest<'_>,
|
||||
) -> Option<ImportResponse<'importer, 'ast>> {
|
||||
self.kind
|
||||
@@ -512,7 +517,7 @@ impl<'ast> AstImportKind<'ast> {
|
||||
fn satisfies<'importer>(
|
||||
&'importer self,
|
||||
db: &'_ dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
request: &ImportRequest<'_>,
|
||||
) -> Option<ImportResponseKind<'ast>> {
|
||||
match *self {
|
||||
@@ -639,7 +644,7 @@ impl<'a> ImportRequest<'a> {
|
||||
fn avoid_conflicts(
|
||||
self,
|
||||
db: &dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
members: &MembersInScope,
|
||||
) -> Self {
|
||||
let Some(member) = self.member else {
|
||||
@@ -979,7 +984,11 @@ mod tests {
|
||||
Importer::new(
|
||||
&self.db,
|
||||
&self.cursor.stylist,
|
||||
PythonFile::new(&self.db, self.cursor.file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
self.cursor.file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
self.cursor.source.as_str(),
|
||||
&self.cursor.parsed,
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::importer::{ImportAction, ImportRequest, Importer, MembersInScope};
|
||||
use crate::{Db, HasNavigationTargets, NavigationTarget};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
@@ -13,6 +12,7 @@ use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, ExprUnaryOp, Stmt, UnaryOp
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_module_resolver::file_to_module;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::types::ide_support::inlay_hint_call_argument_details;
|
||||
use ty_python_semantic::types::{Type, TypeDetail};
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
@@ -103,7 +103,8 @@ impl InlayHint {
|
||||
.as_deref()
|
||||
.unwrap_or(&details.label[start..end]);
|
||||
|
||||
let module = file_to_module(db, definition.python_file(db))?;
|
||||
let file = definition.program_file(db);
|
||||
let module = file_to_module(db, file.resolver_file(db))?;
|
||||
|
||||
if should_skip_import(db, module, *ty) {
|
||||
return None;
|
||||
@@ -291,11 +292,11 @@ pub struct InlayHintTextEdit {
|
||||
|
||||
pub fn inlay_hints(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
range: TextRange,
|
||||
settings: &InlayHintSettings,
|
||||
) -> Vec<InlayHint> {
|
||||
let ast = parsed_module(db, file).load(db);
|
||||
let ast = parsed_module(db, file.python_file(db)).load(db);
|
||||
let source_file = file.file(db);
|
||||
|
||||
let source = source_text(db, source_file);
|
||||
@@ -348,7 +349,7 @@ impl Default for InlayHintSettings {
|
||||
|
||||
struct InlayHintImportContext<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
importer: &'a Importer<'db>,
|
||||
dynamic_imports: &'a mut FxHashMap<DynamicallyImportedMember, ImportAction>,
|
||||
}
|
||||
@@ -370,7 +371,7 @@ struct InlayHintVisitor<'a, 'db> {
|
||||
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
fn new(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
importer: Importer<'db>,
|
||||
range: TextRange,
|
||||
settings: &'a InlayHintSettings,
|
||||
@@ -399,7 +400,7 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
|
||||
let context = InlayHintImportContext {
|
||||
db: self.db,
|
||||
file: self.model.python_file(),
|
||||
file: self.model.program_file(),
|
||||
importer: &self.importer,
|
||||
dynamic_imports: &mut self.dynamic_imports,
|
||||
};
|
||||
@@ -882,7 +883,11 @@ mod tests {
|
||||
fn inlay_hints_with_settings(&mut self, settings: &InlayHintSettings) -> String {
|
||||
let hints = inlay_hints(
|
||||
&self.db,
|
||||
PythonFile::new(&self.db, self.file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
self.file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
self.range,
|
||||
settings,
|
||||
);
|
||||
|
||||
@@ -403,7 +403,6 @@ mod tests {
|
||||
use insta::internals::SettingsBindDropGuard;
|
||||
|
||||
use ruff_db::Db;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, UnifiedFile,
|
||||
};
|
||||
@@ -416,7 +415,8 @@ mod tests {
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_module_resolver::SearchPathSettings;
|
||||
use ty_project::{Db as _, ProjectMetadata};
|
||||
use ty_project::{Db as _, ProjectMetadata, SemanticDb as _};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
use ty_python_core::program::{FallibleStrategy, Program, ProgramSettings};
|
||||
use ty_python_semantic::PythonVersionWithSource;
|
||||
@@ -440,8 +440,8 @@ mod tests {
|
||||
CursorTestBuilder::default()
|
||||
}
|
||||
|
||||
pub(super) fn python_file(&self, file: File) -> PythonFile<'_> {
|
||||
PythonFile::new(&self.db, file, self.db.python_version())
|
||||
pub(super) fn program_file(&self, file: File) -> ProgramFile<'_> {
|
||||
self.db.program_file(file)
|
||||
}
|
||||
|
||||
pub(super) fn write_file(
|
||||
@@ -569,8 +569,7 @@ mod tests {
|
||||
|
||||
let source = source_text(&db, file);
|
||||
let parsed =
|
||||
parsed_module(&db, PythonFile::new(&db, file, db.python_version()))
|
||||
.load(&db);
|
||||
parsed_module(&db, db.program_file(file).python_file(&db)).load(&db);
|
||||
let stylist =
|
||||
Stylist::from_tokens(parsed.tokens(), source.as_str()).into_owned();
|
||||
cursor = Some(Cursor {
|
||||
@@ -723,8 +722,7 @@ mod tests {
|
||||
|
||||
let source = source_text(&db, file);
|
||||
let parsed =
|
||||
parsed_module(&db, PythonFile::new(&db, file, db.python_version()))
|
||||
.load(&db);
|
||||
parsed_module(&db, db.program_file(file).python_file(&db)).load(&db);
|
||||
let stylist =
|
||||
Stylist::from_tokens(parsed.tokens(), source.as_str()).into_owned();
|
||||
cursor = Some(Cursor {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
use crate::goto::{Definitions, GotoTarget};
|
||||
use crate::{Db, ReferenceKind, ReferenceTarget};
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::find_node::{CoveringNode, covering_node};
|
||||
use ruff_python_ast::token::Tokens;
|
||||
@@ -23,6 +22,7 @@ use ruff_python_ast::{
|
||||
};
|
||||
use ruff_text_size::Ranged;
|
||||
use ty_project::parallel::{ParallelIteratorExt, minimum_parallel_job_len};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::{Definition, DefinitionKind, DefinitionState};
|
||||
use ty_python_core::scope::{FileScopeId, NodeWithScopeKind, ScopeKind};
|
||||
use ty_python_semantic::{
|
||||
@@ -87,7 +87,7 @@ impl ReferencesMode {
|
||||
/// Search for references across all files in the project.
|
||||
pub(crate) fn references(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
goto_target: &GotoTarget,
|
||||
mode: ReferencesMode,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
@@ -118,8 +118,8 @@ pub(crate) fn references(
|
||||
let is_parameter = parameter_owner_is_externally_visible(db, &target_definitions);
|
||||
|
||||
if search_across_files && (is_parameter || is_externally_visible_symbol) {
|
||||
let program = model.program();
|
||||
let files = db.project().files(db);
|
||||
let python_version = file.python_version(db);
|
||||
let files: Vec<_> = files
|
||||
.iter()
|
||||
.copied()
|
||||
@@ -135,7 +135,7 @@ pub(crate) fn references(
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let other_file = PythonFile::new(db, other_file, python_version);
|
||||
let other_file = ProgramFile::new(db, other_file, program);
|
||||
|
||||
if is_externally_visible_symbol {
|
||||
references_for_file(db, other_file, &target_definitions, &target_text, mode)
|
||||
@@ -164,7 +164,7 @@ pub(crate) fn references(
|
||||
|
||||
fn references_for_keyword_arguments_in_file(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
target_definitions: &Definitions<'_>,
|
||||
target_text: &str,
|
||||
mode: ReferencesMode,
|
||||
@@ -176,7 +176,7 @@ fn references_for_keyword_arguments_in_file(
|
||||
"keyword-label cross-file scan should not run in DocumentHighlights mode"
|
||||
);
|
||||
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let mut references = Vec::new();
|
||||
@@ -225,12 +225,12 @@ fn is_slots_assignment(node: AnyNodeRef<'_>, value: AnyNodeRef<'_>) -> bool {
|
||||
/// The behavior depends on the provided mode.
|
||||
fn references_for_file(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
target_definitions: &Definitions<'_>,
|
||||
target_text: &str,
|
||||
mode: ReferencesMode,
|
||||
) -> Vec<ReferenceTarget> {
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let mut references = Vec::new();
|
||||
@@ -261,7 +261,7 @@ pub(crate) fn has_any_external_visible_definitions(
|
||||
ScopeKind::Comprehension => {
|
||||
matches!(definition.kind(db), DefinitionKind::NamedExpression(_))
|
||||
&& definition.place(db).as_symbol().is_some_and(|symbol_id| {
|
||||
ty_python_core::semantic_index(db, definition.python_file(db))
|
||||
ty_python_core::semantic_index(db, definition.program_file(db))
|
||||
.symbol_resolves_to_global_scope(symbol_id, definition.file_scope(db))
|
||||
})
|
||||
}
|
||||
@@ -707,7 +707,7 @@ impl<'a> LocalReferencesFinder<'a> {
|
||||
let file = self.model.file();
|
||||
let class_range = class.range();
|
||||
let module = ruff_db::parsed::parsed_module(db, self.model.python_file()).load(db);
|
||||
let index = ty_python_core::semantic_index(db, self.model.python_file());
|
||||
let index = ty_python_core::semantic_index(db, self.model.program_file());
|
||||
|
||||
// The nearest class scope lexically enclosing `scope`, if any. `ancestor_scopes` skips
|
||||
// class scopes for name resolution, so we walk the lexical parents directly to stop at the
|
||||
@@ -808,7 +808,7 @@ mod tests {
|
||||
use crate::tests::{CursorTest, cursor_test};
|
||||
|
||||
fn cursor_target_is_externally_visible(test: &CursorTest) -> bool {
|
||||
let model = SemanticModel::new(&test.db, test.python_file(test.cursor.file));
|
||||
let model = SemanticModel::new(&test.db, test.program_file(test.cursor.file));
|
||||
let goto_target =
|
||||
find_goto_target(&model, &test.cursor.parsed, test.cursor.offset).unwrap();
|
||||
let definitions = goto_target
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::goto::find_goto_target;
|
||||
use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
||||
/// Returns the range of the symbol if it can be renamed, None if not.
|
||||
pub fn can_rename(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<ruff_text_size::TextRange> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let source_file = file.file(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
@@ -56,11 +56,11 @@ pub fn can_rename(
|
||||
/// Returns all locations that need to be updated with the new name.
|
||||
pub fn rename(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
new_name: &str,
|
||||
) -> Option<Vec<ReferenceTarget>> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
@@ -108,7 +108,7 @@ mod tests {
|
||||
let Some(range) = salsa::attach(&self.db, || {
|
||||
can_rename(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}) else {
|
||||
@@ -122,13 +122,13 @@ mod tests {
|
||||
let rename_results = salsa::attach(&self.db, || {
|
||||
can_rename(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)?;
|
||||
|
||||
rename(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
new_name,
|
||||
)
|
||||
|
||||
@@ -437,7 +437,7 @@ b"123a𝐁<CURSOR>c"
|
||||
fn selection_range(&self) -> String {
|
||||
let ranges = selection_range(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file).python_file(&self.db),
|
||||
self.cursor.offset,
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
use crate::Db;
|
||||
use bitflags::bitflags;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{
|
||||
SourceOrderVisitor, TraversalSignal, walk_arguments, walk_expr,
|
||||
@@ -40,6 +39,7 @@ use ruff_python_ast::{
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use std::ops::Deref;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::{Definition, DefinitionKind, ParameterDefinitionNodeKind};
|
||||
use ty_python_semantic::{
|
||||
HasType, ImportAliasResolution, ResolvedDefinition, SemanticModel, definitions_for_attribute,
|
||||
@@ -185,10 +185,10 @@ impl Deref for SemanticTokens {
|
||||
/// Pass None to get tokens for the entire file.
|
||||
pub fn semantic_tokens(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
range: Option<TextRange>,
|
||||
) -> SemanticTokens {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
let mut visitor = SemanticTokenVisitor::new(&model, range);
|
||||
@@ -303,7 +303,7 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
) -> Option<(SemanticTokenType, SemanticTokenModifier)> {
|
||||
let mut modifiers = SemanticTokenModifier::empty();
|
||||
let db = self.model.db();
|
||||
let model = SemanticModel::new(db, definition.python_file(db));
|
||||
let model = SemanticModel::new(db, definition.program_file(db));
|
||||
|
||||
if model.is_type_alias_definition(definition) {
|
||||
return Some((SemanticTokenType::Class, modifiers));
|
||||
@@ -4723,7 +4723,11 @@ from pathlib import Missing as Alias
|
||||
fn highlight_file(&self) -> SemanticTokens {
|
||||
semantic_tokens(
|
||||
&self.db,
|
||||
PythonFile::new(&self.db, self.file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
self.file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
@@ -4732,7 +4736,11 @@ from pathlib import Missing as Alias
|
||||
fn highlight_range(&self, range: TextRange) -> SemanticTokens {
|
||||
semantic_tokens(
|
||||
&self.db,
|
||||
PythonFile::new(&self.db, self.file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
self.file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
Some(range),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ use crate::Db;
|
||||
use crate::FxIndexMap;
|
||||
use crate::docstring::Docstring;
|
||||
use crate::goto::docstring_for_call_definition;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::find_node::covering_node;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
@@ -76,10 +76,10 @@ pub struct SignatureHelpInfo<'db> {
|
||||
/// Signature help information for function calls at the given position
|
||||
pub fn signature_help<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
offset: TextSize,
|
||||
) -> Option<SignatureHelpInfo<'db>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
|
||||
// Get the call expression at the given position.
|
||||
let (call_expr, current_arg_index) = get_call_expr(&parsed, offset)?;
|
||||
@@ -1464,7 +1464,7 @@ def ab(a: int, *, c: int):
|
||||
fn signature_help(&self) -> Option<SignatureHelpInfo<'_>> {
|
||||
crate::signature_help::signature_help(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ use regex::Regex;
|
||||
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_index::{IndexVec, newtype_index};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::{Name, UnqualifiedName};
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, resolve_module};
|
||||
use ty_project::Db;
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
use crate::completion::CompletionKind;
|
||||
|
||||
@@ -392,8 +392,8 @@ impl SymbolKind {
|
||||
/// The flattened list includes parent/child information and can be
|
||||
/// converted into a hierarchical collection of symbols.
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn symbols_for_file(db: &dyn Db, file: PythonFile<'_>) -> FlatSymbols {
|
||||
let parsed = parsed_module(db, file);
|
||||
pub(crate) fn symbols_for_file(db: &dyn Db, file: ProgramFile<'_>) -> FlatSymbols {
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
|
||||
let mut visitor = SymbolVisitor::tree(db, file);
|
||||
@@ -411,9 +411,9 @@ pub(crate) fn symbols_for_file(db: &dyn Db, file: PythonFile<'_>) -> FlatSymbols
|
||||
cycle_initial=|_, _, _| FlatSymbols::default(),
|
||||
heap_size=ruff_memory_usage::heap_size,
|
||||
)]
|
||||
pub(crate) fn symbols_for_file_global_only(db: &dyn Db, file: PythonFile<'_>) -> FlatSymbols {
|
||||
pub(crate) fn symbols_for_file_global_only(db: &dyn Db, file: ProgramFile<'_>) -> FlatSymbols {
|
||||
let source_file = file.file(db);
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
|
||||
let mut visitor = SymbolVisitor::globals(db, file);
|
||||
@@ -455,7 +455,7 @@ impl ImportedFrom {
|
||||
|
||||
fn import_from(
|
||||
db: &dyn Db,
|
||||
importing_file: PythonFile<'_>,
|
||||
importing_file: ImportingFile<'_>,
|
||||
ast: &ast::StmtImportFrom,
|
||||
kind: ImportKind,
|
||||
) -> Option<ImportedFrom> {
|
||||
@@ -596,16 +596,22 @@ impl<'db> Imports<'db> {
|
||||
fn get_module_symbols(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
name: &ModuleName,
|
||||
) -> Option<&'db FlatSymbols> {
|
||||
let module_name = match self.module_names.get(name.as_str())? {
|
||||
let module_kind = self.module_names.get(name.as_str())?;
|
||||
let importing_file =
|
||||
ImportingFile::File(program_file.file(db), program_file.resolver_environment(db));
|
||||
let module_name = match module_kind {
|
||||
ImportModuleKind::Definitive(name) | ImportModuleKind::Possible(name) => {
|
||||
name.to_module_name(db, importing_file)?
|
||||
}
|
||||
};
|
||||
let module = resolve_module(db, importing_file, &module_name)?;
|
||||
Some(symbols_for_file_global_only(db, module.python_file(db)?))
|
||||
Some(symbols_for_file_global_only(
|
||||
db,
|
||||
ProgramFile::new(db, module.file(db)?, program_file.program(db)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +663,7 @@ impl<'db> ImportModuleName<'db> {
|
||||
fn to_module_name(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
) -> Option<ModuleName> {
|
||||
match self {
|
||||
ImportModuleName::Import(name) => ModuleName::new(name),
|
||||
@@ -694,7 +700,7 @@ impl Ranged for AstImport<'_> {
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
struct SymbolVisitor<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
symbols: IndexVec<SymbolId, SymbolTree>,
|
||||
symbol_stack: Vec<SymbolId>,
|
||||
/// Track if we're currently inside a function at any point.
|
||||
@@ -732,7 +738,7 @@ struct SymbolVisitor<'db> {
|
||||
}
|
||||
|
||||
impl<'db> SymbolVisitor<'db> {
|
||||
fn tree(db: &'db dyn Db, file: PythonFile<'db>) -> Self {
|
||||
fn tree(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
@@ -750,7 +756,7 @@ impl<'db> SymbolVisitor<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn globals(db: &'db dyn Db, file: PythonFile<'db>) -> Self {
|
||||
fn globals(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
exports_only: true,
|
||||
..Self::tree(db, file)
|
||||
@@ -906,9 +912,15 @@ impl<'db> SymbolVisitor<'db> {
|
||||
let full_range = import.range();
|
||||
let Some(imported_from) = (match import {
|
||||
AstImport::Import(_) => ImportedFrom::import(alias, import_kind),
|
||||
AstImport::ImportFrom(ast) => {
|
||||
ImportedFrom::import_from(self.db, self.file, ast, import_kind)
|
||||
}
|
||||
AstImport::ImportFrom(ast) => ImportedFrom::import_from(
|
||||
self.db,
|
||||
ImportingFile::File(
|
||||
self.file.file(self.db),
|
||||
self.file.resolver_environment(self.db),
|
||||
),
|
||||
ast,
|
||||
import_kind,
|
||||
),
|
||||
}) else {
|
||||
tracing::debug!(
|
||||
"Dropping imported symbol {name} since its module name could not be discovered",
|
||||
@@ -1073,6 +1085,10 @@ impl<'db> SymbolVisitor<'db> {
|
||||
.iter()
|
||||
.find(|alias| &alias.name == "*")
|
||||
.map(Ranged::range);
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file.file(self.db),
|
||||
self.file.resolver_environment(self.db),
|
||||
);
|
||||
self.symbols
|
||||
.extend(symbols.symbols.iter().filter_map(|symbol| {
|
||||
// If there's no `__all__`, then names with an underscore
|
||||
@@ -1088,7 +1104,7 @@ impl<'db> SymbolVisitor<'db> {
|
||||
}
|
||||
let Some(imported_from) = ImportedFrom::import_from(
|
||||
self.db,
|
||||
self.file,
|
||||
importing_file,
|
||||
import_from,
|
||||
ImportKind::Wildcard,
|
||||
) else {
|
||||
@@ -1133,12 +1149,16 @@ impl<'db> SymbolVisitor<'db> {
|
||||
&self,
|
||||
import_from: &ast::StmtImportFrom,
|
||||
) -> Option<&'db FlatSymbols> {
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file.file(self.db),
|
||||
self.file.resolver_environment(self.db),
|
||||
);
|
||||
let module_name =
|
||||
ModuleName::from_import_statement(self.db, self.file, import_from).ok()?;
|
||||
let module = resolve_module(self.db, self.file, &module_name)?;
|
||||
ModuleName::from_import_statement(self.db, importing_file, import_from).ok()?;
|
||||
let module = resolve_module(self.db, importing_file, &module_name)?;
|
||||
Some(symbols_for_file_global_only(
|
||||
self.db,
|
||||
module.python_file(self.db)?,
|
||||
ProgramFile::new(self.db, module.file(self.db)?, self.file.program(self.db)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1599,12 +1619,12 @@ mod tests {
|
||||
use insta::internals::SettingsBindDropGuard;
|
||||
|
||||
use ruff_db::Db;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{FileRootKind, system_path_to_file};
|
||||
use ruff_db::system::{DbWithWritableSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ty_project::{ProjectMetadata, TestDb};
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
use super::symbols_for_file_global_only;
|
||||
|
||||
@@ -3165,7 +3185,11 @@ class C: ...
|
||||
let file = system_path_to_file(&self.db, path.as_ref()).unwrap();
|
||||
symbols_for_file_global_only(
|
||||
&self.db,
|
||||
PythonFile::new(&self.db, file, self.db.python_version()),
|
||||
ProgramFile::new(
|
||||
&self.db,
|
||||
file,
|
||||
self.db.program_environment().program(&self.db),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::Db;
|
||||
use crate::goto::find_goto_target;
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ty_project::parallel::ParallelIteratorExt;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_semantic::TypeHierarchyClass;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::{ProgramEnvironment, SemanticModel};
|
||||
@@ -31,10 +31,10 @@ pub struct TypeHierarchyItem {
|
||||
/// Returns `None` if the position is not on a class definition or class reference.
|
||||
pub fn prepare_type_hierarchy(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Option<TypeHierarchyItem> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
let ty = goto_target.inferred_type(&model)?;
|
||||
@@ -47,7 +47,7 @@ pub fn prepare_type_hierarchy(
|
||||
/// Get the supertypes (base classes) of a type hierarchy item.
|
||||
pub fn type_hierarchy_supertypes(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Vec<TypeHierarchyItem> {
|
||||
let Some(ty) = resolve_type_at(db, file, offset) else {
|
||||
@@ -65,14 +65,14 @@ pub fn type_hierarchy_supertypes(
|
||||
/// This scans all available modules and can be expensive in large projects.
|
||||
pub fn type_hierarchy_subtypes(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
offset: TextSize,
|
||||
) -> Vec<TypeHierarchyItem> {
|
||||
let Some(ty) = resolve_type_at(db, file, offset) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
ty_module_resolver::all_modules(db, file.python_version(db))
|
||||
ty_module_resolver::all_modules(db, file.resolver_environment(db))
|
||||
.into_par_iter()
|
||||
.map_with_db(db, |db, module| {
|
||||
let env = ProgramEnvironment::from_file(file);
|
||||
@@ -91,10 +91,10 @@ pub fn type_hierarchy_subtypes(
|
||||
/// not be inferred, `None` is returned.
|
||||
fn resolve_type_at<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
offset: TextSize,
|
||||
) -> Option<Type<'db>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
let goto_target = find_goto_target(&model, &module, offset)?;
|
||||
@@ -733,7 +733,7 @@ Public = _Internal
|
||||
fn prepare(&self) -> Option<TypeHierarchyItem> {
|
||||
prepare_type_hierarchy(
|
||||
&self.db,
|
||||
self.python_file(self.cursor.file),
|
||||
self.program_file(self.cursor.file),
|
||||
self.cursor.offset,
|
||||
)
|
||||
}
|
||||
@@ -744,7 +744,7 @@ Public = _Internal
|
||||
};
|
||||
type_hierarchy_supertypes(
|
||||
&self.db,
|
||||
self.python_file(item.file),
|
||||
self.program_file(item.file),
|
||||
item.selection_range.start(),
|
||||
)
|
||||
}
|
||||
@@ -755,7 +755,7 @@ Public = _Internal
|
||||
};
|
||||
type_hierarchy_subtypes(
|
||||
&self.db,
|
||||
self.python_file(item.file),
|
||||
self.program_file(item.file),
|
||||
item.selection_range.start(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file};
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ty_project::{Db, parallel::ParallelIteratorExt};
|
||||
|
||||
@@ -31,7 +30,7 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
|
||||
);
|
||||
let _entered = symbols_for_file_span.entered();
|
||||
|
||||
symbols_for_file(db, PythonFile::new(db, file, db.python_version()))
|
||||
symbols_for_file(db, db.program_file(file))
|
||||
.search(&query)
|
||||
.map(|(_, symbol)| WorkspaceSymbolInfo {
|
||||
symbol: symbol.to_owned(),
|
||||
|
||||
@@ -20,6 +20,7 @@ anyhow = { workspace = true }
|
||||
camino = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
get-size2 = { workspace = true }
|
||||
ordermap = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
regex-syntax = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
use ruff_db::Db as SourceDb;
|
||||
|
||||
use crate::resolve::SearchPaths;
|
||||
|
||||
#[salsa::db]
|
||||
pub trait Db: SourceDb {
|
||||
/// Returns the search paths for module resolution.
|
||||
fn search_paths(&self) -> &SearchPaths;
|
||||
}
|
||||
pub trait Db: SourceDb {}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
@@ -19,7 +14,7 @@ pub(crate) mod tests {
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::Db;
|
||||
use crate::resolve::SearchPaths;
|
||||
use crate::{ResolverEnvironment, resolve::SearchPaths};
|
||||
|
||||
type Events = Arc<Mutex<Vec<salsa::Event>>>;
|
||||
|
||||
@@ -66,15 +61,19 @@ pub(crate) mod tests {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn python_version(&self) -> PythonVersion {
|
||||
self.python_version
|
||||
}
|
||||
|
||||
pub(crate) fn set_search_paths(&mut self, search_paths: SearchPaths) {
|
||||
search_paths.try_register_static_roots(self);
|
||||
self.search_paths = Arc::new(search_paths);
|
||||
}
|
||||
|
||||
pub(crate) fn search_paths(&self) -> &SearchPaths {
|
||||
&self.search_paths
|
||||
}
|
||||
|
||||
pub(crate) fn resolver_environment(&self) -> ResolverEnvironment<'_> {
|
||||
ResolverEnvironment::new(self, self.python_version, self.search_paths.as_ref())
|
||||
}
|
||||
|
||||
/// Takes the salsa events.
|
||||
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
|
||||
let mut events = self.events.lock().unwrap();
|
||||
@@ -113,11 +112,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for TestDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
&self.search_paths
|
||||
}
|
||||
}
|
||||
impl Db for TestDb {}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::{Db, ModuleResolveMode, SearchPaths, search_paths};
|
||||
|
||||
/// The Python version and search paths used to resolve modules.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
pub struct ResolverEnvironment<'db> {
|
||||
#[returns(copy)]
|
||||
pub python_version: PythonVersion,
|
||||
|
||||
#[returns(ref)]
|
||||
pub search_paths: SearchPaths,
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for ResolverEnvironment<'_> {}
|
||||
|
||||
impl<'db> ResolverEnvironment<'db> {
|
||||
pub fn display_search_paths(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
mode: ModuleResolveMode,
|
||||
) -> DisplaySearchPaths<'db> {
|
||||
DisplaySearchPaths {
|
||||
db,
|
||||
resolver_environment: self,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplaySearchPaths<'db> {
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
mode: ModuleResolveMode,
|
||||
}
|
||||
|
||||
impl fmt::Display for DisplaySearchPaths<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut paths = search_paths(self.db, self.resolver_environment, self.mode).peekable();
|
||||
|
||||
if paths.peek().is_none() {
|
||||
return f.write_str("[]");
|
||||
}
|
||||
|
||||
writeln!(f, "[")?;
|
||||
for path in paths {
|
||||
writeln!(f, " {path},")?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
|
||||
/// A file interpreted within a particular module-resolution environment.
|
||||
///
|
||||
/// The same file can resolve imports differently depending on the Python version and search paths
|
||||
/// used to interpret it.
|
||||
///
|
||||
/// For example, consider a file containing:
|
||||
///
|
||||
/// ```python
|
||||
/// from zipfile._path import Path
|
||||
/// ```
|
||||
///
|
||||
/// Typeshed makes `zipfile._path` available only on Python 3.12 and newer:
|
||||
///
|
||||
/// ```text
|
||||
/// resolve_module(ResolverFile(shared.py, Python 3.11), "zipfile._path")
|
||||
/// -> unresolved
|
||||
///
|
||||
/// resolve_module(ResolverFile(shared.py, Python 3.12), "zipfile._path")
|
||||
/// -> zipfile/_path/__init__.pyi
|
||||
/// ```
|
||||
///
|
||||
/// Search paths can also change which file an import resolves to, even when the Python version is
|
||||
/// identical:
|
||||
///
|
||||
/// ```text
|
||||
/// resolve_module(ResolverFile(shared.py, project environment), "dependency")
|
||||
/// -> .venv/lib/dependency.py
|
||||
///
|
||||
/// resolve_module(ResolverFile(shared.py, script environment), "dependency")
|
||||
/// -> .script-venv/lib/dependency.py
|
||||
/// ```
|
||||
///
|
||||
/// Including the resolver environment in the file's identity keeps these resolution results
|
||||
/// separate. Projects and scripts with equivalent resolver environments can still share resolution
|
||||
/// results.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
pub struct ResolverFile<'db> {
|
||||
#[returns(copy)]
|
||||
pub file: File,
|
||||
|
||||
#[returns(copy)]
|
||||
pub environment: ResolverEnvironment<'db>,
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for ResolverFile<'_> {}
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_db::system::SystemPath;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
pub use db::Db;
|
||||
pub use environment::{ResolverEnvironment, ResolverFile};
|
||||
pub use module::KnownModule;
|
||||
pub use module::Module;
|
||||
pub use module_name::{ModuleName, ModuleNameResolutionError};
|
||||
pub use module_name::{ImportingFile, ModuleName, ModuleNameResolutionError};
|
||||
pub use path::{SearchPath, SearchPathError};
|
||||
pub use resolve::{
|
||||
SearchPaths, file_to_module, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
@@ -20,6 +23,7 @@ pub use module_glob::{ModuleGlobError, ModuleGlobSet, ModuleGlobSetBuilder, Modu
|
||||
pub use resolve::{ModuleResolveMode, SearchPathIterator, search_paths};
|
||||
|
||||
mod db;
|
||||
mod environment;
|
||||
mod list;
|
||||
mod module;
|
||||
mod module_glob;
|
||||
@@ -30,15 +34,20 @@ mod settings;
|
||||
mod strategy;
|
||||
mod typeshed;
|
||||
|
||||
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
/// Returns an iterator over all search paths pointing to a system path
|
||||
pub fn system_module_search_paths(db: &dyn Db) -> SystemModuleSearchPathsIter<'_> {
|
||||
pub fn system_module_search_paths<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
) -> SystemModuleSearchPathsIter<'db> {
|
||||
SystemModuleSearchPathsIter {
|
||||
// Always run in `Typing` mode because we want to include as much as possible
|
||||
// and we don't care about the "real" stdlib
|
||||
inner: search_paths(db, ModuleResolveMode::Typing),
|
||||
inner: search_paths(db, resolver_environment, ModuleResolveMode::Typing),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::btree_map::{BTreeMap, Entry};
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::directory_listing;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::ResolverEnvironment;
|
||||
use crate::db::Db;
|
||||
use crate::module::{Module, ModuleKind};
|
||||
use crate::module_name::ModuleName;
|
||||
@@ -12,8 +11,11 @@ use crate::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
|
||||
use crate::resolve::{ModuleResolveMode, ResolverContext, resolve_file_module, search_paths};
|
||||
|
||||
/// List all available modules, including all sub-modules, sorted in lexicographic order.
|
||||
pub fn all_modules(db: &dyn Db, python_version: PythonVersion) -> Vec<Module<'_>> {
|
||||
let mut modules = list_modules(db, python_version).to_vec();
|
||||
pub fn all_modules<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
) -> Vec<Module<'db>> {
|
||||
let mut modules = list_modules(db, resolver_environment).to_vec();
|
||||
let mut stack = modules.clone();
|
||||
while let Some(module) = stack.pop() {
|
||||
for &submodule in module.all_submodules(db) {
|
||||
@@ -26,27 +28,23 @@ pub fn all_modules(db: &dyn Db, python_version: PythonVersion) -> Vec<Module<'_>
|
||||
}
|
||||
|
||||
/// List all available top-level modules.
|
||||
pub fn list_modules(db: &dyn Db, python_version: PythonVersion) -> &[Module<'_>] {
|
||||
list_modules_impl(db, PythonVersionIngredient::new(db, python_version))
|
||||
}
|
||||
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
struct PythonVersionIngredient<'db> {
|
||||
#[returns(copy)]
|
||||
python_version: PythonVersion,
|
||||
pub fn list_modules<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
) -> &'db [Module<'db>] {
|
||||
list_modules_impl(db, resolver_environment)
|
||||
}
|
||||
|
||||
#[salsa::tracked(returns(deref))]
|
||||
fn list_modules_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
version: PythonVersionIngredient<'db>,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
) -> Box<[Module<'db>]> {
|
||||
let python_version = version.python_version(db);
|
||||
let mut modules: BTreeMap<&ModuleName, ListedModule<'_>> = BTreeMap::new();
|
||||
for search_path in search_paths(db, ModuleResolveMode::Typing) {
|
||||
for search_path in search_paths(db, resolver_environment, ModuleResolveMode::Typing) {
|
||||
for &new in list_modules_in(
|
||||
db,
|
||||
SearchPathIngredient::new(db, search_path.clone(), python_version),
|
||||
SearchPathIngredient::new(db, resolver_environment, search_path.clone()),
|
||||
) {
|
||||
match modules.entry(new.module(db).name(db)) {
|
||||
Entry::Vacant(entry) => {
|
||||
@@ -83,10 +81,10 @@ fn list_modules_impl<'db>(
|
||||
|
||||
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
struct SearchPathIngredient<'db> {
|
||||
#[returns(copy)]
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
#[returns(ref)]
|
||||
path: SearchPath,
|
||||
#[returns(copy)]
|
||||
python_version: PythonVersion,
|
||||
}
|
||||
|
||||
/// List all available top-level modules in the given `SearchPath`.
|
||||
@@ -97,7 +95,7 @@ fn list_modules_in<'db>(
|
||||
) -> Vec<ListedModule<'db>> {
|
||||
let path = search_path.path(db);
|
||||
tracing::debug!("Listing modules in search path '{}'", path);
|
||||
let mut lister = Lister::new(db, path, search_path.python_version(db));
|
||||
let mut lister = Lister::new(db, search_path.resolver_environment(db), path);
|
||||
match path.as_path() {
|
||||
SystemOrVendoredPathRef::System(system_search_path) => {
|
||||
let Ok(listing) = directory_listing(db, system_search_path) else {
|
||||
@@ -138,7 +136,7 @@ impl get_size2::GetSize for ListedModule<'_> {}
|
||||
struct Lister<'db> {
|
||||
db: &'db dyn Db,
|
||||
search_path: &'db SearchPath,
|
||||
python_version: PythonVersion,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
modules: BTreeMap<&'db ModuleName, ListedModule<'db>>,
|
||||
}
|
||||
|
||||
@@ -147,13 +145,13 @@ impl<'db> Lister<'db> {
|
||||
/// of file paths.
|
||||
fn new(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
search_path: &'db SearchPath,
|
||||
python_version: PythonVersion,
|
||||
) -> Lister<'db> {
|
||||
Lister {
|
||||
db,
|
||||
search_path,
|
||||
python_version,
|
||||
resolver_environment,
|
||||
modules: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -205,10 +203,11 @@ impl<'db> Lister<'db> {
|
||||
&module_path,
|
||||
Module::file_module(
|
||||
self.db,
|
||||
file,
|
||||
self.resolver_environment,
|
||||
Cow::Owned(module_name),
|
||||
ModuleKind::Package,
|
||||
self.search_path.clone(),
|
||||
PythonFile::new(self.db, file, self.python_version),
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -251,8 +250,8 @@ impl<'db> Lister<'db> {
|
||||
&module_path,
|
||||
Module::namespace_package(
|
||||
self.db,
|
||||
self.resolver_environment,
|
||||
Cow::Owned(module_name),
|
||||
self.python_version,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -281,10 +280,11 @@ impl<'db> Lister<'db> {
|
||||
&module_path,
|
||||
Module::file_module(
|
||||
self.db,
|
||||
file,
|
||||
self.resolver_environment,
|
||||
Cow::Owned(module_name),
|
||||
ModuleKind::Module,
|
||||
self.search_path.clone(),
|
||||
PythonFile::new(self.db, file, self.python_version),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -347,14 +347,17 @@ impl<'db> Lister<'db> {
|
||||
|
||||
/// Returns true if the given module name cannot be shadowable.
|
||||
fn is_non_shadowable(&self, name: &ModuleName) -> bool {
|
||||
ModuleResolveMode::Typing.is_non_shadowable(self.python_version.minor, name.as_str())
|
||||
ModuleResolveMode::Typing.is_non_shadowable(
|
||||
self.resolver_environment.python_version(self.db).minor,
|
||||
name.as_str(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs a resolver context for use with some APIs that require it.
|
||||
fn context(&self) -> ResolverContext<'db> {
|
||||
ResolverContext {
|
||||
db: self.db,
|
||||
python_version: self.python_version,
|
||||
resolver_environment: self.resolver_environment,
|
||||
// We don't currently support listing modules
|
||||
// in a "no stubs allowed" mode.
|
||||
mode: ModuleResolveMode::Typing,
|
||||
@@ -432,7 +435,7 @@ mod tests {
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
fn list_modules(db: &TestDb) -> &[Module<'_>] {
|
||||
super::list_modules(db, db.python_version())
|
||||
super::list_modules(db, db.resolver_environment())
|
||||
}
|
||||
|
||||
struct ModuleDebugSnapshot<'db> {
|
||||
@@ -449,14 +452,11 @@ mod tests {
|
||||
Module::File(module) => {
|
||||
// For snapshots, just normalize all paths to using
|
||||
// Unix slashes for simplicity.
|
||||
let path_components =
|
||||
match module.python_file(self.db).file(self.db).path(self.db) {
|
||||
FilePath::System(path) => path.components(),
|
||||
FilePath::Vendored(path) => path.components(),
|
||||
FilePath::SystemVirtual(path) => {
|
||||
Utf8Path::new(path.as_str()).components()
|
||||
}
|
||||
};
|
||||
let path_components = match module.file(self.db).path(self.db) {
|
||||
FilePath::System(path) => path.components(),
|
||||
FilePath::Vendored(path) => path.components(),
|
||||
FilePath::SystemVirtual(path) => Utf8Path::new(path.as_str()).components(),
|
||||
};
|
||||
let nice_path = path_components
|
||||
// Avoid including a root component, since that
|
||||
// results in a platform dependent separator.
|
||||
@@ -1465,7 +1465,11 @@ not_a_directory
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
dynamic_resolution_paths,
|
||||
ModuleResolveModeIngredient::new(&db, ModuleResolveMode::Typing),
|
||||
ModuleResolveModeIngredient::new(
|
||||
&db,
|
||||
db.resolver_environment(),
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
&events,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{File, directory_listing, system_path_to_file, vendored_path_to_file};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ruff_db::vendored::VendoredPath;
|
||||
@@ -10,9 +9,9 @@ use ruff_python_ast::PythonVersion;
|
||||
use salsa::Database;
|
||||
use salsa::plumbing::AsId;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::path::{SearchPath, SystemOrVendoredPathRef};
|
||||
use crate::{Db, ResolverEnvironment};
|
||||
|
||||
/// Representation of a Python module.
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq, salsa::Supertype, salsa::SalsaValue)]
|
||||
@@ -28,22 +27,39 @@ impl get_size2::GetSize for Module<'_> {}
|
||||
impl<'db> Module<'db> {
|
||||
pub(crate) fn file_module(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: Cow<'_, ModuleName>,
|
||||
kind: ModuleKind,
|
||||
search_path: SearchPath,
|
||||
file: PythonFile<'db>,
|
||||
) -> Self {
|
||||
let known = KnownModule::try_from_search_path_and_name(&search_path, &name);
|
||||
|
||||
Self::File(FileModule::new(db, name, kind, search_path, file, known))
|
||||
Self::File(FileModule::new(
|
||||
db,
|
||||
name,
|
||||
kind,
|
||||
search_path,
|
||||
file,
|
||||
resolver_environment,
|
||||
known,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn namespace_package(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: Cow<'_, ModuleName>,
|
||||
python_version: PythonVersion,
|
||||
) -> Self {
|
||||
Self::Namespace(NamespacePackage::new(db, name, python_version))
|
||||
Self::Namespace(NamespacePackage::new(db, resolver_environment, name))
|
||||
}
|
||||
|
||||
/// The resolver environment used to resolve this module.
|
||||
pub fn resolver_environment(self, db: &'db dyn Database) -> ResolverEnvironment<'db> {
|
||||
match self {
|
||||
Module::File(module) => module.resolver_environment(db),
|
||||
Module::Namespace(module) => module.resolver_environment(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// The absolute name of the module (e.g. `foo.bar`)
|
||||
@@ -59,27 +75,14 @@ impl<'db> Module<'db> {
|
||||
/// This is `None` for namespace packages.
|
||||
pub fn file(self, db: &'db dyn Database) -> Option<File> {
|
||||
match self {
|
||||
Module::File(module) => Some(module.python_file(db).file(db)),
|
||||
Module::Namespace(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The versioned file used to parse this module.
|
||||
///
|
||||
/// This is `None` for namespace packages.
|
||||
pub fn python_file(self, db: &'db dyn Database) -> Option<PythonFile<'db>> {
|
||||
match self {
|
||||
Module::File(module) => Some(module.python_file(db)),
|
||||
Module::File(module) => Some(module.file(db)),
|
||||
Module::Namespace(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The Python version used to resolve this module.
|
||||
pub fn python_version(self, db: &'db dyn Database) -> PythonVersion {
|
||||
match self {
|
||||
Module::File(module) => module.python_file(db).python_version(db),
|
||||
Module::Namespace(module) => module.python_version(db),
|
||||
}
|
||||
self.resolver_environment(db).python_version(db)
|
||||
}
|
||||
|
||||
/// Is this a module that we special-case somehow? If so, which one?
|
||||
@@ -187,14 +190,14 @@ fn all_submodule_names_for_package<'db>(
|
||||
return None;
|
||||
}
|
||||
|
||||
let python_file = module.python_file(db);
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, python_file.file(db))?;
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, module.file(db))?;
|
||||
debug_assert!(
|
||||
matches!(path.file_name(), Some("__init__.py" | "__init__.pyi")),
|
||||
"expected package file `{:?}` to be `__init__.py` or `__init__.pyi`",
|
||||
path.file_name(),
|
||||
);
|
||||
|
||||
let resolver_environment = module.resolver_environment(db);
|
||||
Some(match path.parent()? {
|
||||
SystemOrVendoredPathRef::System(parent_directory) => {
|
||||
directory_listing(db, parent_directory)
|
||||
@@ -230,10 +233,11 @@ fn all_submodule_names_for_package<'db>(
|
||||
};
|
||||
Some(Module::file_module(
|
||||
db,
|
||||
file,
|
||||
resolver_environment,
|
||||
Cow::Owned(name),
|
||||
kind,
|
||||
module.search_path(db).clone(),
|
||||
PythonFile::new(db, file, python_file.python_version(db)),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
@@ -267,10 +271,11 @@ fn all_submodule_names_for_package<'db>(
|
||||
};
|
||||
Some(Module::file_module(
|
||||
db,
|
||||
file,
|
||||
resolver_environment,
|
||||
Cow::Owned(name),
|
||||
kind,
|
||||
module.search_path(db).clone(),
|
||||
PythonFile::new(db, file, python_file.python_version(db)),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
@@ -287,7 +292,9 @@ pub struct FileModule<'db> {
|
||||
#[returns(ref)]
|
||||
pub(super) search_path: SearchPath,
|
||||
#[returns(copy)]
|
||||
pub(super) python_file: PythonFile<'db>,
|
||||
pub(super) file: File,
|
||||
#[returns(copy)]
|
||||
pub(super) resolver_environment: ResolverEnvironment<'db>,
|
||||
#[returns(copy)]
|
||||
pub(super) known: Option<KnownModule>,
|
||||
}
|
||||
@@ -298,10 +305,10 @@ pub struct FileModule<'db> {
|
||||
/// multiple possible paths and they have no corresponding code file.
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct NamespacePackage<'db> {
|
||||
#[returns(copy)]
|
||||
pub(super) resolver_environment: ResolverEnvironment<'db>,
|
||||
#[returns(ref)]
|
||||
pub(super) name: ModuleName,
|
||||
#[returns(copy)]
|
||||
pub(super) python_version: PythonVersion,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::ops::Deref;
|
||||
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, PythonVersion};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::resolve::file_to_module;
|
||||
use crate::{ResolverEnvironment, ResolverFile};
|
||||
|
||||
/// A module name, e.g. `foo.bar`.
|
||||
///
|
||||
@@ -305,13 +306,12 @@ impl ModuleName {
|
||||
/// Extracts a module name from the AST of a `from <module> import ...`
|
||||
/// statement.
|
||||
///
|
||||
/// `importing_file` must be the [`PythonFile`] that contains the import
|
||||
/// statement.
|
||||
/// `importing_file` must be the file that contains the import statement.
|
||||
///
|
||||
/// This handles relative import statements.
|
||||
pub fn from_import_statement<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
node: &'db ast::StmtImportFrom,
|
||||
) -> Result<Self, ModuleNameResolutionError> {
|
||||
let ast::StmtImportFrom {
|
||||
@@ -328,12 +328,12 @@ impl ModuleName {
|
||||
/// Computes the absolute module name from the LHS components of `from LHS import RHS`
|
||||
pub fn from_identifier_parts<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
module: Option<&str>,
|
||||
level: u32,
|
||||
) -> Result<Self, ModuleNameResolutionError> {
|
||||
if let Some(level) = NonZeroU32::new(level) {
|
||||
relative_module_name(db, importing_file, module, level)
|
||||
relative_module_name(db, importing_file.resolver_file(db), module, level)
|
||||
} else {
|
||||
module
|
||||
.and_then(Self::new)
|
||||
@@ -346,7 +346,7 @@ impl ModuleName {
|
||||
/// i.e. this resolves `.`
|
||||
pub fn package_for_file<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
) -> Result<Self, ModuleNameResolutionError> {
|
||||
Self::from_identifier_parts(db, importing_file, None, 1)
|
||||
}
|
||||
@@ -468,6 +468,67 @@ impl std::fmt::Display for ModuleName {
|
||||
}
|
||||
}
|
||||
|
||||
/// The file from which an import is resolved.
|
||||
///
|
||||
/// Most absolute imports only need the resolver environment. Creating a [`ResolverFile`] for each
|
||||
/// such import would unnecessarily intern the file and environment together, even though that
|
||||
/// combined identity is never used:
|
||||
///
|
||||
/// ```text
|
||||
/// resolve_module(ImportingFile::File(shared.py, environment), "dependency")
|
||||
/// -> resolve using environment; no ResolverFile needed
|
||||
/// ```
|
||||
///
|
||||
/// Relative imports, on the other hand, need the importing file's module identity and therefore
|
||||
/// require a [`ResolverFile`]:
|
||||
///
|
||||
/// ```text
|
||||
/// from .dependency import value
|
||||
/// -> importing_file.resolver_file(db)
|
||||
/// -> ResolverFile(shared.py, environment)
|
||||
/// ```
|
||||
///
|
||||
/// [`ImportingFile::File`] defers interning until such a code path actually calls
|
||||
/// [`ImportingFile::resolver_file`]. Callers that already have an interned resolver file can pass
|
||||
/// [`ImportingFile::ResolverFile`] to reuse it directly.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ImportingFile<'db> {
|
||||
/// An already-interned resolver key that can be reused without materialization.
|
||||
ResolverFile(ResolverFile<'db>),
|
||||
/// An importing file and resolver environment whose combined key is materialized lazily.
|
||||
File(File, ResolverEnvironment<'db>),
|
||||
}
|
||||
|
||||
impl<'db> ImportingFile<'db> {
|
||||
pub fn file(self, db: &dyn Db) -> File {
|
||||
match self {
|
||||
Self::ResolverFile(file) => file.file(db),
|
||||
Self::File(file, _) => file,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolver_environment(self, db: &'db dyn Db) -> ResolverEnvironment<'db> {
|
||||
match self {
|
||||
Self::ResolverFile(file) => file.environment(db),
|
||||
Self::File(_, resolver_environment) => resolver_environment,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn python_version(self, db: &'db dyn Db) -> PythonVersion {
|
||||
self.resolver_environment(db).python_version(db)
|
||||
}
|
||||
|
||||
/// Returns the existing resolver key or materializes one when required.
|
||||
pub fn resolver_file(self, db: &'db dyn Db) -> ResolverFile<'db> {
|
||||
match self {
|
||||
Self::ResolverFile(file) => file,
|
||||
Self::File(file, resolver_environment) => {
|
||||
ResolverFile::new(db, file, resolver_environment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a `from .foo import bar` relative import, resolve the relative module
|
||||
/// we're importing `bar` from into an absolute [`ModuleName`]
|
||||
/// using the name of the module we're currently analyzing.
|
||||
@@ -480,7 +541,7 @@ impl std::fmt::Display for ModuleName {
|
||||
/// - `from ..foo.bar import baz` => `tail == "foo.bar"`
|
||||
fn relative_module_name<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ResolverFile<'db>,
|
||||
tail: Option<&str>,
|
||||
level: NonZeroU32,
|
||||
) -> Result<ModuleName, ModuleNameResolutionError> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::resolve::{PyTyped, ResolverContext};
|
||||
use crate::typeshed::{TypeshedVersionsQueryResult, typeshed_versions};
|
||||
use crate::typeshed::TypeshedVersionsQueryResult;
|
||||
|
||||
/// A path that points to a Python module.
|
||||
///
|
||||
@@ -428,13 +428,14 @@ fn query_stdlib_version(
|
||||
let Some(module_name) = stdlib_path_to_module_name(relative_path) else {
|
||||
return TypeshedVersionsQueryResult::DoesNotExist;
|
||||
};
|
||||
let ResolverContext {
|
||||
db,
|
||||
python_version,
|
||||
mode: _,
|
||||
} = context;
|
||||
|
||||
typeshed_versions(*db).query_module(&module_name, *python_version)
|
||||
context
|
||||
.resolver_environment
|
||||
.search_paths(context.db)
|
||||
.typeshed_versions()
|
||||
.query_module(
|
||||
&module_name,
|
||||
context.resolver_environment.python_version(context.db),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -894,6 +895,7 @@ mod tests {
|
||||
use ruff_db::Db;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::ResolverEnvironment;
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::resolve::ModuleResolveMode;
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
@@ -1160,7 +1162,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let asyncio_regular_package = stdlib_path.join("asyncio");
|
||||
assert!(asyncio_regular_package.is_directory(&resolver));
|
||||
@@ -1190,7 +1196,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let xml_namespace_package = stdlib_path.join("xml");
|
||||
assert!(xml_namespace_package.is_directory(&resolver));
|
||||
@@ -1212,7 +1222,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let functools_module = stdlib_path.join("functools.pyi");
|
||||
assert!(functools_module.to_file(&resolver).is_some());
|
||||
@@ -1228,7 +1242,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let collections_regular_package = stdlib_path.join("collections");
|
||||
assert_eq!(collections_regular_package.to_file(&resolver), None);
|
||||
@@ -1244,7 +1262,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||
assert_eq!(importlib_namespace_package.to_file(&resolver), None);
|
||||
@@ -1265,7 +1287,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY38, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
let non_existent = stdlib_path.join("doesnt_even_exist");
|
||||
assert_eq!(non_existent.to_file(&resolver), None);
|
||||
@@ -1293,7 +1319,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY39, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
// Since we've set the target version to Py39,
|
||||
// `collections` should now exist as a directory, according to VERSIONS...
|
||||
@@ -1324,7 +1354,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY39, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
// The `importlib` directory now also exists
|
||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||
@@ -1348,7 +1382,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
|
||||
let resolver = ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::Typing);
|
||||
let resolver = ResolverContext::new(
|
||||
&db,
|
||||
ResolverEnvironment::new(&db, PythonVersion::PY39, db.search_paths()),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
// The `xml` package no longer exists on py39:
|
||||
let xml_namespace_package = stdlib_path.join("xml");
|
||||
|
||||
@@ -32,7 +32,6 @@ specifies ty's implementation of Python's import resolution algorithm.
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
@@ -43,29 +42,30 @@ use ruff_db::source::source_text;
|
||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::{
|
||||
self as ast, PySourceType, PythonVersion,
|
||||
self as ast, PySourceType,
|
||||
visitor::{Visitor, walk_body},
|
||||
};
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::module::{Module, ModuleKind};
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_name::{ImportingFile, ModuleName};
|
||||
use crate::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
|
||||
use crate::strategy::MisconfigurationStrategy;
|
||||
use crate::typeshed::{TypeshedVersions, vendored_typeshed_versions};
|
||||
use crate::{SearchPathSettings, SearchPathSettingsError};
|
||||
use crate::{ResolverEnvironment, ResolverFile, SearchPathSettings, SearchPathSettingsError};
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
pub fn resolve_module<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
let resolver_environment = importing_file.resolver_environment(db);
|
||||
let interned_name = ModuleNameIngredient::new(
|
||||
db,
|
||||
module_name,
|
||||
ModuleResolveMode::Typing,
|
||||
importing_file.python_version(db),
|
||||
resolver_environment,
|
||||
);
|
||||
|
||||
resolve_module_query(db, interned_name)
|
||||
@@ -78,11 +78,15 @@ pub fn resolve_module<'db>(
|
||||
/// we don't have a well-defined importing file.
|
||||
pub fn resolve_module_confident<'db>(
|
||||
db: &'db dyn Db,
|
||||
python_version: PythonVersion,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
let interned_name =
|
||||
ModuleNameIngredient::new(db, module_name, ModuleResolveMode::Typing, python_version);
|
||||
let interned_name = ModuleNameIngredient::new(
|
||||
db,
|
||||
module_name,
|
||||
ModuleResolveMode::Typing,
|
||||
resolver_environment,
|
||||
);
|
||||
|
||||
resolve_module_query(db, interned_name)
|
||||
}
|
||||
@@ -90,14 +94,15 @@ pub fn resolve_module_confident<'db>(
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
pub fn resolve_real_module<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
let resolver_environment = importing_file.resolver_environment(db);
|
||||
let interned_name = ModuleNameIngredient::new(
|
||||
db,
|
||||
module_name,
|
||||
ModuleResolveMode::Runtime,
|
||||
importing_file.python_version(db),
|
||||
resolver_environment,
|
||||
);
|
||||
|
||||
resolve_module_query(db, interned_name)
|
||||
@@ -110,11 +115,15 @@ pub fn resolve_real_module<'db>(
|
||||
/// we don't have a well-defined importing file.
|
||||
pub fn resolve_real_module_confident<'db>(
|
||||
db: &'db dyn Db,
|
||||
python_version: PythonVersion,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
let interned_name =
|
||||
ModuleNameIngredient::new(db, module_name, ModuleResolveMode::Runtime, python_version);
|
||||
let interned_name = ModuleNameIngredient::new(
|
||||
db,
|
||||
module_name,
|
||||
ModuleResolveMode::Runtime,
|
||||
resolver_environment,
|
||||
);
|
||||
|
||||
resolve_module_query(db, interned_name)
|
||||
}
|
||||
@@ -132,14 +141,15 @@ pub fn resolve_real_module_confident<'db>(
|
||||
/// are involved in an import cycle with `builtins`.
|
||||
pub fn resolve_real_shadowable_module<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
let resolver_environment = importing_file.resolver_environment(db);
|
||||
let interned_name = ModuleNameIngredient::new(
|
||||
db,
|
||||
module_name,
|
||||
ModuleResolveMode::RuntimeSomeShadowingAllowed,
|
||||
importing_file.python_version(db),
|
||||
resolver_environment,
|
||||
);
|
||||
|
||||
resolve_module_query(db, interned_name)
|
||||
@@ -174,6 +184,8 @@ pub enum ModuleResolveMode {
|
||||
#[salsa::interned(heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ModuleResolveModeIngredient<'db> {
|
||||
#[returns(copy)]
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
#[returns(copy)]
|
||||
mode: ModuleResolveMode,
|
||||
}
|
||||
@@ -228,10 +240,10 @@ fn resolve_module_query<'db>(
|
||||
) -> Option<Module<'db>> {
|
||||
let name = module_name.name(db);
|
||||
let mode = module_name.mode(db);
|
||||
let python_version = module_name.python_version(db);
|
||||
let resolver_environment = module_name.resolver_environment(db);
|
||||
let _span = tracing::trace_span!("resolve_module", %name).entered();
|
||||
|
||||
let Some(resolved) = resolve_name(db, name, mode, python_version) else {
|
||||
let Some(resolved) = resolve_name(db, resolver_environment, name, mode) else {
|
||||
tracing::debug!("Module `{name}` not found in search paths");
|
||||
return None;
|
||||
};
|
||||
@@ -239,7 +251,7 @@ fn resolve_module_query<'db>(
|
||||
resolved
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|candidate| candidate.into_module(db, name, python_version))
|
||||
.map(|candidate| candidate.into_module(db, resolver_environment, name))
|
||||
}
|
||||
|
||||
/// Like `resolve_module_query` but for cases where it failed to resolve the module
|
||||
@@ -262,10 +274,11 @@ fn desperately_resolve_module<'db>(
|
||||
) -> Option<Module<'db>> {
|
||||
let name = module_name.name(db);
|
||||
let mode = module_name.mode(db);
|
||||
let python_version = module_name.python_version(db);
|
||||
let resolver_environment = module_name.resolver_environment(db);
|
||||
let _span = tracing::trace_span!("desperately_resolve_module", %name).entered();
|
||||
|
||||
let Some(resolved) = desperately_resolve_name(db, importing_file, name, mode, python_version)
|
||||
let Some(resolved) =
|
||||
desperately_resolve_name(db, importing_file, resolver_environment, name, mode)
|
||||
else {
|
||||
let mode = match mode {
|
||||
ModuleResolveMode::Typing => "typing mode",
|
||||
@@ -281,7 +294,7 @@ fn desperately_resolve_module<'db>(
|
||||
resolved
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|candidate| candidate.into_module(db, name, python_version))
|
||||
.map(|candidate| candidate.into_module(db, resolver_environment, name))
|
||||
}
|
||||
|
||||
/// Resolves the module for the given path.
|
||||
@@ -290,8 +303,8 @@ fn desperately_resolve_module<'db>(
|
||||
#[allow(unused)]
|
||||
pub(crate) fn path_to_module<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
path: &FilePath,
|
||||
python_version: PythonVersion,
|
||||
) -> Option<Module<'db>> {
|
||||
// It's not entirely clear on first sight why this method calls `file_to_module` instead of
|
||||
// it being the other way round, considering that the first thing that `file_to_module` does
|
||||
@@ -302,7 +315,7 @@ pub(crate) fn path_to_module<'db>(
|
||||
// `VfsFile` is. So what we do here is to retrieve the `path`'s `VfsFile` so that we can make
|
||||
// use of Salsa's caching and invalidation.
|
||||
let file = path.to_file(db)?;
|
||||
file_to_module(db, PythonFile::new(db, file, python_version))
|
||||
file_to_module(db, ResolverFile::new(db, file, resolver_environment))
|
||||
}
|
||||
|
||||
/// Resolves the module for the file with the given id.
|
||||
@@ -314,25 +327,35 @@ pub(crate) fn path_to_module<'db>(
|
||||
/// This intuition is particularly useful for understanding why it's correct that we pass
|
||||
/// the file itself as `importing_file` to various subroutines.
|
||||
#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn file_to_module<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> Option<Module<'db>> {
|
||||
let source_file = file.file(db);
|
||||
let _span = tracing::trace_span!("file_to_module", file=?source_file).entered();
|
||||
pub fn file_to_module<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_file: ResolverFile<'db>,
|
||||
) -> Option<Module<'db>> {
|
||||
let resolver_environment = resolver_file.environment(db);
|
||||
let file = resolver_file.file(db);
|
||||
let _span = tracing::trace_span!("file_to_module", ?file).entered();
|
||||
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, source_file)?;
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, file)?;
|
||||
|
||||
file_to_module_impl(db, file, path, search_paths(db, ModuleResolveMode::Typing)).or_else(|| {
|
||||
file_to_module_impl(
|
||||
db,
|
||||
resolver_file,
|
||||
path,
|
||||
search_paths(db, resolver_environment, ModuleResolveMode::Typing),
|
||||
)
|
||||
.or_else(|| {
|
||||
file_to_module_impl(
|
||||
db,
|
||||
file,
|
||||
resolver_file,
|
||||
path,
|
||||
relative_desperate_search_paths(db, source_file).iter(),
|
||||
relative_desperate_search_paths(db, resolver_file).iter(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn file_to_module_impl<'db, 'a>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
resolver_file: ResolverFile<'db>,
|
||||
path: SystemOrVendoredPathRef<'a>,
|
||||
mut search_paths: impl Iterator<Item = &'a SearchPath>,
|
||||
) -> Option<Module<'db>> {
|
||||
@@ -348,20 +371,21 @@ fn file_to_module_impl<'db, 'a>(
|
||||
// If it doesn't, then that means that multiple modules have the same name in different
|
||||
// root paths, but that the module corresponding to `path` is in a lower priority search path,
|
||||
// in which case we ignore it.
|
||||
let module = resolve_module(db, file, &module_name)?;
|
||||
let module = resolve_module(db, ImportingFile::ResolverFile(resolver_file), &module_name)?;
|
||||
let module_file = module.file(db)?;
|
||||
|
||||
let source_file = file.file(db);
|
||||
let file_path = source_file.path(db);
|
||||
let file: File = resolver_file.file(db);
|
||||
let file_path = file.path(db);
|
||||
if file_path == module_file.path(db) {
|
||||
return Some(module);
|
||||
} else if source_file.source_type(db) == PySourceType::Python
|
||||
} else if file.source_type(db) == PySourceType::Python
|
||||
&& module_file.source_type(db) == PySourceType::Stub
|
||||
{
|
||||
// If a .py and .pyi are both defined, the .pyi will be the one returned by `resolve_module().file`,
|
||||
// which would make us erroneously believe the `.py` is *not* also this module (breaking things
|
||||
// like relative imports). So here we try `resolve_real_module().file` to cover both cases.
|
||||
let module = resolve_real_module(db, file, &module_name)?;
|
||||
let module =
|
||||
resolve_real_module(db, ImportingFile::ResolverFile(resolver_file), &module_name)?;
|
||||
let module_file = module.file(db)?;
|
||||
if file_path == module_file.path(db) {
|
||||
return Some(module);
|
||||
@@ -377,8 +401,20 @@ fn file_to_module_impl<'db, 'a>(
|
||||
None
|
||||
}
|
||||
|
||||
pub fn search_paths(db: &dyn Db, resolve_mode: ModuleResolveMode) -> SearchPathIterator<'_> {
|
||||
db.search_paths().iter(db, resolve_mode)
|
||||
pub fn search_paths<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
resolve_mode: ModuleResolveMode,
|
||||
) -> SearchPathIterator<'db> {
|
||||
let search_paths = resolver_environment.search_paths(db);
|
||||
|
||||
SearchPathIterator {
|
||||
db,
|
||||
static_paths: search_paths.static_paths.iter(),
|
||||
stdlib_path: search_paths.stdlib(resolve_mode),
|
||||
dynamic_paths: None,
|
||||
mode: ModuleResolveModeIngredient::new(db, resolver_environment, resolve_mode),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -467,8 +503,14 @@ impl StubPackageIndex {
|
||||
/// Returns an index of search paths that may contain a top-level stub package, preserving their
|
||||
/// resolution order relative to stdlib.
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn stub_package_index(db: &dyn Db) -> StubPackageIndex {
|
||||
StubPackageIndex::from_search_paths(db, search_paths(db, ModuleResolveMode::Typing))
|
||||
fn stub_package_index(
|
||||
db: &dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'_>,
|
||||
) -> StubPackageIndex {
|
||||
StubPackageIndex::from_search_paths(
|
||||
db,
|
||||
search_paths(db, resolver_environment, ModuleResolveMode::Typing),
|
||||
)
|
||||
}
|
||||
|
||||
fn search_path_may_contain_stub_package(db: &dyn Db, search_path: &SearchPath) -> bool {
|
||||
@@ -490,21 +532,26 @@ fn search_path_may_contain_stub_package(db: &dyn Db, search_path: &SearchPath) -
|
||||
///
|
||||
/// We exclude `__init__.py(i)` dirs to avoid truncating packages.
|
||||
#[salsa::tracked(returns(as_deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn absolute_desperate_search_paths(db: &dyn Db, importing_file: File) -> Option<Box<[SearchPath]>> {
|
||||
fn absolute_desperate_search_paths(
|
||||
db: &dyn Db,
|
||||
importing_file: ResolverFile<'_>,
|
||||
) -> Option<Box<[SearchPath]>> {
|
||||
let resolver_environment = importing_file.environment(db);
|
||||
let importing_file = importing_file.file(db);
|
||||
let system = db.system();
|
||||
let importing_path = importing_file.path(db).as_system_path()?;
|
||||
|
||||
// Only allow this if the importing_file is under the first-party search path
|
||||
let (base_path, rel_path) =
|
||||
search_paths(db, ModuleResolveMode::Typing).find_map(|search_path| {
|
||||
if !search_path.is_first_party() {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
search_path.as_system_path()?,
|
||||
search_path.relativize_system_path_only(importing_path)?,
|
||||
))
|
||||
})?;
|
||||
let (base_path, rel_path) = search_paths(db, resolver_environment, ModuleResolveMode::Typing)
|
||||
.find_map(|search_path| {
|
||||
if !search_path.is_first_party() {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
search_path.as_system_path()?,
|
||||
search_path.relativize_system_path_only(importing_path)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Only allow searching up to the first-party path's root
|
||||
let mut search_paths = Vec::new();
|
||||
@@ -554,21 +601,26 @@ fn absolute_desperate_search_paths(db: &dyn Db, importing_file: File) -> Option<
|
||||
/// chaotic things. In particular, all files under a given pyproject.toml will currently
|
||||
/// agree on this being their desperate search-path, which is really nice.
|
||||
#[salsa::tracked(returns(clone), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn relative_desperate_search_paths(db: &dyn Db, importing_file: File) -> Option<SearchPath> {
|
||||
fn relative_desperate_search_paths(
|
||||
db: &dyn Db,
|
||||
importing_file: ResolverFile<'_>,
|
||||
) -> Option<SearchPath> {
|
||||
let resolver_environment = importing_file.environment(db);
|
||||
let importing_file = importing_file.file(db);
|
||||
let system = db.system();
|
||||
let importing_path = importing_file.path(db).as_system_path()?;
|
||||
|
||||
// Only allow this if the importing_file is under the first-party search path
|
||||
let (base_path, rel_path) =
|
||||
search_paths(db, ModuleResolveMode::Typing).find_map(|search_path| {
|
||||
if !search_path.is_first_party() {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
search_path.as_system_path()?,
|
||||
search_path.relativize_system_path_only(importing_path)?,
|
||||
))
|
||||
})?;
|
||||
let (base_path, rel_path) = search_paths(db, resolver_environment, ModuleResolveMode::Typing)
|
||||
.find_map(|search_path| {
|
||||
if !search_path.is_first_party() {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
search_path.as_system_path()?,
|
||||
search_path.relativize_system_path_only(importing_path)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Only allow searching up to the first-party path's root
|
||||
for rel_dir in rel_path.ancestors() {
|
||||
@@ -587,7 +639,7 @@ fn relative_desperate_search_paths(db: &dyn Db, importing_file: File) -> Option<
|
||||
|
||||
None
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
pub struct SearchPaths {
|
||||
/// Search paths that have been statically determined purely from reading
|
||||
/// ty's configuration settings. These shouldn't ever change unless the
|
||||
@@ -815,17 +867,6 @@ impl SearchPaths {
|
||||
}
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self, db: &'a dyn Db, mode: ModuleResolveMode) -> SearchPathIterator<'a> {
|
||||
let stdlib_path = self.stdlib(mode);
|
||||
SearchPathIterator {
|
||||
db,
|
||||
static_paths: self.static_paths.iter(),
|
||||
stdlib_path,
|
||||
dynamic_paths: None,
|
||||
mode: ModuleResolveModeIngredient::new(db, mode),
|
||||
}
|
||||
}
|
||||
|
||||
fn stdlib(&self, mode: ModuleResolveMode) -> Option<&SearchPath> {
|
||||
match mode {
|
||||
ModuleResolveMode::Typing => self.stdlib_path.as_ref(),
|
||||
@@ -835,18 +876,6 @@ impl SearchPaths {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display<'a>(
|
||||
&'a self,
|
||||
db: &'a dyn Db,
|
||||
mode: ModuleResolveMode,
|
||||
) -> DisplaySearchPaths<'a> {
|
||||
DisplaySearchPaths {
|
||||
search_paths: self,
|
||||
db,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_stdlib(&self) -> Option<&SystemPath> {
|
||||
self.stdlib_path
|
||||
.as_ref()
|
||||
@@ -858,28 +887,6 @@ impl SearchPaths {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplaySearchPaths<'a> {
|
||||
search_paths: &'a SearchPaths,
|
||||
db: &'a dyn Db,
|
||||
mode: ModuleResolveMode,
|
||||
}
|
||||
|
||||
impl fmt::Display for DisplaySearchPaths<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut paths = self.search_paths.iter(self.db, self.mode).peekable();
|
||||
|
||||
if paths.peek().is_none() {
|
||||
return f.write_str("[]");
|
||||
}
|
||||
|
||||
writeln!(f, "[")?;
|
||||
for path in paths {
|
||||
writeln!(f, " {path},")?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all dynamic search paths. For each `site-packages` path:
|
||||
/// - Collect that `site-packages` path
|
||||
/// - Collect any search paths listed in `.pth` files in that `site-packages` directory
|
||||
@@ -901,7 +908,7 @@ pub(crate) fn dynamic_resolution_paths<'db>(
|
||||
site_packages,
|
||||
typeshed_versions: _,
|
||||
real_stdlib_path,
|
||||
} = db.search_paths();
|
||||
} = mode.resolver_environment(db).search_paths(db);
|
||||
|
||||
let mut dynamic_paths = Vec::new();
|
||||
|
||||
@@ -1070,7 +1077,7 @@ impl<'db> Iterator for SearchPathIterator<'db> {
|
||||
|
||||
impl FusedIterator for SearchPathIterator<'_> {}
|
||||
|
||||
/// A thin wrapper around a module name, resolution mode, and Python version to make them a Salsa
|
||||
/// A thin wrapper around a module name, resolution mode, and resolver environment to make them a Salsa
|
||||
/// ingredient.
|
||||
///
|
||||
/// This is needed because Salsa requires that all query arguments are salsa ingredients.
|
||||
@@ -1081,23 +1088,25 @@ struct ModuleNameIngredient<'db> {
|
||||
#[returns(copy)]
|
||||
pub(super) mode: ModuleResolveMode,
|
||||
#[returns(copy)]
|
||||
pub(super) python_version: PythonVersion,
|
||||
pub(super) resolver_environment: ResolverEnvironment<'db>,
|
||||
}
|
||||
|
||||
/// Given a module name and a list of search paths in which to lookup modules,
|
||||
/// attempt to resolve the module name
|
||||
fn resolve_name(
|
||||
db: &dyn Db,
|
||||
fn resolve_name<'db>(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: &ModuleName,
|
||||
mode: ModuleResolveMode,
|
||||
python_version: PythonVersion,
|
||||
) -> Option<ResolvedNames> {
|
||||
let resolver = NameResolver::new(db, name, mode, python_version);
|
||||
let resolver = NameResolver::new(db, resolver_environment, name, mode);
|
||||
|
||||
match mode {
|
||||
ModuleResolveMode::Typing => resolver.resolve_typing(stub_package_index(db)),
|
||||
ModuleResolveMode::Typing => {
|
||||
resolver.resolve_typing(stub_package_index(db, resolver_environment))
|
||||
}
|
||||
ModuleResolveMode::Runtime | ModuleResolveMode::RuntimeSomeShadowingAllowed => {
|
||||
resolver.resolve_runtime(search_paths(db, mode))
|
||||
resolver.resolve_runtime(search_paths(db, resolver_environment, mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1106,15 +1115,16 @@ fn resolve_name(
|
||||
/// and we are now Getting Desperate and willing to try the ancestor directories of
|
||||
/// the `importing_file` as potential temporary search paths that are private
|
||||
/// to this import.
|
||||
fn desperately_resolve_name(
|
||||
db: &dyn Db,
|
||||
fn desperately_resolve_name<'db>(
|
||||
db: &'db dyn Db,
|
||||
importing_file: File,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: &ModuleName,
|
||||
mode: ModuleResolveMode,
|
||||
python_version: PythonVersion,
|
||||
) -> Option<ResolvedNames> {
|
||||
let importing_file = ResolverFile::new(db, importing_file, resolver_environment);
|
||||
let search_paths = absolute_desperate_search_paths(db, importing_file).unwrap_or_default();
|
||||
let resolver = NameResolver::new(db, name, mode, python_version);
|
||||
let resolver = NameResolver::new(db, resolver_environment, name, mode);
|
||||
|
||||
match mode {
|
||||
ModuleResolveMode::Typing => resolver.resolve_desperate_typing(search_paths),
|
||||
@@ -1201,13 +1211,13 @@ impl ModuleResolutionCandidate {
|
||||
fn into_module<'db>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: &ModuleName,
|
||||
python_version: PythonVersion,
|
||||
) -> Module<'db> {
|
||||
match self.module {
|
||||
ResolvedModule::NamespacePackage => {
|
||||
tracing::trace!("Resolve namespace package `{name}`");
|
||||
Module::namespace_package(db, Cow::Borrowed(name), python_version)
|
||||
Module::namespace_package(db, resolver_environment, Cow::Borrowed(name))
|
||||
}
|
||||
ResolvedModule::LegacyNamespacePackage(file) => {
|
||||
// legacy namespace packages behave like regular packages
|
||||
@@ -1218,10 +1228,11 @@ impl ModuleResolutionCandidate {
|
||||
);
|
||||
Module::file_module(
|
||||
db,
|
||||
file,
|
||||
resolver_environment,
|
||||
Cow::Borrowed(name),
|
||||
ModuleKind::Package,
|
||||
self.path.into_search_path(),
|
||||
PythonFile::new(db, file, python_version),
|
||||
)
|
||||
}
|
||||
ResolvedModule::RegularPackage(file) => {
|
||||
@@ -1231,20 +1242,22 @@ impl ModuleResolutionCandidate {
|
||||
);
|
||||
Module::file_module(
|
||||
db,
|
||||
file,
|
||||
resolver_environment,
|
||||
Cow::Borrowed(name),
|
||||
ModuleKind::Package,
|
||||
self.path.into_search_path(),
|
||||
PythonFile::new(db, file, python_version),
|
||||
)
|
||||
}
|
||||
ResolvedModule::Module(file) => {
|
||||
tracing::trace!("Resolved module `{name}` to `{path}`", path = file.path(db));
|
||||
Module::file_module(
|
||||
db,
|
||||
file,
|
||||
resolver_environment,
|
||||
Cow::Borrowed(name),
|
||||
ModuleKind::Module,
|
||||
self.path.into_search_path(),
|
||||
PythonFile::new(db, file, python_version),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1286,12 +1299,13 @@ struct NameResolver<'db, 'name> {
|
||||
impl<'db, 'name> NameResolver<'db, 'name> {
|
||||
fn new(
|
||||
db: &'db dyn Db,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
name: &'name ModuleName,
|
||||
mode: ModuleResolveMode,
|
||||
python_version: PythonVersion,
|
||||
) -> Self {
|
||||
let python_version = resolver_environment.python_version(db);
|
||||
Self {
|
||||
context: ResolverContext::new(db, python_version, mode),
|
||||
context: ResolverContext::new(db, resolver_environment, mode),
|
||||
name,
|
||||
is_non_shadowable: mode.is_non_shadowable(python_version.minor, name.as_str()),
|
||||
}
|
||||
@@ -1303,11 +1317,13 @@ impl<'db, 'name> NameResolver<'db, 'name> {
|
||||
/// a fallback when no stub provides the requested module. A stub overlay may use runtime
|
||||
/// packages as parents, but its final module must come from a stub file.
|
||||
fn resolve_typing(&self, stub_packages: &StubPackageIndex) -> Option<ResolvedNames> {
|
||||
let search_paths = self.context.db.search_paths();
|
||||
|
||||
if self.name.components().nth(1).is_none() {
|
||||
let candidates = self.discover_roots(
|
||||
search_paths.iter(self.context.db, ModuleResolveMode::Typing),
|
||||
search_paths(
|
||||
self.context.db,
|
||||
self.context.resolver_environment,
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
stub_packages.all(),
|
||||
);
|
||||
return self.resolve_remaining(candidates, ComponentFileFilter::ByMode);
|
||||
@@ -1318,9 +1334,12 @@ impl<'db, 'name> NameResolver<'db, 'name> {
|
||||
// normal fallback so that each extra path is probed only once.
|
||||
let (overlay_stub_packages, remaining_stub_packages) = stub_packages.split_overlay();
|
||||
let mut candidates = self.discover_roots(
|
||||
search_paths
|
||||
.iter(self.context.db, ModuleResolveMode::Typing)
|
||||
.take_while(|search_path| search_path.is_extra()),
|
||||
search_paths(
|
||||
self.context.db,
|
||||
self.context.resolver_environment,
|
||||
ModuleResolveMode::Typing,
|
||||
)
|
||||
.take_while(|search_path| search_path.is_extra()),
|
||||
overlay_stub_packages,
|
||||
);
|
||||
if let Some(resolved) =
|
||||
@@ -1330,9 +1349,12 @@ impl<'db, 'name> NameResolver<'db, 'name> {
|
||||
}
|
||||
|
||||
let remaining_candidates = self.discover_roots(
|
||||
search_paths
|
||||
.iter(self.context.db, ModuleResolveMode::Typing)
|
||||
.skip_while(|search_path| search_path.is_extra()),
|
||||
search_paths(
|
||||
self.context.db,
|
||||
self.context.resolver_environment,
|
||||
ModuleResolveMode::Typing,
|
||||
)
|
||||
.skip_while(|search_path| search_path.is_extra()),
|
||||
remaining_stub_packages,
|
||||
);
|
||||
candidates.extend(remaining_candidates);
|
||||
@@ -1738,7 +1760,11 @@ fn is_legacy_namespace_package(
|
||||
// but hey, this is better than nothing!
|
||||
let parsed = ruff_db::parsed::parsed_module(
|
||||
context.db,
|
||||
ruff_db::PythonFile::new(context.db, init, context.python_version),
|
||||
PythonFile::new(
|
||||
context.db,
|
||||
init,
|
||||
context.resolver_environment.python_version(context.db),
|
||||
),
|
||||
);
|
||||
let mut visitor = LegacyNamespacePackageVisitor::default();
|
||||
visitor.visit_body(parsed.load(context.db).suite());
|
||||
@@ -1774,19 +1800,19 @@ impl PyTyped {
|
||||
|
||||
pub(super) struct ResolverContext<'db> {
|
||||
pub(super) db: &'db dyn Db,
|
||||
pub(super) python_version: PythonVersion,
|
||||
pub(super) resolver_environment: ResolverEnvironment<'db>,
|
||||
pub(super) mode: ModuleResolveMode,
|
||||
}
|
||||
|
||||
impl<'db> ResolverContext<'db> {
|
||||
pub(super) fn new(
|
||||
db: &'db dyn Db,
|
||||
python_version: PythonVersion,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
mode: ModuleResolveMode,
|
||||
) -> Self {
|
||||
Self {
|
||||
db,
|
||||
python_version,
|
||||
resolver_environment,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
@@ -2017,18 +2043,18 @@ mod tests {
|
||||
db: &'db TestDb,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
super::resolve_module_confident(db, db.python_version(), module_name)
|
||||
super::resolve_module_confident(db, db.resolver_environment(), module_name)
|
||||
}
|
||||
|
||||
fn resolve_real_module_confident<'db>(
|
||||
db: &'db TestDb,
|
||||
module_name: &ModuleName,
|
||||
) -> Option<Module<'db>> {
|
||||
super::resolve_real_module_confident(db, db.python_version(), module_name)
|
||||
super::resolve_real_module_confident(db, db.resolver_environment(), module_name)
|
||||
}
|
||||
|
||||
fn path_to_module<'db>(db: &'db TestDb, path: &FilePath) -> Option<Module<'db>> {
|
||||
super::path_to_module(db, path, db.python_version())
|
||||
super::path_to_module(db, db.resolver_environment(), path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2102,10 +2128,13 @@ mod tests {
|
||||
])
|
||||
.build();
|
||||
let importing_file = system_path_to_file(&db, src.join("nested/main.py")).unwrap();
|
||||
let importing_file = PythonFile::new(&db, importing_file, db.python_version());
|
||||
|
||||
let foo =
|
||||
resolve_module(&db, importing_file, &ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo = resolve_module(
|
||||
&db,
|
||||
ImportingFile::File(importing_file, db.resolver_environment()),
|
||||
&ModuleName::new_static("foo").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
foo.file(&db).unwrap().path(&db),
|
||||
&src.join("nested/foo-stubs/__init__.pyi")
|
||||
@@ -2303,7 +2332,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_module_uses_importing_file_python_version() {
|
||||
fn resolve_module_uses_resolver_environment_python_version() {
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: &[("_sha256.pyi", ""), ("py312_only.pyi", "")],
|
||||
versions: "_sha256: 3.11-\npy312_only: 3.12-",
|
||||
@@ -2321,12 +2350,13 @@ mod tests {
|
||||
.with_python_version(PythonVersion::PY311)
|
||||
.build();
|
||||
let importing_file = system_path_to_file(&db, src.join("main.py")).unwrap();
|
||||
let py311 = PythonFile::new(&db, importing_file, PythonVersion::PY311);
|
||||
let py312 = PythonFile::new(&db, importing_file, PythonVersion::PY312);
|
||||
|
||||
let py311 = ResolverEnvironment::new(&db, PythonVersion::PY311, db.search_paths());
|
||||
let py312 = ResolverEnvironment::new(&db, PythonVersion::PY312, db.search_paths());
|
||||
let sha256 = ModuleName::new_static("_sha256").unwrap();
|
||||
let py311_module = resolve_module(&db, py311, &sha256).unwrap();
|
||||
let py312_module = resolve_module(&db, py312, &sha256).unwrap();
|
||||
let py311_module =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py311), &sha256).unwrap();
|
||||
let py312_module =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py312), &sha256).unwrap();
|
||||
assert_eq!(
|
||||
py311_module.file(&db).unwrap().path(&db),
|
||||
&stdlib.join("_sha256.pyi")
|
||||
@@ -2339,8 +2369,10 @@ mod tests {
|
||||
assert_eq!(py312_module.python_version(&db), PythonVersion::PY312);
|
||||
|
||||
let namespace = ModuleName::new_static("namespace").unwrap();
|
||||
let py311_namespace = resolve_module(&db, py311, &namespace).unwrap();
|
||||
let py312_namespace = resolve_module(&db, py312, &namespace).unwrap();
|
||||
let py311_namespace =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py311), &namespace).unwrap();
|
||||
let py312_namespace =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py312), &namespace).unwrap();
|
||||
assert!(matches!(py311_namespace, Module::Namespace(_)));
|
||||
assert!(matches!(py312_namespace, Module::Namespace(_)));
|
||||
assert_eq!(py311_namespace.python_version(&db), PythonVersion::PY311);
|
||||
@@ -2348,9 +2380,11 @@ mod tests {
|
||||
assert_ne!(py311_namespace, py312_namespace);
|
||||
|
||||
let py312_only = ModuleName::new_static("py312_only").unwrap();
|
||||
assert!(resolve_module(&db, py311, &py312_only).is_none());
|
||||
assert!(
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py311), &py312_only).is_none()
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_module(&db, py312, &py312_only)
|
||||
resolve_module(&db, ImportingFile::File(importing_file, py312), &py312_only)
|
||||
.and_then(|module| module.file(&db))
|
||||
.unwrap()
|
||||
.path(&db),
|
||||
@@ -2358,6 +2392,44 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_module_uses_resolver_environment_search_paths() {
|
||||
let TestCase { mut db, src, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(&[("main.py", ""), ("shared.py", "from_src = True")])
|
||||
.with_vendored_typeshed()
|
||||
.build();
|
||||
db.write_file("/alternate/shared.py", "from_alternate = True")
|
||||
.unwrap();
|
||||
|
||||
let alternate_paths = SearchPathSettings {
|
||||
src_roots: vec![SystemPathBuf::from("/alternate")],
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored(), &FallibleStrategy)
|
||||
.unwrap();
|
||||
alternate_paths.try_register_static_roots(&db);
|
||||
|
||||
let primary = db.resolver_environment();
|
||||
let alternate = ResolverEnvironment::new(&db, PythonVersion::default(), &alternate_paths);
|
||||
let importing_file = system_path_to_file(&db, src.join("main.py")).unwrap();
|
||||
let name = ModuleName::new_static("shared").unwrap();
|
||||
|
||||
let primary_module =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, primary), &name).unwrap();
|
||||
let alternate_module =
|
||||
resolve_module(&db, ImportingFile::File(importing_file, alternate), &name).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
primary_module.file(&db).unwrap().path(&db),
|
||||
&src.join("shared.py")
|
||||
);
|
||||
assert_eq!(
|
||||
alternate_module.file(&db).unwrap().path(&db),
|
||||
&SystemPathBuf::from("/alternate/shared.py")
|
||||
);
|
||||
assert_ne!(primary_module, alternate_module);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdlib_resolution_respects_versions_file_py38_existing_modules() {
|
||||
const VERSIONS: &str = "\
|
||||
@@ -2894,7 +2966,7 @@ mod tests {
|
||||
&db,
|
||||
functools_module_name,
|
||||
ModuleResolveMode::Typing,
|
||||
db.python_version(),
|
||||
db.resolver_environment(),
|
||||
),
|
||||
&events,
|
||||
);
|
||||
@@ -3153,7 +3225,11 @@ not_a_directory
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
dynamic_resolution_paths,
|
||||
ModuleResolveModeIngredient::new(&db, ModuleResolveMode::Typing),
|
||||
ModuleResolveModeIngredient::new(
|
||||
&db,
|
||||
db.resolver_environment(),
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
&events,
|
||||
);
|
||||
}
|
||||
@@ -3172,7 +3248,11 @@ not_a_directory
|
||||
|
||||
dynamic_resolution_paths(
|
||||
&db,
|
||||
ModuleResolveModeIngredient::new(&db, ModuleResolveMode::Typing),
|
||||
ModuleResolveModeIngredient::new(
|
||||
&db,
|
||||
db.resolver_environment(),
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
);
|
||||
db.clear_salsa_events();
|
||||
|
||||
@@ -3180,14 +3260,22 @@ not_a_directory
|
||||
.unwrap();
|
||||
dynamic_resolution_paths(
|
||||
&db,
|
||||
ModuleResolveModeIngredient::new(&db, ModuleResolveMode::Typing),
|
||||
ModuleResolveModeIngredient::new(
|
||||
&db,
|
||||
db.resolver_environment(),
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
);
|
||||
|
||||
let events = db.take_salsa_events();
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
dynamic_resolution_paths,
|
||||
ModuleResolveModeIngredient::new(&db, ModuleResolveMode::Typing),
|
||||
ModuleResolveModeIngredient::new(
|
||||
&db,
|
||||
db.resolver_environment(),
|
||||
ModuleResolveMode::Typing,
|
||||
),
|
||||
&events,
|
||||
);
|
||||
}
|
||||
@@ -3284,7 +3372,8 @@ not_a_directory
|
||||
.with_site_packages_files(&[("_foo.pth", "/src")])
|
||||
.build();
|
||||
|
||||
let search_paths: Vec<&SearchPath> = search_paths(&db, ModuleResolveMode::Typing).collect();
|
||||
let search_paths: Vec<&SearchPath> =
|
||||
search_paths(&db, db.resolver_environment(), ModuleResolveMode::Typing).collect();
|
||||
|
||||
assert!(search_paths.contains(
|
||||
&&SearchPath::first_party(db.system(), SystemPathBuf::from("/src")).unwrap()
|
||||
@@ -3432,7 +3521,7 @@ not_a_directory
|
||||
let foo_module_file = File::new(&db, FilePath::from(installed_foo_module));
|
||||
let module = file_to_module(
|
||||
&db,
|
||||
PythonFile::new(&db, foo_module_file, db.python_version()),
|
||||
ResolverFile::new(&db, foo_module_file, db.resolver_environment()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(module.search_path(&db).unwrap(), &site_packages);
|
||||
|
||||
@@ -6,9 +6,8 @@ use std::str::FromStr;
|
||||
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::{PythonVersion, PythonVersionDeserializationError};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::FxOrderMap;
|
||||
use crate::module_name::ModuleName;
|
||||
|
||||
pub(crate) fn vendored_typeshed_versions(vendored: &VendoredFileSystem) -> TypeshedVersions {
|
||||
@@ -20,10 +19,6 @@ pub(crate) fn vendored_typeshed_versions(vendored: &VendoredFileSystem) -> Types
|
||||
.expect("The VERSIONS file in the vendored typeshed stubs should be well-formed")
|
||||
}
|
||||
|
||||
pub(crate) fn typeshed_versions(db: &dyn Db) -> &TypeshedVersions {
|
||||
db.search_paths().typeshed_versions()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TypeshedVersionsParseError {
|
||||
line_number: Option<NonZeroU16>,
|
||||
@@ -71,8 +66,8 @@ pub enum TypeshedVersionsParseErrorKind {
|
||||
VersionParseError(#[from] PythonVersionDeserializationError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub struct TypeshedVersions(FxHashMap<ModuleName, PyVersionRange>);
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
pub struct TypeshedVersions(FxOrderMap<ModuleName, PyVersionRange>);
|
||||
|
||||
impl TypeshedVersions {
|
||||
#[must_use]
|
||||
@@ -164,7 +159,7 @@ impl FromStr for TypeshedVersions {
|
||||
type Err = TypeshedVersionsParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut map = FxHashMap::default();
|
||||
let mut map = FxOrderMap::default();
|
||||
|
||||
for (line_index, line) in s.lines().enumerate() {
|
||||
// humans expect line numbers to be 1-indexed
|
||||
|
||||
+16
-14
@@ -15,7 +15,7 @@ use ruff_db::system::System;
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use salsa::{Database, Event, Setter};
|
||||
use ty_module_resolver::SearchPaths;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::program::{
|
||||
FallibleStrategy, MisconfigurationStrategy, Program, UseDefaultStrategy,
|
||||
};
|
||||
@@ -538,11 +538,7 @@ impl SalsaMemoryDump {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for ProjectDatabase {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
impl ty_module_resolver::Db for ProjectDatabase {}
|
||||
|
||||
#[salsa::db]
|
||||
impl SemanticDb for ProjectDatabase {
|
||||
@@ -550,6 +546,10 @@ impl SemanticDb for ProjectDatabase {
|
||||
ProjectDatabase::check_file(self, file)
|
||||
}
|
||||
|
||||
fn program_file(&self, file: File) -> ProgramFile<'_> {
|
||||
Program::get(self).program_file(self, file)
|
||||
}
|
||||
|
||||
fn rule_selection(&self, file: File) -> &RuleSelection {
|
||||
let settings = file_settings(self, file);
|
||||
settings.rules(self)
|
||||
@@ -650,6 +650,7 @@ pub(crate) mod testing {
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::SearchPathSettings;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
use ty_python_core::program::{FallibleStrategy, Program, ProgramSettings};
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
@@ -737,7 +738,7 @@ pub(crate) mod testing {
|
||||
}
|
||||
|
||||
pub fn program_environment(&self) -> ProgramEnvironment<'_> {
|
||||
ProgramEnvironment::from_program(self.python_version())
|
||||
ProgramEnvironment::from_program(Program::get(self).resolver_environment(self))
|
||||
}
|
||||
|
||||
/// Takes the salsa events.
|
||||
@@ -774,11 +775,7 @@ pub(crate) mod testing {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for TestDb {
|
||||
fn search_paths(&self) -> &ty_module_resolver::SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
impl ty_module_resolver::Db for TestDb {}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_python_core::Db for TestDb {
|
||||
@@ -794,6 +791,10 @@ pub(crate) mod testing {
|
||||
crate::check_file(self, file)
|
||||
}
|
||||
|
||||
fn program_file(&self, file: File) -> ProgramFile<'_> {
|
||||
Program::get(self).program_file(self, file)
|
||||
}
|
||||
|
||||
fn rule_selection(&self, _file: ruff_db::files::File) -> &RuleSelection {
|
||||
self.project().rules(self)
|
||||
}
|
||||
@@ -844,8 +845,9 @@ mod tests {
|
||||
use ruff_db::files::FileRootKind;
|
||||
use ruff_db::system::{SystemPathBuf, TestSystem};
|
||||
use ty_module_resolver::list_modules;
|
||||
use ty_python_core::program::Program;
|
||||
|
||||
use crate::{Db as _, ProjectDatabase, ProjectMetadata};
|
||||
use crate::{ProjectDatabase, ProjectMetadata};
|
||||
|
||||
#[test]
|
||||
fn frozen_inputs_support_a_one_shot_check() -> anyhow::Result<()> {
|
||||
@@ -889,7 +891,7 @@ mod tests {
|
||||
let metadata = ProjectMetadata::discover(&project, &system)?;
|
||||
let db = ProjectDatabase::fallible(metadata, system)?;
|
||||
|
||||
let modules = list_modules(&db, db.python_version());
|
||||
let modules = list_modules(&db, Program::get(&db).resolver_environment(&db));
|
||||
assert!(
|
||||
modules
|
||||
.iter()
|
||||
|
||||
@@ -14,7 +14,6 @@ use files::{Index, Indexed, IndexedFiles};
|
||||
use metadata::settings::Settings;
|
||||
pub use metadata::{ProjectMetadata, ProjectMetadataError};
|
||||
use rayon::prelude::*;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticId, Severity, SubDiagnostic, SubDiagnosticSeverity,
|
||||
};
|
||||
@@ -28,6 +27,7 @@ use std::collections::{BTreeSet, hash_set};
|
||||
use std::iter::FusedIterator;
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::sync::Arc;
|
||||
use ty_python_core::ProgramFile;
|
||||
pub use ty_python_semantic::Db as SemanticDb;
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
|
||||
@@ -394,15 +394,16 @@ impl Project {
|
||||
let check_file_span =
|
||||
tracing::debug_span!(parent: &project_span, "check_file", ?file);
|
||||
let _entered = check_file_span.entered();
|
||||
let python_file = PythonFile::new(db, file, db.python_version());
|
||||
let program_file = db.program_file(file);
|
||||
|
||||
match check_file_impl(db, python_file) {
|
||||
match check_file_impl(db, program_file) {
|
||||
Ok(diagnostics) => {
|
||||
reporter.report_checked_file(db, file, diagnostics);
|
||||
|
||||
// This is outside `check_file_impl` to avoid that opening or closing
|
||||
// a file invalidates the `check_file_impl` query of every file!
|
||||
if !open_files.contains(&file) {
|
||||
let python_file = program_file.python_file(db);
|
||||
// The module has already been parsed by `check_file_impl`.
|
||||
// We only retrieve it here so that we can call `clear` on it.
|
||||
let parsed = parsed_module(db, python_file);
|
||||
@@ -660,7 +661,7 @@ fn check_file(db: &dyn Db, file: File) -> Vec<Diagnostic> {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
check_file_impl(db, PythonFile::new(db, file, db.python_version()))
|
||||
check_file_impl(db, db.program_file(file))
|
||||
.map(<[Diagnostic]>::to_vec)
|
||||
.unwrap_or_else(|diagnostic| vec![diagnostic.clone()])
|
||||
}
|
||||
@@ -744,7 +745,7 @@ pub enum ProjectReloadResult {
|
||||
#[salsa::tracked(returns(as_deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn check_file_impl(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
) -> Result<Box<[Diagnostic]>, Diagnostic> {
|
||||
let source_file = file.file(db);
|
||||
{
|
||||
@@ -894,11 +895,11 @@ mod tests {
|
||||
use crate::db::Db as _;
|
||||
use crate::db::testing::TestDb;
|
||||
use crate::{IncludeResult, ProjectMetadata};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ty_python_semantic::Db as _;
|
||||
use ty_python_semantic::types::check_types;
|
||||
|
||||
#[test]
|
||||
@@ -917,7 +918,7 @@ mod tests {
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
assert_eq!(
|
||||
check_file_impl(&db, PythonFile::new(&db, file, db.python_version()))
|
||||
check_file_impl(&db, db.program_file(file))
|
||||
.as_ref()
|
||||
.unwrap_err()
|
||||
.headline_message()
|
||||
@@ -926,12 +927,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let events = db.take_salsa_events();
|
||||
assert_function_query_was_not_run(
|
||||
&db,
|
||||
check_types,
|
||||
PythonFile::new(&db, file, db.python_version()),
|
||||
&events,
|
||||
);
|
||||
assert_function_query_was_not_run(&db, check_types, db.program_file(file), &events);
|
||||
|
||||
// The user now creates a new file with an empty text. The source text
|
||||
// content returned by `source_text` remains unchanged, but the diagnostics should get updated.
|
||||
@@ -939,7 +935,7 @@ mod tests {
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
assert_eq!(
|
||||
check_file_impl(&db, PythonFile::new(&db, file, db.python_version()))
|
||||
check_file_impl(&db, db.program_file(file))
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
|
||||
@@ -6,6 +6,7 @@ use tracing::info;
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ty_module_resolver::system_module_search_paths;
|
||||
use ty_python_core::program::Program;
|
||||
|
||||
use crate::db::{Db, ProjectDatabase};
|
||||
use crate::watch::Watcher;
|
||||
@@ -40,7 +41,8 @@ impl ProjectWatcher {
|
||||
}
|
||||
|
||||
pub fn update(&mut self, db: &ProjectDatabase) {
|
||||
let search_paths: Vec<_> = system_module_search_paths(db).collect();
|
||||
let environment = Program::get(db).resolver_environment(db);
|
||||
let search_paths: Vec<_> = system_module_search_paths(db, environment).collect();
|
||||
let project_path = db.project().root(db);
|
||||
|
||||
let new_cache_key = Self::compute_cache_key(project_path, &search_paths);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_index::{IndexVec, newtype_index};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::ExprRef;
|
||||
|
||||
use crate::Db;
|
||||
use crate::ProgramFile;
|
||||
use crate::frozen::FrozenMap;
|
||||
use crate::scope::FileScopeId;
|
||||
use crate::semantic_index;
|
||||
@@ -55,7 +55,7 @@ impl AstIds {
|
||||
}
|
||||
}
|
||||
|
||||
fn ast_ids<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> &'db AstIds {
|
||||
fn ast_ids<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> &'db AstIds {
|
||||
semantic_index(db, file).ast_ids()
|
||||
}
|
||||
|
||||
@@ -66,46 +66,46 @@ pub struct ScopedUseId;
|
||||
|
||||
pub trait HasScopedUseId {
|
||||
/// Returns the ID that uniquely identifies the use in its scope.
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId;
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId;
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::Identifier {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let ast_ids = ast_ids(db, file);
|
||||
ast_ids.use_id(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprName {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let expression_ref = ExprRef::from(self);
|
||||
expression_ref.scoped_use_id(db, file)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprAttribute {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let expression_ref = ExprRef::from(self);
|
||||
expression_ref.scoped_use_id(db, file)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprSubscript {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let expression_ref = ExprRef::from(self);
|
||||
expression_ref.scoped_use_id(db, file)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::Keyword {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let ast_ids = ast_ids(db, file);
|
||||
ast_ids.use_id(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprRef<'_> {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: PythonFile<'_>) -> ScopedUseId {
|
||||
fn scoped_use_id(&self, db: &dyn Db, file: ProgramFile<'_>) -> ScopedUseId {
|
||||
let ast_ids = ast_ids(db, file);
|
||||
ast_ids.use_id(*self)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::source::{SourceText, source_text};
|
||||
use ruff_index::IndexVec;
|
||||
use ruff_python_ast::name::Name;
|
||||
@@ -20,9 +19,10 @@ use ruff_python_parser::semantic_errors::{
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use smallvec::SmallVec;
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, ResolverEnvironment, resolve_module};
|
||||
|
||||
use crate::HasTrackedScope;
|
||||
use crate::ProgramFile;
|
||||
use crate::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::ast_ids::{AstIdsBuilder, ScopedUseId};
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
@@ -229,7 +229,7 @@ impl ConditionFlowSnapshot {
|
||||
pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
// Builder state
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
source_type: PySourceType,
|
||||
module: &'ast ParsedModuleRef,
|
||||
scope_stack: Vec<ScopeInfo<'ast>>,
|
||||
@@ -253,7 +253,7 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
in_type_checking_block: bool,
|
||||
|
||||
// Used for checking semantic syntax errors
|
||||
python_version: PythonVersion,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
source_text: OnceCell<SourceText>,
|
||||
semantic_checker: SemanticSyntaxChecker,
|
||||
in_try: bool,
|
||||
@@ -302,7 +302,7 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
pub(super) fn new(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
module_ref: &'ast ParsedModuleRef,
|
||||
) -> Self {
|
||||
let mut builder = Self {
|
||||
@@ -344,7 +344,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
|
||||
enclosing_snapshots: FxHashMap::default(),
|
||||
|
||||
python_version: file.python_version(db),
|
||||
resolver_environment: file.resolver_environment(db),
|
||||
source_text: OnceCell::new(),
|
||||
semantic_checker: SemanticSyntaxChecker::default(),
|
||||
in_try: false,
|
||||
@@ -977,7 +977,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
symbol.name().to_string(),
|
||||
),
|
||||
range: declaration.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
// This `nonlocal` is resolved.
|
||||
@@ -1040,7 +1040,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalWithoutBinding(name.to_string()),
|
||||
range: declaration.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3166,14 +3166,18 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
// in one function is visible in another function.
|
||||
let mut is_self_import = false;
|
||||
let source_file = self.file.file(self.db);
|
||||
let resolver_environment = self.resolver_environment;
|
||||
if source_file.is_package(self.db)
|
||||
&& let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
self.db,
|
||||
self.file,
|
||||
ImportingFile::File(source_file, resolver_environment),
|
||||
node.module.as_deref(),
|
||||
node.level,
|
||||
)
|
||||
&& let Ok(thispackage) = ModuleName::package_for_file(self.db, self.file)
|
||||
&& let Ok(thispackage) = ModuleName::package_for_file(
|
||||
self.db,
|
||||
ImportingFile::File(source_file, resolver_environment),
|
||||
)
|
||||
{
|
||||
// Record whether this is equivalent to `from . import ...`
|
||||
is_self_import = module_name == thispackage;
|
||||
@@ -3246,19 +3250,27 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(module_name) =
|
||||
ModuleName::from_import_statement(self.db, self.file, node)
|
||||
else {
|
||||
let Ok(module_name) = ModuleName::from_import_statement(
|
||||
self.db,
|
||||
ImportingFile::File(source_file, resolver_environment),
|
||||
node,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(module) = resolve_module(self.db, self.file, &module_name) else {
|
||||
let Some(module) = resolve_module(
|
||||
self.db,
|
||||
ImportingFile::File(source_file, resolver_environment),
|
||||
&module_name,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(referenced_parse_file) = module.python_file(self.db) else {
|
||||
let Some(referenced_file) = module.file(self.db) else {
|
||||
continue;
|
||||
};
|
||||
let referenced_program_file =
|
||||
ProgramFile::new(self.db, referenced_file, self.file.program(self.db));
|
||||
// In order to understand the reachability of definitions created by a `*` import,
|
||||
// we need to know the reachability of the global-scope definitions in the
|
||||
// `referenced_module` the symbols imported from. Much like predicates for `if`
|
||||
@@ -3273,14 +3285,14 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
// ```
|
||||
//
|
||||
// For more details, see the doc-comment on `StarImportPlaceholderPredicate`.
|
||||
for export in exported_names(self.db, referenced_parse_file) {
|
||||
for export in exported_names(self.db, referenced_program_file) {
|
||||
let symbol_id = self.add_symbol(export.clone());
|
||||
let node_ref = StarImportDefinitionNodeRef { node, symbol_id };
|
||||
let star_import = StarImportPlaceholderPredicate::new(
|
||||
self.db,
|
||||
self.file,
|
||||
symbol_id,
|
||||
referenced_parse_file,
|
||||
referenced_program_file,
|
||||
);
|
||||
|
||||
let star_import_predicate = self.add_predicate(star_import.into());
|
||||
@@ -3461,7 +3473,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
// Check whether the variable has been declared nonlocal.
|
||||
@@ -3471,7 +3483,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
name.id.as_str().into(),
|
||||
),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4230,7 +4242,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
start: name.range.start(),
|
||||
},
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
// Check whether the variable has also been declared nonlocal.
|
||||
@@ -4238,7 +4250,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
// Never mark a symbol both global and nonlocal, even in this error case.
|
||||
continue;
|
||||
@@ -4283,7 +4295,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
start: name.range.start(),
|
||||
},
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
}
|
||||
// Check whether the variable has also been declared global.
|
||||
@@ -4291,7 +4303,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
python_version: self.python_version(),
|
||||
});
|
||||
// Never mark a symbol both global and nonlocal, even in this error case.
|
||||
continue;
|
||||
@@ -5003,7 +5015,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||
}
|
||||
|
||||
fn python_version(&self) -> PythonVersion {
|
||||
self.python_version
|
||||
self.resolver_environment.python_version(self.db)
|
||||
}
|
||||
|
||||
fn source(&self) -> &str {
|
||||
|
||||
@@ -21,9 +21,7 @@ pub(crate) mod tests {
|
||||
};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{
|
||||
Db as ModuleResolverDb, FallibleStrategy, SearchPathSettings, SearchPaths,
|
||||
};
|
||||
use ty_module_resolver::{Db as ModuleResolverDb, FallibleStrategy, SearchPathSettings};
|
||||
use ty_site_packages::{PythonVersionSource, PythonVersionWithSource};
|
||||
|
||||
use crate::platform::PythonPlatform;
|
||||
@@ -93,11 +91,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ModuleResolverDb for TestDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
impl ModuleResolverDb for TestDb {}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {}
|
||||
|
||||
@@ -11,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::LoopHeaderId;
|
||||
use crate::ProgramFile;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::member::ScopedMemberId;
|
||||
use crate::node_key::NodeKey;
|
||||
@@ -88,7 +89,11 @@ impl<'db> Definition<'db> {
|
||||
self.scope_id(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program {
|
||||
pub fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
self.scope_id(db).program_file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.scope_id(db).program(db)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::Program;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::db::Db;
|
||||
use crate::scope::ScopeId;
|
||||
use crate::{Program, ProgramFile};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
@@ -80,7 +80,11 @@ impl<'db> Expression<'db> {
|
||||
self.scope_id(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program {
|
||||
pub fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
self.scope_id(db).program_file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.scope_id(db).program(db)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use std::sync::Arc;
|
||||
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_index::{FrozenIndexVec, IndexSlice};
|
||||
use ruff_python_ast::NodeIndex;
|
||||
use ruff_python_parser::semantic_errors::SemanticSyntaxError;
|
||||
@@ -16,11 +15,11 @@ use ruff_text_size::TextRange;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa::plumbing::AsId;
|
||||
use smallvec::SmallVec;
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_module_resolver::{ModuleName, ResolverEnvironment};
|
||||
|
||||
// FIXME: Replace this temporary alias once semantic query keys can use the environment-bearing
|
||||
// `Program` Salsa ingredient directly.
|
||||
pub type Program = ast::PythonVersion;
|
||||
pub type Program<'db> = ResolverEnvironment<'db>;
|
||||
|
||||
use crate::frozen::{FrozenMap, FrozenSet};
|
||||
use crate::place::ScopedPlaceId;
|
||||
@@ -66,15 +65,17 @@ pub mod unpack;
|
||||
mod use_def;
|
||||
pub use db::Db;
|
||||
pub mod program;
|
||||
pub mod program_file;
|
||||
pub use program_file::ProgramFile;
|
||||
|
||||
/// Returns the semantic index for `file`.
|
||||
///
|
||||
/// Prefer using [`symbol_table`] when working with symbols from a single scope.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn semantic_index<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> SemanticIndex<'db> {
|
||||
pub fn semantic_index<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> SemanticIndex<'db> {
|
||||
let _span = tracing::trace_span!("semantic_index", ?file).entered();
|
||||
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
|
||||
SemanticIndexBuilder::new(db, file, &module).build()
|
||||
}
|
||||
@@ -86,9 +87,9 @@ pub fn semantic_index<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> SemanticIn
|
||||
/// is unchanged.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<PlaceTable> {
|
||||
let python_file = scope.python_file(db);
|
||||
let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?python_file).entered();
|
||||
let index = semantic_index(db, python_file);
|
||||
let program_file = scope.program_file(db);
|
||||
let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?program_file).entered();
|
||||
let index = semantic_index(db, program_file);
|
||||
Arc::clone(&index.place_tables[scope.file_scope_id(db)])
|
||||
}
|
||||
|
||||
@@ -99,9 +100,9 @@ pub fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<PlaceTable>
|
||||
/// is unchanged.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn use_def_map<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<UseDefMap<'db>> {
|
||||
let python_file = scope.python_file(db);
|
||||
let _span = tracing::trace_span!("use_def_map", scope=?scope.as_id(), ?python_file).entered();
|
||||
let index = semantic_index(db, python_file);
|
||||
let program_file = scope.program_file(db);
|
||||
let _span = tracing::trace_span!("use_def_map", scope=?scope.as_id(), ?program_file).entered();
|
||||
let index = semantic_index(db, program_file);
|
||||
Arc::clone(&index.use_def_maps[scope.file_scope_id(db)])
|
||||
}
|
||||
|
||||
@@ -176,7 +177,7 @@ pub fn attribute_scopes<'db>(
|
||||
db: &'db dyn Db,
|
||||
class_body_scope: ScopeId<'db>,
|
||||
) -> impl Iterator<Item = FileScopeId> + 'db {
|
||||
let index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let index = semantic_index(db, class_body_scope.program_file(db));
|
||||
let class_scope_id = class_body_scope.file_scope_id(db);
|
||||
ChildrenIter::new(&index.scopes, class_scope_id)
|
||||
.filter_map(move |(child_scope_id, scope)| {
|
||||
@@ -225,7 +226,7 @@ pub fn attribute_scopes<'db>(
|
||||
|
||||
/// Returns the module global scope of `file`.
|
||||
#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn global_scope<'db>(db: &'db dyn Db, file: PythonFile<'db>) -> ScopeId<'db> {
|
||||
pub fn global_scope<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> ScopeId<'db> {
|
||||
let _span = tracing::trace_span!("global_scope", ?file).entered();
|
||||
|
||||
FileScopeId::global().to_scope_id(db, file)
|
||||
@@ -1124,8 +1125,8 @@ mod tests {
|
||||
TestCase { db, file }
|
||||
}
|
||||
|
||||
fn python_file(db: &TestDb, file: File) -> PythonFile<'_> {
|
||||
PythonFile::new(db, file, Program::get(db).python_version(db))
|
||||
fn program_file(db: &TestDb, file: File) -> ProgramFile<'_> {
|
||||
Program::get(db).program_file(db, file)
|
||||
}
|
||||
|
||||
fn names(table: &PlaceTable) -> Vec<String> {
|
||||
@@ -1138,7 +1139,7 @@ mod tests {
|
||||
#[test]
|
||||
fn empty() {
|
||||
let TestCase { db, file } = test_case("");
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
let global_names = names(global_table);
|
||||
|
||||
@@ -1148,7 +1149,7 @@ mod tests {
|
||||
#[test]
|
||||
fn simple() {
|
||||
let TestCase { db, file } = test_case("x");
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert_eq!(names(global_table), vec!["x"]);
|
||||
}
|
||||
@@ -1156,7 +1157,7 @@ mod tests {
|
||||
#[test]
|
||||
fn annotation_only() {
|
||||
let TestCase { db, file } = test_case("x: int");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["int", "x"]);
|
||||
@@ -1174,7 +1175,7 @@ mod tests {
|
||||
#[test]
|
||||
fn import() {
|
||||
let TestCase { db, file } = test_case("import foo");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
@@ -1188,7 +1189,7 @@ mod tests {
|
||||
#[test]
|
||||
fn import_sub() {
|
||||
let TestCase { db, file } = test_case("import foo.bar");
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
}
|
||||
@@ -1196,7 +1197,7 @@ mod tests {
|
||||
#[test]
|
||||
fn import_as() {
|
||||
let TestCase { db, file } = test_case("import foo.bar as baz");
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert_eq!(names(global_table), vec!["baz"]);
|
||||
}
|
||||
@@ -1204,7 +1205,7 @@ mod tests {
|
||||
#[test]
|
||||
fn import_from() {
|
||||
let TestCase { db, file } = test_case("from bar import foo");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
@@ -1225,7 +1226,7 @@ mod tests {
|
||||
#[test]
|
||||
fn assign() {
|
||||
let TestCase { db, file } = test_case("x = foo");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo", "x"]);
|
||||
@@ -1245,7 +1246,7 @@ mod tests {
|
||||
#[test]
|
||||
fn augmented_assignment() {
|
||||
let TestCase { db, file } = test_case("x += 1");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["x"]);
|
||||
@@ -1270,12 +1271,12 @@ class C:
|
||||
y = 2
|
||||
",
|
||||
);
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert_eq!(names(global_table), vec!["C", "y"]);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
|
||||
let [(class_scope_id, class_scope)] = index
|
||||
.child_scopes(FileScopeId::global())
|
||||
@@ -1286,7 +1287,7 @@ y = 2
|
||||
assert_eq!(class_scope.kind(), ScopeKind::Class);
|
||||
assert_eq!(
|
||||
class_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"C"
|
||||
);
|
||||
@@ -1310,8 +1311,8 @@ def func():
|
||||
y = 2
|
||||
",
|
||||
);
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["func", "y"]);
|
||||
@@ -1325,7 +1326,7 @@ y = 2
|
||||
assert_eq!(function_scope.kind(), ScopeKind::Function);
|
||||
assert_eq!(
|
||||
function_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"func"
|
||||
);
|
||||
@@ -1349,8 +1350,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert_eq!(names(global_table), vec!["str", "int", "f"]);
|
||||
|
||||
@@ -1394,8 +1395,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
fn lambda_parameter_symbols() {
|
||||
let TestCase { db, file } = test_case("lambda a, b, c=1, *args, d=2, **kwargs: None");
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, python_file(&db, file)));
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
|
||||
|
||||
assert!(names(global_table).is_empty());
|
||||
|
||||
@@ -1460,8 +1461,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
",
|
||||
);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["iter1"]);
|
||||
@@ -1476,7 +1477,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension);
|
||||
assert_eq!(
|
||||
comprehension_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"<listcomp>"
|
||||
);
|
||||
@@ -1511,7 +1512,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let [(comprehension_scope_id, _)] = index
|
||||
.child_scopes(FileScopeId::global())
|
||||
.collect::<Vec<_>>()[..]
|
||||
@@ -1521,7 +1522,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(comprehension_scope_id);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let syntax = module.syntax();
|
||||
let element = syntax.body[0]
|
||||
.as_expr_stmt()
|
||||
@@ -1532,7 +1533,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
.elt
|
||||
.as_name_expr()
|
||||
.unwrap();
|
||||
let element_use_id = element.scoped_use_id(&db, python_file(&db, file));
|
||||
let element_use_id = element.scoped_use_id(&db, program_file(&db, file));
|
||||
|
||||
let binding = use_def.first_binding_at_use(element_use_id).unwrap();
|
||||
let DefinitionKind::Comprehension(comprehension) = binding.kind(&db) else {
|
||||
@@ -1556,8 +1557,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
",
|
||||
);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["iter1"]);
|
||||
@@ -1572,7 +1573,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension);
|
||||
assert_eq!(
|
||||
comprehension_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"<listcomp>"
|
||||
);
|
||||
@@ -1591,7 +1592,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
assert_eq!(inner_comprehension_scope.kind(), ScopeKind::Comprehension);
|
||||
assert_eq!(
|
||||
inner_comprehension_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"<setcomp>"
|
||||
);
|
||||
@@ -1610,7 +1611,7 @@ with item1 as x, item2 as y:
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["item1", "x", "item2", "y"]);
|
||||
@@ -1633,7 +1634,7 @@ with context() as (x, y):
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["context", "x", "y"]);
|
||||
@@ -1657,8 +1658,8 @@ def func():
|
||||
y = 2
|
||||
",
|
||||
);
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["func"]);
|
||||
@@ -1676,14 +1677,14 @@ def func():
|
||||
|
||||
assert_eq!(
|
||||
func_scope1_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"func"
|
||||
);
|
||||
assert_eq!(func_scope_2.kind(), ScopeKind::Function);
|
||||
assert_eq!(
|
||||
func_scope2_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"func"
|
||||
);
|
||||
@@ -1709,8 +1710,8 @@ def func[T]():
|
||||
",
|
||||
);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["func"]);
|
||||
@@ -1725,7 +1726,7 @@ def func[T]():
|
||||
assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
|
||||
assert_eq!(
|
||||
ann_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"func"
|
||||
);
|
||||
@@ -1740,7 +1741,7 @@ def func[T]():
|
||||
assert_eq!(func_scope.kind(), ScopeKind::Function);
|
||||
assert_eq!(
|
||||
func_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"func"
|
||||
);
|
||||
@@ -1757,8 +1758,8 @@ class C[T]:
|
||||
",
|
||||
);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(global_table), vec!["C"]);
|
||||
@@ -1773,7 +1774,7 @@ class C[T]:
|
||||
assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
|
||||
assert_eq!(
|
||||
ann_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"C"
|
||||
);
|
||||
@@ -1795,7 +1796,7 @@ class C[T]:
|
||||
assert_eq!(class_scope.kind(), ScopeKind::Class);
|
||||
assert_eq!(
|
||||
class_scope_id
|
||||
.to_scope_id(&db, python_file(&db, file))
|
||||
.to_scope_id(&db, program_file(&db, file))
|
||||
.name(&db, &module),
|
||||
"C"
|
||||
);
|
||||
@@ -1805,8 +1806,8 @@ class C[T]:
|
||||
#[test]
|
||||
fn reachability_trivial() {
|
||||
let TestCase { db, file } = test_case("x = 1; x");
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let ast = module.syntax();
|
||||
let ast::Stmt::Expr(ast::StmtExpr {
|
||||
value: x_use_expr, ..
|
||||
@@ -1817,7 +1818,7 @@ class C[T]:
|
||||
let ast::Expr::Name(x_use_expr_name) = x_use_expr.as_ref() else {
|
||||
panic!("expected a Name");
|
||||
};
|
||||
let x_use_id = x_use_expr_name.scoped_use_id(&db, python_file(&db, file));
|
||||
let x_use_id = x_use_expr_name.scoped_use_id(&db, program_file(&db, file));
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def.first_binding_at_use(x_use_id).unwrap();
|
||||
let DefinitionKind::Assignment(assignment) = binding.kind(&db) else {
|
||||
@@ -1837,8 +1838,8 @@ class C[T]:
|
||||
fn expression_scope() {
|
||||
let TestCase { db, file } = test_case("x = 1;\ndef test():\n y = 4");
|
||||
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let ast = module.syntax();
|
||||
|
||||
let x_stmt = ast.body[0].as_assign_stmt().unwrap();
|
||||
@@ -1866,10 +1867,7 @@ class C[T]:
|
||||
.into_iter()
|
||||
.map(|(scope_id, _)| {
|
||||
scope_id
|
||||
.to_scope_id(
|
||||
db,
|
||||
PythonFile::new(db, file, Program::get(db).python_version(db)),
|
||||
)
|
||||
.to_scope_id(db, Program::get(db).program_file(db, file))
|
||||
.name(db, module)
|
||||
})
|
||||
.collect()
|
||||
@@ -1888,8 +1886,8 @@ def x():
|
||||
pass",
|
||||
);
|
||||
|
||||
let module = parsed_module(&db, python_file(&db, file)).load(&db);
|
||||
let index = semantic_index(&db, python_file(&db, file));
|
||||
let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
|
||||
let index = semantic_index(&db, program_file(&db, file));
|
||||
|
||||
let descendants = index.descendent_scopes(FileScopeId::global());
|
||||
assert_eq!(
|
||||
@@ -1935,7 +1933,7 @@ match subject:
|
||||
",
|
||||
);
|
||||
|
||||
let global_scope_id = global_scope(&db, python_file(&db, file));
|
||||
let global_scope_id = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope_id);
|
||||
|
||||
assert!(global_table.symbol_by_name("Foo").unwrap().is_used());
|
||||
@@ -1967,7 +1965,7 @@ match 1:
|
||||
",
|
||||
);
|
||||
|
||||
let global_scope_id = global_scope(&db, python_file(&db, file));
|
||||
let global_scope_id = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, global_scope_id);
|
||||
|
||||
assert_eq!(names(global_table), vec!["first", "second"]);
|
||||
@@ -1984,7 +1982,7 @@ match 1:
|
||||
#[test]
|
||||
fn for_loops_single_assignment() {
|
||||
let TestCase { db, file } = test_case("for x in a: pass");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["a", "x"]);
|
||||
@@ -2000,7 +1998,7 @@ match 1:
|
||||
#[test]
|
||||
fn for_loops_simple_unpacking() {
|
||||
let TestCase { db, file } = test_case("for (x, y) in a: pass");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["a", "x", "y"]);
|
||||
@@ -2020,7 +2018,7 @@ match 1:
|
||||
#[test]
|
||||
fn for_loops_complex_unpacking() {
|
||||
let TestCase { db, file } = test_case("for [((a,) b), (c, d)] in e: pass");
|
||||
let scope = global_scope(&db, python_file(&db, file));
|
||||
let scope = global_scope(&db, program_file(&db, file));
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["e", "a", "b", "c", "d"]);
|
||||
|
||||
@@ -13,6 +13,7 @@ use ruff_db::files::File;
|
||||
use ruff_index::{FrozenIndexVec, Idx, IndexVec};
|
||||
use ruff_python_ast::{Singleton, name::Name};
|
||||
|
||||
use crate::ProgramFile;
|
||||
use crate::ast_ids::ExpressionNodeKey;
|
||||
use crate::db::Db;
|
||||
use crate::expression::Expression;
|
||||
@@ -233,7 +234,7 @@ pub enum PatternPredicateKind<'db> {
|
||||
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct PatternPredicate<'db> {
|
||||
#[returns(copy)]
|
||||
pub python_file: PythonFile<'db>,
|
||||
pub program_file: ProgramFile<'db>,
|
||||
|
||||
#[returns(copy)]
|
||||
pub file_scope: FileScopeId,
|
||||
@@ -257,14 +258,18 @@ impl get_size2::GetSize for PatternPredicate<'_> {}
|
||||
|
||||
impl<'db> PatternPredicate<'db> {
|
||||
pub fn file(self, db: &'db dyn Db) -> File {
|
||||
self.python_file(db).file(db)
|
||||
self.program_file(db).file(db)
|
||||
}
|
||||
|
||||
pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
|
||||
self.program_file(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.file_scope(db).to_scope_id(db, self.python_file(db))
|
||||
self.file_scope(db).to_scope_id(db, self.program_file(db))
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program {
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.scope(db).program(db)
|
||||
}
|
||||
}
|
||||
@@ -312,7 +317,7 @@ impl<'db> PatternPredicate<'db> {
|
||||
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct StarImportPlaceholderPredicate<'db> {
|
||||
#[returns(copy)]
|
||||
pub importing_parse_file: PythonFile<'db>,
|
||||
pub importing_file: ProgramFile<'db>,
|
||||
|
||||
/// Each symbol imported by a `*` import has a separate predicate associated with it:
|
||||
/// this field identifies which symbol that is.
|
||||
@@ -327,7 +332,7 @@ pub struct StarImportPlaceholderPredicate<'db> {
|
||||
pub symbol_id: ScopedSymbolId,
|
||||
|
||||
#[returns(copy)]
|
||||
pub referenced_parse_file: PythonFile<'db>,
|
||||
pub referenced_file: ProgramFile<'db>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
@@ -337,7 +342,7 @@ impl<'db> StarImportPlaceholderPredicate<'db> {
|
||||
pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
// See doc-comment above [`StarImportPlaceholderPredicate::symbol_id`]:
|
||||
// valid `*`-import definitions can only take place in the global scope.
|
||||
global_scope(db, self.importing_parse_file(db))
|
||||
global_scope(db, self.importing_file(db))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use crate::{Db, platform::PythonPlatform};
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::system::SystemPath;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use salsa::Durability;
|
||||
use salsa::Setter;
|
||||
use ty_module_resolver::SearchPaths;
|
||||
use ty_module_resolver::{ResolverEnvironment, SearchPaths};
|
||||
use ty_site_packages::PythonVersionWithSource;
|
||||
|
||||
use crate::ProgramFile;
|
||||
|
||||
// Re-export the misconfiguration strategy types from ty_module_resolver.
|
||||
pub use ty_module_resolver::{FallibleStrategy, MisconfigurationStrategy, UseDefaultStrategy};
|
||||
|
||||
@@ -22,6 +25,7 @@ pub struct Program {
|
||||
pub search_paths: SearchPaths,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl Program {
|
||||
pub fn init_or_update(db: &mut dyn Db, settings: ProgramSettings) -> Self {
|
||||
match Self::try_get(db) {
|
||||
@@ -51,6 +55,15 @@ impl Program {
|
||||
self.python_version_with_source(db).version
|
||||
}
|
||||
|
||||
/// Returns the module-resolution environment for this program.
|
||||
pub fn resolver_environment(self, db: &dyn Db) -> ResolverEnvironment<'_> {
|
||||
ResolverEnvironment::new(db, self.python_version(db), self.search_paths(db))
|
||||
}
|
||||
|
||||
pub fn program_file(self, db: &dyn Db, file: File) -> ProgramFile<'_> {
|
||||
ProgramFile::new(db, file, self.resolver_environment(db))
|
||||
}
|
||||
|
||||
pub fn update_from_settings(self, db: &mut dyn Db, settings: ProgramSettings) {
|
||||
let ProgramSettings {
|
||||
python_version,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{ResolverEnvironment, ResolverFile};
|
||||
|
||||
use crate::{Db, Program};
|
||||
|
||||
/// A file interpreted within a particular Python program.
|
||||
///
|
||||
/// The same file can participate in multiple programs, each with different Python versions, search
|
||||
/// paths, or other settings that affect type inference.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```text
|
||||
/// project/
|
||||
/// ├── app.py # Project program: Python 3.11
|
||||
/// ├── generate.py # Script program: Python 3.12
|
||||
/// └── shared.py # Imported by both
|
||||
/// ```
|
||||
///
|
||||
/// In `shared.py`, version-dependent code can produce different types:
|
||||
///
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info >= (3, 12):
|
||||
/// value = 1
|
||||
/// else:
|
||||
/// value = "one"
|
||||
/// ```
|
||||
///
|
||||
/// The two interpretations therefore need separate semantic identities:
|
||||
///
|
||||
/// ```text
|
||||
/// ProgramFile(shared.py, project program) -> value: str
|
||||
/// ProgramFile(shared.py, script program) -> value: int
|
||||
/// ```
|
||||
///
|
||||
/// Semantic queries, such as `semantic_index`, use `ProgramFile` to avoid sharing results between
|
||||
/// incompatible programs. Lower-level operations use narrower identities where possible:
|
||||
///
|
||||
/// ```text
|
||||
/// program_file.python_file(db) -> File + Python version
|
||||
/// program_file.resolver_file(db) -> File + resolver environment
|
||||
/// ```
|
||||
///
|
||||
/// This allows programs with the same Python version to share parsed syntax, and programs with
|
||||
/// equivalent resolver environments to share module resolution, while keeping type inference
|
||||
/// isolated.
|
||||
#[salsa::interned(
|
||||
debug,
|
||||
constructor = new_internal,
|
||||
heap_size = ruff_memory_usage::heap_size
|
||||
)]
|
||||
pub struct ProgramFile<'db> {
|
||||
/// The cached parser key for `file` and the environment's Python version.
|
||||
#[returns(copy)]
|
||||
pub python_file: PythonFile<'db>,
|
||||
|
||||
#[returns(copy)]
|
||||
pub resolver_environment: ResolverEnvironment<'db>,
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for ProgramFile<'_> {}
|
||||
|
||||
impl<'db> ProgramFile<'db> {
|
||||
pub fn new(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
resolver_environment: ResolverEnvironment<'db>,
|
||||
) -> Self {
|
||||
let python_file = PythonFile::new(db, file, resolver_environment.python_version(db));
|
||||
Self::new_internal(db, python_file, resolver_environment)
|
||||
}
|
||||
|
||||
/// Returns the physical file represented by this program file.
|
||||
pub fn file(self, db: &'db dyn Db) -> File {
|
||||
self.python_file(db).file(db)
|
||||
}
|
||||
|
||||
/// Returns the resolver key for this file.
|
||||
pub fn resolver_file(self, db: &'db dyn Db) -> ResolverFile<'db> {
|
||||
ResolverFile::new(db, self.file(db), self.resolver_environment(db))
|
||||
}
|
||||
|
||||
/// Returns the program associated with this file.
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.resolver_environment(db)
|
||||
}
|
||||
|
||||
/// Returns the Python version associated with this file's resolver environment.
|
||||
pub fn python_version(self, db: &'db dyn Db) -> PythonVersion {
|
||||
self.resolver_environment(db).python_version(db)
|
||||
}
|
||||
}
|
||||
@@ -22,24 +22,23 @@
|
||||
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
name::Name,
|
||||
visitor::{Visitor, walk_expr, walk_pattern, walk_stmt},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, resolve_module};
|
||||
|
||||
use crate::Db;
|
||||
use crate::{Db, ProgramFile};
|
||||
|
||||
#[salsa::tracked(
|
||||
returns(deref),
|
||||
cycle_initial=|_, _, _| Box::default(),
|
||||
heap_size=ruff_memory_usage::heap_size)
|
||||
]
|
||||
pub(super) fn exported_names(db: &dyn Db, file: PythonFile<'_>) -> Box<[Name]> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
pub(super) fn exported_names(db: &dyn Db, file: ProgramFile<'_>) -> Box<[Name]> {
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let mut finder = ExportFinder::new(db, file);
|
||||
finder.visit_body(module.suite());
|
||||
|
||||
@@ -53,17 +52,17 @@ pub(super) fn exported_names(db: &dyn Db, file: PythonFile<'_>) -> Box<[Name]> {
|
||||
|
||||
struct ExportFinder<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
visiting_stub_file: bool,
|
||||
exports: FxHashMap<&'db Name, PossibleExportKind>,
|
||||
dunder_all: DunderAll,
|
||||
}
|
||||
|
||||
impl<'db> ExportFinder<'db> {
|
||||
fn new(db: &'db dyn Db, file: PythonFile<'db>) -> Self {
|
||||
fn new(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
program_file: file,
|
||||
visiting_stub_file: file.file(db).is_stub(db),
|
||||
exports: FxHashMap::default(),
|
||||
dunder_all: DunderAll::NotPresent,
|
||||
@@ -250,20 +249,35 @@ impl<'db> Visitor<'db> for ExportFinder<'db> {
|
||||
if &name.name.id == "*" {
|
||||
if !found_star {
|
||||
found_star = true;
|
||||
for export in
|
||||
ModuleName::from_import_statement(self.db, self.file, node)
|
||||
.ok()
|
||||
.and_then(|module_name| {
|
||||
resolve_module(self.db, self.file, &module_name)
|
||||
let db = self.db;
|
||||
let program_file = self.program_file;
|
||||
let file = program_file.file(db);
|
||||
let resolver_environment = program_file.resolver_environment(db);
|
||||
for export in ModuleName::from_import_statement(
|
||||
db,
|
||||
ImportingFile::File(file, resolver_environment),
|
||||
node,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|module_name| {
|
||||
resolve_module(
|
||||
db,
|
||||
ImportingFile::File(file, resolver_environment),
|
||||
&module_name,
|
||||
)
|
||||
})
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module
|
||||
.file(db)
|
||||
.map(|file| {
|
||||
exported_names(
|
||||
db,
|
||||
ProgramFile::new(db, file, program_file.program(db)),
|
||||
)
|
||||
})
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module
|
||||
.python_file(self.db)
|
||||
.map(|file| exported_names(self.db, file))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
{
|
||||
.unwrap_or_default()
|
||||
}) {
|
||||
self.possibly_add_export(export, PossibleExportKind::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_index::newtype_index;
|
||||
use ruff_python_ast::{self as ast, NodeIndex};
|
||||
|
||||
use crate::{
|
||||
Db, Program, SemanticIndex, ast_node_ref::AstNodeRef, definition::Definition,
|
||||
Db, Program, ProgramFile, SemanticIndex, ast_node_ref::AstNodeRef, definition::Definition,
|
||||
node_key::NodeKey, semantic_index,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct ScopeId<'db> {
|
||||
#[returns(copy)]
|
||||
pub python_file: PythonFile<'db>,
|
||||
pub program_file: ProgramFile<'db>,
|
||||
|
||||
#[returns(copy)]
|
||||
pub file_scope_id: FileScopeId,
|
||||
@@ -24,11 +24,15 @@ impl get_size2::GetSize for ScopeId<'_> {}
|
||||
|
||||
impl<'db> ScopeId<'db> {
|
||||
pub fn file(self, db: &dyn Db) -> File {
|
||||
self.python_file(db).file(db)
|
||||
self.program_file(db).file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &dyn Db) -> Program {
|
||||
self.python_file(db).python_version(db)
|
||||
pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
|
||||
self.program_file(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.program_file(db).program(db)
|
||||
}
|
||||
|
||||
pub fn is_annotation(self, db: &'db dyn Db) -> bool {
|
||||
@@ -52,12 +56,12 @@ impl<'db> ScopeId<'db> {
|
||||
}
|
||||
|
||||
pub fn scope(self, db: &'db dyn Db) -> &'db Scope {
|
||||
semantic_index(db, self.python_file(db)).scope(self.file_scope_id(db))
|
||||
semantic_index(db, self.program_file(db)).scope(self.file_scope_id(db))
|
||||
}
|
||||
|
||||
/// Returns the class definition for the enclosing class if this scope is a method body.
|
||||
pub fn class_definition_of_method(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
semantic_index(db, self.python_file(db)).class_definition_of_method(self.file_scope_id(db))
|
||||
semantic_index(db, self.program_file(db)).class_definition_of_method(self.file_scope_id(db))
|
||||
}
|
||||
|
||||
pub fn is_method_scope(self, db: &'db dyn Db) -> bool {
|
||||
@@ -105,7 +109,7 @@ impl FileScopeId {
|
||||
self == FileScopeId::global()
|
||||
}
|
||||
|
||||
pub fn to_scope_id<'db>(self, db: &'db dyn Db, file: PythonFile<'db>) -> ScopeId<'db> {
|
||||
pub fn to_scope_id<'db>(self, db: &'db dyn Db, file: ProgramFile<'db>) -> ScopeId<'db> {
|
||||
let index = semantic_index(db, file);
|
||||
index.scope_ids_by_scope[self]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::Program;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::db::Db;
|
||||
use crate::definition::Definition;
|
||||
use crate::expression::Expression;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::scope::{FileScopeId, ScopeId};
|
||||
use crate::{Program, ProgramFile};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
@@ -40,7 +40,7 @@ pub enum Statement<'db> {
|
||||
pub struct StatementInner<'db> {
|
||||
/// The file in which the statement occurs.
|
||||
#[returns(copy)]
|
||||
pub python_file: PythonFile<'db>,
|
||||
pub program_file: ProgramFile<'db>,
|
||||
|
||||
/// The scope in which the statement occurs.
|
||||
#[returns(copy)]
|
||||
@@ -58,14 +58,18 @@ impl get_size2::GetSize for StatementInner<'_> {}
|
||||
|
||||
impl<'db> StatementInner<'db> {
|
||||
pub fn file(self, db: &'db dyn Db) -> File {
|
||||
self.python_file(db).file(db)
|
||||
self.program_file(db).file(db)
|
||||
}
|
||||
|
||||
pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
|
||||
self.program_file(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.file_scope(db).to_scope_id(db, self.python_file(db))
|
||||
self.file_scope(db).to_scope_id(db, self.program_file(db))
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program {
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.scope(db).program(db)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Db;
|
||||
use crate::EvaluationMode;
|
||||
use crate::ProgramFile;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::expression::Expression;
|
||||
use crate::scope::{FileScopeId, ScopeId};
|
||||
@@ -32,7 +33,7 @@ use crate::scope::{FileScopeId, ScopeId};
|
||||
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct Unpack<'db> {
|
||||
#[returns(copy)]
|
||||
pub python_file: PythonFile<'db>,
|
||||
pub program_file: ProgramFile<'db>,
|
||||
|
||||
#[returns(copy)]
|
||||
pub(crate) value_file_scope: FileScopeId,
|
||||
@@ -58,7 +59,11 @@ impl get_size2::GetSize for Unpack<'_> {}
|
||||
|
||||
impl<'db> Unpack<'db> {
|
||||
pub fn file(self, db: &'db dyn Db) -> File {
|
||||
self.python_file(db).file(db)
|
||||
self.program_file(db).file(db)
|
||||
}
|
||||
|
||||
pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
|
||||
self.program_file(db).python_file(db)
|
||||
}
|
||||
|
||||
pub fn target<'ast>(self, db: &'db dyn Db, parsed: &'ast ParsedModuleRef) -> &'ast ast::Expr {
|
||||
@@ -68,10 +73,10 @@ impl<'db> Unpack<'db> {
|
||||
/// Returns the scope where the unpack target expression belongs to.
|
||||
pub fn target_scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.target_file_scope(db)
|
||||
.to_scope_id(db, self.python_file(db))
|
||||
.to_scope_id(db, self.program_file(db))
|
||||
}
|
||||
|
||||
pub fn program(self, db: &'db dyn Db) -> Program {
|
||||
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
|
||||
self.target_scope(db).program(db)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,16 @@ use crate::AnalysisSettings;
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::File;
|
||||
use ty_python_core::Db as PythonCoreDb;
|
||||
use ty_python_core::{Db as PythonCoreDb, ProgramFile};
|
||||
|
||||
/// Database giving access to semantic information about a Python program.
|
||||
#[salsa::db]
|
||||
pub trait Db: PythonCoreDb {
|
||||
fn check_file(&self, file: File) -> Vec<Diagnostic>;
|
||||
|
||||
/// Returns the program file for `file`.
|
||||
fn program_file(&self, file: File) -> ProgramFile<'_>;
|
||||
|
||||
/// Resolves the rule selection for a given file.
|
||||
fn rule_selection(&self, file: File) -> &RuleSelection;
|
||||
|
||||
@@ -37,14 +40,14 @@ pub(crate) mod tests {
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
|
||||
use crate::{ProgramEnvironment, check_file_unwrap, default_lint_registry};
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::files::Files;
|
||||
use ruff_db::system::{
|
||||
DbWithTestSystem, DbWithWritableSystem as _, System, SystemPath, SystemPathBuf, TestSystem,
|
||||
};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_db::{Db as SourceDb, PythonFile};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{Db as ModuleResolverDb, SearchPathSettings, SearchPaths};
|
||||
use ty_module_resolver::{Db as ModuleResolverDb, SearchPathSettings};
|
||||
use ty_python_core::program::{FallibleStrategy, Program, ProgramSettings};
|
||||
use ty_site_packages::{PythonVersionSource, PythonVersionWithSource};
|
||||
|
||||
@@ -90,7 +93,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
pub(crate) fn program_environment(&self) -> ProgramEnvironment<'_> {
|
||||
ProgramEnvironment::from_program(self.python_version())
|
||||
ProgramEnvironment::from_program(Program::get(self).resolver_environment(self))
|
||||
}
|
||||
|
||||
/// Marks `file` as open in the editor.
|
||||
@@ -155,7 +158,11 @@ pub(crate) mod tests {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
check_file_unwrap(self, PythonFile::new(self, file, self.python_version()))
|
||||
check_file_unwrap(self, self.program_file(file))
|
||||
}
|
||||
|
||||
fn program_file(&self, file: File) -> ProgramFile<'_> {
|
||||
Program::get(self).program_file(self, file)
|
||||
}
|
||||
|
||||
fn rule_selection(&self, _file: File) -> &RuleSelection {
|
||||
@@ -184,11 +191,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ModuleResolverDb for TestDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
impl ModuleResolverDb for TestDb {}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, resolve_module};
|
||||
|
||||
use crate::types::{Type, TypeContext, infer_expression_types};
|
||||
use crate::{Db, ProgramEnvironment};
|
||||
use ty_python_core::{SemanticIndex, Truthiness, semantic_index};
|
||||
use ty_python_core::{ProgramFile, SemanticIndex, Truthiness, semantic_index};
|
||||
|
||||
/// Returns a set of names in the `__all__` variable for `file`, [`None`] if it is not defined or
|
||||
/// if it contains invalid elements.
|
||||
#[salsa::tracked(returns(as_ref), cycle_initial=|_, _, _| None, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn dunder_all_names(db: &dyn Db, file: PythonFile<'_>) -> Option<FxHashSet<Name>> {
|
||||
pub(crate) fn dunder_all_names(db: &dyn Db, file: ProgramFile<'_>) -> Option<FxHashSet<Name>> {
|
||||
let source_file = file.file(db);
|
||||
let _span = tracing::trace_span!("dunder_all_names", file=?source_file.path(db)).entered();
|
||||
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let index = semantic_index(db, file);
|
||||
let mut collector = DunderAllNamesCollector::new(db, file, index);
|
||||
collector.visit_body(module.suite());
|
||||
@@ -28,7 +27,7 @@ pub(crate) fn dunder_all_names(db: &dyn Db, file: PythonFile<'_>) -> Option<FxHa
|
||||
struct DunderAllNamesCollector<'db> {
|
||||
db: &'db dyn Db,
|
||||
env: ProgramEnvironment<'db>,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
|
||||
/// The semantic index for the module.
|
||||
index: &'db SemanticIndex<'db>,
|
||||
@@ -45,7 +44,7 @@ struct DunderAllNamesCollector<'db> {
|
||||
}
|
||||
|
||||
impl<'db> DunderAllNamesCollector<'db> {
|
||||
fn new(db: &'db dyn Db, file: PythonFile<'db>, index: &'db SemanticIndex<'db>) -> Self {
|
||||
fn new(db: &'db dyn Db, file: ProgramFile<'db>, index: &'db SemanticIndex<'db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
env: ProgramEnvironment::from_file(file),
|
||||
@@ -94,7 +93,8 @@ impl<'db> DunderAllNamesCollector<'db> {
|
||||
};
|
||||
let Some(module_dunder_all_names) = module_literal
|
||||
.module(db)
|
||||
.python_file(db)
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, self.env.program(db)))
|
||||
.and_then(|file| dunder_all_names(db, file))
|
||||
else {
|
||||
// The module either does not have a `__all__` variable or it is invalid.
|
||||
@@ -163,9 +163,15 @@ impl<'db> DunderAllNamesCollector<'db> {
|
||||
) -> Option<&'db FxHashSet<Name>> {
|
||||
let db = self.db;
|
||||
|
||||
let module_name = ModuleName::from_import_statement(db, self.file, import_from).ok()?;
|
||||
let module = resolve_module(db, self.file, &module_name)?;
|
||||
dunder_all_names(db, module.python_file(db)?)
|
||||
let importing_file =
|
||||
ImportingFile::File(self.file.file(db), self.env.resolver_environment(db));
|
||||
let module_name =
|
||||
ModuleName::from_import_statement(db, importing_file, import_from).ok()?;
|
||||
let module = resolve_module(db, importing_file, &module_name)?;
|
||||
dunder_all_names(
|
||||
db,
|
||||
ProgramFile::new(db, module.file(db)?, self.env.program(db)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Infer the type of a standalone expression.
|
||||
|
||||
@@ -11,7 +11,6 @@ use ruff_db::{
|
||||
source::source_text,
|
||||
};
|
||||
use ruff_diagnostics::{Applicability, Edit, Fix, IsolationLevel, SourceMap};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa::Setter as _;
|
||||
@@ -39,13 +38,11 @@ pub struct FixAllResults {
|
||||
/// If the `db`'s system isn't [writable](WritableSystem).
|
||||
pub fn suppress_all_diagnostics(
|
||||
db: &mut dyn Db,
|
||||
python_version: PythonVersion,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> Result<FixAllResults, Canceled> {
|
||||
fix_all(
|
||||
db,
|
||||
python_version,
|
||||
diagnostics,
|
||||
FixMode::Suppress,
|
||||
cancellation_token,
|
||||
@@ -61,14 +58,12 @@ pub fn suppress_all_diagnostics(
|
||||
/// If the `db`'s system isn't [writable](WritableSystem).
|
||||
pub fn fix_all_diagnostics(
|
||||
db: &mut dyn Db,
|
||||
python_version: PythonVersion,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
applicability: Applicability,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> Result<FixAllResults, Canceled> {
|
||||
fix_all(
|
||||
db,
|
||||
python_version,
|
||||
diagnostics,
|
||||
FixMode::ApplyFixes(applicability),
|
||||
cancellation_token,
|
||||
@@ -83,7 +78,6 @@ const MAX_ITERATIONS: usize = 10;
|
||||
/// `check_file` is a separate parameter so that tests can easily mock out a file's diagnostics.
|
||||
fn fix_all<F>(
|
||||
db: &mut dyn Db,
|
||||
python_version: PythonVersion,
|
||||
mut diagnostics: Vec<Diagnostic>,
|
||||
fix_mode: FixMode,
|
||||
cancellation_token: &CancellationToken,
|
||||
@@ -135,7 +129,7 @@ where
|
||||
continue;
|
||||
};
|
||||
|
||||
let python_file = PythonFile::new(db, file, python_version);
|
||||
let python_file = db.program_file(file).python_file(db);
|
||||
let parsed = parsed_module(db, python_file);
|
||||
if parsed.load(db).has_syntax_errors() {
|
||||
tracing::warn!("Skipping file `{path}` with syntax errors");
|
||||
@@ -184,7 +178,6 @@ where
|
||||
// This is done outside the above loop so that it can run in parallel.
|
||||
let check_results = recheck_files(
|
||||
&*db,
|
||||
python_version,
|
||||
unstaged_fixes,
|
||||
fix_mode,
|
||||
cancellation_token,
|
||||
@@ -757,7 +750,6 @@ enum CheckResult<'a> {
|
||||
|
||||
fn recheck_files<'a, F>(
|
||||
db: &dyn Db,
|
||||
python_version: PythonVersion,
|
||||
changes: Vec<(QueuedFile<'a>, usize)>,
|
||||
fix_mode: FixMode,
|
||||
cancellation_token: &CancellationToken,
|
||||
@@ -783,7 +775,7 @@ where
|
||||
|
||||
let db = &*db;
|
||||
|
||||
let python_file = PythonFile::new(db, file.file, python_version);
|
||||
let python_file = db.program_file(file.file).python_file(db);
|
||||
let parsed = parsed_module(db, python_file);
|
||||
let parsed = parsed.load(db);
|
||||
|
||||
@@ -814,7 +806,6 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::cancellation::CancellationTokenSource;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics,
|
||||
@@ -1725,12 +1716,10 @@ class B(A):
|
||||
};
|
||||
|
||||
let initial_diagnostics = check_file(&db, file);
|
||||
let python_version = db.python_version();
|
||||
|
||||
let cancellation_token_source = CancellationTokenSource::new();
|
||||
let fixes = fix_all(
|
||||
&mut db,
|
||||
python_version,
|
||||
initial_diagnostics,
|
||||
FixMode::ApplyFixes(Applicability::Safe),
|
||||
&cancellation_token_source.token(),
|
||||
@@ -1804,12 +1793,10 @@ class B(A):
|
||||
};
|
||||
|
||||
let initial_diagnostics = check_file(&db, file);
|
||||
let python_version = db.python_version();
|
||||
|
||||
let cancellation_token_source = CancellationTokenSource::new();
|
||||
let fixes = fix_all(
|
||||
&mut db,
|
||||
python_version,
|
||||
initial_diagnostics,
|
||||
FixMode::ApplyFixes(Applicability::Safe),
|
||||
&cancellation_token_source.token(),
|
||||
@@ -1881,10 +1868,8 @@ class B(A):
|
||||
create_diagnostics(file)
|
||||
};
|
||||
|
||||
let python_version = db.python_version();
|
||||
let result = fix_all(
|
||||
&mut db,
|
||||
python_version,
|
||||
initial_diagnostics,
|
||||
FixMode::ApplyFixes(Applicability::Safe),
|
||||
&cancellation_token_source.token(),
|
||||
@@ -1983,12 +1968,10 @@ class B(A):
|
||||
};
|
||||
|
||||
let initial_diagnostics = check_file(&db, file);
|
||||
let python_version = db.python_version();
|
||||
|
||||
let cancellation_token_source = CancellationTokenSource::new();
|
||||
let fixes = fix_all(
|
||||
&mut db,
|
||||
python_version,
|
||||
initial_diagnostics,
|
||||
FixMode::ApplyFixes(Applicability::Safe),
|
||||
&cancellation_token_source.token(),
|
||||
@@ -2020,8 +2003,7 @@ class B(A):
|
||||
|
||||
let file = system_path_to_file(&db, "test.py").unwrap();
|
||||
|
||||
let python_version = db.python_version();
|
||||
let parsed_before = parsed_module(&db, PythonFile::new(&db, file, python_version));
|
||||
let parsed_before = parsed_module(&db, db.program_file(file).python_file(&db));
|
||||
let had_syntax_errors = parsed_before.load(&db).has_syntax_errors();
|
||||
|
||||
let diagnostics = db.check_file(file);
|
||||
@@ -2036,13 +2018,9 @@ class B(A):
|
||||
.cloned()
|
||||
.collect();
|
||||
let cancellation_token_source = CancellationTokenSource::new();
|
||||
let fixes = suppress_all_diagnostics(
|
||||
&mut db,
|
||||
python_version,
|
||||
diagnostics,
|
||||
&cancellation_token_source.token(),
|
||||
)
|
||||
.expect("operation never gets cancelled");
|
||||
let fixes =
|
||||
suppress_all_diagnostics(&mut db, diagnostics, &cancellation_token_source.token())
|
||||
.expect("operation never gets cancelled");
|
||||
|
||||
if had_syntax_errors {
|
||||
assert_eq!(fixes.count, 0);
|
||||
@@ -2076,7 +2054,7 @@ class B(A):
|
||||
|
||||
let fixed = source_text(&db, file);
|
||||
|
||||
let parsed = parsed_module(&db, PythonFile::new(&db, file, python_version));
|
||||
let parsed = parsed_module(&db, db.program_file(file).python_file(&db));
|
||||
let parsed = parsed.load(&db);
|
||||
|
||||
let diagnostics_after_applying_fixes = db.check_file(file);
|
||||
|
||||
@@ -29,6 +29,7 @@ pub(crate) use suppression::{
|
||||
};
|
||||
use ty_module_resolver::ModuleGlobSet;
|
||||
pub use ty_python_core::Program;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::docstring_from_body;
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
use ty_python_core::scope::ScopeId;
|
||||
@@ -130,7 +131,7 @@ pub(crate) fn attribute_assignments<'db, 's>(
|
||||
class_body_scope: ScopeId<'db>,
|
||||
name: &'s str,
|
||||
) -> impl Iterator<Item = (BindingWithConstraintsIterator<'db, 'db>, FileScopeId)> + use<'s, 'db> {
|
||||
let index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let index = semantic_index(db, class_body_scope.program_file(db));
|
||||
|
||||
attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
@@ -150,7 +151,7 @@ pub(crate) fn attribute_declarations<'db, 's>(
|
||||
class_body_scope: ScopeId<'db>,
|
||||
name: &'s str,
|
||||
) -> impl Iterator<Item = (DeclarationsIterator<'db, 'db>, FileScopeId)> + use<'s, 'db> {
|
||||
let index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let index = semantic_index(db, class_body_scope.program_file(db));
|
||||
|
||||
attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
@@ -170,13 +171,13 @@ pub(crate) fn module_docstring(db: &dyn Db, file: PythonFile<'_>) -> Option<Stri
|
||||
.map(|docstring_expr| docstring_expr.value.to_str().to_owned())
|
||||
}
|
||||
|
||||
pub fn check_file_unwrap(db: &dyn Db, file: PythonFile<'_>) -> Vec<Diagnostic> {
|
||||
pub fn check_file_unwrap(db: &dyn Db, file: ProgramFile<'_>) -> Vec<Diagnostic> {
|
||||
check_file(db, file)
|
||||
.map(<[ruff_db::diagnostic::Diagnostic]>::into_vec)
|
||||
.unwrap_or_else(|error| vec![error])
|
||||
}
|
||||
|
||||
pub fn check_file(db: &dyn Db, file: PythonFile<'_>) -> Result<Box<[Diagnostic]>, Diagnostic> {
|
||||
pub fn check_file(db: &dyn Db, file: ProgramFile<'_>) -> Result<Box<[Diagnostic]>, Diagnostic> {
|
||||
let source_file = file.file(db);
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
@@ -191,7 +192,7 @@ pub fn check_file(db: &dyn Db, file: PythonFile<'_>) -> Result<Box<[Diagnostic]>
|
||||
.to_diagnostic());
|
||||
}
|
||||
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
|
||||
let parsed_ref = parsed.load(db);
|
||||
diagnostics.extend(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::ProgramEnvironment;
|
||||
use itertools::Either;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_index::IndexSlice;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{
|
||||
@@ -28,8 +27,8 @@ use ty_python_core::reachability_constraints::{
|
||||
use ty_python_core::scope::ScopeId;
|
||||
use ty_python_core::{
|
||||
BindingWithConstraints, BindingWithConstraintsIterator, BoundnessAnalysis,
|
||||
DeclarationWithConstraint, DeclarationsIterator, Truthiness, global_scope, place_table,
|
||||
use_def_map,
|
||||
DeclarationWithConstraint, DeclarationsIterator, ProgramFile, Truthiness, global_scope,
|
||||
place_table, use_def_map,
|
||||
};
|
||||
|
||||
pub(crate) use implicit_globals::{
|
||||
@@ -487,7 +486,7 @@ pub(crate) fn symbol<'db>(
|
||||
/// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports).
|
||||
pub(crate) fn explicit_global_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
symbol_impl(
|
||||
@@ -509,7 +508,7 @@ pub(crate) fn explicit_global_symbol<'db>(
|
||||
#[allow(unused)]
|
||||
pub(crate) fn global_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
let env = ProgramEnvironment::from_file(file);
|
||||
@@ -527,12 +526,12 @@ pub(crate) fn global_symbol<'db>(
|
||||
pub(crate) fn imported_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
file: Option<PythonFile<'db>>,
|
||||
file: Option<ProgramFile<'db>>,
|
||||
name: &str,
|
||||
requires_explicit_reexport: Option<RequiresExplicitReExport>,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if let Some(file) = file {
|
||||
debug_assert_eq!(file.python_version(db), env.python_version(db));
|
||||
debug_assert_eq!(file.program(db), env.program(db));
|
||||
}
|
||||
|
||||
// If it's not found in the global scope, check if it's present as an instance on
|
||||
@@ -664,10 +663,10 @@ fn builtins_symbol_impl<'db>(
|
||||
symbol: &str,
|
||||
visibility: BuiltinVisibility,
|
||||
) -> Option<(ScopeId<'db>, PlaceAndQualifiers<'db>)> {
|
||||
let python_version = env.python_version(db);
|
||||
let program = env.program(db);
|
||||
let resolver = |module: Module<'db>| {
|
||||
let python_file = module.python_file(db)?;
|
||||
let scope = global_scope(db, python_file);
|
||||
let file = ProgramFile::new(db, module.file(db)?, program);
|
||||
let scope = global_scope(db, file);
|
||||
let found_symbol = symbol_impl(
|
||||
db,
|
||||
scope,
|
||||
@@ -679,7 +678,7 @@ fn builtins_symbol_impl<'db>(
|
||||
// We're looking up in the builtins namespace and not the module, so we should
|
||||
// do the normal lookup in `types.ModuleType` and not the special one as in
|
||||
// `imported_symbol`.
|
||||
module_type_implicit_global_symbol(db, python_file, symbol)
|
||||
module_type_implicit_global_symbol(db, file, symbol)
|
||||
});
|
||||
found_symbol.ignore_possibly_undefined()?;
|
||||
|
||||
@@ -696,13 +695,12 @@ fn builtins_symbol_impl<'db>(
|
||||
// If this symbol is not present in project-level builtins, search in the default ones.
|
||||
resolve_module_confident(
|
||||
db,
|
||||
python_version,
|
||||
program,
|
||||
&ModuleName::new_static("__builtins__").unwrap(),
|
||||
)
|
||||
.and_then(&resolver)
|
||||
.or_else(|| {
|
||||
resolve_module_confident(db, python_version, &KnownModule::Builtins.name())
|
||||
.and_then(resolver)
|
||||
resolve_module_confident(db, program, &KnownModule::Builtins.name()).and_then(resolver)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -715,9 +713,9 @@ pub(crate) fn known_module_symbol<'db>(
|
||||
known_module: KnownModule,
|
||||
symbol: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
resolve_module_confident(db, env.python_version(db), &known_module.name())
|
||||
resolve_module_confident(db, env.resolver_environment(db), &known_module.name())
|
||||
.and_then(|module| {
|
||||
let file = module.python_file(db)?;
|
||||
let file = ProgramFile::new(db, module.file(db)?, env.program(db));
|
||||
Some(imported_symbol(db, env, Some(file), symbol, None))
|
||||
})
|
||||
.unwrap_or_default()
|
||||
@@ -766,8 +764,12 @@ fn core_module_scope<'db>(
|
||||
env: &ProgramEnvironment<'db>,
|
||||
core_module: KnownModule,
|
||||
) -> Option<ScopeId<'db>> {
|
||||
let module = resolve_module_confident(db, env.python_version(db), &core_module.name())?;
|
||||
Some(global_scope(db, module.python_file(db)?))
|
||||
let program = env.program(db);
|
||||
let module = resolve_module_confident(db, env.resolver_environment(db), &core_module.name())?;
|
||||
Some(global_scope(
|
||||
db,
|
||||
ProgramFile::new(db, module.file(db)?, program),
|
||||
))
|
||||
}
|
||||
|
||||
/// Infer the combined type from an iterator of bindings, and return it
|
||||
@@ -1412,7 +1414,7 @@ fn symbol_impl<'db>(
|
||||
let _span = tracing::trace_span!("symbol", ?name).entered();
|
||||
|
||||
let is_known_module = |known_module| {
|
||||
file_to_module(db, scope.python_file(db))
|
||||
file_to_module(db, scope.program_file(db).resolver_file(db))
|
||||
.is_some_and(|module| module.is_known(db, known_module))
|
||||
};
|
||||
|
||||
@@ -2120,7 +2122,7 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
|
||||
// At this point, the definition should either be an `import` or `from ... import` statement.
|
||||
// This is because the default value of `is_reexported` is `true` for any other kind of
|
||||
// definition.
|
||||
let Some(all_names) = dunder_all_names(db, definition.python_file(db)) else {
|
||||
let Some(all_names) = dunder_all_names(db, definition.program_file(db)) else {
|
||||
return false;
|
||||
};
|
||||
let table = place_table(db, definition.scope(db));
|
||||
@@ -2130,7 +2132,6 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) mod implicit_globals {
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
@@ -2146,7 +2147,7 @@ pub(crate) mod implicit_globals {
|
||||
use ty_python_core::definition::{DefinitionKind, DefinitionState};
|
||||
use ty_python_core::scope::{NodeWithScopeRef, ScopeId};
|
||||
use ty_python_core::symbol::Symbol;
|
||||
use ty_python_core::{place_table, semantic_index, use_def_map};
|
||||
use ty_python_core::{ProgramFile, place_table, semantic_index, use_def_map};
|
||||
|
||||
use super::{DefinedPlace, Place, core_module_scope, is_reexported, place_from_declarations};
|
||||
|
||||
@@ -2159,15 +2160,15 @@ pub(crate) mod implicit_globals {
|
||||
module_scope: ScopeId<'db>,
|
||||
name: &str,
|
||||
) -> Option<ScopeId<'db>> {
|
||||
let python_file = module_scope.python_file(db);
|
||||
let file = python_file.file(db);
|
||||
let program_file = module_scope.program_file(db);
|
||||
let file = program_file.file(db);
|
||||
if !file.path(db).is_vendored_path() {
|
||||
return None;
|
||||
}
|
||||
let symbol_id = place_table(db, module_scope).symbol_id(name)?;
|
||||
let use_def = use_def_map(db, module_scope);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let index = semantic_index(db, python_file);
|
||||
let module = parsed_module(db, program_file.python_file(db)).load(db);
|
||||
let index = semantic_index(db, program_file);
|
||||
let mut body_scope = None;
|
||||
|
||||
for binding in use_def.end_of_scope_symbol_bindings(symbol_id) {
|
||||
@@ -2187,7 +2188,7 @@ pub(crate) mod implicit_globals {
|
||||
};
|
||||
let class_scope = index
|
||||
.node_scope(NodeWithScopeRef::Class(class.node(&module)))
|
||||
.to_scope_id(db, python_file);
|
||||
.to_scope_id(db, program_file);
|
||||
if body_scope.is_some_and(|body_scope| body_scope != class_scope) {
|
||||
return None;
|
||||
}
|
||||
@@ -2202,15 +2203,14 @@ pub(crate) mod implicit_globals {
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
) -> Option<ScopeId<'db>> {
|
||||
module_type_body_scope_inner(db, env.program(db), ())
|
||||
module_type_body_scope_inner(db, env.program(db))
|
||||
}
|
||||
|
||||
#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn module_type_body_scope_inner(
|
||||
db: &dyn Db,
|
||||
program: Program,
|
||||
_: (), // FIXME: Remove once `Program` is a Salsa-interned struct.
|
||||
) -> Option<ScopeId<'_>> {
|
||||
fn module_type_body_scope_inner<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program<'db>,
|
||||
) -> Option<ScopeId<'db>> {
|
||||
let env = ProgramEnvironment::from_program(program);
|
||||
let module_scope = core_module_scope(db, &env, KnownModule::Types)?;
|
||||
try_vendored_class_scope(db, module_scope, "ModuleType").or_else(|| {
|
||||
@@ -2262,7 +2262,7 @@ pub(crate) mod implicit_globals {
|
||||
/// global scope if they're being imported **from a different file**.
|
||||
pub(crate) fn module_type_implicit_global_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
let env = ProgramEnvironment::from_file(file);
|
||||
@@ -2275,7 +2275,7 @@ pub(crate) mod implicit_globals {
|
||||
// We special-case `__doc__` because a module with a literal docstring has `__doc__`
|
||||
// set to that string at runtime. We only narrow when a docstring is present: `__doc__`
|
||||
// may be set dynamically, so we fall back to the typeshed's `str | None`.
|
||||
"__doc__" if module_docstring(db, file).is_some() => {
|
||||
"__doc__" if module_docstring(db, file.python_file(db)).is_some() => {
|
||||
// Docstrings are stripped in `-OO` optimized mode, but here we assume that the
|
||||
// existence of an actual docstring AND the usage of `__doc__` is reason enough to
|
||||
// believe that it will exist at runtime.
|
||||
@@ -2384,18 +2384,17 @@ pub(crate) mod implicit_globals {
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
) -> &'db [ast::name::Name] {
|
||||
module_type_symbols_inner(db, env.program(db), ())
|
||||
module_type_symbols_inner(db, env.program(db))
|
||||
}
|
||||
|
||||
#[salsa::tracked(
|
||||
returns(deref),
|
||||
cycle_initial=|_, _, _, ()| smallvec::SmallVec::default(),
|
||||
cycle_initial=|_, _, _| smallvec::SmallVec::default(),
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
)]
|
||||
fn module_type_symbols_inner(
|
||||
db: &dyn Db,
|
||||
program: Program,
|
||||
_: (), // FIXME: Remove once `Program` is a Salsa-interned struct.
|
||||
fn module_type_symbols_inner<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program<'db>,
|
||||
) -> smallvec::SmallVec<[ast::name::Name; 8]> {
|
||||
let env = ProgramEnvironment::from_program(program);
|
||||
let Some(module_type_scope) = module_type_body_scope(db, &env) else {
|
||||
@@ -2413,7 +2412,7 @@ pub(crate) mod implicit_globals {
|
||||
/// for the current module, not `str | None`).
|
||||
pub(crate) fn all_implicit_module_globals<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
) -> impl Iterator<Item = (Name, Type<'db>)> + 'db {
|
||||
// Special-cased implicit globals that are not in `module_type_symbols`
|
||||
let special_cased = ["__builtins__", "__debug__", "__warningregistry__"]
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
//! (Mdtest uses the `pull_types` function via the `ty_test` crate.)
|
||||
|
||||
use crate::{Db, HasType, SemanticModel};
|
||||
use ruff_db::{PythonFile, parsed::parsed_module};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{
|
||||
self as ast, visitor::source_order, visitor::source_order::SourceOrderVisitor,
|
||||
};
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
pub fn pull_types(db: &dyn Db, file: PythonFile<'_>) {
|
||||
pub fn pull_types(db: &dyn Db, file: ProgramFile<'_>) {
|
||||
let mut visitor = PullTypesVisitor::new(db, file);
|
||||
|
||||
let ast = parsed_module(db, file).load(db);
|
||||
let ast = parsed_module(db, file.python_file(db)).load(db);
|
||||
|
||||
visitor.visit_body(ast.suite());
|
||||
}
|
||||
@@ -22,7 +23,7 @@ struct PullTypesVisitor<'db> {
|
||||
}
|
||||
|
||||
impl<'db> PullTypesVisitor<'db> {
|
||||
fn new(db: &'db dyn Db, file: PythonFile<'db>) -> Self {
|
||||
fn new(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
model: SemanticModel::new(db, file),
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ fn type_narrowed_by_pattern<'db>(
|
||||
predicate: PatternPredicate<'db>,
|
||||
subject_ty: Type<'db>,
|
||||
) -> Type<'db> {
|
||||
let env = ProgramEnvironment::from_file(predicate.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(predicate.program_file(db));
|
||||
pattern_binding_fallthrough_type(db, &env, predicate.kind(db), subject_ty)
|
||||
}
|
||||
|
||||
@@ -1538,8 +1538,8 @@ fn analyze_single(db: &dyn Db, env: &ProgramEnvironment<'_>, predicate: &Predica
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
let symbol = place_table.symbol(star_import.symbol_id(db));
|
||||
let python_file = star_import.referenced_parse_file(db);
|
||||
let requires_explicit_reexport = match dunder_all_names(db, python_file) {
|
||||
let program_file = star_import.referenced_file(db);
|
||||
let requires_explicit_reexport = match dunder_all_names(db, program_file) {
|
||||
Some(all_names) => {
|
||||
if all_names.contains(symbol.name()) {
|
||||
Some(RequiresExplicitReExport::No)
|
||||
@@ -1547,7 +1547,7 @@ fn analyze_single(db: &dyn Db, env: &ProgramEnvironment<'_>, predicate: &Predica
|
||||
tracing::trace!(
|
||||
"Symbol `{}` (via star import) not found in `__all__` of `{}`",
|
||||
symbol.name(),
|
||||
python_file.file(db).path(db)
|
||||
program_file.file(db).path(db)
|
||||
);
|
||||
return Truthiness::AlwaysFalse;
|
||||
}
|
||||
@@ -1558,7 +1558,7 @@ fn analyze_single(db: &dyn Db, env: &ProgramEnvironment<'_>, predicate: &Predica
|
||||
match imported_symbol(
|
||||
db,
|
||||
env,
|
||||
Some(python_file),
|
||||
Some(program_file),
|
||||
symbol.name(),
|
||||
requires_explicit_reexport,
|
||||
)
|
||||
@@ -1790,9 +1790,9 @@ impl<'db> DeclarationsIteratorExtension<'db> for DeclarationsIterator<'_, 'db> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::tests::setup_db;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::DbWithWritableSystem as _;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::narrowing_constraints::InteriorNode;
|
||||
use ty_python_core::predicate::Predicates;
|
||||
use ty_python_core::semantic_index;
|
||||
@@ -1826,8 +1826,8 @@ class TargetB:
|
||||
db.write_files([("/src/a.py", a.as_str()), ("/src/b.py", b.as_str())])?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let python_file = PythonFile::new(&db, file, db.python_version());
|
||||
let index = semantic_index(&db, python_file);
|
||||
let program_file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let index = semantic_index(&db, program_file);
|
||||
let class_scope = index
|
||||
.child_scopes(FileScopeId::global())
|
||||
.find(|(_, scope)| scope.node().as_class().is_some())
|
||||
@@ -1838,7 +1838,7 @@ class TargetB:
|
||||
.find(|(_, scope)| scope.node().as_function().is_some())
|
||||
.unwrap()
|
||||
.0
|
||||
.to_scope_id(&db, python_file);
|
||||
.to_scope_id(&db, program_file);
|
||||
|
||||
// Enter the range directly so it becomes the cycle head when inferring `other.target`
|
||||
// reaches the other module and then re-enters this scope.
|
||||
@@ -1860,13 +1860,13 @@ class TargetB:
|
||||
|
||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||
let function_scope = {
|
||||
let python_file = PythonFile::new(&db, file, db.python_version());
|
||||
let index = semantic_index(&db, python_file);
|
||||
let program_file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let index = semantic_index(&db, program_file);
|
||||
index.child_scopes(FileScopeId::global()).next().unwrap().0
|
||||
};
|
||||
{
|
||||
let python_file = PythonFile::new(&db, file, db.python_version());
|
||||
let scope = function_scope.to_scope_id(&db, python_file);
|
||||
let program_file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let scope = function_scope.to_scope_id(&db, program_file);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
assert!(
|
||||
evaluate_reachability_constraint(&db, scope, use_def.end_of_scope_reachability(),)
|
||||
@@ -1879,8 +1879,8 @@ class TargetB:
|
||||
"from typing import NoReturn\ndef callback() -> NoReturn: ...",
|
||||
)?;
|
||||
|
||||
let python_file = PythonFile::new(&db, file, db.python_version());
|
||||
let scope = function_scope.to_scope_id(&db, python_file);
|
||||
let program_file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let scope = function_scope.to_scope_id(&db, program_file);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
assert!(
|
||||
evaluate_reachability_constraint(&db, scope, use_def.end_of_scope_reachability(),)
|
||||
@@ -1908,7 +1908,9 @@ class TargetB:
|
||||
)?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||
let index = semantic_index(&db, PythonFile::new(&db, file, db.python_version()));
|
||||
let program_file =
|
||||
ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let index = semantic_index(&db, program_file);
|
||||
let function_scope = index.child_scopes(FileScopeId::global()).next().unwrap().0;
|
||||
let use_def = index.use_def_map(function_scope);
|
||||
let predicate = use_def
|
||||
|
||||
@@ -11,7 +11,8 @@ use ruff_source_file::LineIndex;
|
||||
use ruff_text_size::Ranged;
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_module_resolver::{
|
||||
KnownModule, Module, ModuleName, list_modules, resolve_module, resolve_real_shadowable_module,
|
||||
ImportingFile, KnownModule, Module, ModuleName, list_modules, resolve_module,
|
||||
resolve_real_shadowable_module,
|
||||
};
|
||||
|
||||
use crate::Db;
|
||||
@@ -27,6 +28,7 @@ use ty_python_core::place_table;
|
||||
use ty_python_core::scope::{FileScopeId, Scope};
|
||||
use ty_python_core::semantic_index;
|
||||
use ty_python_core::symbol::Symbol;
|
||||
use ty_python_core::{Program, ProgramFile};
|
||||
|
||||
/// The primary interface the LSP should use for querying semantic information about a [`File`].
|
||||
///
|
||||
@@ -41,14 +43,14 @@ use ty_python_core::symbol::Symbol;
|
||||
/// methods will automatically handle using the string literal's AST node when necessary.
|
||||
pub struct SemanticModel<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
/// If `Some` then this `SemanticModel` is for analyzing the sub-AST of a string annotation.
|
||||
/// This expression will be used as a witness to the scope/location we're analyzing.
|
||||
in_string_annotation_expr: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
impl<'db> SemanticModel<'db> {
|
||||
pub fn new(db: &'db dyn Db, file: PythonFile<'db>) -> Self {
|
||||
pub fn new(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
@@ -65,11 +67,19 @@ impl<'db> SemanticModel<'db> {
|
||||
}
|
||||
|
||||
pub fn python_file(&self) -> PythonFile<'db> {
|
||||
self.file.python_file(self.db)
|
||||
}
|
||||
|
||||
pub fn program_file(&self) -> ProgramFile<'db> {
|
||||
self.file
|
||||
}
|
||||
|
||||
pub fn program(&self) -> Program<'db> {
|
||||
self.file.program(self.db)
|
||||
}
|
||||
|
||||
pub fn program_environment(&self) -> ProgramEnvironment<'db> {
|
||||
ProgramEnvironment::from_file(self.python_file())
|
||||
ProgramEnvironment::from_file(self.program_file())
|
||||
}
|
||||
|
||||
pub fn file_path(&self) -> &FilePath {
|
||||
@@ -91,8 +101,8 @@ impl<'db> SemanticModel<'db> {
|
||||
) -> FxHashMap<Name, MemberDefinition<'db>> {
|
||||
let db = self.db;
|
||||
let mut members = FxHashMap::default();
|
||||
let python_file = self.python_file();
|
||||
let index = semantic_index(self.db, python_file);
|
||||
let program_file = self.program_file();
|
||||
let index = semantic_index(self.db, program_file);
|
||||
let Some(file_scope) = self.scope(node) else {
|
||||
return members;
|
||||
};
|
||||
@@ -102,7 +112,8 @@ impl<'db> SemanticModel<'db> {
|
||||
.into_iter()
|
||||
.rev()
|
||||
{
|
||||
for memberdef in all_reachable_members(db, file_scope.to_scope_id(self.db, python_file))
|
||||
for memberdef in
|
||||
all_reachable_members(db, file_scope.to_scope_id(self.db, program_file))
|
||||
{
|
||||
members.insert(
|
||||
memberdef.member.name,
|
||||
@@ -119,24 +130,33 @@ impl<'db> SemanticModel<'db> {
|
||||
/// Resolve the given import made in this file to a Type
|
||||
pub fn resolve_module_type(&self, module: Option<&str>, level: u32) -> Option<Type<'db>> {
|
||||
let module = self.resolve_module(module, level)?;
|
||||
Some(Type::module_literal(self.db, self.python_file(), module))
|
||||
Some(Type::module_literal(self.db, self.program_file(), module))
|
||||
}
|
||||
|
||||
/// Resolve the given import made in this file to a Module
|
||||
pub fn resolve_module(&self, module: Option<&str>, level: u32) -> Option<Module<'db>> {
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(self.db),
|
||||
);
|
||||
let module_name =
|
||||
ModuleName::from_identifier_parts(self.db, self.python_file(), module, level).ok()?;
|
||||
resolve_module(self.db, self.python_file(), &module_name)
|
||||
ModuleName::from_identifier_parts(self.db, importing_file, module, level).ok()?;
|
||||
resolve_module(self.db, importing_file, &module_name)
|
||||
}
|
||||
|
||||
/// Returns completions for symbols available in a `import <CURSOR>` context.
|
||||
pub fn import_completions(&self) -> Vec<Completion<'db>> {
|
||||
let typing_extensions = ModuleName::new_static("typing_extensions").unwrap();
|
||||
let file = self.file();
|
||||
let resolver_environment = self.program_environment().resolver_environment(self.db);
|
||||
let is_typing_extensions_available = file.is_stub(self.db)
|
||||
|| resolve_real_shadowable_module(self.db, self.python_file(), &typing_extensions)
|
||||
.is_some();
|
||||
list_modules(self.db, self.python_file().python_version(self.db))
|
||||
|| resolve_real_shadowable_module(
|
||||
self.db,
|
||||
ImportingFile::File(file, resolver_environment),
|
||||
&typing_extensions,
|
||||
)
|
||||
.is_some();
|
||||
list_modules(self.db, resolver_environment)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|module| {
|
||||
@@ -144,7 +164,7 @@ impl<'db> SemanticModel<'db> {
|
||||
})
|
||||
.map(|module| {
|
||||
let builtin = module.is_known(self.db, KnownModule::Builtins);
|
||||
let ty = Type::module_literal(self.db, self.python_file(), module);
|
||||
let ty = Type::module_literal(self.db, self.program_file(), module);
|
||||
Completion {
|
||||
name: CompactString::new(module.name(self.db).as_str()),
|
||||
ty: Some(ty),
|
||||
@@ -158,7 +178,10 @@ impl<'db> SemanticModel<'db> {
|
||||
pub fn from_import_completions(&self, import: &ast::StmtImportFrom) -> Vec<Completion<'db>> {
|
||||
let module_name = match ModuleName::from_import_statement(
|
||||
self.db,
|
||||
self.python_file(),
|
||||
ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(self.db),
|
||||
),
|
||||
import,
|
||||
) {
|
||||
Ok(module_name) => module_name,
|
||||
@@ -179,7 +202,14 @@ impl<'db> SemanticModel<'db> {
|
||||
&self,
|
||||
module_name: &ModuleName,
|
||||
) -> Vec<Completion<'db>> {
|
||||
let Some(module) = resolve_module(self.db, self.python_file(), module_name) else {
|
||||
let Some(module) = resolve_module(
|
||||
self.db,
|
||||
ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(self.db),
|
||||
),
|
||||
module_name,
|
||||
) else {
|
||||
tracing::debug!("Could not resolve module from `{module_name:?}`");
|
||||
return vec![];
|
||||
};
|
||||
@@ -190,11 +220,18 @@ impl<'db> SemanticModel<'db> {
|
||||
/// it were imported by this model's `File`.
|
||||
fn module_completions(&self, module_name: &ModuleName) -> Vec<Completion<'db>> {
|
||||
let db = self.db;
|
||||
let Some(module) = resolve_module(self.db, self.python_file(), module_name) else {
|
||||
let Some(module) = resolve_module(
|
||||
self.db,
|
||||
ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(self.db),
|
||||
),
|
||||
module_name,
|
||||
) else {
|
||||
tracing::debug!("Could not resolve module from `{module_name:?}`");
|
||||
return vec![];
|
||||
};
|
||||
let ty = Type::module_literal(self.db, self.python_file(), module);
|
||||
let ty = Type::module_literal(self.db, self.program_file(), module);
|
||||
let builtin = module.is_known(self.db, KnownModule::Builtins);
|
||||
|
||||
let mut completions = vec![];
|
||||
@@ -219,7 +256,7 @@ impl<'db> SemanticModel<'db> {
|
||||
|
||||
let mut completions = vec![];
|
||||
for submodule in module.all_submodules(self.db) {
|
||||
let ty = Type::module_literal(self.db, self.python_file(), *submodule);
|
||||
let ty = Type::module_literal(self.db, self.program_file(), *submodule);
|
||||
let base = submodule.name(self.db).last_component();
|
||||
completions.push(Completion {
|
||||
name: CompactString::new(base),
|
||||
@@ -254,15 +291,15 @@ impl<'db> SemanticModel<'db> {
|
||||
/// scope of this model's `File` are returned.
|
||||
pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec<Completion<'db>> {
|
||||
let db = self.db;
|
||||
let python_file = self.python_file();
|
||||
let index = semantic_index(self.db, python_file);
|
||||
let program_file = self.program_file();
|
||||
let index = semantic_index(self.db, program_file);
|
||||
let Some(file_scope) = self.scope(node) else {
|
||||
return vec![];
|
||||
};
|
||||
let mut completions = vec![];
|
||||
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
||||
completions.extend(
|
||||
all_reachable_members(db, file_scope.to_scope_id(self.db, python_file)).map(
|
||||
all_reachable_members(db, file_scope.to_scope_id(self.db, program_file)).map(
|
||||
|memberdef| Completion {
|
||||
name: CompactString::new(memberdef.member.name),
|
||||
ty: Some(memberdef.member.ty),
|
||||
@@ -286,7 +323,9 @@ impl<'db> SemanticModel<'db> {
|
||||
|
||||
// Project-level builtins take precedence over the standard builtins.
|
||||
let project_builtins = ModuleName::new_static("__builtins__").unwrap();
|
||||
if resolve_module(self.db, self.file, &project_builtins).is_some() {
|
||||
let importing_file =
|
||||
ImportingFile::File(self.file(), self.file.resolver_environment(self.db));
|
||||
if resolve_module(self.db, importing_file, &project_builtins).is_some() {
|
||||
completions.extend(self.module_completions(&project_builtins).into_iter().map(
|
||||
|mut completion| {
|
||||
completion.builtin = true;
|
||||
@@ -309,7 +348,7 @@ impl<'db> SemanticModel<'db> {
|
||||
/// Returns `true` if the given class definition's name was previously
|
||||
/// bound in the same scope (i.e., the class definition is a re-assignment).
|
||||
pub fn is_class_name_reassigned(&self, class_def: &ast::StmtClassDef) -> bool {
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
let definition = index.expect_single_definition(class_def);
|
||||
let scope = definition.scope(self.db);
|
||||
let table = place_table(self.db, scope);
|
||||
@@ -319,7 +358,7 @@ impl<'db> SemanticModel<'db> {
|
||||
|
||||
/// Returns the scope in which `node` is defined (handles string annotations).
|
||||
pub fn scope(&self, node: ast::AnyNodeRef<'_>) -> Option<FileScopeId> {
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
match self.node_in_ast(node) {
|
||||
ast::AnyNodeRef::Identifier(identifier) => index.try_expression_scope_id(identifier),
|
||||
|
||||
@@ -373,7 +412,7 @@ impl<'db> SemanticModel<'db> {
|
||||
&self,
|
||||
node: ast::AnyNodeRef<'_>,
|
||||
) -> impl Iterator<Item = (FileScopeId, &Scope)> + '_ {
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
self.scope(node)
|
||||
.into_iter()
|
||||
.flat_map(move |scope| index.ancestor_scopes(scope))
|
||||
@@ -391,8 +430,8 @@ impl<'db> SemanticModel<'db> {
|
||||
&self,
|
||||
covering_node: &CoveringNode<'_>,
|
||||
) -> Option<Definition<'db>> {
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let parsed = parsed_module(self.db, self.file).load(self.db);
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
let parsed = parsed_module(self.db, self.python_file()).load(self.db);
|
||||
let target_range = covering_node.node().range();
|
||||
|
||||
for node in covering_node.ancestors() {
|
||||
@@ -462,11 +501,11 @@ impl<'db> SemanticModel<'db> {
|
||||
) -> Option<(Parsed<ModExpression>, Self)> {
|
||||
// Ask the inference engine whether this is actually a string annotation
|
||||
let expr = ExprRef::StringLiteral(string_expr);
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
// When looking up scopes, use the expr in the top-level AST
|
||||
// (we might be trying to enter a sub-sub-AST, so this isn't silly)
|
||||
let file_scope = index.expression_scope_id(&self.expr_ref_in_ast(expr));
|
||||
let scope = file_scope.to_scope_id(self.db, self.python_file());
|
||||
let scope = file_scope.to_scope_id(self.db, self.program_file());
|
||||
// When querying whether the expr is a string annotation, we do however use the actual expr
|
||||
// (the inference engine should record this information even for sub-nodes)
|
||||
if !infer_complete_scope_types(self.db, scope).is_string_annotation(expr) {
|
||||
@@ -507,7 +546,7 @@ impl<'db> SemanticModel<'db> {
|
||||
DefinitionKind::TypeAlias(_) => true,
|
||||
DefinitionKind::AnnotatedAssignment(assignment) => {
|
||||
let parsed = parsed_module(self.db, definition.python_file(self.db));
|
||||
let model = Self::new(self.db, definition.python_file(self.db));
|
||||
let model = Self::new(self.db, definition.program_file(self.db));
|
||||
model.is_type_alias_annotation(assignment.annotation(&parsed.load(self.db)))
|
||||
}
|
||||
_ => false,
|
||||
@@ -619,9 +658,9 @@ impl<'db> SemanticModel<'db> {
|
||||
string_expr: &ast::ExprStringLiteral,
|
||||
) -> Option<Type<'db>> {
|
||||
let expr = ast::ExprRef::from(string_expr);
|
||||
let index = semantic_index(self.db, self.python_file());
|
||||
let index = semantic_index(self.db, self.program_file());
|
||||
let file_scope = index.try_expression_scope_id(&self.expr_ref_in_ast(expr))?;
|
||||
let scope = file_scope.to_scope_id(self.db, self.python_file());
|
||||
let scope = file_scope.to_scope_id(self.db, self.program_file());
|
||||
|
||||
infer_complete_scope_types(self.db, scope).try_expected_type(expr)
|
||||
}
|
||||
@@ -720,7 +759,7 @@ pub(crate) trait HasOptionalDefinition {
|
||||
|
||||
impl HasType for ast::ExprRef<'_> {
|
||||
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
|
||||
let file = model.python_file();
|
||||
let file = model.program_file();
|
||||
let index = semantic_index(model.db, file);
|
||||
// TODO(#1637): semantic tokens is making this crash even with
|
||||
// `try_expr_ref_in_ast` guarding this, for now just use `try_expression_scope_id`.
|
||||
@@ -824,7 +863,7 @@ macro_rules! impl_binding_has_ty_def {
|
||||
impl HasDefinition for $ty {
|
||||
#[inline]
|
||||
fn definition<'db>(&self, model: &SemanticModel<'db>) -> Definition<'db> {
|
||||
let index = semantic_index(model.db, model.python_file());
|
||||
let index = semantic_index(model.db, model.program_file());
|
||||
index.expect_single_definition(self)
|
||||
}
|
||||
}
|
||||
@@ -853,7 +892,7 @@ impl HasType for ast::Alias {
|
||||
if &self.name == "*" {
|
||||
return Some(Type::Never);
|
||||
}
|
||||
let index = semantic_index(model.db, model.python_file());
|
||||
let index = semantic_index(model.db, model.program_file());
|
||||
Some(binding_type(
|
||||
model.db(),
|
||||
index.expect_single_definition(self),
|
||||
@@ -865,7 +904,7 @@ impl HasOptionalDefinition for ast::ExceptHandlerExceptHandler {
|
||||
fn optional_definition<'db>(&self, model: &SemanticModel<'db>) -> Option<Definition<'db>> {
|
||||
self.name.as_ref()?;
|
||||
|
||||
let index = semantic_index(model.db, model.python_file());
|
||||
let index = semantic_index(model.db, model.program_file());
|
||||
Some(index.expect_single_definition(self))
|
||||
}
|
||||
}
|
||||
@@ -881,9 +920,9 @@ impl HasType for ast::ExceptHandlerExceptHandler {
|
||||
mod tests {
|
||||
use crate::db::tests::TestDbBuilder;
|
||||
use crate::{HasType, SemanticModel};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
#[test]
|
||||
fn function_type() -> anyhow::Result<()> {
|
||||
@@ -893,8 +932,8 @@ mod tests {
|
||||
|
||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
|
||||
let foo = PythonFile::new(&db, foo, db.python_version());
|
||||
let ast = parsed_module(&db, foo).load(&db);
|
||||
let foo = ProgramFile::new(&db, foo, db.program_environment().program(&db));
|
||||
let ast = parsed_module(&db, foo.python_file(&db)).load(&db);
|
||||
|
||||
let function = ast.suite()[0].as_function_def_stmt().unwrap();
|
||||
let model = SemanticModel::new(&db, foo);
|
||||
@@ -913,8 +952,8 @@ mod tests {
|
||||
|
||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
|
||||
let foo = PythonFile::new(&db, foo, db.python_version());
|
||||
let ast = parsed_module(&db, foo).load(&db);
|
||||
let foo = ProgramFile::new(&db, foo, db.program_environment().program(&db));
|
||||
let ast = parsed_module(&db, foo.python_file(&db)).load(&db);
|
||||
|
||||
let class = ast.suite()[0].as_class_def_stmt().unwrap();
|
||||
let model = SemanticModel::new(&db, foo);
|
||||
@@ -934,8 +973,8 @@ mod tests {
|
||||
|
||||
let bar = system_path_to_file(&db, "/src/bar.py").unwrap();
|
||||
|
||||
let bar = PythonFile::new(&db, bar, db.python_version());
|
||||
let ast = parsed_module(&db, bar).load(&db);
|
||||
let bar = ProgramFile::new(&db, bar, db.program_environment().program(&db));
|
||||
let ast = parsed_module(&db, bar.python_file(&db)).load(&db);
|
||||
|
||||
let import = ast.suite()[0].as_import_from_stmt().unwrap();
|
||||
let alias = &import.names[0];
|
||||
|
||||
@@ -14,14 +14,13 @@ use call::{CallDunderError, CallError, CallErrorKind};
|
||||
use context::InferContext;
|
||||
pub use context::ProgramEnvironment;
|
||||
use ruff_db::Instant;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Span};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::Ranged;
|
||||
use smallvec::smallvec_inline;
|
||||
use ty_module_resolver::{KnownModule, Module, ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, KnownModule, Module, ModuleName, resolve_module};
|
||||
|
||||
pub(crate) use self::callable::UpcastPolicy;
|
||||
pub use self::cyclic::CycleDetector;
|
||||
@@ -114,7 +113,7 @@ pub(crate) use special_form::TypedDictModule;
|
||||
use ty_python_core::definition::{Definition, DefinitionKind};
|
||||
use ty_python_core::place::ScopedPlaceId;
|
||||
use ty_python_core::scope::ScopeId;
|
||||
use ty_python_core::{Truthiness, place_table, semantic_index, use_def_map};
|
||||
use ty_python_core::{ProgramFile, Truthiness, place_table, semantic_index, use_def_map};
|
||||
|
||||
mod attribute_write;
|
||||
mod bool;
|
||||
@@ -173,7 +172,7 @@ mod definition;
|
||||
mod property_tests;
|
||||
mod subscript;
|
||||
|
||||
pub fn check_types(db: &dyn Db, file: PythonFile<'_>) -> Vec<Diagnostic> {
|
||||
pub fn check_types(db: &dyn Db, file: ProgramFile<'_>) -> Vec<Diagnostic> {
|
||||
let source_file = file.file(db);
|
||||
let _span = tracing::trace_span!("check_types", ?source_file).entered();
|
||||
tracing::debug!("Checking file '{path}'", path = source_file.path(db));
|
||||
@@ -204,7 +203,7 @@ pub fn check_types(db: &dyn Db, file: PythonFile<'_>) -> Vec<Diagnostic> {
|
||||
.map(|error| Diagnostic::invalid_syntax(source_file, error, error)),
|
||||
);
|
||||
|
||||
let diagnostics = check_suppressions(db, file, diagnostics);
|
||||
let diagnostics = check_suppressions(db, file.python_file(db), diagnostics);
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed >= Duration::from_millis(100) {
|
||||
@@ -237,7 +236,7 @@ pub(crate) fn binding_type<'db>(db: &'db dyn Db, definition: Definition<'db>) ->
|
||||
/// ```
|
||||
#[salsa::tracked(returns(copy))]
|
||||
pub(crate) fn exists_at_runtime<'db>(db: &'db dyn Db, definition: Definition<'db>) -> bool {
|
||||
let file = definition.python_file(db);
|
||||
let file = definition.program_file(db);
|
||||
let inference = infer_definition_types(db, definition);
|
||||
let ty = inference.binding_type(definition);
|
||||
|
||||
@@ -250,7 +249,7 @@ pub(crate) fn exists_at_runtime<'db>(db: &'db dyn Db, definition: Definition<'db
|
||||
return false;
|
||||
}
|
||||
|
||||
let parsed = parsed_module(db, file);
|
||||
let parsed = parsed_module(db, file.python_file(db));
|
||||
let module = parsed.load(db);
|
||||
|
||||
// Definitions inside an `if TYPE_CHECKING` block are never available at runtime.
|
||||
@@ -337,7 +336,7 @@ fn definition_expression_type<'db>(
|
||||
definition: Definition<'db>,
|
||||
expression: &ast::Expr,
|
||||
) -> Type<'db> {
|
||||
let file = definition.python_file(db);
|
||||
let file = definition.program_file(db);
|
||||
let index = semantic_index(db, file);
|
||||
let file_scope = index.expression_scope_id(expression);
|
||||
let scope = file_scope.to_scope_id(db, file);
|
||||
@@ -364,7 +363,7 @@ fn definition_expression_annotation<'db>(
|
||||
definition: Definition<'db>,
|
||||
expression: &ast::Expr,
|
||||
) -> TypeAndQualifiers<'db> {
|
||||
let file = definition.python_file(db);
|
||||
let file = definition.program_file(db);
|
||||
let index = semantic_index(db, file);
|
||||
let file_scope = index.expression_scope_id(expression);
|
||||
let scope = file_scope.to_scope_id(db, file);
|
||||
@@ -638,7 +637,7 @@ impl Default for MemberLookupPolicy {
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
struct MemberLookupKey<'db> {
|
||||
#[returns(copy)]
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
#[returns(copy)]
|
||||
ty: Type<'db>,
|
||||
#[returns(ref)]
|
||||
@@ -1192,7 +1191,7 @@ impl<T> InstanceProjection<T> {
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
struct TypePair<'db> {
|
||||
#[returns(copy)]
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
#[returns(copy)]
|
||||
first: Type<'db>,
|
||||
#[returns(copy)]
|
||||
@@ -1694,7 +1693,7 @@ impl<'db> Type<'db> {
|
||||
fn cached_materialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Type<'db> {
|
||||
let env = &ProgramEnvironment::from_program(program);
|
||||
@@ -1894,7 +1893,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
pub(crate) fn module_literal(
|
||||
db: &'db dyn Db,
|
||||
importing_file: PythonFile<'db>,
|
||||
importing_file: ProgramFile<'db>,
|
||||
submodule: Module<'db>,
|
||||
) -> Self {
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(
|
||||
@@ -2873,7 +2872,7 @@ impl<'db> Type<'db> {
|
||||
#[salsa::tracked(returns(copy), cycle_initial=|_, _, _, _| None, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn lookup_dunder_new_inner<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
ty: Type<'db>,
|
||||
) -> Option<PlaceAndQualifiers<'db>> {
|
||||
let env = &ProgramEnvironment::from_program(program);
|
||||
@@ -3463,7 +3462,7 @@ impl<'db> Type<'db> {
|
||||
#[salsa::tracked(returns(copy), cycle_initial=|_, _, _, _, _, _| None, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn try_call_dunder_get_inner<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
ty: Type<'db>,
|
||||
instance: Option<Type<'db>>,
|
||||
owner: Type<'db>,
|
||||
@@ -3798,7 +3797,11 @@ impl<'db> Type<'db> {
|
||||
cycle_initial=|_, _, _, _| true,
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
)]
|
||||
fn is_definitely_non_data_descriptor_impl(self, db: &'db dyn Db, program: Program) -> bool {
|
||||
fn is_definitely_non_data_descriptor_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
program: Program<'db>,
|
||||
) -> bool {
|
||||
let env = &ProgramEnvironment::from_program(program);
|
||||
match self {
|
||||
Type::Dynamic(_) | Type::Divergent(_) | Type::TypeVar(_) => false,
|
||||
@@ -3826,7 +3829,7 @@ impl<'db> Type<'db> {
|
||||
fn is_data_descriptor_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
any_of_union: bool,
|
||||
) -> bool {
|
||||
let env = &ProgramEnvironment::from_program(program);
|
||||
@@ -6562,7 +6565,7 @@ impl<'db> Type<'db> {
|
||||
fallback_type: Type::unknown(),
|
||||
});
|
||||
}
|
||||
let index = semantic_index(db, scope_id.python_file(db));
|
||||
let index = semantic_index(db, scope_id.program_file(db));
|
||||
Ok(bind_typevar(
|
||||
db,
|
||||
index,
|
||||
@@ -7647,7 +7650,7 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
)]
|
||||
fn expand_eagerly_(self, db: &'db dyn Db, program: Program) -> Type<'db> {
|
||||
fn expand_eagerly_(self, db: &'db dyn Db, program: Program<'db>) -> Type<'db> {
|
||||
let env = &ProgramEnvironment::from_program(program);
|
||||
self.apply_type_mapping(
|
||||
db,
|
||||
@@ -9058,7 +9061,7 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||
&& function_body_scope
|
||||
.scope(db)
|
||||
.parent()
|
||||
.map(|parent| parent.to_scope_id(db, function_body_scope.python_file(db)))
|
||||
.map(|parent| parent.to_scope_id(db, function_body_scope.program_file(db)))
|
||||
== builtins_module_scope(db, env)
|
||||
{
|
||||
diagnostic.set_primary_annotation_message("Did you mean `collections.abc.Callable`?");
|
||||
@@ -9198,14 +9201,14 @@ pub struct ModuleLiteralType<'db> {
|
||||
/// the same underlying single-file module are understood by ty as being equivalent types
|
||||
/// in all situations.
|
||||
#[returns(copy)]
|
||||
_importing_file: Option<PythonFile<'db>>,
|
||||
_importing_file: Option<ProgramFile<'db>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for ModuleLiteralType<'_> {}
|
||||
|
||||
impl<'db> ModuleLiteralType<'db> {
|
||||
fn importing_file(self, db: &'db dyn Db) -> Option<PythonFile<'db>> {
|
||||
fn importing_file(self, db: &'db dyn Db) -> Option<ProgramFile<'db>> {
|
||||
debug_assert_eq!(
|
||||
self._importing_file(db).is_some(),
|
||||
self.module(db).kind(db).is_package()
|
||||
@@ -9277,7 +9280,14 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
let relative_submodule_name = ModuleName::new(name)?;
|
||||
let mut absolute_submodule_name = self.module(db).name(db).clone();
|
||||
absolute_submodule_name.extend(&relative_submodule_name);
|
||||
let submodule = resolve_module(db, importing_file, &absolute_submodule_name)?;
|
||||
let submodule = resolve_module(
|
||||
db,
|
||||
ImportingFile::File(
|
||||
importing_file.file(db),
|
||||
importing_file.resolver_environment(db),
|
||||
),
|
||||
&absolute_submodule_name,
|
||||
)?;
|
||||
Some(Type::module_literal(db, importing_file, submodule))
|
||||
}
|
||||
|
||||
@@ -9290,7 +9300,10 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
// For module literals, we want to try calling the module's own `__getattr__` function
|
||||
// if it exists. First, we need to look up the `__getattr__` function in the module's scope.
|
||||
let module = self.module(db);
|
||||
if let Some(file) = module.python_file(db) {
|
||||
if let Some(file) = module
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, env.program(db)))
|
||||
{
|
||||
let getattr_symbol = imported_symbol(db, env, Some(file), "__getattr__", None);
|
||||
// If we found a __getattr__ function, try to call it with the name argument
|
||||
if let Place::Defined(place) = getattr_symbol.place
|
||||
@@ -9345,7 +9358,10 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
|
||||
let place_and_qualifiers = imported_symbol(db, env, module.python_file(db), name, None);
|
||||
let file = module
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, env.program(db)));
|
||||
let place_and_qualifiers = imported_symbol(db, env, file, name, None);
|
||||
|
||||
// If the normal lookup failed, try to call the module's `__getattr__` function
|
||||
if place_and_qualifiers.place.is_undefined() {
|
||||
|
||||
@@ -161,7 +161,7 @@ impl<'db> Type<'db> {
|
||||
#[salsa::tracked(returns(copy), cycle_initial=|_, _, _, _, _, _| None, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn try_call_bin_op_return_type_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
left_ty: Type<'db>,
|
||||
op: ast::Operator,
|
||||
right_ty: Type<'db>,
|
||||
|
||||
@@ -76,7 +76,7 @@ use crate::types::{
|
||||
use crate::{DisplaySettings, FxOrderSet};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, ArgOrKeyword, PythonVersion};
|
||||
use ty_python_core::semantic_index;
|
||||
use ty_python_core::{ProgramFile, semantic_index};
|
||||
|
||||
pub(crate) use self::constructor::ConstructorCallableKind;
|
||||
|
||||
@@ -2305,7 +2305,8 @@ impl<'db> Bindings<'db> {
|
||||
Type::ModuleLiteral(module_literal) => {
|
||||
let all_names = module_literal
|
||||
.module(db)
|
||||
.python_file(db)
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, env.program(db)))
|
||||
.map(|file| dunder_all_names(db, file))
|
||||
.unwrap_or_default();
|
||||
match all_names {
|
||||
@@ -7658,7 +7659,7 @@ impl<'db> CallableDescription<'db> {
|
||||
db: &'db dyn Db,
|
||||
function: FunctionType<'db>,
|
||||
) -> Cow<'db, str> {
|
||||
let semantic_index = semantic_index(db, function.python_file(db));
|
||||
let semantic_index = semantic_index(db, function.program_file(db));
|
||||
let enclosing_scope = semantic_index.scope(function.definition(db).file_scope(db));
|
||||
if let Some(class_node) = enclosing_scope.node().as_class()
|
||||
&& let Some(class) =
|
||||
|
||||
@@ -52,7 +52,6 @@ use crate::{
|
||||
},
|
||||
types::{MetaclassCandidate, TypeDefinition, UnionType},
|
||||
};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::diagnostic::Span;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
@@ -61,7 +60,7 @@ use ruff_python_ast::{self as ast, NodeIndex};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_python_core::definition::Definition;
|
||||
use ty_python_core::scope::ScopeId;
|
||||
use ty_python_core::{place_table, use_def_map};
|
||||
use ty_python_core::{ProgramFile, place_table, use_def_map};
|
||||
|
||||
mod dynamic_literal;
|
||||
mod enum_literal;
|
||||
@@ -495,7 +494,7 @@ impl<'db> GenericAlias<'db> {
|
||||
typevar: BoundTypeVarIdentity<'db>,
|
||||
) -> TypeVarVariance {
|
||||
let origin = self.origin(db);
|
||||
let env = ProgramEnvironment::from_file(origin.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(origin.program_file(db));
|
||||
|
||||
let specialization = self.specialization(db);
|
||||
|
||||
@@ -783,13 +782,13 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
|
||||
pub(crate) fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.python_file(db),
|
||||
Self::Dynamic(class) => class.scope(db).python_file(db),
|
||||
Self::DynamicNamedTuple(class) => class.scope(db).python_file(db),
|
||||
Self::DynamicTypedDict(class) => class.scope(db).python_file(db),
|
||||
Self::DynamicEnum(enum_lit) => enum_lit.scope(db).python_file(db),
|
||||
Self::Static(class) => class.program_file(db),
|
||||
Self::Dynamic(class) => class.scope(db).program_file(db),
|
||||
Self::DynamicNamedTuple(class) => class.scope(db).program_file(db),
|
||||
Self::DynamicTypedDict(class) => class.scope(db).program_file(db),
|
||||
Self::DynamicEnum(enum_lit) => enum_lit.scope(db).program_file(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1384,7 +1383,7 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
|
||||
let mut abstract_methods: FxIndexMap<Name, _> = FxIndexMap::default();
|
||||
let env = &ProgramEnvironment::from_file(self.class_literal(db).python_file(db));
|
||||
let env = &ProgramEnvironment::from_file(self.class_literal(db).program_file(db));
|
||||
|
||||
// Iterate through the MRO in reverse order,
|
||||
// skipping `object` (we know it doesn't define any abstract methods)
|
||||
@@ -2203,7 +2202,7 @@ impl<'db> ClassType<'db> {
|
||||
db: &'db dyn Db,
|
||||
receiver: Type<'db>,
|
||||
) -> CallableTypes<'db> {
|
||||
let env = &ProgramEnvironment::from_file(self.class_literal(db).python_file(db));
|
||||
let env = &ProgramEnvironment::from_file(self.class_literal(db).program_file(db));
|
||||
// TODO: This mimics a lot of the logic in Type::try_call_from_constructor. Can we
|
||||
// consolidate the two? Can we invoke a class by upcasting the class into a Callable, and
|
||||
// then relying on the call binding machinery to Just Work™?
|
||||
@@ -2986,7 +2985,7 @@ impl<'db> QualifiedClassName<'db> {
|
||||
let body_scope = class.body_scope(self.db);
|
||||
// Skip the class body scope itself.
|
||||
(
|
||||
body_scope.python_file(self.db),
|
||||
body_scope.program_file(self.db),
|
||||
body_scope.file_scope_id(self.db),
|
||||
1,
|
||||
)
|
||||
@@ -2994,20 +2993,20 @@ impl<'db> QualifiedClassName<'db> {
|
||||
ClassLiteral::Dynamic(class) => {
|
||||
// Dynamic classes don't have a body scope; start from the enclosing scope.
|
||||
let scope = class.scope(self.db);
|
||||
(scope.python_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
(scope.program_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(namedtuple) => {
|
||||
// Dynamic namedtuples don't have a body scope; start from the enclosing scope.
|
||||
let scope = namedtuple.scope(self.db);
|
||||
(scope.python_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
(scope.program_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
ClassLiteral::DynamicTypedDict(typeddict) => {
|
||||
let scope = typeddict.scope(self.db);
|
||||
(scope.python_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
(scope.program_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
ClassLiteral::DynamicEnum(enum_lit) => {
|
||||
let scope = enum_lit.scope(self.db);
|
||||
(scope.python_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
(scope.program_file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -202,8 +202,9 @@ impl<'db> DynamicClassLiteral<'db> {
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> Box<[Type<'db>]> {
|
||||
let python_file = definition.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
|
||||
let value = definition
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::{
|
||||
known_instance::DeprecatedInstance,
|
||||
},
|
||||
};
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use rustc_hash::FxHashSet;
|
||||
@@ -21,7 +20,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
use ty_module_resolver::{KnownModule, file_to_module};
|
||||
use ty_module_resolver::{ImportingFile, KnownModule, file_to_module};
|
||||
use ty_python_core::{SemanticIndex, Truthiness, scope::NodeWithScopeKind};
|
||||
|
||||
/// Non-exhaustive enumeration of known classes (e.g. `builtins.int`, `typing.Any`, ...) to allow
|
||||
@@ -1569,7 +1568,7 @@ impl KnownClass {
|
||||
|
||||
pub(crate) fn try_from_file_and_name(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ImportingFile<'_>,
|
||||
class_name: &str,
|
||||
) -> Option<Self> {
|
||||
// We assert that this match is exhaustive over the right-hand side in the unit test
|
||||
@@ -1682,9 +1681,8 @@ impl KnownClass {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let module = file_to_module(db, file)?.known(db)?;
|
||||
let module = file_to_module(db, file.resolver_file(db))?.known(db)?;
|
||||
let python_version = file.python_version(db);
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.copied()
|
||||
@@ -1967,7 +1965,7 @@ struct KnownClassArgument {
|
||||
class: KnownClass,
|
||||
|
||||
#[returns(copy)]
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
}
|
||||
|
||||
/// Enumeration of ways in which looking up a [`KnownClass`] in its canonical module could fail.
|
||||
@@ -2076,6 +2074,7 @@ mod tests {
|
||||
source: PythonVersionSource::default(),
|
||||
});
|
||||
let python_version = db.python_version();
|
||||
let resolver_environment = db.program_environment().resolver_environment(&db);
|
||||
for class in KnownClass::iter() {
|
||||
if class.canonical_module(python_version).is_third_party() {
|
||||
continue;
|
||||
@@ -2083,7 +2082,7 @@ mod tests {
|
||||
let class_name = class.name(python_version);
|
||||
let class_module = resolve_module_confident(
|
||||
&db,
|
||||
python_version,
|
||||
resolver_environment,
|
||||
&class.canonical_module(python_version).name(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -2091,7 +2090,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
KnownClass::try_from_file_and_name(
|
||||
&db,
|
||||
class_module.python_file(&db).unwrap(),
|
||||
ImportingFile::File(class_module.file(&db).unwrap(), resolver_environment),
|
||||
class_name
|
||||
),
|
||||
Some(class),
|
||||
|
||||
@@ -62,7 +62,7 @@ use crate::{
|
||||
};
|
||||
use crate::{attribute_assignments, attribute_declarations};
|
||||
use ty_python_core::{
|
||||
attribute_scopes,
|
||||
ProgramFile, attribute_scopes,
|
||||
definition::{Definition, DefinitionKind, DefinitionState, TargetKind},
|
||||
place_table,
|
||||
scope::{Scope, ScopeId},
|
||||
@@ -404,11 +404,12 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
)]
|
||||
fn pep695_generic_context_inner(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
|
||||
let scope = self.body_scope(db);
|
||||
let python_file = scope.python_file(db);
|
||||
let program_file = scope.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let parsed = parsed_module(db, python_file).load(db);
|
||||
let class_def_node = scope.node(db).expect_class().node(&parsed);
|
||||
class_def_node.type_params.as_ref().map(|type_params| {
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
let definition = index.expect_single_definition(class_def_node);
|
||||
GenericContext::from_type_params(db, index, definition, type_params)
|
||||
})
|
||||
@@ -522,6 +523,10 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
self.body_scope(db).python_file(db)
|
||||
}
|
||||
|
||||
pub(crate) fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
self.body_scope(db).program_file(db)
|
||||
}
|
||||
|
||||
/// Return the original [`ast::StmtClassDef`] node associated with this class
|
||||
///
|
||||
/// ## Note
|
||||
@@ -533,7 +538,7 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
let body_scope = self.body_scope(db);
|
||||
let index = semantic_index(db, body_scope.python_file(db));
|
||||
let index = semantic_index(db, body_scope.program_file(db));
|
||||
index.expect_single_definition(body_scope.node(db).expect_class())
|
||||
}
|
||||
|
||||
@@ -625,12 +630,13 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
class.name(db)
|
||||
);
|
||||
|
||||
let python_file = class.python_file(db);
|
||||
let program_file = class.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let class_stmt = class.node(db, &module);
|
||||
|
||||
let class_definition =
|
||||
semantic_index(db, python_file).expect_single_definition(class_stmt);
|
||||
semantic_index(db, program_file).expect_single_definition(class_stmt);
|
||||
expanded_class_base_entries(db, class.known(db), class_stmt, class_definition)
|
||||
.into_iter()
|
||||
.map(ExpandedClassBaseEntry::ty)
|
||||
@@ -716,7 +722,8 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
fn decorators_inner(self, db: &'db dyn Db) -> Box<[Type<'db>]> {
|
||||
tracing::trace!("StaticClassLiteral::decorators: {}", self.name(db));
|
||||
|
||||
let python_file = self.python_file(db);
|
||||
let program_file = self.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
|
||||
let class_stmt = self.node(db, &module);
|
||||
@@ -725,7 +732,7 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
}
|
||||
|
||||
let class_definition =
|
||||
semantic_index(db, self.python_file(db)).expect_single_definition(class_stmt);
|
||||
semantic_index(db, self.program_file(db)).expect_single_definition(class_stmt);
|
||||
|
||||
class_stmt
|
||||
.decorator_list
|
||||
@@ -749,10 +756,12 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
/// Iterate through the decorators on this class, returning the index of the first one
|
||||
/// that is either `@dataclass` or `@dataclass(...)`.
|
||||
pub(crate) fn find_dataclass_decorator_position(self, db: &'db dyn Db) -> Option<usize> {
|
||||
let python_file = self.python_file(db);
|
||||
let program_file = self.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let class_stmt = self.node(db, &module);
|
||||
let class_definition = semantic_index(db, python_file).expect_single_definition(class_stmt);
|
||||
let class_definition =
|
||||
semantic_index(db, program_file).expect_single_definition(class_stmt);
|
||||
|
||||
class_stmt.decorator_list.iter().position(|decorator| {
|
||||
let decorator_callable = decorator
|
||||
@@ -1073,8 +1082,9 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
db: &'db dyn Db,
|
||||
class: StaticClassLiteral<'db>,
|
||||
) -> Result<(Type<'db>, Option<MetaclassTransformInfo<'db>>), MetaclassError<'db>> {
|
||||
let python_file = class.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let program_file = class.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
tracing::trace!("StaticClassLiteral::try_metaclass: {}", class.name(db));
|
||||
|
||||
// Identify the class's own metaclass (or take the first base class's metaclass).
|
||||
@@ -2777,8 +2787,9 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
let class_body_scope = attribute.class_body_scope(db);
|
||||
let name = attribute.name(db).as_str();
|
||||
let target_method_decorator = attribute.target_method_decorator(db);
|
||||
let python_file = class_body_scope.python_file(db);
|
||||
let env = &ProgramEnvironment::from_file(python_file);
|
||||
let program_file = class_body_scope.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let env = &ProgramEnvironment::from_file(program_file);
|
||||
|
||||
// If we do not see any declarations of an attribute, neither in the class body nor in
|
||||
// any method, we build a union of the raw types inferred from all bindings of that
|
||||
@@ -2790,7 +2801,7 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
let mut provenance = Provenance::Unknown;
|
||||
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
let class_map = use_def_map(db, class_body_scope);
|
||||
let class_table = place_table(db, class_body_scope);
|
||||
let is_valid_scope = |method_scope: &Scope| {
|
||||
@@ -3594,10 +3605,10 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
return TypeVarVariance::Bivariant;
|
||||
}
|
||||
let class_body_scope = self.body_scope(db);
|
||||
let python_file = class_body_scope.python_file(db);
|
||||
let program_file = class_body_scope.program_file(db);
|
||||
let python_version = env.python_version(db);
|
||||
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let explicit_bases_variances = self
|
||||
.explicit_bases(db)
|
||||
@@ -3792,7 +3803,7 @@ impl get_size2::GetSize for ImplicitAttributeName<'_> {}
|
||||
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn implicit_attribute_names<'db>(db: &'db dyn Db, class_body_scope: ScopeId<'db>) -> Box<[Name]> {
|
||||
let index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let index = semantic_index(db, class_body_scope.program_file(db));
|
||||
let mut names = Vec::new();
|
||||
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
|
||||
@@ -3897,7 +3897,7 @@ impl<'db> Type<'db> {
|
||||
)]
|
||||
fn assignable_solutions_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
source: Type<'db>,
|
||||
target: Type<'db>,
|
||||
inferable: TypeVarSet<'db>,
|
||||
|
||||
@@ -25,9 +25,10 @@ use crate::{
|
||||
lint::{LintId, LintMetadata},
|
||||
suppression::suppressions,
|
||||
};
|
||||
use ty_module_resolver::ResolverEnvironment;
|
||||
use ty_python_core::definition::Definition;
|
||||
use ty_python_core::scope::ScopeId;
|
||||
use ty_python_core::semantic_index;
|
||||
use ty_python_core::{ProgramFile, semantic_index};
|
||||
|
||||
/// The lazily resolved program used by a semantic operation.
|
||||
#[derive(Clone)]
|
||||
@@ -37,8 +38,8 @@ pub struct ProgramEnvironment<'db> {
|
||||
}
|
||||
|
||||
impl<'db> ProgramEnvironment<'db> {
|
||||
/// Creates an environment that lazily obtains its Python version from `file`.
|
||||
pub fn from_file(file: PythonFile<'db>) -> Self {
|
||||
/// Creates an environment that lazily obtains its program from `file`.
|
||||
pub fn from_file(file: ProgramFile<'db>) -> Self {
|
||||
Self {
|
||||
environment: Cell::new(ProgramSource::File(file.as_id())),
|
||||
lifetime: PhantomData,
|
||||
@@ -62,22 +63,23 @@ impl<'db> ProgramEnvironment<'db> {
|
||||
}
|
||||
|
||||
/// Creates an environment with an already-established program.
|
||||
pub const fn from_program(program: Program) -> Self {
|
||||
pub fn from_program(program: Program<'db>) -> Self {
|
||||
Self {
|
||||
environment: Cell::new(ProgramSource::Program(program)),
|
||||
environment: Cell::new(ProgramSource::Program(program.as_id())),
|
||||
lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the program used by this operation.
|
||||
pub fn program(&self, db: &'db dyn Db) -> Program {
|
||||
#[inline]
|
||||
pub fn program(&self, db: &'db dyn Db) -> Program<'db> {
|
||||
let program = match self.environment.get() {
|
||||
ProgramSource::Program(program) => return program,
|
||||
ProgramSource::Program(id) => return ResolverEnvironment::from_id(id),
|
||||
ProgramSource::File(file) => {
|
||||
cold_path();
|
||||
// The source handle and database share `'db`; re-wrapping the stored ingredient
|
||||
// ID immediately before the read restores the original database lifetime.
|
||||
PythonFile::from_id(file).python_version(db)
|
||||
ProgramFile::from_id(file).program(db)
|
||||
}
|
||||
ProgramSource::Definition(definition) => {
|
||||
cold_path();
|
||||
@@ -93,23 +95,30 @@ impl<'db> ProgramEnvironment<'db> {
|
||||
}
|
||||
};
|
||||
|
||||
self.environment.set(ProgramSource::Program(program));
|
||||
self.environment
|
||||
.set(ProgramSource::Program(program.as_id()));
|
||||
program
|
||||
}
|
||||
|
||||
/// Returns the Python version used by this operation.
|
||||
#[inline]
|
||||
pub fn python_version(&self, db: &'db dyn Db) -> PythonVersion {
|
||||
self.program(db).python_version(db)
|
||||
}
|
||||
|
||||
/// Returns the resolver environment used by this operation.
|
||||
#[inline]
|
||||
pub fn resolver_environment(&self, db: &'db dyn Db) -> ResolverEnvironment<'db> {
|
||||
self.program(db)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ProgramSource {
|
||||
Program(Program),
|
||||
// Salsa interned handles are thin `Id` wrappers, so converting between `PythonFile` and `Id`
|
||||
Program(Id),
|
||||
// Salsa interned handles are thin `Id` wrappers, so converting between `ProgramFile` and `Id`
|
||||
// is an inlined representation change with no database lookup. Keeping the lifetime-bearing
|
||||
// `PythonFile` out of the `Cell` preserves covariance in `'db`; replacing this variant after
|
||||
// `ProgramFile` out of the `Cell` preserves covariance in `'db`; replacing this variant after
|
||||
// the first read avoids repeated Salsa ingredient reads in hot, recursive type operations.
|
||||
File(Id),
|
||||
Definition(Id),
|
||||
@@ -133,7 +142,7 @@ pub(crate) struct InferContext<'db, 'ast> {
|
||||
program_environment: &'ast ProgramEnvironment<'db>,
|
||||
scope: ScopeId<'db>,
|
||||
file: File,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
module: &'ast ParsedModuleRef,
|
||||
diagnostics: std::cell::RefCell<TypeCheckDiagnostics>,
|
||||
diagnostics_suppressed: bool,
|
||||
@@ -148,11 +157,12 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||
program_environment: &'ast ProgramEnvironment<'db>,
|
||||
scope: ScopeId<'db>,
|
||||
file: File,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
module: &'ast ParsedModuleRef,
|
||||
) -> Self {
|
||||
debug_assert_eq!(scope.python_file(db), python_file);
|
||||
debug_assert_eq!(python_file.file(db), file);
|
||||
debug_assert_eq!(scope.program_file(db), program_file);
|
||||
debug_assert_eq!(program_file.file(db), file);
|
||||
debug_assert_eq!(program_environment.program(db), scope.program(db));
|
||||
|
||||
Self {
|
||||
db,
|
||||
@@ -160,7 +170,7 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||
scope,
|
||||
module,
|
||||
file,
|
||||
python_file,
|
||||
program_file,
|
||||
diagnostics: std::cell::RefCell::new(TypeCheckDiagnostics::default()),
|
||||
diagnostics_suppressed: false,
|
||||
inference_flags: InferenceFlags::empty(),
|
||||
@@ -176,7 +186,11 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||
}
|
||||
|
||||
pub(crate) fn python_file(&self) -> PythonFile<'db> {
|
||||
self.python_file
|
||||
self.program_file.python_file(self.db())
|
||||
}
|
||||
|
||||
pub(crate) fn program_file(&self) -> ProgramFile<'db> {
|
||||
self.program_file
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -302,7 +316,7 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||
|
||||
// Accessing the semantic index here is fine because
|
||||
// the index belongs to the same file as for which we emit the diagnostic.
|
||||
let index = semantic_index(self.db(), self.python_file);
|
||||
let index = semantic_index(self.db(), self.program_file);
|
||||
|
||||
let scope_id = self.scope.file_scope_id(self.db());
|
||||
|
||||
@@ -330,7 +344,7 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||
/// specific statement or expression containing this range is reachable.
|
||||
fn is_range_reachable(&self, range: TextRange) -> bool {
|
||||
let db = self.db;
|
||||
let index = semantic_index(self.db(), self.python_file);
|
||||
let index = semantic_index(self.db(), self.program_file);
|
||||
let scope_id = self.scope.file_scope_id(self.db());
|
||||
is_range_reachable(db, index, scope_id, range)
|
||||
}
|
||||
@@ -366,7 +380,7 @@ impl fmt::Debug for InferContext<'_, '_> {
|
||||
.field("db", &"<dyn Db>")
|
||||
.field("scope", &self.scope)
|
||||
.field("file", &self.file)
|
||||
.field("python_file", &self.python_file)
|
||||
.field("program_file", &self.program_file)
|
||||
.field("diagnostics", &self.diagnostics)
|
||||
.field("diagnostics_suppressed", &self.diagnostics_suppressed)
|
||||
.field("inference_flags", &self.inference_flags)
|
||||
|
||||
@@ -690,11 +690,11 @@ mod tests {
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::place::global_symbol;
|
||||
use crate::types::Type;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::DbWithWritableSystem;
|
||||
use std::cell::Cell;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
struct TestVisit;
|
||||
|
||||
@@ -765,7 +765,7 @@ mod tests {
|
||||
name: &str,
|
||||
) -> Type<'db> {
|
||||
let file = system_path_to_file(db, "/src/a.py").unwrap();
|
||||
let file = PythonFile::new(db, file, env.python_version(db));
|
||||
let file = ProgramFile::new(db, file, env.program(db));
|
||||
global_symbol(db, file, name)
|
||||
.place
|
||||
.expect_type()
|
||||
|
||||
@@ -171,7 +171,7 @@ impl<'db> FieldMetadata<'db> {
|
||||
// using `StrictInt = Annotated[int, Strict()]`. Since we don't retain the `Annotated`
|
||||
// metadata, we need to follow the alias back to its definition and parse the metadata
|
||||
// from there.
|
||||
let model = SemanticModel::new(db, definition.python_file(db));
|
||||
let model = SemanticModel::new(db, definition.program_file(db));
|
||||
let Some(alias_definition) = definitions_for_name(
|
||||
&model,
|
||||
name.id.as_str(),
|
||||
@@ -1189,7 +1189,7 @@ fn instance_symbol<'db>(
|
||||
ty: Type<'db>,
|
||||
) -> Option<(KnownModule, &'db str, StaticClassLiteral<'db>)> {
|
||||
let class = ty.nominal_class(db, env)?.class_literal(db).as_static()?;
|
||||
let module = file_to_module(db, class.python_file(db))?.known(db)?;
|
||||
let module = file_to_module(db, class.program_file(db).resolver_file(db))?.known(db)?;
|
||||
Some((module, class.name(db).as_str(), class))
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ use std::fmt::{self, Formatter};
|
||||
use ty_module_resolver::{KnownModule, Module, ModuleName, file_to_module};
|
||||
use ty_python_core::definition::{Definition, DefinitionKind};
|
||||
use ty_python_core::place::{PlaceTable, ScopedPlaceId};
|
||||
use ty_python_core::{global_scope, place_table, use_def_map};
|
||||
use ty_python_core::{ProgramFile, global_scope, place_table, use_def_map};
|
||||
|
||||
const RUNTIME_CHECKABLE_DOCS_URL: &str =
|
||||
"https://docs.python.org/3/library/typing.html#typing.runtime_checkable";
|
||||
@@ -1477,8 +1477,8 @@ pub(super) fn note_numbers_module_not_supported<'db>(
|
||||
let file = target_instance
|
||||
.class(db, env)
|
||||
.class_literal(db)
|
||||
.python_file(db);
|
||||
if let Some(module) = file_to_module(db, file)
|
||||
.program_file(db);
|
||||
if let Some(module) = file_to_module(db, file.resolver_file(db))
|
||||
&& module.is_known(db, KnownModule::Numbers)
|
||||
{
|
||||
let is_numeric = value_ty.is_subtype_of(
|
||||
@@ -4736,6 +4736,7 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
/// misconfigured their Python version.
|
||||
pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db: &dyn Db,
|
||||
env: &ProgramEnvironment<'_>,
|
||||
mut diagnostic: LintDiagnosticGuard,
|
||||
value_type: Type,
|
||||
attr: &str,
|
||||
@@ -4748,7 +4749,7 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
return;
|
||||
};
|
||||
let module = module_ty.module(db);
|
||||
let Some(file) = module.python_file(db) else {
|
||||
let Some(file) = module.file(db) else {
|
||||
return;
|
||||
};
|
||||
let Some(search_path) = module.search_path(db) else {
|
||||
@@ -4761,7 +4762,8 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
// We populate place_table entries for stdlib items across all known versions and platforms,
|
||||
// so if this lookup succeeds then we know that this lookup *could* succeed with possible
|
||||
// configuration changes.
|
||||
let symbol_table = place_table(db, global_scope(db, file));
|
||||
let program_file = ProgramFile::new(db, file, env.program(db));
|
||||
let symbol_table = place_table(db, global_scope(db, program_file));
|
||||
let Some(symbol) = symbol_table.symbol_by_name(attr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::collections::hash_map::Entry;
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FilePath;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
@@ -38,6 +37,7 @@ use crate::types::{
|
||||
SpecialFormType, StringLiteralType, SubclassOfInner, SubclassOfType, Type, TypeAliasType,
|
||||
TypeGuardLike, TypedDictModule, TypedDictType, UnionType, WrapperDescriptorKind, visitor,
|
||||
};
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::definition::Definition;
|
||||
use ty_python_core::scope::{FileScopeId, ScopeKind};
|
||||
use ty_python_core::semantic_index;
|
||||
@@ -742,11 +742,11 @@ fn fmt_file_location<'db>(
|
||||
/// A vector of path components in order (e.g., `["module", "OuterClass", "InnerClass"]`)
|
||||
pub(super) fn qualified_name_components_from_scope(
|
||||
db: &dyn Db,
|
||||
file: PythonFile<'_>,
|
||||
file: ProgramFile<'_>,
|
||||
file_scope_id: FileScopeId,
|
||||
skip_count: usize,
|
||||
) -> Vec<String> {
|
||||
let module_ast = parsed_module(db, file).load(db);
|
||||
let module_ast = parsed_module(db, file.python_file(db)).load(db);
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
let mut name_parts = vec![];
|
||||
@@ -772,7 +772,7 @@ pub(super) fn qualified_name_components_from_scope(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(module) = file_to_module(db, file) {
|
||||
if let Some(module) = file_to_module(db, file.resolver_file(db)) {
|
||||
let module_name = module.name(db);
|
||||
name_parts.push(module_name.as_str().to_string());
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ fn enum_class_literal<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
) -> Option<EnumClassLiteral<'db>> {
|
||||
let env = ProgramEnvironment::from_file(class.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(class.program_file(db));
|
||||
let metadata = enum_metadata(db, class)?;
|
||||
let members = metadata
|
||||
.members
|
||||
@@ -1056,7 +1056,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
return None;
|
||||
}
|
||||
|
||||
let env = ProgramEnvironment::from_file(class.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(class.program_file(db));
|
||||
|
||||
if !is_enum_class_by_inheritance(db, &env, class) {
|
||||
return None;
|
||||
|
||||
@@ -1108,7 +1108,7 @@ fn enum_class_key_profile<'db>(
|
||||
enum_class: EnumClassLiteral<'db>,
|
||||
operator: ComparisonOperator,
|
||||
) -> EnumClassKeyProfile<'db> {
|
||||
let env = ProgramEnvironment::from_file(enum_class.class_literal(db).python_file(db));
|
||||
let env = ProgramEnvironment::from_file(enum_class.class_literal(db).program_file(db));
|
||||
let semantics = KnownComparisonSemantics::of_instance(
|
||||
db,
|
||||
&env,
|
||||
|
||||
@@ -63,7 +63,7 @@ use ruff_python_ast::find_node::covering_node;
|
||||
use ruff_python_ast::{self as ast, OperatorPrecedence, ParameterWithDefault};
|
||||
use ruff_text_size::Ranged;
|
||||
use salsa::plumbing::AsId;
|
||||
use ty_module_resolver::{KnownModule, ModuleName, file_to_module, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, KnownModule, ModuleName, file_to_module, resolve_module};
|
||||
|
||||
use crate::place::{DefinedPlace, Definedness, Place, place_from_bindings};
|
||||
use crate::types::call::{Binding, CallArguments};
|
||||
@@ -101,7 +101,7 @@ use crate::{Db, FxOrderSet, ProgramEnvironment};
|
||||
use ty_python_core::ast_ids::HasScopedUseId;
|
||||
use ty_python_core::definition::Definition;
|
||||
use ty_python_core::scope::ScopeId;
|
||||
use ty_python_core::{FileScopeId, SemanticIndex, semantic_index};
|
||||
use ty_python_core::{FileScopeId, ProgramFile, SemanticIndex, semantic_index};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct RecursiveTypeNormalizationKey {
|
||||
@@ -336,6 +336,10 @@ impl<'db> OverloadLiteral<'db> {
|
||||
self.body_scope(db).python_file(db)
|
||||
}
|
||||
|
||||
pub(crate) fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
self.body_scope(db).program_file(db)
|
||||
}
|
||||
|
||||
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
|
||||
self.decorators(db).contains(decorator)
|
||||
}
|
||||
@@ -441,7 +445,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||
/// over-invalidation.
|
||||
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
let body_scope = self.body_scope(db);
|
||||
let index = semantic_index(db, body_scope.python_file(db));
|
||||
let index = semantic_index(db, body_scope.program_file(db));
|
||||
index.expect_single_definition(body_scope.node(db).expect_function())
|
||||
}
|
||||
|
||||
@@ -453,14 +457,14 @@ impl<'db> OverloadLiteral<'db> {
|
||||
let scope = self.definition(db).scope(db);
|
||||
let module = parsed_module(db, self.python_file(db)).load(db);
|
||||
let use_def =
|
||||
semantic_index(db, scope.python_file(db)).use_def_map(scope.file_scope_id(db));
|
||||
semantic_index(db, scope.program_file(db)).use_def_map(scope.file_scope_id(db));
|
||||
let use_id = self
|
||||
.body_scope(db)
|
||||
.node(db)
|
||||
.expect_function()
|
||||
.node(&module)
|
||||
.name
|
||||
.scoped_use_id(db, self.python_file(db));
|
||||
.scoped_use_id(db, self.program_file(db));
|
||||
|
||||
let env = ProgramEnvironment::from_scope(scope);
|
||||
let Place::Defined(DefinedPlace {
|
||||
@@ -513,16 +517,17 @@ impl<'db> OverloadLiteral<'db> {
|
||||
/// over-invalidation.
|
||||
pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> {
|
||||
let scope = self.body_scope(db);
|
||||
let python_file = self.python_file(db);
|
||||
let program_file = self.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let mut signature = self.raw_signature(db, ReturnCallableTypeVarScope::Public);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let function_node = scope.node(db).expect_function().node(&module);
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
let file_scope_id = scope.file_scope_id(db);
|
||||
let is_generator = file_scope_id.is_generator_function(index);
|
||||
|
||||
if function_node.is_async && !is_generator {
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
signature = signature.wrap_coroutine_return_type(db, &env);
|
||||
}
|
||||
|
||||
@@ -615,11 +620,12 @@ impl<'db> OverloadLiteral<'db> {
|
||||
|
||||
let env = &ProgramEnvironment::from_scope(self.body_scope(db));
|
||||
let scope = self.body_scope(db);
|
||||
let python_file = self.python_file(db);
|
||||
let program_file = self.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let function_stmt_node = scope.node(db).expect_function().node(&module);
|
||||
let definition = self.definition(db);
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
let pep695_ctx = function_stmt_node.type_params.as_ref().map(|type_params| {
|
||||
GenericContext::from_type_params(db, index, definition, type_params)
|
||||
});
|
||||
@@ -685,7 +691,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||
if method_has_explicit_self || class_is_generic || class_is_fallback {
|
||||
let scope_id = definition.scope(db);
|
||||
let typevar_binding_context = Some(definition);
|
||||
let index = semantic_index(db, scope_id.python_file(db));
|
||||
let index = semantic_index(db, scope_id.program_file(db));
|
||||
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
||||
|
||||
let typing_self = typing_self(db, scope_id, typevar_binding_context, class.into())
|
||||
@@ -1016,8 +1022,9 @@ impl<'db> FunctionLiteral<'db> {
|
||||
implementation: OverloadLiteral<'db>,
|
||||
) -> FunctionBodyKind {
|
||||
let definition = implementation.definition(db);
|
||||
let python_file = definition.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
let file = python_file.file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let node = implementation.node(db, file, &module);
|
||||
@@ -1343,6 +1350,10 @@ impl<'db> FunctionType<'db> {
|
||||
self.literal(db).last_definition.python_file(db)
|
||||
}
|
||||
|
||||
pub(crate) fn program_file(self, db: &'db dyn Db) -> ProgramFile<'db> {
|
||||
self.literal(db).last_definition.program_file(db)
|
||||
}
|
||||
|
||||
/// Returns the AST node for this function.
|
||||
pub(super) fn node<'ast>(
|
||||
self,
|
||||
@@ -2376,7 +2387,9 @@ impl KnownFunction {
|
||||
|
||||
let candidate = Self::from_str(name).ok()?;
|
||||
candidate
|
||||
.check_module(file_to_module(db, definition.python_file(db))?.known(db)?)
|
||||
.check_module(
|
||||
file_to_module(db, definition.program_file(db).resolver_file(db))?.known(db)?,
|
||||
)
|
||||
.then_some(candidate)
|
||||
}
|
||||
|
||||
@@ -2894,11 +2907,15 @@ impl KnownFunction {
|
||||
let Some(module_name) = ModuleName::new(module_name) else {
|
||||
return;
|
||||
};
|
||||
let Some(module) = resolve_module(db, context.python_file(), &module_name) else {
|
||||
let importing_file = ImportingFile::File(
|
||||
context.file(),
|
||||
context.program_environment().resolver_environment(db),
|
||||
);
|
||||
let Some(module) = resolve_module(db, importing_file, &module_name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
overload.set_return_type(Type::module_literal(db, context.python_file(), module));
|
||||
overload.set_return_type(Type::module_literal(db, context.program_file(), module));
|
||||
}
|
||||
|
||||
KnownFunction::TotalOrdering => {
|
||||
|
||||
@@ -256,7 +256,7 @@ pub(crate) fn typing_self<'db>(
|
||||
class: ClassLiteral<'db>,
|
||||
) -> Option<BoundTypeVarInstance<'db>> {
|
||||
let env = ProgramEnvironment::from_scope(scope_id);
|
||||
let index = semantic_index(db, scope_id.python_file(db));
|
||||
let index = semantic_index(db, scope_id.program_file(db));
|
||||
|
||||
let identity = TypeVarIdentity::new(
|
||||
db,
|
||||
@@ -344,7 +344,7 @@ pub(crate) fn typing_self<'db>(
|
||||
#[salsa::interned(debug, constructor=new_internal, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct GenericContext<'db> {
|
||||
#[returns(copy)]
|
||||
pub(crate) program: Program,
|
||||
pub(crate) program: Program<'db>,
|
||||
|
||||
#[returns(ref)]
|
||||
variables_inner: FxOrderMap<BoundTypeVarIdentity<'db>, BoundTypeVarInstance<'db>>,
|
||||
@@ -437,7 +437,7 @@ impl<'db> GenericContext<'db> {
|
||||
|
||||
fn from_typevar_instances_in_program(
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
program: Program<'db>,
|
||||
type_params: impl IntoIterator<Item = BoundTypeVarInstance<'db>>,
|
||||
) -> Self {
|
||||
Self::new_internal(
|
||||
|
||||
@@ -15,16 +15,15 @@ use crate::types::{
|
||||
};
|
||||
use crate::{Db, DisplaySettings, HasDefinition, HasType, ProgramEnvironment, SemanticModel};
|
||||
use itertools::Either;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, name::Name};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_module_resolver::Module;
|
||||
use ty_module_resolver::{ImportingFile, Module, ResolverFile};
|
||||
use ty_python_core::definition::{Definition, DefinitionKind, NestedBindingExecution};
|
||||
use ty_python_core::{attribute_scopes, global_scope, semantic_index, use_def_map};
|
||||
use ty_python_core::{ProgramFile, attribute_scopes, global_scope, semantic_index, use_def_map};
|
||||
|
||||
mod unreachable_code;
|
||||
#[path = "ide_support/unused_bindings.rs"]
|
||||
@@ -64,7 +63,8 @@ pub fn definitions_for_name<'db>(
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let db = model.db();
|
||||
let file = model.python_file();
|
||||
let env = model.program_environment();
|
||||
let file = model.program_file();
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
// Get the scope for this name expression
|
||||
@@ -165,12 +165,11 @@ pub fn definitions_for_name<'db>(
|
||||
let mut resolved_definitions = Vec::new();
|
||||
|
||||
for definition in &all_definitions {
|
||||
let resolved = resolve_definition(db, *definition, Some(name_str), alias_resolution);
|
||||
let resolved = resolve_definition(db, &env, *definition, Some(name_str), alias_resolution);
|
||||
resolved_definitions.extend(resolved);
|
||||
}
|
||||
|
||||
// If we didn't find any definitions in scopes, fallback to builtins
|
||||
let env = model.program_environment();
|
||||
if resolved_definitions.is_empty()
|
||||
&& let Some(builtins_scope) = implicit_builtins_symbol_scope(db, &env, name_str)
|
||||
{
|
||||
@@ -209,6 +208,7 @@ pub fn definitions_for_name<'db>(
|
||||
.flat_map(|def| {
|
||||
resolve_definition(
|
||||
db,
|
||||
&env,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
@@ -275,11 +275,16 @@ pub fn definitions_for_attribute<'db>(
|
||||
for ty in expanded_tys {
|
||||
// Handle modules
|
||||
if let Type::ModuleLiteral(module_literal) = ty {
|
||||
if let Some(module_file) = module_literal.module(db).python_file(db) {
|
||||
if let Some(module_file) = module_literal
|
||||
.module(db)
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, model.program_environment().program(db)))
|
||||
{
|
||||
let module_scope = global_scope(db, module_file);
|
||||
for def in find_symbol_in_scope(db, module_scope, name_str) {
|
||||
resolved.extend(resolve_definition(
|
||||
db,
|
||||
&env,
|
||||
def,
|
||||
Some(name_str),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
@@ -437,7 +442,7 @@ impl<'db> ImplementationsFinder<'db> {
|
||||
pub fn implementations_for_file<'scan>(
|
||||
&'scan self,
|
||||
db: &'scan dyn Db,
|
||||
file: PythonFile<'scan>,
|
||||
file: ProgramFile<'scan>,
|
||||
) -> Vec<ResolvedDefinition<'scan>>
|
||||
where
|
||||
'db: 'scan,
|
||||
@@ -524,7 +529,7 @@ impl<'db> ImplementationsFinder<'db> {
|
||||
.and_then(Type::as_property_instance)
|
||||
.and_then(|property| property.accessor_role(db, function_definition));
|
||||
let class_node = containing_scope.node(db).as_class()?;
|
||||
let class_definition = semantic_index(db, containing_scope.python_file(db))
|
||||
let class_definition = semantic_index(db, containing_scope.program_file(db))
|
||||
.expect_single_definition(class_node);
|
||||
let class_ty = binding_type(db, class_definition);
|
||||
let root = extract_class_literal(db, &env, class_ty)?;
|
||||
@@ -637,7 +642,7 @@ impl<'db> ImplementationsFinder<'db> {
|
||||
/// Finds subclasses of `roots` defined in `file`.
|
||||
fn class_implementations_for_file<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
roots: &FxHashSet<ClassLiteral<'db>>,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
if !contains_identifier(&source_text(db, file.file(db)), "class") {
|
||||
@@ -680,6 +685,7 @@ fn definitions_for_attribute_in_class_hierarchy<'db>(
|
||||
attribute_name: &str,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let db = model.db();
|
||||
let env = model.program_environment();
|
||||
let mut resolved = Vec::new();
|
||||
'scopes: for ancestor in class_literal
|
||||
.iter_mro(db)
|
||||
@@ -694,6 +700,7 @@ fn definitions_for_attribute_in_class_hierarchy<'db>(
|
||||
let use_def = use_def_map(db, class_scope);
|
||||
let resolved_in_scope = resolve_reachable_definitions(
|
||||
db,
|
||||
&env,
|
||||
attribute_name,
|
||||
use_def
|
||||
.reachable_symbol_declarations(place_id)
|
||||
@@ -711,7 +718,7 @@ fn definitions_for_attribute_in_class_hierarchy<'db>(
|
||||
}
|
||||
|
||||
// Look for instance attributes in method scopes (e.g., self.x = 1)
|
||||
let index = semantic_index(db, class_scope.python_file(db));
|
||||
let index = semantic_index(db, class_scope.program_file(db));
|
||||
|
||||
for function_scope_id in attribute_scopes(db, class_scope) {
|
||||
if let Some(place_id) = index
|
||||
@@ -721,6 +728,7 @@ fn definitions_for_attribute_in_class_hierarchy<'db>(
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
let resolved_in_scope = resolve_reachable_definitions(
|
||||
db,
|
||||
&env,
|
||||
attribute_name,
|
||||
use_def
|
||||
.reachable_member_declarations(place_id)
|
||||
@@ -745,7 +753,7 @@ fn definitions_for_attribute_in_class_hierarchy<'db>(
|
||||
/// Finds member implementations contributed by subclasses of `roots` defined in `file`.
|
||||
fn member_implementations_for_file<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
roots: &FxHashSet<ClassLiteral<'db>>,
|
||||
member_name: &str,
|
||||
accessor_role: Option<PropertyAccessorRole>,
|
||||
@@ -877,7 +885,7 @@ fn own_member_definitions<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
let file = class_scope.python_file(db);
|
||||
let file = class_scope.program_file(db);
|
||||
let index = semantic_index(db, file);
|
||||
let mut instance_definitions = Vec::new();
|
||||
for function_scope_id in attribute_scopes(db, class_scope) {
|
||||
@@ -1073,7 +1081,7 @@ fn user_visible_definitions<'db>(
|
||||
|
||||
match definition.kind(db) {
|
||||
DefinitionKind::NestedBindings(nested) => {
|
||||
let index = semantic_index(db, definition.python_file(db));
|
||||
let index = semantic_index(db, definition.program_file(db));
|
||||
let sources = nested
|
||||
.visible_binding_sources(index, definition.file_scope(db))
|
||||
.flatten()
|
||||
@@ -1110,8 +1118,8 @@ fn is_reachable_implementation_definition<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> bool {
|
||||
let file = definition.python_file(db);
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let file = definition.program_file(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
is_range_reachable(
|
||||
db,
|
||||
semantic_index(db, file),
|
||||
@@ -1152,6 +1160,7 @@ fn is_ascii_identifier_continue(byte: u8) -> bool {
|
||||
|
||||
fn resolve_reachable_definitions<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
symbol_name: &str,
|
||||
definitions: impl IntoIterator<Item = Definition<'db>>,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
@@ -1160,6 +1169,7 @@ fn resolve_reachable_definitions<'db>(
|
||||
.flat_map(|definition| {
|
||||
resolve_definition(
|
||||
db,
|
||||
env,
|
||||
definition,
|
||||
Some(symbol_name),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
@@ -1267,9 +1277,11 @@ pub fn definitions_for_imported_symbol<'db>(
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let mut visited = FxHashSet::default();
|
||||
let env = model.program_environment();
|
||||
resolve_definition::resolve_from_import_definitions(
|
||||
model.db(),
|
||||
model.python_file(),
|
||||
&env,
|
||||
ImportingFile::File(model.file(), env.resolver_environment(model.db())),
|
||||
import_node,
|
||||
symbol_name,
|
||||
&mut visited,
|
||||
@@ -2056,7 +2068,6 @@ mod resolve_definition {
|
||||
}
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::{FileRange, vendored_path_to_file};
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_db::system::SystemPath;
|
||||
@@ -2066,14 +2077,17 @@ mod resolve_definition {
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashSet;
|
||||
use tracing::trace;
|
||||
use ty_module_resolver::{ModuleName, file_to_module, resolve_module, resolve_real_module};
|
||||
use ty_module_resolver::{
|
||||
ImportingFile, ModuleName, file_to_module, resolve_module, resolve_real_module,
|
||||
};
|
||||
|
||||
use crate::Db;
|
||||
use crate::ProgramEnvironment;
|
||||
use crate::module_docstring;
|
||||
use crate::types::binding_type;
|
||||
use ty_python_core::definition::{Definition, DefinitionCategory, DefinitionKind};
|
||||
use ty_python_core::scope::{NodeWithScopeKind, ScopeId};
|
||||
use ty_python_core::{global_scope, place_table, semantic_index, use_def_map};
|
||||
use ty_python_core::{ProgramFile, global_scope, place_table, semantic_index, use_def_map};
|
||||
|
||||
/// Represents the result of resolving an import to either a specific definition or
|
||||
/// a specific range within a file.
|
||||
@@ -2085,7 +2099,7 @@ mod resolve_definition {
|
||||
/// The import resolved to a specific definition within a module
|
||||
Definition(Definition<'db>),
|
||||
/// The import resolved to an entire module
|
||||
Module(PythonFile<'db>),
|
||||
Module(ProgramFile<'db>),
|
||||
/// The import resolved to a file with a specific range
|
||||
FileWithRange(FileRange),
|
||||
}
|
||||
@@ -2126,9 +2140,9 @@ mod resolve_definition {
|
||||
}
|
||||
}
|
||||
|
||||
fn python_file(&self, db: &'db dyn Db) -> Option<PythonFile<'db>> {
|
||||
fn program_file(&self, db: &'db dyn Db) -> Option<ProgramFile<'db>> {
|
||||
match *self {
|
||||
ResolvedDefinition::Definition(definition) => Some(definition.python_file(db)),
|
||||
ResolvedDefinition::Definition(definition) => Some(definition.program_file(db)),
|
||||
ResolvedDefinition::Module(file) => Some(file),
|
||||
ResolvedDefinition::FileWithRange(_) => None,
|
||||
}
|
||||
@@ -2137,7 +2151,7 @@ mod resolve_definition {
|
||||
pub fn docstring(&self, db: &'db dyn Db) -> Option<String> {
|
||||
match self {
|
||||
ResolvedDefinition::Definition(definition) => definition.docstring(db),
|
||||
ResolvedDefinition::Module(file) => module_docstring(db, *file),
|
||||
ResolvedDefinition::Module(file) => module_docstring(db, file.python_file(db)),
|
||||
ResolvedDefinition::FileWithRange(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -2198,6 +2212,7 @@ mod resolve_definition {
|
||||
/// Always returns at least the original definition as a fallback if resolution fails.
|
||||
pub(crate) fn resolve_definition<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
definition: Definition<'db>,
|
||||
symbol_name: Option<&str>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
@@ -2205,6 +2220,7 @@ mod resolve_definition {
|
||||
let mut visited = FxHashSet::default();
|
||||
let resolved = resolve_definition_recursive(
|
||||
db,
|
||||
env,
|
||||
definition,
|
||||
&mut visited,
|
||||
symbol_name,
|
||||
@@ -2222,6 +2238,7 @@ mod resolve_definition {
|
||||
/// Helper function to resolve import definitions recursively.
|
||||
fn resolve_definition_recursive<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
definition: Definition<'db>,
|
||||
visited: &mut FxHashSet<Definition<'db>>,
|
||||
symbol_name: Option<&str>,
|
||||
@@ -2237,8 +2254,8 @@ mod resolve_definition {
|
||||
|
||||
match kind {
|
||||
DefinitionKind::Import(import_def) => {
|
||||
let file = definition.python_file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let file = definition.program_file(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let alias = import_def.alias(&module);
|
||||
|
||||
if alias.asname.is_some()
|
||||
@@ -2253,13 +2270,16 @@ mod resolve_definition {
|
||||
};
|
||||
|
||||
// Resolve the module to its file
|
||||
let Some(resolved_module) = resolve_module(db, file, &module_name) else {
|
||||
let importing_file =
|
||||
ImportingFile::File(file.file(db), env.resolver_environment(db));
|
||||
let Some(resolved_module) = resolve_module(db, importing_file, &module_name) else {
|
||||
return Vec::new(); // Module not found, return empty list
|
||||
};
|
||||
|
||||
let Some(module_file) = resolved_module.python_file(db) else {
|
||||
let Some(module_file) = resolved_module.file(db) else {
|
||||
return Vec::new(); // No file for module, return empty list
|
||||
};
|
||||
let module_file = ProgramFile::new(db, module_file, env.program(db));
|
||||
|
||||
// For simple imports like "import os", we want to navigate to the module itself.
|
||||
// Return the module file directly instead of trying to find definitions within it.
|
||||
@@ -2267,8 +2287,8 @@ mod resolve_definition {
|
||||
}
|
||||
|
||||
DefinitionKind::ImportFrom(import_from_def) => {
|
||||
let file = definition.python_file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let file = definition.program_file(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let import_node = import_from_def.import(&module);
|
||||
let alias = import_from_def.alias(&module);
|
||||
|
||||
@@ -2282,7 +2302,8 @@ mod resolve_definition {
|
||||
// (alias.name), not the local alias (symbol_name)
|
||||
resolve_from_import_definitions(
|
||||
db,
|
||||
file,
|
||||
env,
|
||||
ImportingFile::File(file.file(db), env.resolver_environment(db)),
|
||||
import_node,
|
||||
&alias.name,
|
||||
visited,
|
||||
@@ -2292,15 +2313,16 @@ mod resolve_definition {
|
||||
|
||||
// For star imports, try to resolve to the specific symbol being accessed
|
||||
DefinitionKind::StarImport(star_import_def) => {
|
||||
let file = definition.python_file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let file = definition.program_file(db);
|
||||
let module = parsed_module(db, file.python_file(db)).load(db);
|
||||
let import_node = star_import_def.import(&module);
|
||||
|
||||
// If we have a symbol name, use the helper to resolve it in the target module
|
||||
if let Some(symbol_name) = symbol_name {
|
||||
resolve_from_import_definitions(
|
||||
db,
|
||||
file,
|
||||
env,
|
||||
ImportingFile::File(file.file(db), env.resolver_environment(db)),
|
||||
import_node,
|
||||
symbol_name,
|
||||
visited,
|
||||
@@ -2320,7 +2342,8 @@ mod resolve_definition {
|
||||
/// Helper function to resolve import definitions for `ImportFrom` and `StarImport` cases.
|
||||
pub(crate) fn resolve_from_import_definitions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
import_node: &ast::StmtImportFrom,
|
||||
symbol_name: &str,
|
||||
visited: &mut FxHashSet<Definition<'db>>,
|
||||
@@ -2331,7 +2354,7 @@ mod resolve_definition {
|
||||
if let Some(asname) = &alias.asname {
|
||||
if asname.as_str() == symbol_name {
|
||||
return vec![ResolvedDefinition::FileWithRange(FileRange::new(
|
||||
file.file(db),
|
||||
importing_file.file(db),
|
||||
asname.range,
|
||||
))];
|
||||
}
|
||||
@@ -2340,22 +2363,26 @@ mod resolve_definition {
|
||||
}
|
||||
|
||||
// Resolve the module being imported from (handles both relative and absolute imports)
|
||||
let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok()
|
||||
let Some(module_name) =
|
||||
ModuleName::from_import_statement(db, importing_file, import_node).ok()
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(resolved_module) = resolve_module(db, file, &module_name) else {
|
||||
let Some(resolved_module) = resolve_module(db, importing_file, &module_name) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
// Resolve the target module file
|
||||
let module_file = resolved_module.python_file(db);
|
||||
let module_file = resolved_module
|
||||
.file(db)
|
||||
.map(|file| ProgramFile::new(db, file, env.program(db)));
|
||||
|
||||
let Some(module_file) = module_file else {
|
||||
// No file means this is a namespace package, try to import the submodule
|
||||
return Vec::from_iter(resolve_from_import_submodule_definitions(
|
||||
db,
|
||||
file,
|
||||
env,
|
||||
importing_file,
|
||||
symbol_name,
|
||||
module_name,
|
||||
));
|
||||
@@ -2368,8 +2395,14 @@ mod resolve_definition {
|
||||
// Recursively resolve any import definitions found in the target module
|
||||
let mut resolved_definitions = Vec::new();
|
||||
for def in definitions_in_module {
|
||||
let resolved =
|
||||
resolve_definition_recursive(db, def, visited, Some(symbol_name), alias_resolution);
|
||||
let resolved = resolve_definition_recursive(
|
||||
db,
|
||||
env,
|
||||
def,
|
||||
visited,
|
||||
Some(symbol_name),
|
||||
alias_resolution,
|
||||
);
|
||||
resolved_definitions.extend(resolved);
|
||||
}
|
||||
|
||||
@@ -2382,7 +2415,8 @@ mod resolve_definition {
|
||||
// `child` has no binding in `pkg/__init__.py`.
|
||||
Vec::from_iter(resolve_from_import_submodule_definitions(
|
||||
db,
|
||||
file,
|
||||
env,
|
||||
importing_file,
|
||||
symbol_name,
|
||||
module_name,
|
||||
))
|
||||
@@ -2394,15 +2428,16 @@ mod resolve_definition {
|
||||
// Helper to resolve `from x.y import z` assuming `x.y.z` is a module.
|
||||
fn resolve_from_import_submodule_definitions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
importing_file: ImportingFile<'db>,
|
||||
symbol_name: &str,
|
||||
module_name: ModuleName,
|
||||
) -> Option<ResolvedDefinition<'db>> {
|
||||
let submodule_name = ModuleName::new(symbol_name)?;
|
||||
let mut full_submodule_name = module_name;
|
||||
full_submodule_name.extend(&submodule_name);
|
||||
let module = resolve_module(db, file, &full_submodule_name)?;
|
||||
let file = module.python_file(db)?;
|
||||
let module = resolve_module(db, importing_file, &full_submodule_name)?;
|
||||
let file = ProgramFile::new(db, module.file(db)?, env.program(db));
|
||||
|
||||
Some(ResolvedDefinition::Module(file))
|
||||
}
|
||||
@@ -2449,13 +2484,14 @@ mod resolve_definition {
|
||||
def: &ResolvedDefinition<'db>,
|
||||
cached_vendored_typeshed: Option<&SystemPath>,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
let Some(stub_parse_file) = def.python_file(db) else {
|
||||
let Some(stub_program_file) = def.program_file(db) else {
|
||||
trace!("Found arbitrary FileWithRange while stub mapping, giving up");
|
||||
return None;
|
||||
};
|
||||
let env = ProgramEnvironment::from_file(stub_program_file);
|
||||
|
||||
// If the file isn't a stub, this is presumably the real definition
|
||||
let stub_file = stub_parse_file.file(db);
|
||||
let stub_file = stub_program_file.file(db);
|
||||
trace!("Stub mapping definition in: {}", stub_file.path(db));
|
||||
if !stub_file.is_stub(db) {
|
||||
trace!("File isn't a stub, no stub mapping to do");
|
||||
@@ -2472,7 +2508,7 @@ mod resolve_definition {
|
||||
// we're in typeshed to successfully stub-map to the Real Stdlib. So here we attempt
|
||||
// to do just that. The resulting file must not be used for anything other than
|
||||
// this module lookup, as the `ResolvedDefinition` we're handling isn't for that file.
|
||||
let mut stub_file_for_module_lookup = stub_parse_file;
|
||||
let mut stub_file_for_module_lookup = stub_program_file;
|
||||
if let Some(vendored_typeshed) = cached_vendored_typeshed
|
||||
&& let Some(stub_path) = stub_file.path(db).as_system_path()
|
||||
&& let Ok(rel_path) = stub_path.strip_prefix(vendored_typeshed)
|
||||
@@ -2483,12 +2519,12 @@ mod resolve_definition {
|
||||
"Stub is cached vendored typeshed: {}",
|
||||
typeshed_file.path(db)
|
||||
);
|
||||
stub_file_for_module_lookup =
|
||||
PythonFile::new(db, typeshed_file, stub_parse_file.python_version(db));
|
||||
stub_file_for_module_lookup = ProgramFile::new(db, typeshed_file, env.program(db));
|
||||
}
|
||||
|
||||
// It's definitely a stub, so now rerun module resolution but with stubs disabled.
|
||||
let stub_module = file_to_module(db, stub_file_for_module_lookup)?;
|
||||
let resolver_file = stub_file_for_module_lookup.resolver_file(db);
|
||||
let stub_module = file_to_module(db, resolver_file)?;
|
||||
trace!("Found stub module: {}", stub_module.name(db));
|
||||
// We need to pass an importing file to `resolve_real_module` which is a bit odd
|
||||
// here because there isn't really an importing file. However this `resolve_real_module`
|
||||
@@ -2502,10 +2538,13 @@ mod resolve_definition {
|
||||
if is_builtin_module(stub_module.python_version(db).minor, stub_module.name(db)) {
|
||||
return None;
|
||||
}
|
||||
let real_module =
|
||||
resolve_real_module(db, stub_file_for_module_lookup, stub_module.name(db))?;
|
||||
let real_module = resolve_real_module(
|
||||
db,
|
||||
ImportingFile::ResolverFile(resolver_file),
|
||||
stub_module.name(db),
|
||||
)?;
|
||||
trace!("Found real module: {}", real_module.name(db));
|
||||
let real_parse_file = real_module.python_file(db)?;
|
||||
let real_parse_file = ProgramFile::new(db, real_module.file(db)?, env.program(db));
|
||||
let real_file = real_parse_file.file(db);
|
||||
trace!("Found real file: {}", real_file.path(db));
|
||||
|
||||
@@ -2539,7 +2578,7 @@ mod resolve_definition {
|
||||
path.push(leaf);
|
||||
|
||||
// Get the ancestors of the path (all the definitions we're nested under)
|
||||
let index = semantic_index(db, definition.python_file(db));
|
||||
let index = semantic_index(db, definition.program_file(db));
|
||||
for (_scope_id, scope) in index.ancestor_scopes(definition.file_scope(db)) {
|
||||
let node = scope.node();
|
||||
let component = definition_path_component_for_node(&stub_ref, node)
|
||||
@@ -2592,6 +2631,7 @@ mod resolve_definition {
|
||||
.flat_map(|definition| {
|
||||
resolve_definition(
|
||||
db,
|
||||
&env,
|
||||
definition,
|
||||
Some(component),
|
||||
ImportAliasResolution::ResolveAliases,
|
||||
@@ -2717,7 +2757,7 @@ pub struct TypeHierarchyClass<'db> {
|
||||
/// The name of the class.
|
||||
pub name: Name,
|
||||
/// The file containing the class definition.
|
||||
pub file: PythonFile<'db>,
|
||||
pub file: ResolverFile<'db>,
|
||||
/// The range covering the full class definition header.
|
||||
pub full_range: TextRange,
|
||||
/// The range of the class name (for selection/focus).
|
||||
@@ -2789,7 +2829,7 @@ pub fn type_hierarchy_subtypes<'db>(
|
||||
let Some(target_class) = extract_class_literal(db, env, ty) else {
|
||||
return vec![];
|
||||
};
|
||||
direct_subtypes(db, target_class, modules)
|
||||
direct_subtypes(db, env, target_class, modules)
|
||||
.into_iter()
|
||||
.map(|class_literal| class_literal_to_hierarchy_info(db, class_literal))
|
||||
.collect()
|
||||
@@ -2806,6 +2846,7 @@ pub fn type_hierarchy_subtypes<'db>(
|
||||
/// For `Animal`, this returns `Dog`, but not `LoudDog`.
|
||||
fn direct_subtypes<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
target_class: ClassLiteral<'db>,
|
||||
modules: &[Module<'db>],
|
||||
) -> Vec<ClassLiteral<'db>> {
|
||||
@@ -2814,10 +2855,9 @@ fn direct_subtypes<'db>(
|
||||
let mut subtypes = vec![];
|
||||
|
||||
for &module in modules {
|
||||
let Some(python_file) = module.python_file(db) else {
|
||||
let Some(file) = module.file(db) else {
|
||||
continue;
|
||||
};
|
||||
let file = python_file.file(db);
|
||||
|
||||
// Note that this will always consider namespace
|
||||
// packages to be "not firsty party." This isn't
|
||||
@@ -2847,8 +2887,9 @@ fn direct_subtypes<'db>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_env = ProgramEnvironment::from_file(python_file);
|
||||
for class_ty in reachable_class_literals_in_file(db, python_file) {
|
||||
let program_file = ProgramFile::new(db, file, env.program(db));
|
||||
let file_env = ProgramEnvironment::from_file(program_file);
|
||||
for class_ty in reachable_class_literals_in_file(db, program_file) {
|
||||
let bases = class_ty.explicit_bases(db);
|
||||
let is_subtype = if target_is_object
|
||||
&& bases.is_empty()
|
||||
@@ -2872,11 +2913,11 @@ fn direct_subtypes<'db>(
|
||||
/// Enumerates the reachable class definitions in `file`.
|
||||
fn reachable_class_literals_in_file<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: PythonFile<'db>,
|
||||
file: ProgramFile<'db>,
|
||||
) -> Vec<ClassLiteral<'db>> {
|
||||
let env = ProgramEnvironment::from_file(file);
|
||||
let index = semantic_index(db, file);
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let mut classes = Vec::new();
|
||||
|
||||
for scope_id in index.scope_ids() {
|
||||
@@ -2945,7 +2986,7 @@ fn class_literal_to_hierarchy_info<'db>(
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) -> TypeHierarchyClass<'db> {
|
||||
let name = class_literal.name(db).clone();
|
||||
let file = class_literal.python_file(db);
|
||||
let file = class_literal.program_file(db).resolver_file(db);
|
||||
|
||||
let (full_range, selection_range) = match class_literal {
|
||||
ClassLiteral::Static(static_class) => {
|
||||
@@ -3076,9 +3117,9 @@ mod tests {
|
||||
use super::{CallArgumentForm, call_argument_forms, contains_identifier};
|
||||
use crate::SemanticModel;
|
||||
use crate::db::tests::TestDbBuilder;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
#[test]
|
||||
fn source_candidate_prefilters_use_identifier_boundaries() {
|
||||
@@ -3105,8 +3146,8 @@ cast(val="", typ=int)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
@@ -3146,8 +3187,8 @@ f(y="", x=1)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
@@ -3183,8 +3224,8 @@ f(val="", typ=int)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
@@ -3221,8 +3262,8 @@ f("", int)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
@@ -3262,8 +3303,8 @@ f(int, x)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
@@ -3307,8 +3348,8 @@ TypeAliasType("Alias", int)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let calls: Vec<_> = parsed
|
||||
.suite()
|
||||
.iter()
|
||||
@@ -3353,8 +3394,8 @@ cast(*args)
|
||||
.build()?;
|
||||
|
||||
let file = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||
let file = PythonFile::new(&db, file, db.python_version());
|
||||
let parsed = parsed_module(&db, file).load(&db);
|
||||
let file = ProgramFile::new(&db, file, db.program_environment().program(&db));
|
||||
let parsed = parsed_module(&db, file.python_file(&db)).load(&db);
|
||||
let call = parsed
|
||||
.suite()
|
||||
.last()
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::Db;
|
||||
use crate::reachability::is_reachable;
|
||||
use get_size2::GetSize;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_text_size::TextRange;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::reachability_constraints::ScopedReachabilityConstraintId;
|
||||
use ty_python_core::semantic_index;
|
||||
|
||||
@@ -45,7 +45,7 @@ pub enum UnreachableKind {
|
||||
/// `ALWAYS_FALSE` constraints are classified as unconditional; all others are
|
||||
/// unreachable only under the current analysis.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn unreachable_ranges(db: &dyn Db, file: PythonFile<'_>) -> Box<[UnreachableRange]> {
|
||||
pub fn unreachable_ranges(db: &dyn Db, file: ProgramFile<'_>) -> Box<[UnreachableRange]> {
|
||||
let index = semantic_index(db, file);
|
||||
let mut unreachable = Vec::new();
|
||||
for scope_id in index.scope_ids() {
|
||||
@@ -94,13 +94,13 @@ mod tests {
|
||||
use super::{UnreachableKind, unreachable_ranges};
|
||||
use crate::db::tests::{TestDb, TestDbBuilder};
|
||||
use insta::assert_snapshot;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity,
|
||||
};
|
||||
use ruff_db::files::{FileRange, system_path_to_file};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ty_python_core::ProgramFile;
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
|
||||
const TEST_PATH: &str = "/src/main.py";
|
||||
@@ -147,23 +147,26 @@ mod tests {
|
||||
|
||||
fn render_unreachable_diagnostics(db: &TestDb, path: &str) -> String {
|
||||
let file = system_path_to_file(db, path).unwrap();
|
||||
let diagnostics = unreachable_ranges(db, PythonFile::new(db, file, db.python_version()))
|
||||
.iter()
|
||||
.map(|range| {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DiagnosticId::lint("unreachable-code"),
|
||||
Severity::Info,
|
||||
match range.kind {
|
||||
UnreachableKind::Unconditional => "Code is always unreachable",
|
||||
UnreachableKind::CurrentAnalysis => "Code is unreachable",
|
||||
},
|
||||
);
|
||||
diagnostic.annotate(Annotation::primary(
|
||||
FileRange::new(file, range.range).into(),
|
||||
));
|
||||
diagnostic
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let diagnostics = unreachable_ranges(
|
||||
db,
|
||||
ProgramFile::new(db, file, db.program_environment().program(db)),
|
||||
)
|
||||
.iter()
|
||||
.map(|range| {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DiagnosticId::lint("unreachable-code"),
|
||||
Severity::Info,
|
||||
match range.kind {
|
||||
UnreachableKind::Unconditional => "Code is always unreachable",
|
||||
UnreachableKind::CurrentAnalysis => "Code is unreachable",
|
||||
},
|
||||
);
|
||||
diagnostic.annotate(Annotation::primary(
|
||||
FileRange::new(file, range.range).into(),
|
||||
));
|
||||
diagnostic
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
DisplayDiagnostics::new(
|
||||
db,
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::reachability::is_reachable;
|
||||
use crate::types::function::FunctionDecorators;
|
||||
use crate::types::infer::function_known_decorator_flags;
|
||||
use get_size2::GetSize;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::TextRange;
|
||||
@@ -11,7 +10,7 @@ use rustc_hash::FxHashSet;
|
||||
use ty_python_core::definition::{DefinitionCategory, DefinitionKind, DefinitionState};
|
||||
use ty_python_core::place::ScopedPlaceId;
|
||||
use ty_python_core::scope::{FileScopeId, ScopeKind};
|
||||
use ty_python_core::{SemanticIndex, semantic_index};
|
||||
use ty_python_core::{ProgramFile, SemanticIndex, semantic_index};
|
||||
|
||||
/// Returns `true` for definition kinds that create user-facing bindings we consider for
|
||||
/// unused-binding diagnostics.
|
||||
@@ -103,9 +102,9 @@ pub struct UnusedBinding {
|
||||
/// without broader reference analysis. Bare local annotations (`x: int`) are also
|
||||
/// reported, but only if the symbol is neither bound nor used elsewhere in the scope.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn unused_bindings(db: &dyn Db, file: PythonFile<'_>) -> Box<[UnusedBinding]> {
|
||||
pub fn unused_bindings(db: &dyn Db, file: ProgramFile<'_>) -> Box<[UnusedBinding]> {
|
||||
let source_file = file.file(db);
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let parsed = parsed_module(db, file.python_file(db)).load(db);
|
||||
let is_stub_file = source_file.is_stub(db);
|
||||
let index = semantic_index(db, file);
|
||||
let mut unused = Vec::new();
|
||||
@@ -234,11 +233,11 @@ pub fn unused_bindings(db: &dyn Db, file: PythonFile<'_>) -> Box<[UnusedBinding]
|
||||
mod tests {
|
||||
use super::{UnusedBinding, unused_bindings};
|
||||
use crate::db::tests::TestDbBuilder;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ty_python_core::ProgramFile;
|
||||
|
||||
fn collect_unused_bindings_in_file(
|
||||
path: &str,
|
||||
@@ -246,8 +245,8 @@ mod tests {
|
||||
) -> anyhow::Result<Vec<UnusedBinding>> {
|
||||
let db = TestDbBuilder::new().with_file(path, source).build()?;
|
||||
let file = system_path_to_file(&db, path).unwrap();
|
||||
let mut bindings =
|
||||
unused_bindings(&db, PythonFile::new(&db, file, db.python_version())).to_vec();
|
||||
let program = db.program_environment().program(&db);
|
||||
let mut bindings = unused_bindings(&db, ProgramFile::new(&db, file, program)).to_vec();
|
||||
bindings.sort_unstable_by_key(|binding| (binding.range.start(), binding.range.end()));
|
||||
Ok(bindings)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,8 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> DefinitionInference<'db> {
|
||||
let python_file = definition.python_file(db);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_definition_types",
|
||||
@@ -144,16 +145,16 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
)
|
||||
.entered();
|
||||
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::Definition(definition),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -196,18 +197,19 @@ pub(crate) fn function_known_decorators<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> FunctionDecoratorInference<'db> {
|
||||
let python_file = definition.python_file(db);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::FunctionDecorators(definition),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -284,7 +286,8 @@ pub(crate) fn infer_deferred_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> DefinitionInference<'db> {
|
||||
let python_file = definition.python_file(db);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_deferred_types",
|
||||
@@ -294,16 +297,16 @@ pub(crate) fn infer_deferred_types<'db>(
|
||||
)
|
||||
.entered();
|
||||
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::Deferred(definition),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -323,13 +326,13 @@ pub(crate) fn infer_complete_scope_types<'db>(
|
||||
// Scopes that may require type context are inferred during the inference of
|
||||
// their outer scope.
|
||||
if scope.accepts_type_context(db) {
|
||||
let python_file = scope.python_file(db);
|
||||
let index = semantic_index(db, python_file);
|
||||
let program_file = scope.program_file(db);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
if let Some(parent_scope) = index.parent_scope_id(scope.file_scope_id(db)) {
|
||||
// Note that nested lambdas or comprehensions may require recursing until we reach
|
||||
// an outer scope that is independent of any type context.
|
||||
return infer_complete_scope_types(db, parent_scope.to_scope_id(db, python_file));
|
||||
return infer_complete_scope_types(db, parent_scope.to_scope_id(db, program_file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +370,8 @@ pub(crate) fn infer_scope_types_impl<'db>(
|
||||
input: InferScope<'db>,
|
||||
) -> ScopeInference<'db> {
|
||||
let (scope, tcx) = input.into_inner(db);
|
||||
let python_file = scope.python_file(db);
|
||||
let program_file = scope.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let _span =
|
||||
tracing::trace_span!("infer_scope_types", scope=?scope.as_id(), ?python_file).entered();
|
||||
|
||||
@@ -375,16 +379,16 @@ pub(crate) fn infer_scope_types_impl<'db>(
|
||||
|
||||
// Using the index here is fine because the code below depends on the AST anyway.
|
||||
// The isolation of the query is by the return inferred types.
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::Scope(scope, tcx),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -419,7 +423,8 @@ pub(super) fn infer_expression_types_impl<'db>(
|
||||
) -> ExpressionInference<'db> {
|
||||
let (expression, tcx) = input.into_inner(db);
|
||||
|
||||
let python_file = expression.python_file(db);
|
||||
let program_file = expression.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_expression_types",
|
||||
@@ -429,16 +434,16 @@ pub(super) fn infer_expression_types_impl<'db>(
|
||||
)
|
||||
.entered();
|
||||
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::Expression(expression, tcx),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -530,7 +535,7 @@ pub(super) fn infer_statement_types<'db>(
|
||||
StatementInferenceInner::cycle_initial(statement.scope(db), Type::divergent(id))
|
||||
},
|
||||
cycle_fn=|db, cycle, previous: &StatementInferenceInner<'db>, inference: StatementInferenceInner<'db>, statement: StatementInner<'db>| {
|
||||
let env = ProgramEnvironment::from_file(statement.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(statement.program_file(db));
|
||||
inference.cycle_normalized(db, &env, previous, cycle)
|
||||
},
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
@@ -539,7 +544,8 @@ fn infer_statement_types_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
statement: StatementInner<'db>,
|
||||
) -> StatementInferenceInner<'db> {
|
||||
let python_file = statement.python_file(db);
|
||||
let program_file = statement.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_statement_types",
|
||||
@@ -549,16 +555,16 @@ fn infer_statement_types_impl<'db>(
|
||||
)
|
||||
.entered();
|
||||
|
||||
let index = semantic_index(db, python_file);
|
||||
let index = semantic_index(db, program_file);
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
|
||||
TypeInferenceBuilder::new(
|
||||
db,
|
||||
&env,
|
||||
InferenceRegion::Statement(statement),
|
||||
python_file.file(db),
|
||||
python_file,
|
||||
program_file,
|
||||
index,
|
||||
&module,
|
||||
)
|
||||
@@ -723,13 +729,14 @@ impl<'db> From<Type<'db>> for TypeContext<'db> {
|
||||
returns(ref),
|
||||
cycle_initial=|_, id, _| UnpackResult::cycle_initial(Type::divergent(id)),
|
||||
cycle_fn=|db, cycle, previous: &UnpackResult<'db>, result: UnpackResult<'db>, unpack: Unpack<'db>| {
|
||||
let env = ProgramEnvironment::from_file(unpack.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(unpack.program_file(db));
|
||||
result.cycle_normalized(db, &env, previous, cycle)
|
||||
},
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
)]
|
||||
pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
|
||||
let python_file = unpack.python_file(db);
|
||||
let program_file = unpack.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_unpack_types",
|
||||
@@ -738,8 +745,8 @@ pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> U
|
||||
)
|
||||
.entered();
|
||||
|
||||
let env = ProgramEnvironment::from_file(python_file);
|
||||
let mut unpacker = Unpacker::new(db, &env, unpack.target_scope(db), python_file, &module);
|
||||
let env = ProgramEnvironment::from_file(program_file);
|
||||
let mut unpacker = Unpacker::new(db, &env, unpack.target_scope(db), program_file, &module);
|
||||
unpacker.unpack(unpack.target(db, &module), unpack.value(db));
|
||||
unpacker.finish()
|
||||
}
|
||||
@@ -1369,7 +1376,8 @@ impl<'db> DefinitionInference<'db> {
|
||||
// Eagerly store more precise types for collection literals to avoid an extra
|
||||
// cycle iteration, i.e., by inferring `list[Divergent]` instead of `Divergent`.
|
||||
if let DefinitionKind::Assignment(assignment) = definition.kind(db) {
|
||||
let python_file = definition.python_file(db);
|
||||
let program_file = definition.program_file(db);
|
||||
let python_file = program_file.python_file(db);
|
||||
let module = parsed_module(db, python_file).load(db);
|
||||
let known_collection = match assignment.value(&module) {
|
||||
ast::Expr::Set(_) => Some(KnownClass::Set),
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::rc::Rc;
|
||||
|
||||
use compact_str::CompactString;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::PythonFile;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
use ruff_db::source::source_text;
|
||||
@@ -20,7 +19,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::SmallVec;
|
||||
use strum::IntoEnumIterator;
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_module_resolver::{ImportingFile, ModuleName, resolve_module};
|
||||
use ty_python_core::ast_ids::HasScopedUseId;
|
||||
use ty_python_core::statement::StatementInner;
|
||||
|
||||
@@ -138,8 +137,8 @@ use ty_python_core::predicate::PatternPredicate;
|
||||
use ty_python_core::scope::{FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind};
|
||||
use ty_python_core::symbol::{ScopedSymbolId, Symbol};
|
||||
use ty_python_core::{
|
||||
ApplicableConstraints, EnclosingSnapshotResult, EvaluationMode, SemanticIndex, Truthiness,
|
||||
unpack::UnpackPosition,
|
||||
ApplicableConstraints, EnclosingSnapshotResult, EvaluationMode, ProgramFile, SemanticIndex,
|
||||
Truthiness, unpack::UnpackPosition,
|
||||
};
|
||||
use ty_python_core::{ExpressionNodeKey, Statement};
|
||||
|
||||
@@ -465,13 +464,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
env: &'ast ProgramEnvironment<'db>,
|
||||
region: InferenceRegion<'db>,
|
||||
file: File,
|
||||
python_file: PythonFile<'db>,
|
||||
program_file: ProgramFile<'db>,
|
||||
index: &'db SemanticIndex<'db>,
|
||||
module: &'ast ParsedModuleRef,
|
||||
) -> Self {
|
||||
let scope = region.scope(db);
|
||||
Self {
|
||||
context: InferContext::new(db, env, scope, file, python_file, module),
|
||||
context: InferContext::new(db, env, scope, file, program_file, module),
|
||||
index,
|
||||
region,
|
||||
scope,
|
||||
@@ -776,8 +775,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.context.file()
|
||||
}
|
||||
|
||||
fn python_file(&self) -> PythonFile<'db> {
|
||||
self.context.python_file()
|
||||
fn program_file(&self) -> ProgramFile<'db> {
|
||||
self.context.program_file()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -985,7 +984,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
/// already in progress for that scope (further up the stack).
|
||||
fn file_expression_type(&self, expression: &ast::Expr) -> Type<'db> {
|
||||
let file_scope = self.index.expression_scope_id(expression);
|
||||
let expr_scope = file_scope.to_scope_id(self.db(), self.python_file());
|
||||
let expr_scope = file_scope.to_scope_id(self.db(), self.program_file());
|
||||
match self.region {
|
||||
InferenceRegion::Scope(scope, _) if scope == expr_scope => {
|
||||
self.expression_type(expression)
|
||||
@@ -997,7 +996,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
/// Get metadata for a type expression from any scope in the same file.
|
||||
fn file_type_expression_flags(&self, expression: &ast::Expr) -> TypeExpressionFlags {
|
||||
let file_scope = self.index.expression_scope_id(expression);
|
||||
let expr_scope = file_scope.to_scope_id(self.db(), self.python_file());
|
||||
let expr_scope = file_scope.to_scope_id(self.db(), self.program_file());
|
||||
match self.region {
|
||||
InferenceRegion::Scope(scope, _) if scope == expr_scope => {
|
||||
self.type_expression_flags(expression)
|
||||
@@ -1641,7 +1640,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if let PlaceExprRef::Symbol(symbol) = &place
|
||||
&& scope.is_global()
|
||||
{
|
||||
module_type_implicit_global_symbol(db, self.python_file(), symbol.name())
|
||||
module_type_implicit_global_symbol(db, self.program_file(), symbol.name())
|
||||
} else {
|
||||
Place::Undefined.into()
|
||||
}
|
||||
@@ -1702,7 +1701,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.map(|symbol| {
|
||||
module_type_implicit_global_symbol(
|
||||
db,
|
||||
self.python_file(),
|
||||
self.program_file(),
|
||||
symbol.name(),
|
||||
)
|
||||
})
|
||||
@@ -2079,7 +2078,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let rhs_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::TypeAlias(type_alias))
|
||||
.to_scope_id(self.db(), self.python_file());
|
||||
.to_scope_id(self.db(), self.program_file());
|
||||
|
||||
let type_alias_ty =
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::PEP695(
|
||||
@@ -3413,7 +3412,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
};
|
||||
|
||||
if let Some(special_form) = target.as_name_expr().and_then(|name| {
|
||||
SpecialFormType::try_from_file_and_name(self.db(), self.python_file(), &name.id)
|
||||
let db = self.db();
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
SpecialFormType::try_from_file_and_name(db, importing_file, &name.id)
|
||||
}) {
|
||||
target_ty = Type::SpecialForm(special_form);
|
||||
}
|
||||
@@ -4457,7 +4461,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if let Some(name_expr) = target.as_name_expr()
|
||||
&& let Some(special_form) = SpecialFormType::try_from_file_and_name(
|
||||
self.db(),
|
||||
self.python_file(),
|
||||
ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(self.db()),
|
||||
),
|
||||
&name_expr.id,
|
||||
)
|
||||
{
|
||||
@@ -5063,7 +5070,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !module_type_implicit_global_symbol(self.db(), self.python_file(), name)
|
||||
if !module_type_implicit_global_symbol(self.db(), self.program_file(), name)
|
||||
.place
|
||||
.is_undefined()
|
||||
{
|
||||
@@ -5088,8 +5095,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
fn module_type_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
|
||||
resolve_module(self.db(), self.python_file(), module_name)
|
||||
.map(|module| Type::module_literal(self.db(), self.python_file(), module))
|
||||
let db = self.db();
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
resolve_module(db, importing_file, module_name)
|
||||
.map(|module| Type::module_literal(self.db(), self.program_file(), module))
|
||||
}
|
||||
|
||||
fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {
|
||||
@@ -7714,7 +7726,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let evaluation_mode =
|
||||
EvaluationMode::from_is_async(scope_id.is_async_comprehension(self.index));
|
||||
let yield_tcx = self.generator_yield_type_context(tcx, evaluation_mode);
|
||||
let scope = scope_id.to_scope_id(self.db(), self.python_file());
|
||||
let scope = scope_id.to_scope_id(self.db(), self.program_file());
|
||||
let inference = infer_scope_types(self.db(), scope, yield_tcx);
|
||||
self.extend_scope(inference);
|
||||
let yield_type = self.comprehension_element_type(elt, inference);
|
||||
@@ -7794,7 +7806,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
else {
|
||||
return Type::unknown();
|
||||
};
|
||||
let scope = scope_id.to_scope_id(self.db(), self.python_file());
|
||||
let scope = scope_id.to_scope_id(self.db(), self.program_file());
|
||||
let inference = infer_scope_types(self.db(), scope, tcx);
|
||||
self.extend_scope(inference);
|
||||
|
||||
@@ -7835,7 +7847,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
else {
|
||||
return Type::unknown();
|
||||
};
|
||||
let scope = scope_id.to_scope_id(self.db(), self.python_file());
|
||||
let scope = scope_id.to_scope_id(self.db(), self.program_file());
|
||||
let inference = infer_scope_types(self.db(), scope, tcx);
|
||||
self.extend_scope(inference);
|
||||
|
||||
@@ -7877,7 +7889,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
else {
|
||||
return Type::unknown();
|
||||
};
|
||||
let scope = scope_id.to_scope_id(self.db(), self.python_file());
|
||||
let scope = scope_id.to_scope_id(self.db(), self.program_file());
|
||||
let inference = infer_scope_types(self.db(), scope, tcx);
|
||||
self.extend_scope(inference);
|
||||
|
||||
@@ -8333,7 +8345,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return Type::unknown();
|
||||
};
|
||||
|
||||
let scope = scope_id.to_scope_id(self.db(), self.python_file());
|
||||
let scope = scope_id.to_scope_id(self.db(), self.program_file());
|
||||
|
||||
// If we have a direct `Callable` type context, we can infer the body with the annotated
|
||||
// return type as type context.
|
||||
@@ -8402,7 +8414,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
// Collect the types of each distinct key.
|
||||
let mut elements: Vec<(&str, Type<'db>)> = Vec::new();
|
||||
for bindings in use_def.multi_bindings_at_use(keyword.scoped_use_id(db, self.python_file()))
|
||||
for bindings in
|
||||
use_def.multi_bindings_at_use(keyword.scoped_use_id(db, self.program_file()))
|
||||
{
|
||||
let place = place_from_bindings_with_reachability_cache(
|
||||
db,
|
||||
@@ -9621,7 +9634,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
|
||||
// These are looked up as attributes on `types.ModuleType`.
|
||||
.or_fall_back_to(db, env, || {
|
||||
module_type_implicit_global_symbol(db, self.python_file(), symbol_name).map_type(
|
||||
module_type_implicit_global_symbol(db, self.program_file(), symbol_name).map_type(
|
||||
|ty| {
|
||||
self.narrow_place_with_applicable_constraints(
|
||||
PlaceExprRef::from(&expr),
|
||||
@@ -9723,7 +9736,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return (place, None);
|
||||
}
|
||||
|
||||
let use_id = expr_ref.scoped_use_id(db, self.python_file());
|
||||
let use_id = expr_ref.scoped_use_id(db, self.program_file());
|
||||
let place = place_from_bindings_with_reachability_cache(
|
||||
db,
|
||||
env,
|
||||
@@ -9813,7 +9826,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return Place::Undefined.into();
|
||||
};
|
||||
|
||||
explicit_global_symbol(self.db(), self.python_file(), symbol_name).map_type(|ty| {
|
||||
explicit_global_symbol(self.db(), self.program_file(), symbol_name).map_type(|ty| {
|
||||
self.narrow_place_with_applicable_constraints(place_expr, ty, constraint_keys)
|
||||
})
|
||||
}
|
||||
@@ -10067,7 +10080,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// We've reached the defining scope of the variable. Infer its public type.
|
||||
debug_assert!(enclosing_place.is_bound() || enclosing_place.is_declared());
|
||||
let enclosing_scope_id =
|
||||
enclosing_scope_file_id.to_scope_id(db, self.python_file());
|
||||
enclosing_scope_file_id.to_scope_id(db, self.program_file());
|
||||
return eagerly_resolved_place.unwrap_or_else(|| {
|
||||
place_by_id(
|
||||
self.db(),
|
||||
@@ -10359,8 +10372,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
{
|
||||
let mut maybe_submodule_name = module_name.clone();
|
||||
maybe_submodule_name.extend(&relative_submodule);
|
||||
if resolve_module(db, self.python_file(), &maybe_submodule_name)
|
||||
.is_some()
|
||||
if resolve_module(
|
||||
db,
|
||||
ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
),
|
||||
&maybe_submodule_name,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
@@ -10488,6 +10508,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
} else {
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db,
|
||||
env,
|
||||
diagnostic,
|
||||
value_type,
|
||||
attr_name,
|
||||
@@ -11542,7 +11563,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.program_environment(),
|
||||
region,
|
||||
self.file(),
|
||||
self.python_file(),
|
||||
self.program_file(),
|
||||
index,
|
||||
self.module(),
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::types::{
|
||||
special_form::TypeQualifier,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, helpers::any_over_expr};
|
||||
use ty_module_resolver::{KnownModule, file_to_module};
|
||||
use ty_module_resolver::{ImportingFile, KnownModule, file_to_module};
|
||||
use ty_python_core::{definition::Definition, scope::NodeWithScopeRef};
|
||||
|
||||
impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
@@ -107,12 +107,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
let body_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::Class(class_node))
|
||||
.to_scope_id(db, self.python_file());
|
||||
.to_scope_id(db, self.program_file());
|
||||
|
||||
let maybe_known_class = KnownClass::try_from_file_and_name(db, self.python_file(), name);
|
||||
let file = self.program_file();
|
||||
let importing_file = ImportingFile::File(file.file(db), env.resolver_environment(db));
|
||||
let maybe_known_class = KnownClass::try_from_file_and_name(db, importing_file, name);
|
||||
|
||||
let known_module =
|
||||
|| file_to_module(db, self.python_file()).and_then(|module| module.known(db));
|
||||
let known_module = || {
|
||||
file_to_module(db, importing_file.resolver_file(db)).and_then(|module| module.known(db))
|
||||
};
|
||||
let in_typing_module = || {
|
||||
matches!(
|
||||
known_module(),
|
||||
|
||||
@@ -63,7 +63,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
|
||||
let class_body_scope = class_literal.body_scope(db);
|
||||
let class_scope_id = class_body_scope.file_scope_id(db);
|
||||
let class_index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let class_index = semantic_index(db, class_body_scope.program_file(db));
|
||||
let place_table = class_index.place_table(class_scope_id);
|
||||
let Some(symbol_id) = place_table.symbol_id(attribute) else {
|
||||
continue;
|
||||
@@ -257,7 +257,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
if let Some((class_literal, _)) = class_ty.static_class_literal(db) {
|
||||
let class_body_scope = class_literal.body_scope(db);
|
||||
let class_scope_id = class_body_scope.file_scope_id(db);
|
||||
let class_index = semantic_index(db, class_body_scope.python_file(db));
|
||||
let class_index = semantic_index(db, class_body_scope.program_file(db));
|
||||
let pt = class_index.place_table(class_scope_id);
|
||||
|
||||
if let Some(symbol) = pt.symbol_by_name(attribute)
|
||||
|
||||
@@ -80,7 +80,7 @@ impl<'db> ExpectedReturnType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
let env = ProgramEnvironment::from_file(function.python_file(db));
|
||||
let env = ProgramEnvironment::from_file(function.program_file(db));
|
||||
let public = normalize(
|
||||
db,
|
||||
&env,
|
||||
@@ -417,7 +417,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let body_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::Function(function))
|
||||
.to_scope_id(db, self.python_file());
|
||||
.to_scope_id(db, self.program_file());
|
||||
|
||||
let overload_literal = OverloadLiteral::new(
|
||||
db,
|
||||
@@ -601,7 +601,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let type_params_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::FunctionTypeParameters(function))
|
||||
.to_scope_id(db, self.python_file());
|
||||
.to_scope_id(db, self.program_file());
|
||||
let type_params_inference =
|
||||
infer_scope_types(self.db(), type_params_scope, TypeContext::default());
|
||||
|
||||
@@ -1016,7 +1016,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
) -> Option<Type<'db>> {
|
||||
let env = self.program_environment();
|
||||
let db = self.db();
|
||||
let file = self.python_file();
|
||||
let file = self.program_file();
|
||||
|
||||
let function_scope_id = self.scope();
|
||||
let function_scope = function_scope_id.scope(db);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_module_resolver::{
|
||||
ModuleName, ModuleNameResolutionError, ModuleResolveMode, resolve_module, search_paths,
|
||||
ImportingFile, ModuleName, ModuleNameResolutionError, ModuleResolveMode, resolve_module,
|
||||
search_paths,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -69,8 +70,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
if level == 0 {
|
||||
if let Some(module_name) = module_name {
|
||||
let program = ty_python_core::program::Program::get(db);
|
||||
let typeshed_versions = program.search_paths(db).typeshed_versions();
|
||||
let resolver_environment = self.program_environment().program(db);
|
||||
let typeshed_versions = resolver_environment.search_paths(db).typeshed_versions();
|
||||
|
||||
// Loop over ancestors in case we have info on the parent module but not submodule
|
||||
for module_name in module_name.ancestors() {
|
||||
@@ -96,16 +97,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
if let Some(better_level) = (0..level).rev().find(|reduced_level| {
|
||||
let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
db,
|
||||
self.python_file(),
|
||||
module,
|
||||
*reduced_level,
|
||||
) else {
|
||||
let Ok(module_name) =
|
||||
ModuleName::from_identifier_parts(db, importing_file, module, *reduced_level)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
resolve_module(db, self.python_file(), &module_name).is_some()
|
||||
resolve_module(db, importing_file, &module_name).is_some()
|
||||
}) {
|
||||
diagnostic
|
||||
.help("The module can be resolved if the number of leading dots is reduced");
|
||||
@@ -124,7 +126,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// Add search paths information to the diagnostic
|
||||
// Use the same search paths function that is used in actual module resolution
|
||||
let verbose = db.verbose();
|
||||
let search_paths = search_paths(db, ModuleResolveMode::Typing);
|
||||
let search_paths = search_paths(
|
||||
db,
|
||||
self.program_environment().resolver_environment(db),
|
||||
ModuleResolveMode::Typing,
|
||||
);
|
||||
|
||||
diagnostic.info(format_args!(
|
||||
"Searched in the following paths during module resolution:"
|
||||
@@ -271,7 +277,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
format_import_from_module(*level, module),
|
||||
self.file().path(db),
|
||||
);
|
||||
let module_name = ModuleName::from_import_statement(db, self.python_file(), import_from);
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
let module_name = ModuleName::from_import_statement(db, importing_file, import_from);
|
||||
|
||||
let module_name = match module_name {
|
||||
Ok(module_name) => module_name,
|
||||
@@ -300,7 +310,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
};
|
||||
|
||||
if resolve_module(db, self.python_file(), &module_name).is_none() {
|
||||
if resolve_module(db, importing_file, &module_name).is_none() {
|
||||
self.report_unresolved_import(module_ref.range(), *level, module, Some(&module_name));
|
||||
}
|
||||
}
|
||||
@@ -313,8 +323,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
) {
|
||||
let db = self.db();
|
||||
|
||||
let Ok(module_name) =
|
||||
ModuleName::from_import_statement(db, self.python_file(), import_from)
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
let Ok(module_name) = ModuleName::from_import_statement(db, importing_file, import_from)
|
||||
else {
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
@@ -334,7 +347,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(module) = resolve_module(db, self.python_file(), &module_name) else {
|
||||
let Some(module) = resolve_module(db, importing_file, &module_name) else {
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
@@ -342,7 +355,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let module_literal = ModuleLiteralType::new(
|
||||
db,
|
||||
module,
|
||||
module.kind(db).is_package().then_some(self.python_file()),
|
||||
module.kind(db).is_package().then_some(self.program_file()),
|
||||
);
|
||||
let module_ty = Type::ModuleLiteral(module_literal);
|
||||
|
||||
@@ -510,6 +523,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if !submodule_hint_added {
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db,
|
||||
self.program_environment(),
|
||||
diagnostic,
|
||||
module_ty,
|
||||
name,
|
||||
@@ -537,15 +551,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let db = self.db();
|
||||
let importing_file = ImportingFile::File(
|
||||
self.file(),
|
||||
self.program_environment().resolver_environment(db),
|
||||
);
|
||||
|
||||
// Get this package's absolute module name by resolving `.`, and make sure it exists
|
||||
let Ok(thispackage_name) = ModuleName::package_for_file(db, self.python_file()) else {
|
||||
let Ok(thispackage_name) = ModuleName::package_for_file(db, importing_file) else {
|
||||
self.add_binding(import_from.into(), definition)
|
||||
.insert(self, Type::unknown());
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(module) = resolve_module(db, self.python_file(), &thispackage_name) else {
|
||||
let Some(module) = resolve_module(db, importing_file, &thispackage_name) else {
|
||||
self.add_binding(import_from.into(), definition)
|
||||
.insert(self, Type::unknown());
|
||||
return;
|
||||
@@ -557,7 +575,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// First we normalize to `whatever.thispackage.x.y`
|
||||
let Some(final_part) = ModuleName::from_identifier_parts(
|
||||
db,
|
||||
self.python_file(),
|
||||
importing_file,
|
||||
import_from.module.as_deref(),
|
||||
import_from.level,
|
||||
)
|
||||
|
||||
@@ -1172,7 +1172,7 @@ fn check_class_namespace_against_metaclass_members<'db>(
|
||||
.filter_map(|class| class.static_class_literal(db).map(|(literal, _)| literal))
|
||||
{
|
||||
let body_scope = metaclass.body_scope(db);
|
||||
let metaclass_index = semantic_index(db, body_scope.python_file(db));
|
||||
let metaclass_index = semantic_index(db, body_scope.program_file(db));
|
||||
let body_scope_id = body_scope.file_scope_id(db);
|
||||
let metaclass_table = metaclass_index.place_table(body_scope_id);
|
||||
let metaclass_use_def = metaclass_index.use_def_map(body_scope_id);
|
||||
|
||||
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user