diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 8950850d4a81..295f37ab1d6b 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -75,6 +75,7 @@ fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::TypeMismatch<'_>) -> Option< remove_semicolon(ctx, d, expr_ptr, &mut fixes); str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); add_await(ctx, d, expr_ptr, &mut fixes); + array_length(ctx, d, expr_ptr, &mut fixes); } if fixes.is_empty() { None } else { Some(fixes) } @@ -402,6 +403,59 @@ fn add_await( Some(()) } +fn array_length( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::TypeMismatch<'_>, + expr_ptr: &InFile>, + acc: &mut Vec, +) -> Option<()> { + let (ty1, expected) = d.expected.as_array(ctx.db())?; + let (ty2, actual) = d.actual.as_array(ctx.db())?; + + if !ty1.could_unify_with(ctx.db(), &ty2) || expected == actual { + return None; + } + + let root = expr_ptr.file_id.parse_or_expand(ctx.db()); + let expr = expr_ptr.value.to_node(&root); + let container = skip_transparent(expr).syntax().parent()?; + let ty = syntax::match_ast! { + match container { + ast::LetStmt(it) => it.ty()?, + ast::Static(it) => it.ty()?, + ast::Const(it) => it.ty()?, + _ => return None, + } + }; + let ast::Type::ArrayType(ty) = ty else { return None }; + let len = ty.const_arg()?.expr()?; + let hir::FileRange { range: len_range, .. } = ctx.sema.original_range_opt(len.syntax())?; + let hir::FileRange { range, file_id } = ctx.sema.original_range_opt(&container)?; + + let edit = TextEdit::replace(len_range, actual.to_string()); + let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.db()), edit); + let label = &format!("Change array length to {actual}"); + acc.push(fix("array_length", label, source_change, range)); + + fn skip_transparent(mut expr: Expr) -> Expr { + while let Some(parent) = expr.syntax().parent() { + if let Some(it) = ast::StmtList::cast(parent.clone()) + && it.statements().next().is_none() + && let Some(parent) = it.syntax().parent().and_then(Expr::cast) + { + expr = parent; + } else if let Some(parent) = ast::ParenExpr::cast(parent) { + expr = parent.into(); + } else { + break; + } + } + expr + } + + Some(()) +} + #[cfg(test)] mod tests { use crate::tests::{ @@ -1460,6 +1514,61 @@ identity! { ); } + #[test] + fn array_length() { + check_fix( + r#" +const VALS: [i32; 2$0] = [1, 2, 3]; + "#, + r#" +const VALS: [i32; 3] = [1, 2, 3]; + "#, + ); + + check_fix( + r#" +static VALS: [i32; 2$0] = [1, 2, 3]; + "#, + r#" +static VALS: [i32; 3] = [1, 2, 3]; + "#, + ); + + check_fix( + r#" +static VALS: [i32; 2$0] = {[1, 2, 3]}; + "#, + r#" +static VALS: [i32; 3] = {[1, 2, 3]}; + "#, + ); + + // Convenient trigger range + check_fix( + r#" +static VALS: [i32; 2] = [$01, 2, 3]; + "#, + r#" +static VALS: [i32; 3] = [1, 2, 3]; + "#, + ); + + check_fix( + r#" +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +identity! { + const VALS: [i32; 2$0] = [1, 2, 3]; +} + "#, + r#" +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +identity! { + const VALS: [i32; 3] = [1, 2, 3]; +} + "#, + ); + } + #[test] fn type_mismatch_range_adjustment() { cov_mark::check!(type_mismatch_range_adjustment);