Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions crates/ide-diagnostics/src/handlers/type_mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down Expand Up @@ -402,6 +403,59 @@ fn add_await(
Some(())
}

fn array_length(
ctx: &DiagnosticsContext<'_, '_>,
d: &hir::TypeMismatch<'_>,
expr_ptr: &InFile<AstPtr<ast::Expr>>,
acc: &mut Vec<Assist>,
) -> 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));

@A4-Tacks A4-Tacks Jul 12, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use the container's range to trigger quick fix more effectively?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the range of the array type. The range of the length is too small, but the range of the container is too large.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the editing point is outside the range of the array, I don't know if this is appropriate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No? We're editing the length of the array which is inside the array.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you said array type, I thought it was an array expression

But I think the array type range is still too small

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not, but I'll let you decide.


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::{
Expand Down Expand Up @@ -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);
Expand Down
Loading