[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:
Micha Reiser authored and GitHub committed 2026-08-04 08:51:17 +02:00
1 parent 7fd30bb81c
commit 95734a549e
150 files changed
+2293 -1684

No files matched your search

Generated
+2
View File
@@ -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",
+11 -8
View File
@@ -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,
+3 -1
View File
@@ -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
View File
@@ -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 {}
+11 -8
View File
@@ -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() {
+24 -16
View File
@@ -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))
}
+3 -8
View File
@@ -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,
+1 -1
View File
@@ -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,
)
}
+2 -4
View File
@@ -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,
))
}
+2 -4
View File
@@ -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)
+16 -10
View File
@@ -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),
);
+7 -6
View File
@@ -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() {
+11 -7
View File
@@ -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,
) {
+35 -30
View File
@@ -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
+4 -4
View File
@@ -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();
+6 -6
View File
@@ -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();
+4 -4
View File
@@ -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 {
+5 -1
View File
@@ -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();
+3 -2
View File
@@ -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)])
}
+4 -4
View 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 {
+4 -4
View 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 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 {
+7 -7
View File
@@ -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 {
+4 -4
View File
@@ -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 {
+2 -2
View File
@@ -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();
+7 -7
View File
@@ -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();
+23 -14
View File
@@ -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,
)
+13 -8
View File
@@ -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,
);
+6 -8
View File
@@ -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 {
+11 -11
View File
@@ -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
+8 -8
View File
@@ -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,
)
+1 -1
View File
@@ -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,
);
+14 -6
View File
@@ -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),
)
}
+4 -4
View File
@@ -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,
)
}
+47 -23
View File
@@ -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),
),
)
}
+11 -11
View File
@@ -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 -2
View File
@@ -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(),
+1
View File
@@ -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 }
+11 -16
View File
@@ -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<'_> {}
+12 -3
View File
@@ -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),
}
}
+41 -37
View File
@@ -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,
);
}
+35 -28
View File
@@ -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)]
+70 -9
View File
@@ -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> {
+55 -17
View File
@@ -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");
+254 -165
View File
@@ -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);
+4 -9
View File
@@ -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
View File
@@ -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()
+10 -14
View File
@@ -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);
+9 -9
View File
@@ -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)
}
+36 -24
View File
@@ -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 {
+2 -8
View File
@@ -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 {}
+6 -1
View File
@@ -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)
}
+6 -2
View File
@@ -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)
}
}
+73 -75
View File
@@ -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"]);
+12 -7
View File
@@ -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))
}
}
+14 -1
View File
@@ -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,
+96
View File
@@ -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)
}
}
+35 -21
View File
@@ -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);
}
}
+12 -8
View File
@@ -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]
}
+9 -5
View File
@@ -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)
}
}
+9 -4
View File
@@ -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)
}
+13 -10
View File
@@ -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 {}
+17 -11
View File
@@ -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.
+7 -29
View File
@@ -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);
+6 -5
View 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(
+41 -42
View File
@@ -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__"]
+5 -4
View File
@@ -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),
}
+18 -16
View 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
+83 -44
View File
@@ -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];
+41 -25
View File
@@ -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() {
+1 -1
View File
@@ -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) =
+15 -16
View File
@@ -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>,
+35 -21
View File
@@ -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());
}
+2 -2
View File
@@ -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,
+33 -16
View File
@@ -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)
}
+41 -33
View File
@@ -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