Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22156 from ada4a/diag-E0067
feat: add diagnostic for E0067
| -rw-r--r-- | crates/hir-ty/src/infer.rs | 3 | ||||
| -rw-r--r-- | crates/hir-ty/src/infer/expr.rs | 3 | ||||
| -rw-r--r-- | crates/hir/src/diagnostics.rs | 84 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/invalid_lhs_of_assignment.rs | 90 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/lib.rs | 2 |
5 files changed, 137 insertions, 45 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index fd0612e066..82c5834e64 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -398,6 +398,9 @@ pub enum InferenceDiagnostic { /// Whether the `GenericArgs` contains a `Self` arg. has_self_arg: bool, }, + InvalidLhsOfAssignment { + lhs: ExprId, + }, } /// A mismatch between an expected and an inferred type. diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 73d81ad16e..79010fbe1b 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -273,13 +273,12 @@ impl<'db> InferenceContext<'_, 'db> { } } - #[expect(clippy::needless_return)] pub(crate) fn check_lhs_assignable(&self, lhs: ExprId) { if self.is_syntactic_place_expr(lhs) { return; } - // FIXME: Emit diagnostic. + self.push_diagnostic(InferenceDiagnostic::InvalidLhsOfAssignment { lhs }); } fn infer_expr_coerce_never( diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 110cc19cf1..1009a8b31b 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -63,6 +63,7 @@ diagnostics![AnyDiagnostic<'db> -> IncorrectCase, InvalidCast<'db>, InvalidDeriveTarget, + InvalidLhsOfAssignment, MacroDefError, MacroError, MacroExpansionParseError, @@ -472,6 +473,11 @@ pub struct GenericDefaultRefersToSelf { pub segment: InFile<AstPtr<ast::PathSegment>>, } +#[derive(Debug)] +pub struct InvalidLhsOfAssignment { + pub lhs: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>, +} + impl<'db> AnyDiagnostic<'db> { pub(crate) fn body_validation_diagnostic( db: &'db dyn HirDatabase, @@ -543,59 +549,47 @@ impl<'db> AnyDiagnostic<'db> { } } BodyValidationDiagnostic::MissingMatchArms { match_expr, uncovered_patterns } => { - match source_map.expr_syntax(match_expr) { - Ok(source_ptr) => { - let root = source_ptr.file_syntax(db); - if let Either::Left(ast::Expr::MatchExpr(match_expr)) = - &source_ptr.value.to_node(&root) - { - match match_expr.expr() { - Some(scrut_expr) if match_expr.match_arm_list().is_some() => { - return Some( - MissingMatchArms { - scrutinee_expr: InFile::new( - source_ptr.file_id, - AstPtr::new(&scrut_expr), - ), - uncovered_patterns, - } - .into(), - ); - } - _ => {} - } + if let Ok(source_ptr) = source_map.expr_syntax(match_expr) + && let root = source_ptr.file_syntax(db) + && let Either::Left(ast::Expr::MatchExpr(match_expr)) = + source_ptr.value.to_node(&root) + && let Some(scrut_expr) = match_expr.expr() + && match_expr.match_arm_list().is_some() + { + return Some( + MissingMatchArms { + scrutinee_expr: InFile::new( + source_ptr.file_id, + AstPtr::new(&scrut_expr), + ), + uncovered_patterns, } - } - Err(SyntheticSyntax) => (), + .into(), + ); } } BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => { - match source_map.pat_syntax(pat) { - Ok(source_ptr) => { - if let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>() { - return Some( - NonExhaustiveLet { - pat: InFile::new(source_ptr.file_id, ast_pat), - uncovered_patterns, - } - .into(), - ); + if let Ok(source_ptr) = source_map.pat_syntax(pat) + && let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>() + { + return Some( + NonExhaustiveLet { + pat: InFile::new(source_ptr.file_id, ast_pat), + uncovered_patterns, } - } - Err(SyntheticSyntax) => {} + .into(), + ); } } BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => { - if let Ok(source_ptr) = source_map.expr_syntax(return_expr) { + if let Ok(source_ptr) = source_map.expr_syntax(return_expr) // Filters out desugared return expressions (e.g. desugared try operators). - if let Some(ptr) = source_ptr.value.cast::<ast::ReturnExpr>() { - return Some( - RemoveTrailingReturn { - return_expr: InFile::new(source_ptr.file_id, ptr), - } + && let Some(ptr) = source_ptr.value.cast::<ast::ReturnExpr>() + { + return Some( + RemoveTrailingReturn { return_expr: InFile::new(source_ptr.file_id, ptr) } .into(), - ); - } + ); } } BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr } => { @@ -802,6 +796,10 @@ impl<'db> AnyDiagnostic<'db> { let expected_kind = GenericArgKind::from_id(param_id); IncorrectGenericsOrder { provided_arg, expected_kind }.into() } + &InferenceDiagnostic::InvalidLhsOfAssignment { lhs } => { + let lhs = expr_syntax(lhs)?; + InvalidLhsOfAssignment { lhs }.into() + } }) } diff --git a/crates/ide-diagnostics/src/handlers/invalid_lhs_of_assignment.rs b/crates/ide-diagnostics/src/handlers/invalid_lhs_of_assignment.rs new file mode 100644 index 0000000000..02716f2b86 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/invalid_lhs_of_assignment.rs @@ -0,0 +1,90 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: invalid-lhs-of-assignment +// +// This diagnostic is triggered if the left-hand side of an assignment can't be assigned to. +pub(crate) fn invalid_lhs_of_assignment( + ctx: &DiagnosticsContext<'_>, + d: &hir::InvalidLhsOfAssignment, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0067"), + "invalid left-hand side of assignment", + d.lhs.map(Into::into), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn unit_struct_literal() { + check_diagnostics( + r#" +//- minicore: add +struct Struct; +impl core::ops::AddAssign for Struct { + fn add_assign(&mut self, _other: Self) {} +} +fn test() { + Struct += Struct; + // ^^^^^^ error: invalid left-hand side of assignment +} + "#, + ); + } + + #[test] + fn struct_literal() { + check_diagnostics( + r#" +//- minicore: add +struct Struct { foo: i32, bar: i32 } +impl core::ops::AddAssign for Struct { + fn add_assign(&mut self, _other: Self) {} +} +fn test() { + Struct { foo: 0, bar: 0 } += Struct { foo: 1, bar: 2 }; + // ^^^^^^^^^^^^^^^^^^^^^^^^^ error: invalid left-hand side of assignment +} + "#, + ); + } + + #[test] + fn destructuring_assignment() { + // no diagnostic, as `=` is not a _compound_ assignment + check_diagnostics( + r#" +//- minicore: add +struct Struct { foo: i32, bar: i32 } +impl core::ops::AddAssign for Struct { + fn add_assign(&mut self, _other: Self) {} +} +fn test(mut foo: i32, mut bar: i32) { + Struct { foo, bar } = Struct { foo: 1, bar: 2 }; +} + "#, + ); + } + + #[test] + fn destructuring_compound_assignment() { + check_diagnostics( + r#" +//- minicore: add +struct Struct { foo: i32, bar: i32 } +impl core::ops::AddAssign for Struct { + fn add_assign(&mut self, _other: Self) {} +} +fn test(foo: i32, bar: i32) { + Struct { foo, bar } += Struct { foo: 1, bar: 2 }; + // ^^^^^^^^^^^^^^^^^^^ error: invalid left-hand side of assignment +} + "#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 54af3a35f4..74f0653660 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -43,6 +43,7 @@ mod handlers { pub(crate) mod incorrect_generics_order; pub(crate) mod invalid_cast; pub(crate) mod invalid_derive_target; + pub(crate) mod invalid_lhs_of_assignment; pub(crate) mod macro_error; pub(crate) mod malformed_derive; pub(crate) mod mismatched_arg_count; @@ -479,6 +480,7 @@ pub fn semantic_diagnostics( AnyDiagnostic::MissingLifetime(d) => handlers::missing_lifetime::missing_lifetime(&ctx, &d), AnyDiagnostic::ElidedLifetimesInPath(d) => handlers::elided_lifetimes_in_path::elided_lifetimes_in_path(&ctx, &d), AnyDiagnostic::GenericDefaultRefersToSelf(d) => handlers::generic_default_refers_to_self::generic_default_refers_to_self(&ctx, &d), + AnyDiagnostic::InvalidLhsOfAssignment(d) => handlers::invalid_lhs_of_assignment::invalid_lhs_of_assignment(&ctx, &d), }; res.push(d) } |