Use accumulator for diagnostics
This commit is contained in:
@@ -145,13 +145,8 @@ reveal_type(f) # revealed: Unknown
|
||||
|
||||
### Non-iterable unpacking
|
||||
|
||||
TODO: Remove duplicate diagnostics. This is happening because for a sequence-like assignment target,
|
||||
multiple definitions are created and the inference engine runs on each of them which results in
|
||||
duplicate diagnostics.
|
||||
|
||||
```py
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
a, b = 1
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use diagnostic::{is_any_diagnostic_enabled, report_not_iterable, report_type_diagnostic};
|
||||
use mro::{ClassBase, Mro, MroError, MroIterator};
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{Ranged as _, TextRange};
|
||||
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
@@ -15,12 +18,11 @@ use crate::stdlib::{
|
||||
builtins_symbol, types_symbol, typeshed_symbol, typing_extensions_symbol, typing_symbol,
|
||||
};
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
use crate::{Db, FxOrderSet, HasTy, Module, SemanticModel};
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub use self::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
pub use self::diagnostic::TypeCheckDiagnostic;
|
||||
pub(crate) use self::display::TypeArrayDisplay;
|
||||
pub(crate) use self::infer::{
|
||||
infer_deferred_types, infer_definition_types, infer_expression_types, infer_scope_types,
|
||||
@@ -34,18 +36,15 @@ mod mro;
|
||||
mod narrow;
|
||||
mod unpacker;
|
||||
|
||||
pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
#[salsa::tracked]
|
||||
pub fn check_types(db: &dyn Db, file: File) {
|
||||
let _span = tracing::trace_span!("check_types", file=?file.path(db)).entered();
|
||||
|
||||
let index = semantic_index(db, file);
|
||||
let mut diagnostics = TypeCheckDiagnostics::new();
|
||||
|
||||
for scope_id in index.scope_ids() {
|
||||
let result = infer_scope_types(db, scope_id);
|
||||
diagnostics.extend(result.diagnostics());
|
||||
let _ = infer_scope_types(db, scope_id);
|
||||
}
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
@@ -1598,19 +1597,21 @@ impl<'db> CallOutcome<'db> {
|
||||
}
|
||||
|
||||
/// Get the return type of the call, emitting default diagnostics if needed.
|
||||
fn unwrap_with_diagnostic<'a>(
|
||||
fn unwrap_with_diagnostic(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
file: File,
|
||||
) -> Type<'db> {
|
||||
match self.return_ty_result(db, node, diagnostics) {
|
||||
match self.return_ty_result(db, node, file) {
|
||||
Ok(return_ty) => return_ty,
|
||||
Err(NotCallableError::Type {
|
||||
not_callable_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
@@ -1625,7 +1626,9 @@ impl<'db> CallOutcome<'db> {
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
@@ -1641,7 +1644,9 @@ impl<'db> CallOutcome<'db> {
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
@@ -1656,11 +1661,11 @@ impl<'db> CallOutcome<'db> {
|
||||
}
|
||||
|
||||
/// Get the return type of the call as a result.
|
||||
fn return_ty_result<'a>(
|
||||
fn return_ty_result(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
file: File,
|
||||
) -> Result<Type<'db>, NotCallableError<'db>> {
|
||||
match self {
|
||||
Self::Callable { return_ty } => Ok(*return_ty),
|
||||
@@ -1668,11 +1673,17 @@ impl<'db> CallOutcome<'db> {
|
||||
return_ty,
|
||||
revealed_ty,
|
||||
} => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
"revealed-type",
|
||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||
);
|
||||
if is_any_diagnostic_enabled(db, file) {
|
||||
CompileDiagnostic::report(
|
||||
db.upcast(),
|
||||
RevealTypeDiagnostic {
|
||||
file,
|
||||
range: node.range(),
|
||||
ty: revealed_ty.display(db).to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(*return_ty)
|
||||
}
|
||||
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
|
||||
@@ -1700,10 +1711,10 @@ impl<'db> CallOutcome<'db> {
|
||||
*return_ty
|
||||
} else {
|
||||
revealed = true;
|
||||
outcome.unwrap_with_diagnostic(db, node, diagnostics)
|
||||
outcome.unwrap_with_diagnostic(db, node, file)
|
||||
}
|
||||
}
|
||||
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
|
||||
_ => outcome.unwrap_with_diagnostic(db, node, file),
|
||||
};
|
||||
union_builder = union_builder.add(return_ty);
|
||||
}
|
||||
@@ -1785,13 +1796,14 @@ enum IterationOutcome<'db> {
|
||||
impl<'db> IterationOutcome<'db> {
|
||||
fn unwrap_with_diagnostic(
|
||||
self,
|
||||
db: &dyn Db,
|
||||
iterable_node: ast::AnyNodeRef,
|
||||
diagnostics: &mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
file: File,
|
||||
) -> Type<'db> {
|
||||
match self {
|
||||
Self::Iterable { element_ty } => element_ty,
|
||||
Self::NotIterable { not_iterable_ty } => {
|
||||
diagnostics.add_not_iterable(iterable_node, not_iterable_ty);
|
||||
report_not_iterable(db, file, iterable_node, not_iterable_ty);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
@@ -2228,6 +2240,35 @@ impl<'db> TupleType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RevealTypeDiagnostic {
|
||||
range: TextRange,
|
||||
ty: String,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for RevealTypeDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
"revealed-type"
|
||||
}
|
||||
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
format!("Revealed type is `{}`", self.ty).into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Info
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,13 +1,198 @@
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// Returns `true` if any diagnostic is enabled for this file.
|
||||
pub(crate) fn is_any_diagnostic_enabled(db: &dyn Db, file: File) -> bool {
|
||||
db.is_file_open(file)
|
||||
}
|
||||
|
||||
pub(crate) fn report_type_diagnostic(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
rule: &str,
|
||||
message: std::fmt::Arguments,
|
||||
) {
|
||||
if !is_any_diagnostic_enabled(db, file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Don't emit the diagnostic if:
|
||||
// * The enclosing node contains any syntax errors
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
|
||||
CompileDiagnostic::report(
|
||||
db.upcast(),
|
||||
TypeCheckDiagnostic {
|
||||
file,
|
||||
rule: rule.to_string(),
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn report_not_iterable(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
not_iterable_ty: Type,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn report_index_out_of_bounds(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type,
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
pub(super) fn report_non_subscriptable(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
non_subscriptable_ty: Type,
|
||||
method: &str,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"non-subscriptable",
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_module<'a>(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
import_node: impl Into<AnyNodeRef<'a>>,
|
||||
level: u32,
|
||||
module: Option<&str>,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
module.unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_slice_step_size_zero(db: &dyn Db, file: File, node: AnyNodeRef) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_assignment(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
declared_ty: Type,
|
||||
assigned_ty: Type,
|
||||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(db)));
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(db)));
|
||||
}
|
||||
_ => {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"invalid-assignment",
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(db),
|
||||
declared_ty.display(db),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_possibly_unresolved_reference(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
expr_name_node: &ast::ExprName,
|
||||
) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_reference(db: &dyn Db, file: File, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
// TODO: Don't use string keys for rules
|
||||
@@ -22,265 +207,29 @@ impl TypeCheckDiagnostic {
|
||||
&self.rule
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for TypeCheckDiagnostic {
|
||||
fn range(&self) -> TextRange {
|
||||
pub fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of type check diagnostics.
|
||||
///
|
||||
/// The diagnostics are wrapped in an `Arc` because they need to be cloned multiple times
|
||||
/// when going from `infer_expression` to `check_file`. We could consider
|
||||
/// making [`TypeCheckDiagnostic`] a Salsa struct to have them Arena-allocated (once the Tables refactor is done).
|
||||
/// Using Salsa struct does have the downside that it leaks the Salsa dependency into diagnostics and
|
||||
/// each Salsa-struct comes with an overhead.
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostics {
|
||||
inner: Vec<std::sync::Arc<TypeCheckDiagnostic>>,
|
||||
}
|
||||
|
||||
impl TypeCheckDiagnostics {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
impl Diagnostic for TypeCheckDiagnostic {
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
Cow::Borrowed(&self.message)
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
|
||||
self.inner.push(Arc::new(diagnostic));
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
self.inner.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<TypeCheckDiagnostic> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = TypeCheckDiagnostic>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter.into_iter().map(std::sync::Arc::new));
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Extend<&'a std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = &'a Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner
|
||||
.extend(iter.into_iter().map(std::sync::Arc::clone));
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TypeCheckDiagnostics {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TypeCheckDiagnostics {
|
||||
type Target = [std::sync::Arc<TypeCheckDiagnostic>];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for TypeCheckDiagnostics {
|
||||
type Item = Arc<TypeCheckDiagnostic>;
|
||||
type IntoIter = std::vec::IntoIter<std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
|
||||
type Item = &'a Arc<TypeCheckDiagnostic>;
|
||||
type IntoIter = std::slice::Iter<'a, std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TypeCheckDiagnosticsBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
}
|
||||
|
||||
impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||
pub(super) fn new(db: &'db dyn Db, file: File) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
diagnostics: TypeCheckDiagnostics::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn add_index_out_of_bounds(
|
||||
&mut self,
|
||||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type<'db>,
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
pub(super) fn add_non_subscriptable(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
non_subscriptable_ty: Type<'db>,
|
||||
method: &str,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"non-subscriptable",
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_module(
|
||||
&mut self,
|
||||
import_node: impl Into<AnyNodeRef<'db>>,
|
||||
level: u32,
|
||||
module: Option<&str>,
|
||||
) {
|
||||
self.add(
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
module.unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
||||
self.add(
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_invalid_assignment(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
declared_ty: Type<'db>,
|
||||
assigned_ty: Type<'db>,
|
||||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
self.add(node, "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!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(self.db)));
|
||||
}
|
||||
_ => {
|
||||
self.add(
|
||||
node,
|
||||
"invalid-assignment",
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(self.db),
|
||||
declared_ty.display(self.db),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_possibly_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Don't emit the diagnostic if:
|
||||
// * The enclosing node contains any syntax errors
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
|
||||
self.diagnostics.push(TypeCheckDiagnostic {
|
||||
file: self.file,
|
||||
rule: rule.to_string(),
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn extend(&mut self, diagnostics: &TypeCheckDiagnostics) {
|
||||
self.diagnostics.extend(diagnostics);
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> TypeCheckDiagnostics {
|
||||
self.diagnostics.shrink_to_fit();
|
||||
self.diagnostics
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
|
||||
fn rule(&self) -> &str {
|
||||
&self.rule
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,6 @@ use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::stdlib::builtins_module_scope;
|
||||
use crate::types::diagnostic::{
|
||||
TypeCheckDiagnostic, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder,
|
||||
};
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
@@ -64,6 +61,12 @@ use crate::unpack::Unpack;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
use crate::Db;
|
||||
|
||||
use super::diagnostic::{
|
||||
is_any_diagnostic_enabled, report_index_out_of_bounds, report_invalid_assignment,
|
||||
report_non_subscriptable, report_possibly_unresolved_reference, report_slice_step_size_zero,
|
||||
report_type_diagnostic, report_unresolved_module, report_unresolved_reference,
|
||||
};
|
||||
|
||||
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
||||
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
||||
/// scope.
|
||||
@@ -212,9 +215,6 @@ pub(crate) struct TypeInference<'db> {
|
||||
/// The types of every declaration in this region.
|
||||
declarations: FxHashMap<Definition<'db>, Type<'db>>,
|
||||
|
||||
/// The diagnostics for this region.
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
|
||||
/// Are there deferred type expressions in this region?
|
||||
has_deferred: bool,
|
||||
|
||||
@@ -228,7 +228,6 @@ impl<'db> TypeInference<'db> {
|
||||
expressions: FxHashMap::default(),
|
||||
bindings: FxHashMap::default(),
|
||||
declarations: FxHashMap::default(),
|
||||
diagnostics: TypeCheckDiagnostics::default(),
|
||||
has_deferred: false,
|
||||
scope,
|
||||
}
|
||||
@@ -253,15 +252,10 @@ impl<'db> TypeInference<'db> {
|
||||
self.declarations[&definition]
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostics(&self) -> &[std::sync::Arc<TypeCheckDiagnostic>] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
self.expressions.shrink_to_fit();
|
||||
self.bindings.shrink_to_fit();
|
||||
self.declarations.shrink_to_fit();
|
||||
self.diagnostics.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,8 +315,6 @@ pub(super) struct TypeInferenceBuilder<'db> {
|
||||
|
||||
/// The type inference results
|
||||
types: TypeInference<'db>,
|
||||
|
||||
diagnostics: TypeCheckDiagnosticsBuilder<'db>,
|
||||
}
|
||||
|
||||
impl<'db> TypeInferenceBuilder<'db> {
|
||||
@@ -352,7 +344,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
region,
|
||||
file,
|
||||
types: TypeInference::empty(scope),
|
||||
diagnostics: TypeCheckDiagnosticsBuilder::new(db, file),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +355,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.declarations
|
||||
.extend(inference.declarations.iter());
|
||||
self.types.expressions.extend(inference.expressions.iter());
|
||||
self.diagnostics.extend(&inference.diagnostics);
|
||||
self.types.has_deferred |= inference.has_deferred;
|
||||
}
|
||||
|
||||
@@ -439,13 +429,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
if infer_definition_types(self.db, *definition).has_deferred {
|
||||
let deferred = infer_deferred_types(self.db, *definition);
|
||||
self.types.expressions.extend(&deferred.expressions);
|
||||
self.diagnostics.extend(&deferred.diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Only call this function when diagnostics are enabled.
|
||||
self.check_class_definitions();
|
||||
// TODO: Test specifically for the check class definition rules
|
||||
if is_any_diagnostic_enabled(self.db, self.file) {
|
||||
self.check_class_definitions();
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all class definitions to check that Python will be able to create a
|
||||
@@ -473,14 +464,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
MroErrorKind::DuplicateBases(duplicates) => {
|
||||
let base_nodes = class.node(self.db).bases();
|
||||
for (index, duplicate) in duplicates {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
(&base_nodes[*index]).into(),
|
||||
"duplicate-base",
|
||||
format_args!("Duplicate base class `{}`", duplicate.name(self.db))
|
||||
);
|
||||
}
|
||||
}
|
||||
MroErrorKind::CyclicClassDefinition => self.diagnostics.add(
|
||||
MroErrorKind::CyclicClassDefinition => report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
class.node(self.db).into(),
|
||||
"cyclic-class-def",
|
||||
format_args!(
|
||||
@@ -492,7 +487,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
MroErrorKind::InvalidBases(bases) => {
|
||||
let base_nodes = class.node(self.db).bases();
|
||||
for (index, base_ty) in bases {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
(&base_nodes[*index]).into(),
|
||||
"invalid-base",
|
||||
format_args!(
|
||||
@@ -502,7 +499,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
}
|
||||
},
|
||||
MroErrorKind::UnresolvableMro{bases_list} => self.diagnostics.add(
|
||||
MroErrorKind::UnresolvableMro{bases_list} => report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
class.node(self.db).into(),
|
||||
"inconsistent-mro",
|
||||
format_args!(
|
||||
@@ -627,7 +626,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
expr.into(),
|
||||
"division-by-zero",
|
||||
format_args!(
|
||||
@@ -652,7 +653,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO point out the conflicting declarations in the diagnostic?
|
||||
let symbol_table = self.index.symbol_table(binding.file_scope(self.db));
|
||||
let symbol_name = symbol_table.symbol(binding.symbol(self.db)).name();
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
node,
|
||||
"conflicting-declarations",
|
||||
format_args!(
|
||||
@@ -664,8 +667,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
},
|
||||
);
|
||||
if !bound_ty.is_assignable_to(self.db, declared_ty) {
|
||||
self.diagnostics
|
||||
.add_invalid_assignment(node, declared_ty, bound_ty);
|
||||
report_invalid_assignment(self.db, self.file, node, declared_ty, bound_ty);
|
||||
// allow declarations to override inference in case of invalid assignment
|
||||
bound_ty = declared_ty;
|
||||
};
|
||||
@@ -682,7 +684,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let ty = if inferred_ty.is_assignable_to(self.db, ty) {
|
||||
ty
|
||||
} else {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
node,
|
||||
"invalid-declaration",
|
||||
format_args!(
|
||||
@@ -708,8 +712,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let inferred_ty = if inferred_ty.is_assignable_to(self.db, declared_ty) {
|
||||
inferred_ty
|
||||
} else {
|
||||
self.diagnostics
|
||||
.add_invalid_assignment(node, declared_ty, inferred_ty);
|
||||
report_invalid_assignment(self.db, self.file, node, declared_ty, inferred_ty);
|
||||
// if the assignment is invalid, fall back to assuming the annotation is correct
|
||||
declared_ty
|
||||
};
|
||||
@@ -1150,7 +1153,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO: Make use of Protocols when we support it (the manager be assignable to `contextlib.AbstractContextManager`).
|
||||
match (enter, exit) {
|
||||
(Symbol::Unbound, Symbol::Unbound) => {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1161,7 +1166,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Unknown
|
||||
}
|
||||
(Symbol::Unbound, _) => {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1173,7 +1180,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
(Symbol::Type(enter_ty, enter_boundness), exit) => {
|
||||
if enter_boundness == Boundness::MayBeUnbound {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1185,9 +1194,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
let target_ty = enter_ty
|
||||
.call(self.db, &[context_expression_ty])
|
||||
.return_ty_result(self.db, context_expression.into(), &mut self.diagnostics)
|
||||
.return_ty_result(self.db, context_expression.into(), self.file)
|
||||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!("
|
||||
@@ -1201,7 +1212,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
match exit {
|
||||
Symbol::Unbound => {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1214,7 +1227,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO: Use the `exit_ty` to determine if any raised exception is suppressed.
|
||||
|
||||
if exit_boundness == Boundness::MayBeUnbound {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1234,14 +1249,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::none(self.db),
|
||||
],
|
||||
)
|
||||
.return_ty_result(
|
||||
self.db,
|
||||
context_expression.into(),
|
||||
&mut self.diagnostics,
|
||||
)
|
||||
.return_ty_result(self.db, context_expression.into(), self.file)
|
||||
.is_err()
|
||||
{
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
context_expression.into(),
|
||||
"invalid-context-manager",
|
||||
format_args!(
|
||||
@@ -1440,7 +1453,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let target_ty = match target {
|
||||
TargetKind::Sequence(unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db, unpack);
|
||||
self.diagnostics.extend(unpacked.diagnostics());
|
||||
unpacked.get(name_ast_id).unwrap_or(Type::Unknown)
|
||||
}
|
||||
TargetKind::Name => value_ty,
|
||||
@@ -1545,11 +1557,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let augmented_return_ty = match call.return_ty_result(
|
||||
self.db,
|
||||
AnyNodeRef::StmtAugAssign(assignment),
|
||||
&mut self.diagnostics,
|
||||
self.file,
|
||||
) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -1570,7 +1584,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
let binary_return_ty = self.infer_binary_expression_type(left_ty, right_ty, op)
|
||||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -1599,7 +1615,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
self.infer_binary_expression_type(left_ty, right_ty, op)
|
||||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
assignment.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -1697,7 +1715,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(iterable.into(), &mut self.diagnostics)
|
||||
.unwrap_with_diagnostic(self.db, iterable.into(), self.file)
|
||||
};
|
||||
|
||||
self.store_expression_type(target, loop_var_value_ty);
|
||||
@@ -1736,7 +1754,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
if let Some(module) = self.module_ty_from_name(&module_name) {
|
||||
module
|
||||
} else {
|
||||
self.diagnostics.add_unresolved_module(alias, 0, Some(name));
|
||||
report_unresolved_module(self.db, self.file, alias, 0, Some(name));
|
||||
Type::Unknown
|
||||
}
|
||||
} else {
|
||||
@@ -1867,7 +1885,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.member(self.db, &ast::name::Name::new(&name.id))
|
||||
.as_type()
|
||||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
AnyNodeRef::Alias(alias),
|
||||
"unresolved-import",
|
||||
format_args!("Module `{module_name}` has no member `{name}`",),
|
||||
@@ -1876,8 +1896,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Unknown
|
||||
})
|
||||
} else {
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
report_unresolved_module(self.db, self.file, import_from, *level, module);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
@@ -1891,8 +1910,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
"Relative module resolution `{}` failed: too many leading dots",
|
||||
format_import_from_module(*level, module),
|
||||
);
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
report_unresolved_module(self.db, self.file, import_from, *level, module);
|
||||
Type::Unknown
|
||||
}
|
||||
Err(ModuleNameResolutionError::UnknownCurrentModule) => {
|
||||
@@ -1901,8 +1919,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
format_import_from_module(*level, module),
|
||||
self.file.path(self.db)
|
||||
);
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
report_unresolved_module(self.db, self.file, import_from, *level, module);
|
||||
Type::Unknown
|
||||
}
|
||||
};
|
||||
@@ -2364,7 +2381,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(iterable.into(), &mut self.diagnostics)
|
||||
.unwrap_with_diagnostic(self.db, iterable.into(), self.file)
|
||||
};
|
||||
|
||||
self.types
|
||||
@@ -2456,7 +2473,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let function_type = self.infer_expression(func);
|
||||
function_type
|
||||
.call(self.db, arg_types.as_slice())
|
||||
.unwrap_with_diagnostic(self.db, func.as_ref().into(), &mut self.diagnostics)
|
||||
.unwrap_with_diagnostic(self.db, func.as_ref().into(), self.file)
|
||||
}
|
||||
|
||||
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
||||
@@ -2467,9 +2484,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
} = starred;
|
||||
|
||||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
|
||||
iterable_ty.iterate(self.db).unwrap_with_diagnostic(
|
||||
self.db,
|
||||
value.as_ref().into(),
|
||||
self.file,
|
||||
);
|
||||
|
||||
// TODO
|
||||
Type::Todo
|
||||
@@ -2488,9 +2507,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let ast::ExprYieldFrom { range: _, value } = yield_from;
|
||||
|
||||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
|
||||
iterable_ty.iterate(self.db).unwrap_with_diagnostic(
|
||||
self.db,
|
||||
value.as_ref().into(),
|
||||
self.file,
|
||||
);
|
||||
|
||||
// TODO get type from `ReturnType` of generator
|
||||
Type::Todo
|
||||
@@ -2558,7 +2579,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
{
|
||||
let mut symbol = builtins_symbol(self.db, name);
|
||||
if symbol.is_unbound() && name == "reveal_type" {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
name_node.into(),
|
||||
"undefined-reveal",
|
||||
format_args!(
|
||||
@@ -2611,7 +2634,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
match self.lookup_name(name) {
|
||||
Symbol::Type(looked_up_ty, looked_up_boundness) => {
|
||||
if looked_up_boundness == Boundness::MayBeUnbound {
|
||||
self.diagnostics.add_possibly_unresolved_reference(name);
|
||||
report_possibly_unresolved_reference(self.db, self.file, name);
|
||||
}
|
||||
|
||||
bindings_ty
|
||||
@@ -2620,9 +2643,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
Symbol::Unbound => {
|
||||
if bindings_ty.is_some() {
|
||||
self.diagnostics.add_possibly_unresolved_reference(name);
|
||||
report_possibly_unresolved_reference(self.db, self.file, name);
|
||||
} else {
|
||||
self.diagnostics.add_unresolved_reference(name);
|
||||
report_unresolved_reference(self.db, self.file, name);
|
||||
}
|
||||
bindings_ty.unwrap_or(Type::Unknown)
|
||||
}
|
||||
@@ -2715,14 +2738,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
{
|
||||
let call = class_member.call(self.db, &[operand_type]);
|
||||
|
||||
match call.return_ty_result(
|
||||
self.db,
|
||||
AnyNodeRef::ExprUnaryOp(unary),
|
||||
&mut self.diagnostics,
|
||||
) {
|
||||
match call.return_ty_result(self.db, AnyNodeRef::ExprUnaryOp(unary), self.file)
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
unary.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -2734,7 +2756,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
unary.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -2775,7 +2799,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
self.infer_binary_expression_type(left_ty, right_ty, *op)
|
||||
.unwrap_or_else(|| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
binary.into(),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -3105,7 +3131,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_binary_type_comparison(left_ty, *op, right_ty)
|
||||
.unwrap_or_else(|error| {
|
||||
// Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
AnyNodeRef::ExprCompare(compare),
|
||||
"unsupported-operator",
|
||||
format_args!(
|
||||
@@ -3495,7 +3523,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||
.copied()
|
||||
.unwrap_or_else(|_| {
|
||||
self.diagnostics.add_index_out_of_bounds(
|
||||
report_index_out_of_bounds(
|
||||
self.db,
|
||||
self.file,
|
||||
"tuple",
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
@@ -3514,7 +3544,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let new_elements: Vec<_> = new_elements.copied().collect();
|
||||
Type::Tuple(TupleType::new(self.db, new_elements.into_boxed_slice()))
|
||||
} else {
|
||||
self.diagnostics.add_slice_step_size_zero(value_node.into());
|
||||
report_slice_step_size_zero(self.db, self.file, value_node.into());
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
@@ -3533,7 +3563,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
))
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
self.diagnostics.add_index_out_of_bounds(
|
||||
report_index_out_of_bounds(
|
||||
self.db,
|
||||
self.file,
|
||||
"string",
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
@@ -3553,7 +3585,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let literal: String = new_chars.collect();
|
||||
Type::StringLiteral(StringLiteralType::new(self.db, literal.into_boxed_str()))
|
||||
} else {
|
||||
self.diagnostics.add_slice_step_size_zero(value_node.into());
|
||||
report_slice_step_size_zero(self.db, self.file, value_node.into());
|
||||
Type::Unknown
|
||||
};
|
||||
result
|
||||
@@ -3570,7 +3602,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::BytesLiteral(BytesLiteralType::new(self.db, [*byte].as_slice()))
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
self.diagnostics.add_index_out_of_bounds(
|
||||
report_index_out_of_bounds(
|
||||
self.db,
|
||||
self.file,
|
||||
"bytes literal",
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
@@ -3589,7 +3623,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let new_bytes: Vec<u8> = new_bytes.copied().collect();
|
||||
Type::BytesLiteral(BytesLiteralType::new(self.db, new_bytes.into_boxed_slice()))
|
||||
} else {
|
||||
self.diagnostics.add_slice_step_size_zero(value_node.into());
|
||||
report_slice_step_size_zero(self.db, self.file, value_node.into());
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
@@ -3613,7 +3647,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Symbol::Unbound => {}
|
||||
Symbol::Type(dunder_getitem_method, boundness) => {
|
||||
if boundness == Boundness::MayBeUnbound {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
"call-possibly-unbound-method",
|
||||
format_args!(
|
||||
@@ -3625,9 +3661,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
return dunder_getitem_method
|
||||
.call(self.db, &[slice_ty])
|
||||
.return_ty_result(self.db, value_node.into(), &mut self.diagnostics)
|
||||
.return_ty_result(self.db, value_node.into(), self.file)
|
||||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
@@ -3657,7 +3695,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Symbol::Unbound => {}
|
||||
Symbol::Type(ty, boundness) => {
|
||||
if boundness == Boundness::MayBeUnbound {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
"call-possibly-unbound-method",
|
||||
format_args!(
|
||||
@@ -3669,9 +3709,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
return ty
|
||||
.call(self.db, &[slice_ty])
|
||||
.return_ty_result(self.db, value_node.into(), &mut self.diagnostics)
|
||||
.return_ty_result(self.db, value_node.into(), self.file)
|
||||
.unwrap_or_else(|err| {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
"call-non-callable",
|
||||
format_args!(
|
||||
@@ -3690,13 +3732,17 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
return KnownClass::GenericAlias.to_instance(self.db);
|
||||
}
|
||||
|
||||
self.diagnostics.add_non_subscriptable(
|
||||
report_non_subscriptable(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
"__class_getitem__",
|
||||
);
|
||||
} else {
|
||||
self.diagnostics.add_non_subscriptable(
|
||||
report_non_subscriptable(
|
||||
self.db,
|
||||
self.file,
|
||||
value_node.into(),
|
||||
value_ty,
|
||||
"__getitem__",
|
||||
@@ -3791,7 +3837,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
pub(super) fn finish(mut self) -> TypeInference<'db> {
|
||||
self.infer_region();
|
||||
self.types.diagnostics = self.diagnostics.finish();
|
||||
self.types.shrink_to_fit();
|
||||
self.types
|
||||
}
|
||||
@@ -4079,7 +4124,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Ok(ty) => ty,
|
||||
Err(nodes) => {
|
||||
for node in nodes {
|
||||
self.diagnostics.add(
|
||||
report_type_diagnostic(
|
||||
self.db,
|
||||
self.file,
|
||||
node.into(),
|
||||
"invalid-literal-parameter",
|
||||
format_args!(
|
||||
@@ -4401,8 +4448,9 @@ fn perform_membership_test_comparison<'db>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use anyhow::Context;
|
||||
use ruff_db::diagnostic::CompileDiagnostic;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
@@ -4503,22 +4551,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_diagnostic_messages(diagnostics: &TypeCheckDiagnostics, expected: &[&str]) {
|
||||
let messages: Vec<&str> = diagnostics
|
||||
fn assert_file_diagnostics(db: &TestDb, filename: &str, expected: &[&str]) {
|
||||
let file = system_path_to_file(db, filename).unwrap();
|
||||
|
||||
let diagnostics = check_types::accumulated::<CompileDiagnostic>(db, file);
|
||||
|
||||
let messages: Vec<_> = diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.message())
|
||||
.collect();
|
||||
assert_eq!(&messages, expected);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_file_diagnostics(db: &TestDb, filename: &str, expected: &[&str]) {
|
||||
let file = system_path_to_file(db, filename).unwrap();
|
||||
let diagnostics = check_types(db, file);
|
||||
|
||||
assert_diagnostic_messages(&diagnostics, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_import_with_no_module_name() -> anyhow::Result<()> {
|
||||
// This test checks that invalid syntax in a `StmtImportFrom` node
|
||||
@@ -5077,7 +5121,14 @@ mod tests {
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_file_diagnostics(&db, "src/a.py", &["Revealed type is `Unknown`"]);
|
||||
assert_file_diagnostics(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&[
|
||||
"Expected one or more exception types",
|
||||
"Revealed type is `Unknown`",
|
||||
],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5337,8 +5388,15 @@ mod tests {
|
||||
|
||||
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "z", "Unknown");
|
||||
|
||||
// (There is a diagnostic for invalid syntax that's emitted, but it's not listed by `assert_file_diagnostics`)
|
||||
assert_file_diagnostics(&db, "src/a.py", &["Name `z` used when not defined"]);
|
||||
assert_file_diagnostics(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&[
|
||||
"Expected an identifier, but found a keyword 'in' that cannot be used here",
|
||||
"Expected 'in', found name",
|
||||
"Name `z` used when not defined",
|
||||
],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5424,7 +5482,7 @@ mod tests {
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
def __iter__(self) -> Iterator: ...
|
||||
|
||||
x = [*NotIterable()]
|
||||
y = [*Iterable()]
|
||||
|
||||
@@ -6,14 +6,14 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::ast_ids::{HasScopedAstId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{TupleType, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::types::{TupleType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
pub(crate) struct Unpacker<'db> {
|
||||
db: &'db dyn Db,
|
||||
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
|
||||
diagnostics: TypeCheckDiagnosticsBuilder<'db>,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl<'db> Unpacker<'db> {
|
||||
@@ -21,7 +21,7 @@ impl<'db> Unpacker<'db> {
|
||||
Self {
|
||||
db,
|
||||
targets: FxHashMap::default(),
|
||||
diagnostics: TypeCheckDiagnosticsBuilder::new(db, file),
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +104,11 @@ impl<'db> Unpacker<'db> {
|
||||
let value_ty = if value_ty.is_literal_string() {
|
||||
Type::LiteralString
|
||||
} else {
|
||||
value_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(AnyNodeRef::from(target), &mut self.diagnostics)
|
||||
value_ty.iterate(self.db).unwrap_with_diagnostic(
|
||||
self.db,
|
||||
AnyNodeRef::from(target),
|
||||
self.file,
|
||||
)
|
||||
};
|
||||
for element in elts {
|
||||
self.unpack(element, value_ty, scope);
|
||||
@@ -120,7 +122,6 @@ impl<'db> Unpacker<'db> {
|
||||
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
|
||||
self.targets.shrink_to_fit();
|
||||
UnpackResult {
|
||||
diagnostics: self.diagnostics.finish(),
|
||||
targets: self.targets,
|
||||
}
|
||||
}
|
||||
@@ -129,15 +130,10 @@ impl<'db> Unpacker<'db> {
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct UnpackResult<'db> {
|
||||
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
}
|
||||
|
||||
impl<'db> UnpackResult<'db> {
|
||||
pub(crate) fn get(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
|
||||
self.targets.get(&expr_id).copied()
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
|
||||
&self.diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user