mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-30 23:27:27 +00:00
[ty] Tell the user why we inferred the Python version we inferred (#18082)
This commit is contained in:
parent
cb9e66927e
commit
d37592175f
20 changed files with 303 additions and 124 deletions
|
@ -27,8 +27,7 @@ error[unsupported-operator]: Operator `|` is unsupported between objects of type
|
|||
| ^^^^^^^^^
|
||||
|
|
||||
info: Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later
|
||||
info: The inferred target version of your project is Python 3.9
|
||||
info: If using a pyproject.toml file, consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field
|
||||
info: Python 3.9 was assumed when resolving types because it was specified on the command line
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -25,8 +25,7 @@ error[unresolved-reference]: Name `aiter` used when not defined
|
|||
| ^^^^^
|
||||
|
|
||||
info: `aiter` was added as a builtin in Python 3.10
|
||||
info: The inferred target version of your project is Python 3.9
|
||||
info: If using a pyproject.toml file, consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field
|
||||
info: Python 3.9 was assumed when resolving types because it was specified on the command line
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -17,7 +17,10 @@ pub(crate) mod tests {
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::{ProgramSettings, PythonPlatform, default_lint_registry};
|
||||
use crate::{
|
||||
ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
|
||||
default_lint_registry,
|
||||
};
|
||||
|
||||
use super::Db;
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
|
@ -179,7 +182,10 @@ pub(crate) mod tests {
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: self.python_version,
|
||||
python_version: PythonVersionWithSource {
|
||||
version: self.python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform: self.python_platform,
|
||||
search_paths: SearchPathSettings::new(vec![src_root]),
|
||||
},
|
||||
|
|
|
@ -7,7 +7,10 @@ use crate::suppression::{INVALID_IGNORE_COMMENT, UNKNOWN_RULE, UNUSED_IGNORE_COM
|
|||
pub use db::Db;
|
||||
pub use module_name::ModuleName;
|
||||
pub use module_resolver::{KnownModule, Module, resolve_module, system_module_search_paths};
|
||||
pub use program::{Program, ProgramSettings, PythonPath, SearchPathSettings};
|
||||
pub use program::{
|
||||
Program, ProgramSettings, PythonPath, PythonVersionSource, PythonVersionWithSource,
|
||||
SearchPathSettings,
|
||||
};
|
||||
pub use python_platform::PythonPlatform;
|
||||
pub use semantic_model::{HasType, SemanticModel};
|
||||
pub use site_packages::SysPrefixPathOrigin;
|
||||
|
|
|
@ -978,7 +978,7 @@ mod tests {
|
|||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::module::ModuleKind;
|
||||
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
use crate::{ProgramSettings, PythonPlatform};
|
||||
use crate::{ProgramSettings, PythonPlatform, PythonVersionWithSource};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -1435,7 +1435,9 @@ mod tests {
|
|||
fn symlink() -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::{PythonPlatform, program::Program};
|
||||
use crate::{
|
||||
PythonPlatform, PythonVersionSource, PythonVersionWithSource, program::Program,
|
||||
};
|
||||
use ruff_db::system::{OsSystem, SystemPath};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
|
@ -1468,7 +1470,10 @@ mod tests {
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersion::PY38,
|
||||
python_version: PythonVersionWithSource {
|
||||
version: PythonVersion::PY38,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
@ -1984,7 +1989,7 @@ not_a_directory
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersion::default(),
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
@ -2063,7 +2068,7 @@ not_a_directory
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersion::default(),
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
@ -2106,7 +2111,7 @@ not_a_directory
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersion::default(),
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
|
|
@ -6,7 +6,9 @@ use ruff_python_ast::PythonVersion;
|
|||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::{ProgramSettings, PythonPath, PythonPlatform};
|
||||
use crate::{
|
||||
ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
|
||||
};
|
||||
|
||||
/// A test case for the module resolver.
|
||||
///
|
||||
|
@ -235,7 +237,10 @@ impl TestCaseBuilder<MockedTypeshed> {
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version,
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform,
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
|
@ -293,7 +298,10 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
|||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version,
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform,
|
||||
search_paths: SearchPathSettings {
|
||||
python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_resolver::SearchPaths;
|
||||
use crate::python_platform::PythonPlatform;
|
||||
|
@ -6,12 +8,14 @@ use crate::site_packages::SysPrefixPathOrigin;
|
|||
use anyhow::Context;
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_text_size::TextRange;
|
||||
use salsa::Durability;
|
||||
use salsa::Setter;
|
||||
|
||||
#[salsa::input(singleton)]
|
||||
pub struct Program {
|
||||
pub python_version: PythonVersion,
|
||||
#[returns(ref)]
|
||||
pub python_version_with_source: PythonVersionWithSource,
|
||||
|
||||
#[returns(ref)]
|
||||
pub python_platform: PythonPlatform,
|
||||
|
@ -23,23 +27,30 @@ pub struct Program {
|
|||
impl Program {
|
||||
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> anyhow::Result<Self> {
|
||||
let ProgramSettings {
|
||||
python_version,
|
||||
python_version: python_version_with_source,
|
||||
python_platform,
|
||||
search_paths,
|
||||
} = settings;
|
||||
|
||||
tracing::info!("Python version: Python {python_version}, platform: {python_platform}");
|
||||
tracing::info!(
|
||||
"Python version: Python {python_version}, platform: {python_platform}",
|
||||
python_version = python_version_with_source.version
|
||||
);
|
||||
|
||||
let search_paths = SearchPaths::from_settings(db, &search_paths)
|
||||
.with_context(|| "Invalid search path settings")?;
|
||||
|
||||
Ok(
|
||||
Program::builder(python_version, python_platform, search_paths)
|
||||
Program::builder(python_version_with_source, python_platform, search_paths)
|
||||
.durability(Durability::HIGH)
|
||||
.new(db),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn python_version(self, db: &dyn Db) -> PythonVersion {
|
||||
self.python_version_with_source(db).version
|
||||
}
|
||||
|
||||
pub fn update_from_settings(
|
||||
self,
|
||||
db: &mut dyn Db,
|
||||
|
@ -56,9 +67,9 @@ impl Program {
|
|||
self.set_python_platform(db).to(python_platform);
|
||||
}
|
||||
|
||||
if python_version != self.python_version(db) {
|
||||
tracing::debug!("Updating python version: Python {python_version}");
|
||||
self.set_python_version(db).to(python_version);
|
||||
if &python_version != self.python_version_with_source(db) {
|
||||
tracing::debug!("Updating python version: `{python_version:?}`");
|
||||
self.set_python_version_with_source(db).to(python_version);
|
||||
}
|
||||
|
||||
self.update_search_paths(db, &search_paths)?;
|
||||
|
@ -87,13 +98,41 @@ impl Program {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct ProgramSettings {
|
||||
pub python_version: PythonVersion,
|
||||
pub python_version: PythonVersionWithSource,
|
||||
pub python_platform: PythonPlatform,
|
||||
pub search_paths: SearchPathSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub enum PythonVersionSource {
|
||||
/// Value loaded from a project's configuration file.
|
||||
File(Arc<SystemPathBuf>, Option<TextRange>),
|
||||
|
||||
/// The value comes from a CLI argument, while it's left open if specified using a short argument,
|
||||
/// long argument (`--extra-paths`) or `--config key=value`.
|
||||
Cli,
|
||||
|
||||
/// We fell back to a default value because the value was not specified via the CLI or a config file.
|
||||
#[default]
|
||||
Default,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct PythonVersionWithSource {
|
||||
pub version: PythonVersion,
|
||||
pub source: PythonVersionSource,
|
||||
}
|
||||
|
||||
impl Default for PythonVersionWithSource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: PythonVersion::latest_ty(),
|
||||
source: PythonVersionSource::Default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the search paths for module resolution.
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
|
|
|
@ -2887,6 +2887,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::{PythonVersionSource, PythonVersionWithSource};
|
||||
use salsa::Setter;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
|
@ -2910,8 +2911,11 @@ mod tests {
|
|||
let mut db = setup_db();
|
||||
|
||||
Program::get(&db)
|
||||
.set_python_version(&mut db)
|
||||
.to(PythonVersion::latest_ty());
|
||||
.set_python_version_with_source(&mut db)
|
||||
.to(PythonVersionWithSource {
|
||||
version: PythonVersion::latest_ty(),
|
||||
source: PythonVersionSource::default(),
|
||||
});
|
||||
|
||||
for class in KnownClass::iter() {
|
||||
assert_ne!(
|
||||
|
@ -2935,8 +2939,11 @@ mod tests {
|
|||
};
|
||||
|
||||
Program::get(&db)
|
||||
.set_python_version(&mut db)
|
||||
.to(version_added);
|
||||
.set_python_version_with_source(&mut db)
|
||||
.to(PythonVersionWithSource {
|
||||
version: version_added,
|
||||
source: PythonVersionSource::default(),
|
||||
});
|
||||
|
||||
assert_ne!(
|
||||
class.to_instance(&db),
|
||||
|
|
|
@ -11,8 +11,9 @@ use crate::types::string_annotation::{
|
|||
RAW_STRING_TYPE_ANNOTATION,
|
||||
};
|
||||
use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral};
|
||||
use crate::{Program, declare_lint};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use crate::{Program, PythonVersionWithSource, declare_lint};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_stdlib::builtins::version_builtin_was_added;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -1670,15 +1671,41 @@ pub(super) fn report_possibly_unbound_attribute(
|
|||
}
|
||||
|
||||
pub(super) fn add_inferred_python_version_hint(db: &dyn Db, mut diagnostic: LintDiagnosticGuard) {
|
||||
diagnostic.info(format_args!(
|
||||
"The inferred target version of your project is Python {}",
|
||||
Program::get(db).python_version(db)
|
||||
));
|
||||
let program = Program::get(db);
|
||||
let PythonVersionWithSource { version, source } = program.python_version_with_source(db);
|
||||
|
||||
diagnostic.info(
|
||||
"If using a pyproject.toml file, \
|
||||
consider adjusting the `project.requires-python` or `tool.ty.environment.python-version` field"
|
||||
);
|
||||
match source {
|
||||
crate::PythonVersionSource::Cli => {
|
||||
diagnostic.info(format_args!(
|
||||
"Python {version} was assumed when resolving types because it was specified on the command line",
|
||||
));
|
||||
}
|
||||
crate::PythonVersionSource::File(path, range) => {
|
||||
if let Ok(file) = system_path_to_file(db.upcast(), &**path) {
|
||||
let mut sub_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
format_args!("Python {version} was assumed when resolving types"),
|
||||
);
|
||||
sub_diagnostic.annotate(
|
||||
Annotation::primary(Span::from(file).with_optional_range(*range)).message(
|
||||
format_args!("Python {version} assumed due to this configuration setting"),
|
||||
),
|
||||
);
|
||||
diagnostic.sub(sub_diagnostic);
|
||||
} else {
|
||||
diagnostic.info(format_args!(
|
||||
"Python {version} was assumed when resolving types because of your configuration file(s)",
|
||||
));
|
||||
}
|
||||
}
|
||||
crate::PythonVersionSource::Default => {
|
||||
diagnostic.info(format_args!(
|
||||
"Python {version} was assumed when resolving types \
|
||||
because it is the newest Python version supported by ty, \
|
||||
and neither a command-line argument nor a configuration setting was provided",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) {
|
||||
|
@ -1692,9 +1719,6 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node
|
|||
diagnostic.info(format_args!(
|
||||
"`{id}` was added as a builtin in Python 3.{version_added_to_builtins}"
|
||||
));
|
||||
|
||||
// TODO: can we tell the user *why* we're inferring this target version?
|
||||
// CLI flag? pyproject.toml? Python environment?
|
||||
add_inferred_python_version_hint(context.db(), diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue