mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:22:24 +00:00
[red-knot] Typed diagnostic id (#14869)
## Summary This PR introduces a structured `DiagnosticId` instead of using a plain `&'static str`. It is the first of three in a stack that implements a basic rules infrastructure for Red Knot. `DiagnosticId` is an enum over all known diagnostic codes. A closed enum reduces the risk of accidentally introducing two identical diagnostic codes. It also opens the possibility of generating reference documentation from the enum in the future (not part of this PR). The enum isn't *fully closed* because it uses a `&'static str` for lint names. This is because we want the flexibility to define lints in different crates, and all names are only known in `red_knot_linter` or above. Still, lower-level crates must already reference the lint names to emit diagnostics. We could define all lint-names in `DiagnosticId` but I decided against it because: * We probably want to share the `DiagnosticId` type between Ruff and Red Knot to avoid extra complexity in the diagnostic crate, and both tools use different lint names. * Lints require a lot of extra metadata beyond just the name. That's why I think defining them close to their implementation is important. In the long term, we may also want to support plugins, which would make it impossible to know all lint names at compile time. The next PR in the stack introduces extra syntax for defining lints. A closed enum does have a few disadvantages: * rustc can't help us detect unused diagnostic codes because the enum is public * Adding a new diagnostic in the workspace crate now requires changes to at least two crates: It requires changing the workspace crate to add the diagnostic and the `ruff_db` crate to define the diagnostic ID. I consider this an acceptable trade. We may want to move `DiagnosticId` to its own crate or into a shared `red_knot_diagnostic` crate. ## Preventing duplicate diagnostic identifiers One goal of this PR is to make it harder to introduce ambiguous diagnostic IDs, which is achieved by defining a closed enum. However, the enum isn't fully "closed" because it doesn't explicitly list the IDs for all lint rules. That leaves the possibility that a lint rule and a diagnostic ID share the same name. I made the names unambiguous in this PR by separating them into different namespaces by using `lint/<rule>` for lint rule codes. I don't mind the `lint` prefix in a *Ruff next* context, but it is a bit weird for a standalone type checker. I'd like to not overfocus on this for now because I see a few different options: * We remove the `lint` prefix and add a unit test in a top-level crate that iterates over all known lint rules and diagnostic IDs to ensure the names are non-overlapping. * We only render `[lint]` as the error code and add a note to the diagnostic mentioning the lint rule. This is similar to clippy and has the advantage that the header line remains short (`lint/some-long-rule-name` is very long ;)) * Any other form of adjusting the diagnostic rendering to make the distinction clear I think we can defer this decision for now because the `DiagnosticId` contains all the relevant information to change the rendering accordingly. ## Why `Lint` and not `LintRule` I see three kinds of diagnostics in Red Knot: * Non-suppressable: Reveal type, IO errors, configuration errors, etc. (any `DiagnosticId`) * Lints: code-related diagnostics that are suppressable. * Lint rules: The same as lints, but they can be enabled or disabled in the configuration. The majority of lints in Red Knot and the Ruff linter. Our current implementation doesn't distinguish between lints and Lint rules because we aren't aware of a suppressible code-related lint that can't be configured in the configuration. The only lint that comes to my mind is maybe `division-by-zero` if we're 99.99% sure that it is always right. However, I want to keep the door open to making this distinction in the future if it proves useful. Another reason why I chose lint over lint rule (or just rule) is that I want to leave room for a future lint rule and lint phase concept: * lint is the *what*: a specific code smell, pattern, or violation * the lint rule is the *how*: I could see a future `LintRule` trait in `red_knot_python_linter` that provides the necessary hooks to run as part of the linter. A lint rule produces diagnostics for exactly one lint. A lint rule differs from all lints in `red_knot_python_semantic` because they don't run as "rules" in the Ruff sense. Instead, they're a side-product of type inference. * the lint phase is a different form of *how*: A lint phase can produce many different lints in a single pass. This is a somewhat common pattern in Ruff where running one analysis collects the necessary information for finding many different lints * diagnostic is the *presentation*: Unlike a lint, the diagnostic isn't the what, but how a specific lint gets presented. I expect that many lints can use one generic `LintDiagnostic`, but a few lints might need more flexibility and implement their custom diagnostic rendering (at least custom `Diagnostic` implementation). ## Test Plan `cargo test`
This commit is contained in:
parent
dc0d944608
commit
5f548072d9
11 changed files with 391 additions and 191 deletions
|
@ -2,7 +2,7 @@ use std::hash::Hash;
|
|||
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_db::diagnostic::DiagnosticId;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
|
@ -2310,7 +2310,7 @@ impl<'db> CallOutcome<'db> {
|
|||
}) => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
not_callable_ty.display(db)
|
||||
|
@ -2325,7 +2325,7 @@ impl<'db> CallOutcome<'db> {
|
|||
}) => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union element `{}`)",
|
||||
called_ty.display(db),
|
||||
|
@ -2341,7 +2341,7 @@ impl<'db> CallOutcome<'db> {
|
|||
}) => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union elements {})",
|
||||
called_ty.display(db),
|
||||
|
@ -2356,7 +2356,7 @@ impl<'db> CallOutcome<'db> {
|
|||
}) => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||
called_ty.display(db)
|
||||
|
@ -2382,7 +2382,7 @@ impl<'db> CallOutcome<'db> {
|
|||
} => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"revealed-type",
|
||||
DiagnosticId::RevealedType,
|
||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||
);
|
||||
Ok(*return_ty)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -11,16 +11,15 @@ use std::sync::Arc;
|
|||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
// TODO: Don't use string keys for rules
|
||||
pub(super) rule: String,
|
||||
pub(super) id: DiagnosticId,
|
||||
pub(super) message: String,
|
||||
pub(super) range: TextRange,
|
||||
pub(super) file: File,
|
||||
}
|
||||
|
||||
impl TypeCheckDiagnostic {
|
||||
pub fn rule(&self) -> &str {
|
||||
&self.rule
|
||||
pub fn id(&self) -> DiagnosticId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &str {
|
||||
|
@ -33,8 +32,8 @@ impl TypeCheckDiagnostic {
|
|||
}
|
||||
|
||||
impl Diagnostic for TypeCheckDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
TypeCheckDiagnostic::rule(self)
|
||||
fn id(&self) -> DiagnosticId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
|
@ -152,7 +151,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
DiagnosticId::lint("not-iterable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(self.db)
|
||||
|
@ -169,7 +168,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
DiagnosticId::lint("not-iterable"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
|
||||
element_ty.display(self.db)
|
||||
|
@ -188,7 +187,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
) {
|
||||
self.add(
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
DiagnosticId::lint("index-out-of-bounds"),
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(self.db)
|
||||
|
@ -205,7 +204,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
) {
|
||||
self.add(
|
||||
node,
|
||||
"non-subscriptable",
|
||||
DiagnosticId::lint("non-subscriptable"),
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(self.db)
|
||||
|
@ -221,7 +220,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
) {
|
||||
self.add(
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
DiagnosticId::lint("unresolved-import"),
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
|
@ -233,7 +232,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
||||
self.add(
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
DiagnosticId::lint("zero-stepsize-in-slice"),
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
@ -246,19 +245,19 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
self.add(node, DiagnosticId::lint("invalid-assignment"), format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(self.db)));
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
self.add(node, DiagnosticId::lint("invalid-assignment"), format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(self.db)));
|
||||
}
|
||||
_ => {
|
||||
self.add(
|
||||
node,
|
||||
"invalid-assignment",
|
||||
DiagnosticId::lint("invalid-assignment"),
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(self.db),
|
||||
|
@ -274,7 +273,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
DiagnosticId::lint("possibly-unresolved-reference"),
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
@ -284,7 +283,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
DiagnosticId::lint("unresolved-reference"),
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
@ -292,7 +291,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
pub(super) fn add_invalid_exception(&mut self, db: &dyn Db, node: &ast::Expr, ty: Type) {
|
||||
self.add(
|
||||
node.into(),
|
||||
"invalid-exception",
|
||||
DiagnosticId::lint("invalid-exception"),
|
||||
format_args!(
|
||||
"Cannot catch object of type `{}` in an exception handler \
|
||||
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
|
||||
|
@ -304,7 +303,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
/// Adds a new diagnostic.
|
||||
///
|
||||
/// The diagnostic does not get added if the rule isn't enabled for this file.
|
||||
pub(super) fn add(&mut self, node: AnyNodeRef, rule: &str, message: std::fmt::Arguments) {
|
||||
pub(super) fn add(&mut self, node: AnyNodeRef, id: DiagnosticId, message: std::fmt::Arguments) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
}
|
||||
|
@ -317,7 +316,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
|||
|
||||
self.diagnostics.push(TypeCheckDiagnostic {
|
||||
file: self.file,
|
||||
rule: rule.to_string(),
|
||||
id,
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
use std::num::NonZeroU32;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_db::diagnostic::DiagnosticId;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, Expr, ExprContext, UnaryOp};
|
||||
|
@ -527,7 +528,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if class.is_cyclically_defined(self.db) {
|
||||
self.diagnostics.add(
|
||||
class_node.into(),
|
||||
"cyclic-class-def",
|
||||
DiagnosticId::lint("cyclic-class-def"),
|
||||
format_args!(
|
||||
"Cyclic definition of `{}` or bases of `{}` (class cannot inherit from itself)",
|
||||
class.name(self.db),
|
||||
|
@ -547,7 +548,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
for (index, duplicate) in duplicates {
|
||||
self.diagnostics.add(
|
||||
(&base_nodes[*index]).into(),
|
||||
"duplicate-base",
|
||||
DiagnosticId::lint("duplicate-base"),
|
||||
format_args!("Duplicate base class `{}`", duplicate.name(self.db)),
|
||||
);
|
||||
}
|
||||
|
@ -557,7 +558,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
for (index, base_ty) in bases {
|
||||
self.diagnostics.add(
|
||||
(&base_nodes[*index]).into(),
|
||||
"invalid-base",
|
||||
DiagnosticId::lint("invalid-base"),
|
||||
format_args!(
|
||||
"Invalid class base with type `{}` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
|
||||
base_ty.display(self.db)
|
||||
|
@ -567,7 +568,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
MroErrorKind::UnresolvableMro { bases_list } => self.diagnostics.add(
|
||||
class_node.into(),
|
||||
"inconsistent-mro",
|
||||
DiagnosticId::lint("inconsistent-mro"),
|
||||
format_args!(
|
||||
"Cannot create a consistent method resolution order (MRO) for class `{}` with bases list `[{}]`",
|
||||
class.name(self.db),
|
||||
|
@ -597,7 +598,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if *candidate1_is_base_class {
|
||||
self.diagnostics.add(
|
||||
node,
|
||||
"conflicting-metaclass",
|
||||
DiagnosticId::lint("conflicting-metaclass"),
|
||||
format_args!(
|
||||
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
|
||||
but `{metaclass1}` (metaclass of base class `{base1}`) and `{metaclass2}` (metaclass of base class `{base2}`) \
|
||||
|
@ -612,7 +613,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} else {
|
||||
self.diagnostics.add(
|
||||
node,
|
||||
"conflicting-metaclass",
|
||||
DiagnosticId::lint("conflicting-metaclass"),
|
||||
format_args!(
|
||||
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
|
||||
but `{metaclass_of_class}` (metaclass of `{class}`) and `{metaclass_of_base}` (metaclass of base class `{base}`) \
|
||||
|
@ -761,7 +762,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
self.diagnostics.add(
|
||||
expr.into(),
|
||||
"division-by-zero",
|
||||
DiagnosticId::lint("division-by-zero"),
|
||||
format_args!(
|
||||
"Cannot {op} object of type `{}` {by_zero}",
|
||||
left.display(self.db)
|
||||
|
@ -786,7 +787,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let symbol_name = symbol_table.symbol(binding.symbol(self.db)).name();
|
||||
self.diagnostics.add(
|
||||
node,
|
||||
"conflicting-declarations",
|
||||
DiagnosticId::lint("conflicting-declarations"),
|
||||
format_args!(
|
||||
"Conflicting declared types for `{symbol_name}`: {}",
|
||||
conflicting.display(self.db)
|
||||
|
@ -816,7 +817,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} else {
|
||||
self.diagnostics.add(
|
||||
node,
|
||||
"invalid-declaration",
|
||||
DiagnosticId::lint("invalid-declaration"),
|
||||
format_args!(
|
||||
"Cannot declare type `{}` for inferred type `{}`",
|
||||
ty.display(self.db),
|
||||
|
@ -1113,7 +1114,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} else {
|
||||
self.diagnostics.add(
|
||||
parameter_with_default.into(),
|
||||
"invalid-parameter-default",
|
||||
DiagnosticId::lint("invalid-parameter-default"),
|
||||
format_args!(
|
||||
"Default value of type `{}` is not assignable to annotated parameter type `{}`",
|
||||
default_ty.display(self.db), declared_ty.display(self.db))
|
||||
|
@ -1425,7 +1426,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
(Symbol::Unbound, Symbol::Unbound) => {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__` and `__exit__`",
|
||||
context_expression_ty.display(self.db)
|
||||
|
@ -1436,7 +1437,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
(Symbol::Unbound, _) => {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__`",
|
||||
context_expression_ty.display(self.db)
|
||||
|
@ -1448,7 +1449,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if enter_boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__enter__` is possibly unbound",
|
||||
context_expression = context_expression_ty.display(self.db),
|
||||
|
@ -1462,7 +1463,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!("
|
||||
Object of type `{context_expression}` cannot be used with `with` because the method `__enter__` of type `{enter_ty}` is not callable", context_expression = context_expression_ty.display(self.db), enter_ty = enter_ty.display(self.db)
|
||||
),
|
||||
|
@ -1474,7 +1475,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Symbol::Unbound => {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__exit__`",
|
||||
context_expression_ty.display(self.db)
|
||||
|
@ -1487,7 +1488,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if exit_boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__exit__` is possibly unbound",
|
||||
context_expression = context_expression_ty.display(self.db),
|
||||
|
@ -1514,7 +1515,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
{
|
||||
self.diagnostics.add(
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
DiagnosticId::lint("invalid-context-manager"),
|
||||
format_args!(
|
||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__exit__` of type `{exit_ty}` is not callable",
|
||||
context_expression = context_expression_ty.display(self.db),
|
||||
|
@ -1610,7 +1611,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if elts.len() < 2 {
|
||||
self.diagnostics.add(
|
||||
expr.into(),
|
||||
"invalid-typevar-constraints",
|
||||
DiagnosticId::lint("invalid-typevar-constraints"),
|
||||
format_args!("TypeVar must have at least two constrained types"),
|
||||
);
|
||||
self.infer_expression(expr);
|
||||
|
@ -1933,7 +1934,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Err(e) => {
|
||||
self.diagnostics.add(
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||
target_type.display(self.db),
|
||||
|
@ -1954,7 +1955,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||
left_ty.display(self.db),
|
||||
|
@ -1983,7 +1984,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||
left_ty.display(self.db),
|
||||
|
@ -2236,7 +2237,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
AnyNodeRef::Alias(alias),
|
||||
"possibly-unbound-import",
|
||||
DiagnosticId::lint("possibly-unbound-import"),
|
||||
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
|
||||
);
|
||||
}
|
||||
|
@ -2246,7 +2247,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Symbol::Unbound => {
|
||||
self.diagnostics.add(
|
||||
AnyNodeRef::Alias(alias),
|
||||
"unresolved-import",
|
||||
DiagnosticId::lint("unresolved-import"),
|
||||
format_args!("Module `{module_name}` has no member `{name}`",),
|
||||
);
|
||||
Type::Unknown
|
||||
|
@ -2953,7 +2954,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if builtins_symbol.is_unbound() && name == "reveal_type" {
|
||||
self.diagnostics.add(
|
||||
name_node.into(),
|
||||
"undefined-reveal",
|
||||
DiagnosticId::lint("undefined-reveal"),
|
||||
format_args!(
|
||||
"`reveal_type` used without importing it; this is allowed for debugging convenience but will fail at runtime"),
|
||||
);
|
||||
|
@ -3050,7 +3051,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
attribute.into(),
|
||||
"possibly-unbound-attribute",
|
||||
DiagnosticId::lint("possibly-unbound-attribute"),
|
||||
format_args!(
|
||||
"Attribute `{}` on type `{}` is possibly unbound",
|
||||
attr.id,
|
||||
|
@ -3064,7 +3065,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Symbol::Unbound => {
|
||||
self.diagnostics.add(
|
||||
attribute.into(),
|
||||
"unresolved-attribute",
|
||||
DiagnosticId::lint("unresolved-attribute"),
|
||||
format_args!(
|
||||
"Type `{}` has no attribute `{}`",
|
||||
value_ty.display(self.db),
|
||||
|
@ -3145,7 +3146,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Err(e) => {
|
||||
self.diagnostics.add(
|
||||
unary.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Unary operator `{op}` is unsupported for type `{}`",
|
||||
operand_type.display(self.db),
|
||||
|
@ -3157,7 +3158,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} else {
|
||||
self.diagnostics.add(
|
||||
unary.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Unary operator `{op}` is unsupported for type `{}`",
|
||||
operand_type.display(self.db),
|
||||
|
@ -3198,7 +3199,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
binary.into(),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Operator `{op}` is unsupported between objects of type `{}` and `{}`",
|
||||
left_ty.display(self.db),
|
||||
|
@ -3508,7 +3509,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
|
||||
self.diagnostics.add(
|
||||
AnyNodeRef::ExprCompare(compare),
|
||||
"unsupported-operator",
|
||||
DiagnosticId::lint("unsupported-operator"),
|
||||
format_args!(
|
||||
"Operator `{}` is not supported for types `{}` and `{}`{}",
|
||||
error.op,
|
||||
|
@ -4165,7 +4166,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
value_node.into(),
|
||||
"call-possibly-unbound-method",
|
||||
DiagnosticId::lint("call-possibly-unbound-method"),
|
||||
format_args!(
|
||||
"Method `__getitem__` of type `{}` is possibly unbound",
|
||||
value_ty.display(self.db),
|
||||
|
@ -4179,7 +4180,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
value_node.into(),
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Method `__getitem__` of type `{}` is not callable on object of type `{}`",
|
||||
err.called_ty().display(self.db),
|
||||
|
@ -4209,7 +4210,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add(
|
||||
value_node.into(),
|
||||
"call-possibly-unbound-method",
|
||||
DiagnosticId::lint("call-possibly-unbound-method"),
|
||||
format_args!(
|
||||
"Method `__class_getitem__` of type `{}` is possibly unbound",
|
||||
value_ty.display(self.db),
|
||||
|
@ -4223,7 +4224,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
value_node.into(),
|
||||
"call-non-callable",
|
||||
DiagnosticId::lint("call-non-callable"),
|
||||
format_args!(
|
||||
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
|
||||
err.called_ty().display(self.db),
|
||||
|
@ -4365,7 +4366,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ast::Expr::BytesLiteral(bytes) => {
|
||||
self.diagnostics.add(
|
||||
bytes.into(),
|
||||
"annotation-byte-string",
|
||||
DiagnosticId::lint("annotation-byte-string"),
|
||||
format_args!("Type expressions cannot use bytes literal"),
|
||||
);
|
||||
Type::Unknown
|
||||
|
@ -4374,7 +4375,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ast::Expr::FString(fstring) => {
|
||||
self.diagnostics.add(
|
||||
fstring.into(),
|
||||
"annotation-f-string",
|
||||
DiagnosticId::lint("annotation-f-string"),
|
||||
format_args!("Type expressions cannot use f-strings"),
|
||||
);
|
||||
self.infer_fstring_expression(fstring);
|
||||
|
@ -4718,7 +4719,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_type_expression(slice);
|
||||
self.diagnostics.add(
|
||||
slice.into(),
|
||||
"invalid-type-form",
|
||||
DiagnosticId::lint("invalid-type-form"),
|
||||
format_args!("type[...] must have exactly one type argument"),
|
||||
);
|
||||
Type::Unknown
|
||||
|
@ -4794,7 +4795,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
for node in nodes {
|
||||
self.diagnostics.add(
|
||||
node.into(),
|
||||
"invalid-literal-parameter",
|
||||
DiagnosticId::lint("invalid-literal-parameter"),
|
||||
format_args!(
|
||||
"Type arguments for `Literal` must be `None`, \
|
||||
a literal value (int, bool, str, or bytes), or an enum value"
|
||||
|
@ -4830,7 +4831,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
|
||||
self.diagnostics.add(
|
||||
subscript.into(),
|
||||
"invalid-type-parameter",
|
||||
DiagnosticId::lint("invalid-type-parameter"),
|
||||
format_args!(
|
||||
"Type `{}` expected no type parameter",
|
||||
known_instance.repr(self.db)
|
||||
|
@ -4841,7 +4842,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
KnownInstanceType::LiteralString => {
|
||||
self.diagnostics.add(
|
||||
subscript.into(),
|
||||
"invalid-type-parameter",
|
||||
DiagnosticId::lint("invalid-type-parameter"),
|
||||
format_args!(
|
||||
"Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?",
|
||||
known_instance.repr(self.db)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use ruff_db::diagnostic::DiagnosticId;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_python_ast::str::raw_contents;
|
||||
|
@ -27,7 +28,7 @@ pub(crate) fn parse_string_annotation(
|
|||
if prefix.is_raw() {
|
||||
diagnostics.add(
|
||||
string_literal.into(),
|
||||
"annotation-raw-string",
|
||||
DiagnosticId::lint("annotation-raw-string"),
|
||||
format_args!("Type expressions cannot use raw string literal"),
|
||||
);
|
||||
// Compare the raw contents (without quotes) of the expression with the parsed contents
|
||||
|
@ -51,7 +52,7 @@ pub(crate) fn parse_string_annotation(
|
|||
Ok(parsed) => return Ok(parsed),
|
||||
Err(parse_error) => diagnostics.add(
|
||||
string_literal.into(),
|
||||
"forward-annotation-syntax-error",
|
||||
DiagnosticId::lint("forward-annotation-syntax-error"),
|
||||
format_args!("Syntax error in forward annotation: {}", parse_error.error),
|
||||
),
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ pub(crate) fn parse_string_annotation(
|
|||
// case for annotations that contain escape sequences.
|
||||
diagnostics.add(
|
||||
string_expr.into(),
|
||||
"annotation-escape-character",
|
||||
DiagnosticId::lint("annotation-escape-character"),
|
||||
format_args!("Type expressions cannot contain escape characters"),
|
||||
);
|
||||
}
|
||||
|
@ -68,7 +69,7 @@ pub(crate) fn parse_string_annotation(
|
|||
// String is implicitly concatenated.
|
||||
diagnostics.add(
|
||||
string_expr.into(),
|
||||
"annotation-implicit-concat",
|
||||
DiagnosticId::lint("annotation-implicit-concat"),
|
||||
format_args!("Type expressions cannot span multiple string literals"),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue