mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
[ty] Tell the user why we inferred a certain Python version when reporting version-specific syntax errors (#18295)
This commit is contained in:
parent
0a11baf29c
commit
6453ac9ea1
9 changed files with 201 additions and 107 deletions
|
@ -14,6 +14,7 @@ pub use program::{
|
|||
pub use python_platform::PythonPlatform;
|
||||
pub use semantic_model::{HasType, SemanticModel};
|
||||
pub use site_packages::SysPrefixPathOrigin;
|
||||
pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
|
||||
pub mod ast_node_ref;
|
||||
mod db;
|
||||
|
|
|
@ -50,6 +50,7 @@ use crate::types::infer::infer_unpack_types;
|
|||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||
use instance::Protocol;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use super::call::CallErrorKind;
|
||||
use super::context::InferContext;
|
||||
use super::mro::DuplicateBaseError;
|
||||
use super::{CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass};
|
||||
use super::{
|
||||
CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass,
|
||||
add_inferred_python_version_hint_to_diagnostic,
|
||||
};
|
||||
use crate::db::Db;
|
||||
use crate::declare_lint;
|
||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||
use crate::suppression::FileSuppressionId;
|
||||
use crate::types::LintDiagnosticGuard;
|
||||
|
@ -12,10 +16,8 @@ use crate::types::string_annotation::{
|
|||
RAW_STRING_TYPE_ANNOTATION,
|
||||
};
|
||||
use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral};
|
||||
use crate::{Program, PythonVersionWithSource, declare_lint};
|
||||
use itertools::Itertools;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_stdlib::builtins::version_builtin_was_added;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -1762,44 +1764,6 @@ pub(super) fn report_possibly_unbound_attribute(
|
|||
));
|
||||
}
|
||||
|
||||
pub(super) fn add_inferred_python_version_hint(db: &dyn Db, mut diagnostic: LintDiagnosticGuard) {
|
||||
let program = Program::get(db);
|
||||
let PythonVersionWithSource { version, source } = program.python_version_with_source(db);
|
||||
|
||||
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) {
|
||||
let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else {
|
||||
return;
|
||||
|
@ -1811,7 +1775,11 @@ 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}"
|
||||
));
|
||||
add_inferred_python_version_hint(context.db(), diagnostic);
|
||||
add_inferred_python_version_hint_to_diagnostic(
|
||||
context.db(),
|
||||
&mut diagnostic,
|
||||
"resolving types",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,9 @@ use super::string_annotation::{
|
|||
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
|
||||
};
|
||||
use super::subclass_of::SubclassOfInner;
|
||||
use super::{BoundSuperError, BoundSuperType, ClassBase};
|
||||
use super::{
|
||||
BoundSuperError, BoundSuperType, ClassBase, add_inferred_python_version_hint_to_diagnostic,
|
||||
};
|
||||
|
||||
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
||||
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
||||
|
@ -6029,7 +6031,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
diag.info(
|
||||
"Note that `X | Y` PEP 604 union syntax is only available in Python 3.10 and later",
|
||||
);
|
||||
diagnostic::add_inferred_python_version_hint(db, diag);
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut diag, "resolving types");
|
||||
}
|
||||
}
|
||||
Type::unknown()
|
||||
|
|
51
crates/ty_python_semantic/src/util/diagnostics.rs
Normal file
51
crates/ty_python_semantic/src/util/diagnostics.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::{Db, Program, PythonVersionWithSource};
|
||||
use ruff_db::{
|
||||
diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic},
|
||||
files::system_path_to_file,
|
||||
};
|
||||
|
||||
/// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred.
|
||||
///
|
||||
/// ty can infer the Python version from various sources, such as command-line arguments,
|
||||
/// configuration files, or defaults.
|
||||
pub fn add_inferred_python_version_hint_to_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &mut Diagnostic,
|
||||
action: &str,
|
||||
) {
|
||||
let program = Program::get(db);
|
||||
let PythonVersionWithSource { version, source } = program.python_version_with_source(db);
|
||||
|
||||
match source {
|
||||
crate::PythonVersionSource::Cli => {
|
||||
diagnostic.info(format_args!(
|
||||
"Python {version} was assumed when {action} 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 {action}"),
|
||||
);
|
||||
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 {action} because of your configuration file(s)",
|
||||
));
|
||||
}
|
||||
}
|
||||
crate::PythonVersionSource::Default => {
|
||||
diagnostic.info(format_args!(
|
||||
"Python {version} was assumed when {action} \
|
||||
because it is the newest Python version supported by ty, \
|
||||
and neither a command-line argument nor a configuration setting was provided",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub(crate) mod diagnostics;
|
||||
pub(crate) mod subscript;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue