mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +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 indexmap::IndexSet;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use ruff_db::diagnostic::DiagnosticId;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
|
@ -2310,7 +2310,7 @@ impl<'db> CallOutcome<'db> {
|
||||||
}) => {
|
}) => {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not callable",
|
"Object of type `{}` is not callable",
|
||||||
not_callable_ty.display(db)
|
not_callable_ty.display(db)
|
||||||
|
@ -2325,7 +2325,7 @@ impl<'db> CallOutcome<'db> {
|
||||||
}) => {
|
}) => {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not callable (due to union element `{}`)",
|
"Object of type `{}` is not callable (due to union element `{}`)",
|
||||||
called_ty.display(db),
|
called_ty.display(db),
|
||||||
|
@ -2341,7 +2341,7 @@ impl<'db> CallOutcome<'db> {
|
||||||
}) => {
|
}) => {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not callable (due to union elements {})",
|
"Object of type `{}` is not callable (due to union elements {})",
|
||||||
called_ty.display(db),
|
called_ty.display(db),
|
||||||
|
@ -2356,7 +2356,7 @@ impl<'db> CallOutcome<'db> {
|
||||||
}) => {
|
}) => {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||||
called_ty.display(db)
|
called_ty.display(db)
|
||||||
|
@ -2382,7 +2382,7 @@ impl<'db> CallOutcome<'db> {
|
||||||
} => {
|
} => {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"revealed-type",
|
DiagnosticId::RevealedType,
|
||||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||||
);
|
);
|
||||||
Ok(*return_ty)
|
Ok(*return_ty)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::types::{ClassLiteralType, Type};
|
use crate::types::{ClassLiteralType, Type};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
@ -11,16 +11,15 @@ use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct TypeCheckDiagnostic {
|
pub struct TypeCheckDiagnostic {
|
||||||
// TODO: Don't use string keys for rules
|
pub(super) id: DiagnosticId,
|
||||||
pub(super) rule: String,
|
|
||||||
pub(super) message: String,
|
pub(super) message: String,
|
||||||
pub(super) range: TextRange,
|
pub(super) range: TextRange,
|
||||||
pub(super) file: File,
|
pub(super) file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeCheckDiagnostic {
|
impl TypeCheckDiagnostic {
|
||||||
pub fn rule(&self) -> &str {
|
pub fn id(&self) -> DiagnosticId {
|
||||||
&self.rule
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message(&self) -> &str {
|
pub fn message(&self) -> &str {
|
||||||
|
@ -33,8 +32,8 @@ impl TypeCheckDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for TypeCheckDiagnostic {
|
impl Diagnostic for TypeCheckDiagnostic {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
TypeCheckDiagnostic::rule(self)
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
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>) {
|
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"not-iterable",
|
DiagnosticId::lint("not-iterable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not iterable",
|
"Object of type `{}` is not iterable",
|
||||||
not_iterable_ty.display(self.db)
|
not_iterable_ty.display(self.db)
|
||||||
|
@ -169,7 +168,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"not-iterable",
|
DiagnosticId::lint("not-iterable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
|
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
|
||||||
element_ty.display(self.db)
|
element_ty.display(self.db)
|
||||||
|
@ -188,7 +187,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"index-out-of-bounds",
|
DiagnosticId::lint("index-out-of-bounds"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||||
tuple_ty.display(self.db)
|
tuple_ty.display(self.db)
|
||||||
|
@ -205,7 +204,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"non-subscriptable",
|
DiagnosticId::lint("non-subscriptable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||||
non_subscriptable_ty.display(self.db)
|
non_subscriptable_ty.display(self.db)
|
||||||
|
@ -221,7 +220,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
self.add(
|
self.add(
|
||||||
import_node.into(),
|
import_node.into(),
|
||||||
"unresolved-import",
|
DiagnosticId::lint("unresolved-import"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot resolve import `{}{}`",
|
"Cannot resolve import `{}{}`",
|
||||||
".".repeat(level as usize),
|
".".repeat(level as usize),
|
||||||
|
@ -233,7 +232,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"zero-stepsize-in-slice",
|
DiagnosticId::lint("zero-stepsize-in-slice"),
|
||||||
format_args!("Slice step size can not be zero"),
|
format_args!("Slice step size can not be zero"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -246,19 +245,19 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
match declared_ty {
|
match declared_ty {
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
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",
|
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||||
class.name(self.db)));
|
class.name(self.db)));
|
||||||
}
|
}
|
||||||
Type::FunctionLiteral(function) => {
|
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",
|
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||||
function.name(self.db)));
|
function.name(self.db)));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.add(
|
self.add(
|
||||||
node,
|
node,
|
||||||
"invalid-assignment",
|
DiagnosticId::lint("invalid-assignment"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not assignable to `{}`",
|
"Object of type `{}` is not assignable to `{}`",
|
||||||
assigned_ty.display(self.db),
|
assigned_ty.display(self.db),
|
||||||
|
@ -274,7 +273,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
|
|
||||||
self.add(
|
self.add(
|
||||||
expr_name_node.into(),
|
expr_name_node.into(),
|
||||||
"possibly-unresolved-reference",
|
DiagnosticId::lint("possibly-unresolved-reference"),
|
||||||
format_args!("Name `{id}` used when possibly not defined"),
|
format_args!("Name `{id}` used when possibly not defined"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +283,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
|
|
||||||
self.add(
|
self.add(
|
||||||
expr_name_node.into(),
|
expr_name_node.into(),
|
||||||
"unresolved-reference",
|
DiagnosticId::lint("unresolved-reference"),
|
||||||
format_args!("Name `{id}` used when not defined"),
|
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) {
|
pub(super) fn add_invalid_exception(&mut self, db: &dyn Db, node: &ast::Expr, ty: Type) {
|
||||||
self.add(
|
self.add(
|
||||||
node.into(),
|
node.into(),
|
||||||
"invalid-exception",
|
DiagnosticId::lint("invalid-exception"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot catch object of type `{}` in an exception handler \
|
"Cannot catch object of type `{}` in an exception handler \
|
||||||
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
|
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
|
||||||
|
@ -304,7 +303,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
/// Adds a new diagnostic.
|
/// Adds a new diagnostic.
|
||||||
///
|
///
|
||||||
/// The diagnostic does not get added if the rule isn't enabled for this file.
|
/// 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) {
|
if !self.db.is_file_open(self.file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +316,7 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||||
|
|
||||||
self.diagnostics.push(TypeCheckDiagnostic {
|
self.diagnostics.push(TypeCheckDiagnostic {
|
||||||
file: self.file,
|
file: self.file,
|
||||||
rule: rule.to_string(),
|
id,
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
range: node.range(),
|
range: node.range(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use ruff_db::diagnostic::DiagnosticId;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef, Expr, ExprContext, UnaryOp};
|
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) {
|
if class.is_cyclically_defined(self.db) {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
class_node.into(),
|
class_node.into(),
|
||||||
"cyclic-class-def",
|
DiagnosticId::lint("cyclic-class-def"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cyclic definition of `{}` or bases of `{}` (class cannot inherit from itself)",
|
"Cyclic definition of `{}` or bases of `{}` (class cannot inherit from itself)",
|
||||||
class.name(self.db),
|
class.name(self.db),
|
||||||
|
@ -547,7 +548,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
for (index, duplicate) in duplicates {
|
for (index, duplicate) in duplicates {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
(&base_nodes[*index]).into(),
|
(&base_nodes[*index]).into(),
|
||||||
"duplicate-base",
|
DiagnosticId::lint("duplicate-base"),
|
||||||
format_args!("Duplicate base class `{}`", duplicate.name(self.db)),
|
format_args!("Duplicate base class `{}`", duplicate.name(self.db)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -557,7 +558,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
for (index, base_ty) in bases {
|
for (index, base_ty) in bases {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
(&base_nodes[*index]).into(),
|
(&base_nodes[*index]).into(),
|
||||||
"invalid-base",
|
DiagnosticId::lint("invalid-base"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Invalid class base with type `{}` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
|
"Invalid class base with type `{}` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
|
||||||
base_ty.display(self.db)
|
base_ty.display(self.db)
|
||||||
|
@ -567,7 +568,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
MroErrorKind::UnresolvableMro { bases_list } => self.diagnostics.add(
|
MroErrorKind::UnresolvableMro { bases_list } => self.diagnostics.add(
|
||||||
class_node.into(),
|
class_node.into(),
|
||||||
"inconsistent-mro",
|
DiagnosticId::lint("inconsistent-mro"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot create a consistent method resolution order (MRO) for class `{}` with bases list `[{}]`",
|
"Cannot create a consistent method resolution order (MRO) for class `{}` with bases list `[{}]`",
|
||||||
class.name(self.db),
|
class.name(self.db),
|
||||||
|
@ -597,7 +598,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if *candidate1_is_base_class {
|
if *candidate1_is_base_class {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"conflicting-metaclass",
|
DiagnosticId::lint("conflicting-metaclass"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
|
"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}`) \
|
but `{metaclass1}` (metaclass of base class `{base1}`) and `{metaclass2}` (metaclass of base class `{base2}`) \
|
||||||
|
@ -612,7 +613,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
} else {
|
} else {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"conflicting-metaclass",
|
DiagnosticId::lint("conflicting-metaclass"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
|
"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}`) \
|
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(
|
self.diagnostics.add(
|
||||||
expr.into(),
|
expr.into(),
|
||||||
"division-by-zero",
|
DiagnosticId::lint("division-by-zero"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot {op} object of type `{}` {by_zero}",
|
"Cannot {op} object of type `{}` {by_zero}",
|
||||||
left.display(self.db)
|
left.display(self.db)
|
||||||
|
@ -786,7 +787,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let symbol_name = symbol_table.symbol(binding.symbol(self.db)).name();
|
let symbol_name = symbol_table.symbol(binding.symbol(self.db)).name();
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"conflicting-declarations",
|
DiagnosticId::lint("conflicting-declarations"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Conflicting declared types for `{symbol_name}`: {}",
|
"Conflicting declared types for `{symbol_name}`: {}",
|
||||||
conflicting.display(self.db)
|
conflicting.display(self.db)
|
||||||
|
@ -816,7 +817,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
} else {
|
} else {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
node,
|
node,
|
||||||
"invalid-declaration",
|
DiagnosticId::lint("invalid-declaration"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Cannot declare type `{}` for inferred type `{}`",
|
"Cannot declare type `{}` for inferred type `{}`",
|
||||||
ty.display(self.db),
|
ty.display(self.db),
|
||||||
|
@ -1113,7 +1114,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
} else {
|
} else {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
parameter_with_default.into(),
|
parameter_with_default.into(),
|
||||||
"invalid-parameter-default",
|
DiagnosticId::lint("invalid-parameter-default"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Default value of type `{}` is not assignable to annotated parameter type `{}`",
|
"Default value of type `{}` is not assignable to annotated parameter type `{}`",
|
||||||
default_ty.display(self.db), declared_ty.display(self.db))
|
default_ty.display(self.db), declared_ty.display(self.db))
|
||||||
|
@ -1425,7 +1426,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
(Symbol::Unbound, Symbol::Unbound) => {
|
(Symbol::Unbound, Symbol::Unbound) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__` and `__exit__`",
|
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__` and `__exit__`",
|
||||||
context_expression_ty.display(self.db)
|
context_expression_ty.display(self.db)
|
||||||
|
@ -1436,7 +1437,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
(Symbol::Unbound, _) => {
|
(Symbol::Unbound, _) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__`",
|
"Object of type `{}` cannot be used with `with` because it doesn't implement `__enter__`",
|
||||||
context_expression_ty.display(self.db)
|
context_expression_ty.display(self.db)
|
||||||
|
@ -1448,7 +1449,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if enter_boundness == Boundness::PossiblyUnbound {
|
if enter_boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__enter__` is possibly unbound",
|
"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),
|
context_expression = context_expression_ty.display(self.db),
|
||||||
|
@ -1462,7 +1463,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!("
|
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)
|
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 => {
|
Symbol::Unbound => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` cannot be used with `with` because it doesn't implement `__exit__`",
|
"Object of type `{}` cannot be used with `with` because it doesn't implement `__exit__`",
|
||||||
context_expression_ty.display(self.db)
|
context_expression_ty.display(self.db)
|
||||||
|
@ -1487,7 +1488,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if exit_boundness == Boundness::PossiblyUnbound {
|
if exit_boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__exit__` is possibly unbound",
|
"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),
|
context_expression = context_expression_ty.display(self.db),
|
||||||
|
@ -1514,7 +1515,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
{
|
{
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
DiagnosticId::lint("invalid-context-manager"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{context_expression}` cannot be used with `with` because the method `__exit__` of type `{exit_ty}` is not callable",
|
"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),
|
context_expression = context_expression_ty.display(self.db),
|
||||||
|
@ -1610,7 +1611,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if elts.len() < 2 {
|
if elts.len() < 2 {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
expr.into(),
|
expr.into(),
|
||||||
"invalid-typevar-constraints",
|
DiagnosticId::lint("invalid-typevar-constraints"),
|
||||||
format_args!("TypeVar must have at least two constrained types"),
|
format_args!("TypeVar must have at least two constrained types"),
|
||||||
);
|
);
|
||||||
self.infer_expression(expr);
|
self.infer_expression(expr);
|
||||||
|
@ -1933,7 +1934,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
assignment.into(),
|
assignment.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||||
target_type.display(self.db),
|
target_type.display(self.db),
|
||||||
|
@ -1954,7 +1955,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
assignment.into(),
|
assignment.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||||
left_ty.display(self.db),
|
left_ty.display(self.db),
|
||||||
|
@ -1983,7 +1984,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
assignment.into(),
|
assignment.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||||
left_ty.display(self.db),
|
left_ty.display(self.db),
|
||||||
|
@ -2236,7 +2237,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if boundness == Boundness::PossiblyUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
AnyNodeRef::Alias(alias),
|
AnyNodeRef::Alias(alias),
|
||||||
"possibly-unbound-import",
|
DiagnosticId::lint("possibly-unbound-import"),
|
||||||
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
|
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2246,7 +2247,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Symbol::Unbound => {
|
Symbol::Unbound => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
AnyNodeRef::Alias(alias),
|
AnyNodeRef::Alias(alias),
|
||||||
"unresolved-import",
|
DiagnosticId::lint("unresolved-import"),
|
||||||
format_args!("Module `{module_name}` has no member `{name}`",),
|
format_args!("Module `{module_name}` has no member `{name}`",),
|
||||||
);
|
);
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
|
@ -2953,7 +2954,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if builtins_symbol.is_unbound() && name == "reveal_type" {
|
if builtins_symbol.is_unbound() && name == "reveal_type" {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
name_node.into(),
|
name_node.into(),
|
||||||
"undefined-reveal",
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"`reveal_type` used without importing it; this is allowed for debugging convenience but will fail at runtime"),
|
"`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 {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
attribute.into(),
|
attribute.into(),
|
||||||
"possibly-unbound-attribute",
|
DiagnosticId::lint("possibly-unbound-attribute"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Attribute `{}` on type `{}` is possibly unbound",
|
"Attribute `{}` on type `{}` is possibly unbound",
|
||||||
attr.id,
|
attr.id,
|
||||||
|
@ -3064,7 +3065,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Symbol::Unbound => {
|
Symbol::Unbound => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
attribute.into(),
|
attribute.into(),
|
||||||
"unresolved-attribute",
|
DiagnosticId::lint("unresolved-attribute"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Type `{}` has no attribute `{}`",
|
"Type `{}` has no attribute `{}`",
|
||||||
value_ty.display(self.db),
|
value_ty.display(self.db),
|
||||||
|
@ -3145,7 +3146,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
unary.into(),
|
unary.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Unary operator `{op}` is unsupported for type `{}`",
|
"Unary operator `{op}` is unsupported for type `{}`",
|
||||||
operand_type.display(self.db),
|
operand_type.display(self.db),
|
||||||
|
@ -3157,7 +3158,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
} else {
|
} else {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
unary.into(),
|
unary.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Unary operator `{op}` is unsupported for type `{}`",
|
"Unary operator `{op}` is unsupported for type `{}`",
|
||||||
operand_type.display(self.db),
|
operand_type.display(self.db),
|
||||||
|
@ -3198,7 +3199,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
binary.into(),
|
binary.into(),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Operator `{op}` is unsupported between objects of type `{}` and `{}`",
|
"Operator `{op}` is unsupported between objects of type `{}` and `{}`",
|
||||||
left_ty.display(self.db),
|
left_ty.display(self.db),
|
||||||
|
@ -3508,7 +3509,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
|
// Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
AnyNodeRef::ExprCompare(compare),
|
AnyNodeRef::ExprCompare(compare),
|
||||||
"unsupported-operator",
|
DiagnosticId::lint("unsupported-operator"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Operator `{}` is not supported for types `{}` and `{}`{}",
|
"Operator `{}` is not supported for types `{}` and `{}`{}",
|
||||||
error.op,
|
error.op,
|
||||||
|
@ -4165,7 +4166,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if boundness == Boundness::PossiblyUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-possibly-unbound-method",
|
DiagnosticId::lint("call-possibly-unbound-method"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__getitem__` of type `{}` is possibly unbound",
|
"Method `__getitem__` of type `{}` is possibly unbound",
|
||||||
value_ty.display(self.db),
|
value_ty.display(self.db),
|
||||||
|
@ -4179,7 +4180,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__getitem__` of type `{}` is not callable on object of type `{}`",
|
"Method `__getitem__` of type `{}` is not callable on object of type `{}`",
|
||||||
err.called_ty().display(self.db),
|
err.called_ty().display(self.db),
|
||||||
|
@ -4209,7 +4210,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
if boundness == Boundness::PossiblyUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-possibly-unbound-method",
|
DiagnosticId::lint("call-possibly-unbound-method"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__class_getitem__` of type `{}` is possibly unbound",
|
"Method `__class_getitem__` of type `{}` is possibly unbound",
|
||||||
value_ty.display(self.db),
|
value_ty.display(self.db),
|
||||||
|
@ -4223,7 +4224,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-non-callable",
|
DiagnosticId::lint("call-non-callable"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
|
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
|
||||||
err.called_ty().display(self.db),
|
err.called_ty().display(self.db),
|
||||||
|
@ -4365,7 +4366,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
ast::Expr::BytesLiteral(bytes) => {
|
ast::Expr::BytesLiteral(bytes) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
bytes.into(),
|
bytes.into(),
|
||||||
"annotation-byte-string",
|
DiagnosticId::lint("annotation-byte-string"),
|
||||||
format_args!("Type expressions cannot use bytes literal"),
|
format_args!("Type expressions cannot use bytes literal"),
|
||||||
);
|
);
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
|
@ -4374,7 +4375,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
ast::Expr::FString(fstring) => {
|
ast::Expr::FString(fstring) => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
fstring.into(),
|
fstring.into(),
|
||||||
"annotation-f-string",
|
DiagnosticId::lint("annotation-f-string"),
|
||||||
format_args!("Type expressions cannot use f-strings"),
|
format_args!("Type expressions cannot use f-strings"),
|
||||||
);
|
);
|
||||||
self.infer_fstring_expression(fstring);
|
self.infer_fstring_expression(fstring);
|
||||||
|
@ -4718,7 +4719,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
self.infer_type_expression(slice);
|
self.infer_type_expression(slice);
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
slice.into(),
|
slice.into(),
|
||||||
"invalid-type-form",
|
DiagnosticId::lint("invalid-type-form"),
|
||||||
format_args!("type[...] must have exactly one type argument"),
|
format_args!("type[...] must have exactly one type argument"),
|
||||||
);
|
);
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
|
@ -4794,7 +4795,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
node.into(),
|
node.into(),
|
||||||
"invalid-literal-parameter",
|
DiagnosticId::lint("invalid-literal-parameter"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Type arguments for `Literal` must be `None`, \
|
"Type arguments for `Literal` must be `None`, \
|
||||||
a literal value (int, bool, str, or bytes), or an enum value"
|
a literal value (int, bool, str, or bytes), or an enum value"
|
||||||
|
@ -4830,7 +4831,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
|
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
subscript.into(),
|
subscript.into(),
|
||||||
"invalid-type-parameter",
|
DiagnosticId::lint("invalid-type-parameter"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Type `{}` expected no type parameter",
|
"Type `{}` expected no type parameter",
|
||||||
known_instance.repr(self.db)
|
known_instance.repr(self.db)
|
||||||
|
@ -4841,7 +4842,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
KnownInstanceType::LiteralString => {
|
KnownInstanceType::LiteralString => {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
subscript.into(),
|
subscript.into(),
|
||||||
"invalid-type-parameter",
|
DiagnosticId::lint("invalid-type-parameter"),
|
||||||
format_args!(
|
format_args!(
|
||||||
"Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?",
|
"Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?",
|
||||||
known_instance.repr(self.db)
|
known_instance.repr(self.db)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use ruff_db::diagnostic::DiagnosticId;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::source::source_text;
|
use ruff_db::source::source_text;
|
||||||
use ruff_python_ast::str::raw_contents;
|
use ruff_python_ast::str::raw_contents;
|
||||||
|
@ -27,7 +28,7 @@ pub(crate) fn parse_string_annotation(
|
||||||
if prefix.is_raw() {
|
if prefix.is_raw() {
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
string_literal.into(),
|
string_literal.into(),
|
||||||
"annotation-raw-string",
|
DiagnosticId::lint("annotation-raw-string"),
|
||||||
format_args!("Type expressions cannot use raw string literal"),
|
format_args!("Type expressions cannot use raw string literal"),
|
||||||
);
|
);
|
||||||
// Compare the raw contents (without quotes) of the expression with the parsed contents
|
// 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),
|
Ok(parsed) => return Ok(parsed),
|
||||||
Err(parse_error) => diagnostics.add(
|
Err(parse_error) => diagnostics.add(
|
||||||
string_literal.into(),
|
string_literal.into(),
|
||||||
"forward-annotation-syntax-error",
|
DiagnosticId::lint("forward-annotation-syntax-error"),
|
||||||
format_args!("Syntax error in forward annotation: {}", parse_error.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.
|
// case for annotations that contain escape sequences.
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
string_expr.into(),
|
string_expr.into(),
|
||||||
"annotation-escape-character",
|
DiagnosticId::lint("annotation-escape-character"),
|
||||||
format_args!("Type expressions cannot contain escape characters"),
|
format_args!("Type expressions cannot contain escape characters"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -68,7 +69,7 @@ pub(crate) fn parse_string_annotation(
|
||||||
// String is implicitly concatenated.
|
// String is implicitly concatenated.
|
||||||
diagnostics.add(
|
diagnostics.add(
|
||||||
string_expr.into(),
|
string_expr.into(),
|
||||||
"annotation-implicit-concat",
|
DiagnosticId::lint("annotation-implicit-concat"),
|
||||||
format_args!("Type expressions cannot span multiple string literals"),
|
format_args!("Type expressions cannot span multiple string literals"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,14 +86,15 @@ fn to_lsp_diagnostic(
|
||||||
|
|
||||||
let severity = match diagnostic.severity() {
|
let severity = match diagnostic.severity() {
|
||||||
Severity::Info => DiagnosticSeverity::INFORMATION,
|
Severity::Info => DiagnosticSeverity::INFORMATION,
|
||||||
Severity::Error => DiagnosticSeverity::ERROR,
|
Severity::Warning => DiagnosticSeverity::WARNING,
|
||||||
|
Severity::Error | Severity::Fatal => DiagnosticSeverity::ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
range,
|
range,
|
||||||
severity: Some(severity),
|
severity: Some(severity),
|
||||||
tags: None,
|
tags: None,
|
||||||
code: Some(NumberOrString::String(diagnostic.rule().to_string())),
|
code: Some(NumberOrString::String(diagnostic.id().to_string())),
|
||||||
code_description: None,
|
code_description: None,
|
||||||
source: Some("red-knot".into()),
|
source: Some("red-knot".into()),
|
||||||
message: diagnostic.message().into_owned(),
|
message: diagnostic.message().into_owned(),
|
||||||
|
|
|
@ -144,7 +144,7 @@ struct DiagnosticWithLine<T> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::diagnostic::Diagnostic;
|
use crate::diagnostic::Diagnostic;
|
||||||
use ruff_db::diagnostic::Severity;
|
use ruff_db::diagnostic::{DiagnosticId, LintName, Severity};
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::source::line_index;
|
use ruff_db::source::line_index;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
@ -190,8 +190,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for DummyDiagnostic {
|
impl Diagnostic for DummyDiagnostic {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
"dummy"
|
DiagnosticId::Lint(LintName::of("dummy"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::assertion::{Assertion, ErrorAssertion, InlineFileAssertions};
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::diagnostic::SortedDiagnostics;
|
use crate::diagnostic::SortedDiagnostics;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use ruff_db::diagnostic::Diagnostic;
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticId};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::source::{line_index, source_text, SourceText};
|
use ruff_db::source::{line_index, source_text, SourceText};
|
||||||
use ruff_source_file::{LineIndex, OneIndexed};
|
use ruff_source_file::{LineIndex, OneIndexed};
|
||||||
|
@ -146,7 +146,7 @@ fn maybe_add_undefined_reveal_clarification<T: Diagnostic>(
|
||||||
diagnostic: &T,
|
diagnostic: &T,
|
||||||
original: std::fmt::Arguments,
|
original: std::fmt::Arguments,
|
||||||
) -> String {
|
) -> String {
|
||||||
if diagnostic.rule() == "undefined-reveal" {
|
if diagnostic.id().is_lint_named("undefined-reveal") {
|
||||||
format!(
|
format!(
|
||||||
"{} add a `# revealed` assertion on this line (original diagnostic: {original})",
|
"{} add a `# revealed` assertion on this line (original diagnostic: {original})",
|
||||||
"used built-in `reveal_type`:".yellow()
|
"used built-in `reveal_type`:".yellow()
|
||||||
|
@ -163,7 +163,7 @@ where
|
||||||
fn unmatched(&self) -> String {
|
fn unmatched(&self) -> String {
|
||||||
maybe_add_undefined_reveal_clarification(
|
maybe_add_undefined_reveal_clarification(
|
||||||
self,
|
self,
|
||||||
format_args!(r#"[{}] "{}""#, self.rule(), self.message()),
|
format_args!(r#"[{}] "{}""#, self.id(), self.message()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ where
|
||||||
fn unmatched_with_column(&self, column: OneIndexed) -> String {
|
fn unmatched_with_column(&self, column: OneIndexed) -> String {
|
||||||
maybe_add_undefined_reveal_clarification(
|
maybe_add_undefined_reveal_clarification(
|
||||||
self,
|
self,
|
||||||
format_args!(r#"{column} [{}] "{}""#, self.rule(), self.message()),
|
format_args!(r#"{column} [{}] "{}""#, self.id(), self.message()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,10 +270,11 @@ impl Matcher {
|
||||||
match assertion {
|
match assertion {
|
||||||
Assertion::Error(error) => {
|
Assertion::Error(error) => {
|
||||||
let position = unmatched.iter().position(|diagnostic| {
|
let position = unmatched.iter().position(|diagnostic| {
|
||||||
!error.rule.is_some_and(|rule| rule != diagnostic.rule())
|
!error.rule.is_some_and(|rule| {
|
||||||
&& !error
|
!(diagnostic.id().is_lint_named(rule) || diagnostic.id().matches(rule))
|
||||||
.column
|
}) && !error
|
||||||
.is_some_and(|col| col != self.column(*diagnostic))
|
.column
|
||||||
|
.is_some_and(|col| col != self.column(*diagnostic))
|
||||||
&& !error
|
&& !error
|
||||||
.message_contains
|
.message_contains
|
||||||
.is_some_and(|needle| !diagnostic.message().contains(needle))
|
.is_some_and(|needle| !diagnostic.message().contains(needle))
|
||||||
|
@ -294,12 +295,12 @@ impl Matcher {
|
||||||
let expected_reveal_type_message = format!("Revealed type is `{expected_type}`");
|
let expected_reveal_type_message = format!("Revealed type is `{expected_type}`");
|
||||||
for (index, diagnostic) in unmatched.iter().enumerate() {
|
for (index, diagnostic) in unmatched.iter().enumerate() {
|
||||||
if matched_revealed_type.is_none()
|
if matched_revealed_type.is_none()
|
||||||
&& diagnostic.rule() == "revealed-type"
|
&& diagnostic.id() == DiagnosticId::RevealedType
|
||||||
&& diagnostic.message() == expected_reveal_type_message
|
&& diagnostic.message() == expected_reveal_type_message
|
||||||
{
|
{
|
||||||
matched_revealed_type = Some(index);
|
matched_revealed_type = Some(index);
|
||||||
} else if matched_undefined_reveal.is_none()
|
} else if matched_undefined_reveal.is_none()
|
||||||
&& diagnostic.rule() == "undefined-reveal"
|
&& diagnostic.id().is_lint_named("undefined-reveal")
|
||||||
{
|
{
|
||||||
matched_undefined_reveal = Some(index);
|
matched_undefined_reveal = Some(index);
|
||||||
}
|
}
|
||||||
|
@ -323,7 +324,7 @@ impl Matcher {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FailuresByLine;
|
use super::FailuresByLine;
|
||||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
use ruff_python_trivia::textwrap::dedent;
|
use ruff_python_trivia::textwrap::dedent;
|
||||||
|
@ -332,16 +333,16 @@ mod tests {
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
struct ExpectedDiagnostic {
|
struct ExpectedDiagnostic {
|
||||||
rule: &'static str,
|
id: DiagnosticId,
|
||||||
message: &'static str,
|
message: &'static str,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExpectedDiagnostic {
|
impl ExpectedDiagnostic {
|
||||||
fn new(rule: &'static str, message: &'static str, offset: usize) -> Self {
|
fn new(id: DiagnosticId, message: &'static str, offset: usize) -> Self {
|
||||||
let offset: u32 = offset.try_into().unwrap();
|
let offset: u32 = offset.try_into().unwrap();
|
||||||
Self {
|
Self {
|
||||||
rule,
|
id,
|
||||||
message,
|
message,
|
||||||
range: TextRange::new(offset.into(), (offset + 1).into()),
|
range: TextRange::new(offset.into(), (offset + 1).into()),
|
||||||
}
|
}
|
||||||
|
@ -349,7 +350,7 @@ mod tests {
|
||||||
|
|
||||||
fn into_diagnostic(self, file: File) -> TestDiagnostic {
|
fn into_diagnostic(self, file: File) -> TestDiagnostic {
|
||||||
TestDiagnostic {
|
TestDiagnostic {
|
||||||
rule: self.rule,
|
id: self.id,
|
||||||
message: self.message,
|
message: self.message,
|
||||||
range: self.range,
|
range: self.range,
|
||||||
file,
|
file,
|
||||||
|
@ -359,15 +360,15 @@ mod tests {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestDiagnostic {
|
struct TestDiagnostic {
|
||||||
rule: &'static str,
|
id: DiagnosticId,
|
||||||
message: &'static str,
|
message: &'static str,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for TestDiagnostic {
|
impl Diagnostic for TestDiagnostic {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
self.rule
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
@ -437,7 +438,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"revealed-type",
|
DiagnosticId::RevealedType,
|
||||||
"Revealed type is `Foo`",
|
"Revealed type is `Foo`",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -451,7 +452,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"not-revealed-type",
|
DiagnosticId::lint("not-revealed-type"),
|
||||||
"Revealed type is `Foo`",
|
"Revealed type is `Foo`",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -463,7 +464,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"unmatched assertion: revealed: Foo",
|
"unmatched assertion: revealed: Foo",
|
||||||
r#"unexpected error: 1 [not-revealed-type] "Revealed type is `Foo`""#,
|
r#"unexpected error: 1 [lint:not-revealed-type] "Revealed type is `Foo`""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -474,7 +475,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"revealed-type",
|
DiagnosticId::RevealedType,
|
||||||
"Something else",
|
"Something else",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -504,8 +505,12 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Foo`", 0),
|
ExpectedDiagnostic::new(DiagnosticId::RevealedType, "Revealed type is `Foo`", 0),
|
||||||
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
|
"Doesn't matter",
|
||||||
|
0,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -517,7 +522,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"undefined-reveal",
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
"Doesn't matter",
|
"Doesn't matter",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -531,8 +536,12 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # revealed: Foo",
|
"x # revealed: Foo",
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Bar`", 0),
|
ExpectedDiagnostic::new(DiagnosticId::RevealedType, "Revealed type is `Bar`", 0),
|
||||||
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
|
"Doesn't matter",
|
||||||
|
0,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -553,8 +562,16 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"reveal_type(1)",
|
"reveal_type(1)",
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
ExpectedDiagnostic::new(
|
||||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
|
"undefined reveal message",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::RevealedType,
|
||||||
|
"Revealed type is `Literal[1]`",
|
||||||
|
12,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -564,7 +581,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
||||||
original diagnostic: [undefined-reveal] \"undefined reveal message\")",
|
original diagnostic: [lint:undefined-reveal] \"undefined reveal message\")",
|
||||||
r#"unexpected error: [revealed-type] "Revealed type is `Literal[1]`""#,
|
r#"unexpected error: [revealed-type] "Revealed type is `Literal[1]`""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
|
@ -576,8 +593,16 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"reveal_type(1) # error: [something-else]",
|
"reveal_type(1) # error: [something-else]",
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
ExpectedDiagnostic::new(
|
||||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
DiagnosticId::lint("undefined-reveal"),
|
||||||
|
"undefined reveal message",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::RevealedType,
|
||||||
|
"Revealed type is `Literal[1]`",
|
||||||
|
12,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -588,7 +613,7 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
"unmatched assertion: error: [something-else]",
|
"unmatched assertion: error: [something-else]",
|
||||||
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
||||||
original diagnostic: 1 [undefined-reveal] \"undefined reveal message\")",
|
original diagnostic: 1 [lint:undefined-reveal] \"undefined reveal message\")",
|
||||||
r#"unexpected error: 13 [revealed-type] "Revealed type is `Literal[1]`""#,
|
r#"unexpected error: 13 [revealed-type] "Revealed type is `Literal[1]`""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
|
@ -606,7 +631,11 @@ mod tests {
|
||||||
fn error_match_rule() {
|
fn error_match_rule() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # error: [some-rule]",
|
"x # error: [some-rule]",
|
||||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("some-rule"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ok(&result);
|
assert_ok(&result);
|
||||||
|
@ -616,7 +645,11 @@ mod tests {
|
||||||
fn error_wrong_rule() {
|
fn error_wrong_rule() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # error: [some-rule]",
|
"x # error: [some-rule]",
|
||||||
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("anything"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -625,7 +658,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"unmatched assertion: error: [some-rule]",
|
"unmatched assertion: error: [some-rule]",
|
||||||
r#"unexpected error: 1 [anything] "Any message""#,
|
r#"unexpected error: 1 [lint:anything] "Any message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -636,7 +669,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: "contains this""#,
|
r#"x # error: "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"anything",
|
DiagnosticId::lint("anything"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -649,7 +682,11 @@ mod tests {
|
||||||
fn error_wrong_message() {
|
fn error_wrong_message() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: "contains this""#,
|
r#"x # error: "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("anything"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -658,7 +695,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
r#"unmatched assertion: error: "contains this""#,
|
r#"unmatched assertion: error: "contains this""#,
|
||||||
r#"unexpected error: 1 [anything] "Any message""#,
|
r#"unexpected error: 1 [lint:anything] "Any message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -668,7 +705,11 @@ mod tests {
|
||||||
fn error_match_column_and_rule() {
|
fn error_match_column_and_rule() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # error: 1 [some-rule]",
|
"x # error: 1 [some-rule]",
|
||||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("some-rule"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ok(&result);
|
assert_ok(&result);
|
||||||
|
@ -678,7 +719,11 @@ mod tests {
|
||||||
fn error_wrong_column() {
|
fn error_wrong_column() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
"x # error: 2 [rule]",
|
"x # error: 2 [rule]",
|
||||||
vec![ExpectedDiagnostic::new("rule", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("rule"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -687,7 +732,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"unmatched assertion: error: 2 [rule]",
|
"unmatched assertion: error: 2 [rule]",
|
||||||
r#"unexpected error: 1 [rule] "Any message""#,
|
r#"unexpected error: 1 [lint:rule] "Any message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -698,7 +743,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: 1 "contains this""#,
|
r#"x # error: 1 "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"anything",
|
DiagnosticId::lint("anything"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -712,7 +757,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: [a-rule] "contains this""#,
|
r#"x # error: [a-rule] "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"a-rule",
|
DiagnosticId::lint("a-rule"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -726,7 +771,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: 1 [a-rule] "contains this""#,
|
r#"x # error: 1 [a-rule] "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"a-rule",
|
DiagnosticId::lint("a-rule"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -740,7 +785,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: 2 [some-rule] "contains this""#,
|
r#"x # error: 2 [some-rule] "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"some-rule",
|
DiagnosticId::lint("some-rule"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -752,7 +797,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
r#"unmatched assertion: error: 2 [some-rule] "contains this""#,
|
r#"unmatched assertion: error: 2 [some-rule] "contains this""#,
|
||||||
r#"unexpected error: 1 [some-rule] "message contains this""#,
|
r#"unexpected error: 1 [lint:some-rule] "message contains this""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -763,7 +808,7 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: 1 [some-rule] "contains this""#,
|
r#"x # error: 1 [some-rule] "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new(
|
vec![ExpectedDiagnostic::new(
|
||||||
"other-rule",
|
DiagnosticId::lint("other-rule"),
|
||||||
"message contains this",
|
"message contains this",
|
||||||
0,
|
0,
|
||||||
)],
|
)],
|
||||||
|
@ -775,7 +820,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
||||||
r#"unexpected error: 1 [other-rule] "message contains this""#,
|
r#"unexpected error: 1 [lint:other-rule] "message contains this""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -785,7 +830,11 @@ mod tests {
|
||||||
fn error_match_all_wrong_message() {
|
fn error_match_all_wrong_message() {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
r#"x # error: 1 [some-rule] "contains this""#,
|
r#"x # error: 1 [some-rule] "contains this""#,
|
||||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("some-rule"),
|
||||||
|
"Any message",
|
||||||
|
0,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -794,7 +843,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
||||||
r#"unexpected error: 1 [some-rule] "Any message""#,
|
r#"unexpected error: 1 [lint:some-rule] "Any message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -818,9 +867,9 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("line-two", "msg", two),
|
ExpectedDiagnostic::new(DiagnosticId::lint("line-two"), "msg", two),
|
||||||
ExpectedDiagnostic::new("line-three", "msg", three),
|
ExpectedDiagnostic::new(DiagnosticId::lint("line-three"), "msg", three),
|
||||||
ExpectedDiagnostic::new("line-five", "msg", five),
|
ExpectedDiagnostic::new(DiagnosticId::lint("line-five"), "msg", five),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -828,9 +877,9 @@ mod tests {
|
||||||
result,
|
result,
|
||||||
&[
|
&[
|
||||||
(1, &["unmatched assertion: error: [line-one]"]),
|
(1, &["unmatched assertion: error: [line-one]"]),
|
||||||
(2, &[r#"unexpected error: [line-two] "msg""#]),
|
(2, &[r#"unexpected error: [lint:line-two] "msg""#]),
|
||||||
(4, &["unmatched assertion: error: [line-four]"]),
|
(4, &["unmatched assertion: error: [line-four]"]),
|
||||||
(5, &[r#"unexpected error: [line-five] "msg""#]),
|
(5, &[r#"unexpected error: [lint:line-five] "msg""#]),
|
||||||
(6, &["unmatched assertion: error: [line-six]"]),
|
(6, &["unmatched assertion: error: [line-six]"]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -849,12 +898,15 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("line-one", "msg", one),
|
ExpectedDiagnostic::new(DiagnosticId::lint("line-one"), "msg", one),
|
||||||
ExpectedDiagnostic::new("line-two", "msg", two),
|
ExpectedDiagnostic::new(DiagnosticId::lint("line-two"), "msg", two),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(result, &[(2, &[r#"unexpected error: [line-two] "msg""#])]);
|
assert_fail(
|
||||||
|
result,
|
||||||
|
&[(2, &[r#"unexpected error: [lint:line-two] "msg""#])],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -870,8 +922,8 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("one-rule"), "msg", x),
|
||||||
ExpectedDiagnostic::new("other-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("other-rule"), "msg", x),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -891,8 +943,8 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("one-rule"), "msg", x),
|
||||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("one-rule"), "msg", x),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -912,15 +964,15 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("one-rule"), "msg", x),
|
||||||
ExpectedDiagnostic::new("other-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("other-rule"), "msg", x),
|
||||||
ExpectedDiagnostic::new("third-rule", "msg", x),
|
ExpectedDiagnostic::new(DiagnosticId::lint("third-rule"), "msg", x),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
result,
|
result,
|
||||||
&[(3, &[r#"unexpected error: 1 [third-rule] "msg""#])],
|
&[(3, &[r#"unexpected error: 1 [lint:third-rule] "msg""#])],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,8 +990,12 @@ mod tests {
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
&source,
|
&source,
|
||||||
vec![
|
vec![
|
||||||
ExpectedDiagnostic::new("undefined-reveal", "msg", reveal),
|
ExpectedDiagnostic::new(DiagnosticId::lint("undefined-reveal"), "msg", reveal),
|
||||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[5]`", reveal),
|
ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::RevealedType,
|
||||||
|
"Revealed type is `Literal[5]`",
|
||||||
|
reveal,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -952,7 +1008,11 @@ mod tests {
|
||||||
let x = source.find('x').unwrap();
|
let x = source.find('x').unwrap();
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
source,
|
source,
|
||||||
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("some-rule"),
|
||||||
|
"some message",
|
||||||
|
x,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -961,7 +1021,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"invalid assertion: no rule or message text",
|
"invalid assertion: no rule or message text",
|
||||||
r#"unexpected error: 1 [some-rule] "some message""#,
|
r#"unexpected error: 1 [lint:some-rule] "some message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -973,7 +1033,11 @@ mod tests {
|
||||||
let x = source.find('x').unwrap();
|
let x = source.find('x').unwrap();
|
||||||
let result = get_result(
|
let result = get_result(
|
||||||
source,
|
source,
|
||||||
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
|
vec![ExpectedDiagnostic::new(
|
||||||
|
DiagnosticId::lint("some-rule"),
|
||||||
|
"some message",
|
||||||
|
x,
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_fail(
|
assert_fail(
|
||||||
|
@ -982,7 +1046,7 @@ mod tests {
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
"invalid assertion: no rule or message text",
|
"invalid assertion: no rule or message text",
|
||||||
r#"unexpected error: 1 [some-rule] "some message""#,
|
r#"unexpected error: 1 [lint:some-rule] "some message""#,
|
||||||
],
|
],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,6 +19,6 @@ fn check() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
vec!["error[unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
|
vec!["error[lint:unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
|
||||||
pub use metadata::{PackageMetadata, WorkspaceDiscoveryError, WorkspaceMetadata};
|
pub use metadata::{PackageMetadata, WorkspaceDiscoveryError, WorkspaceMetadata};
|
||||||
use red_knot_python_semantic::types::check_types;
|
use red_knot_python_semantic::types::check_types;
|
||||||
use red_knot_python_semantic::SearchPathSettings;
|
use red_knot_python_semantic::SearchPathSettings;
|
||||||
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic, Severity};
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, ParseDiagnostic, Severity};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::source::{source_text, SourceTextError};
|
use ruff_db::source::{source_text, SourceTextError};
|
||||||
use ruff_db::system::FileType;
|
use ruff_db::system::FileType;
|
||||||
|
@ -533,8 +533,8 @@ pub struct IOErrorDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for IOErrorDiagnostic {
|
impl Diagnostic for IOErrorDiagnostic {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
"io"
|
DiagnosticId::Io
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
|
|
@ -25,30 +25,30 @@ const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/
|
||||||
|
|
||||||
static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||||
// We don't support `*` imports yet:
|
// We don't support `*` imports yet:
|
||||||
"error[unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
|
"error[lint:unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
|
||||||
// We don't support terminal statements in control flow yet:
|
// We don't support terminal statements in control flow yet:
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:566:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:566:12 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
||||||
"error[conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
|
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
"error[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn get_test_file(name: &str) -> TestFile {
|
fn get_test_file(name: &str) -> TestFile {
|
||||||
|
|
|
@ -1,16 +1,146 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use ruff_python_parser::ParseError;
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
files::File,
|
files::File,
|
||||||
source::{line_index, source_text},
|
source::{line_index, source_text},
|
||||||
Db,
|
Db,
|
||||||
};
|
};
|
||||||
use ruff_python_parser::ParseError;
|
|
||||||
use ruff_text_size::TextRange;
|
/// A string identifier for a lint rule.
|
||||||
use std::borrow::Cow;
|
///
|
||||||
|
/// This string is used in command line and configuration interfaces. The name should always
|
||||||
|
/// be in kebab case, e.g. `no-foo` (all lower case).
|
||||||
|
///
|
||||||
|
/// Rules use kebab case, e.g. `no-foo`.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LintName(&'static str);
|
||||||
|
|
||||||
|
impl LintName {
|
||||||
|
pub const fn of(name: &'static str) -> Self {
|
||||||
|
Self(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn as_str(&self) -> &'static str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for LintName {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for LintName {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for LintName {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.0 == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for LintName {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
self.0 == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uniquely identifies the kind of a diagnostic.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum DiagnosticId {
|
||||||
|
/// Some I/O operation failed
|
||||||
|
Io,
|
||||||
|
|
||||||
|
/// Some code contains a syntax error
|
||||||
|
InvalidSyntax,
|
||||||
|
|
||||||
|
/// A lint violation.
|
||||||
|
///
|
||||||
|
/// Lints can be suppressed and some lints can be enabled or disabled in the configuration.
|
||||||
|
Lint(LintName),
|
||||||
|
|
||||||
|
/// A revealed type: Created by `reveal_type(expression)`.
|
||||||
|
RevealedType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiagnosticId {
|
||||||
|
/// Creates a new `DiagnosticId` for a lint with the given name.
|
||||||
|
pub const fn lint(name: &'static str) -> Self {
|
||||||
|
Self::Lint(LintName::of(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this `DiagnosticId` represents a lint.
|
||||||
|
pub fn is_lint(&self) -> bool {
|
||||||
|
matches!(self, DiagnosticId::Lint(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this `DiagnosticId` represents a lint with the given name.
|
||||||
|
pub fn is_lint_named(&self, name: &str) -> bool {
|
||||||
|
matches!(self, DiagnosticId::Lint(self_name) if self_name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, name: &str) -> bool {
|
||||||
|
match self.as_str() {
|
||||||
|
Ok(id) => id == name,
|
||||||
|
Err(DiagnosticAsStrError::Category { category, name }) => name
|
||||||
|
.strip_prefix(category)
|
||||||
|
.and_then(|prefix| prefix.strip_prefix(":"))
|
||||||
|
.is_some_and(|rest| rest == name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> {
|
||||||
|
match self {
|
||||||
|
DiagnosticId::Io => Ok("io"),
|
||||||
|
DiagnosticId::InvalidSyntax => Ok("invalid-syntax"),
|
||||||
|
DiagnosticId::Lint(name) => Err(DiagnosticAsStrError::Category {
|
||||||
|
category: "lint",
|
||||||
|
name: name.as_str(),
|
||||||
|
}),
|
||||||
|
DiagnosticId::RevealedType => Ok("revealed-type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||||
|
pub enum DiagnosticAsStrError {
|
||||||
|
/// The id can't be converted to a string because it belongs to a sub-category.
|
||||||
|
#[error("id from a sub-category: {category}:{name}")]
|
||||||
|
Category {
|
||||||
|
/// The id's category.
|
||||||
|
category: &'static str,
|
||||||
|
/// The diagnostic id in this category.
|
||||||
|
name: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DiagnosticId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.as_str() {
|
||||||
|
Ok(name) => f.write_str(name),
|
||||||
|
Err(DiagnosticAsStrError::Category { category, name }) => {
|
||||||
|
write!(f, "{category}:{name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
||||||
fn rule(&self) -> &str;
|
fn id(&self) -> DiagnosticId;
|
||||||
|
|
||||||
fn message(&self) -> std::borrow::Cow<str>;
|
fn message(&self) -> Cow<str>;
|
||||||
|
|
||||||
fn file(&self) -> File;
|
fn file(&self) -> File;
|
||||||
|
|
||||||
|
@ -29,10 +159,12 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
Info,
|
Info,
|
||||||
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
|
Fatal,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DisplayDiagnostic<'db> {
|
pub struct DisplayDiagnostic<'db> {
|
||||||
|
@ -50,13 +182,15 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.diagnostic.severity() {
|
match self.diagnostic.severity() {
|
||||||
Severity::Info => f.write_str("info")?,
|
Severity::Info => f.write_str("info")?,
|
||||||
|
Severity::Warning => f.write_str("warning")?,
|
||||||
Severity::Error => f.write_str("error")?,
|
Severity::Error => f.write_str("error")?,
|
||||||
|
Severity::Fatal => f.write_str("fatal")?,
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"[{rule}] {path}",
|
"[{rule}] {path}",
|
||||||
rule = self.diagnostic.rule(),
|
rule = self.diagnostic.id(),
|
||||||
path = self.diagnostic.file().path(self.db)
|
path = self.diagnostic.file().path(self.db)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -77,8 +211,8 @@ impl<T> Diagnostic for Box<T>
|
||||||
where
|
where
|
||||||
T: Diagnostic,
|
T: Diagnostic,
|
||||||
{
|
{
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
(**self).rule()
|
(**self).id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
@ -102,8 +236,8 @@ impl<T> Diagnostic for std::sync::Arc<T>
|
||||||
where
|
where
|
||||||
T: Diagnostic,
|
T: Diagnostic,
|
||||||
{
|
{
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
(**self).rule()
|
(**self).id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> std::borrow::Cow<str> {
|
fn message(&self) -> std::borrow::Cow<str> {
|
||||||
|
@ -124,8 +258,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for Box<dyn Diagnostic> {
|
impl Diagnostic for Box<dyn Diagnostic> {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
(**self).rule()
|
(**self).id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
@ -158,8 +292,8 @@ impl ParseDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for ParseDiagnostic {
|
impl Diagnostic for ParseDiagnostic {
|
||||||
fn rule(&self) -> &str {
|
fn id(&self) -> DiagnosticId {
|
||||||
"invalid-syntax"
|
DiagnosticId::InvalidSyntax
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self) -> Cow<str> {
|
fn message(&self) -> Cow<str> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue