Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 82780d8

Browse files
committed
feat: add an autofix for inserting an unsafe block to missing unsafe diagnostic
1 parent b4d7ea0 commit 82780d8

File tree

1 file changed

+189
-7
lines changed

1 file changed

+189
-7
lines changed

crates/ide-diagnostics/src/handlers/missing_unsafe.rs

Lines changed: 189 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
use crate::{Diagnostic, DiagnosticsContext};
1+
use hir::db::AstDatabase;
2+
use ide_db::{assists::Assist, source_change::SourceChange};
3+
use syntax::ast::{ExprStmt, LetStmt};
4+
use syntax::AstNode;
5+
use syntax::{ast, SyntaxNode};
6+
use text_edit::TextEdit;
7+
8+
use crate::{fix, Diagnostic, DiagnosticsContext};
29

310
// Diagnostic: missing-unsafe
411
//
@@ -9,11 +16,60 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf
916
"this operation is unsafe and requires an unsafe function or block",
1017
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
1118
)
19+
.with_fixes(fixes(ctx, d))
20+
}
21+
22+
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> {
23+
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
24+
let expr = d.expr.value.to_node(&root);
25+
26+
let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(ctx, &expr);
27+
28+
let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text());
29+
let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement);
30+
let source_change =
31+
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
32+
Some(vec![fix("add_unsafe", "Add unsafe block", source_change, expr.syntax().text_range())])
33+
}
34+
35+
// Find the let statement or expression statement closest to the `expr` in the
36+
// ancestor chain.
37+
//
38+
// Why don't we just add an unsafe block around the `expr`?
39+
//
40+
// Consider this example:
41+
// ```
42+
// STATIC_MUT += 1;
43+
// ```
44+
// We can't add an unsafe block to the left-hand side of an assignment.
45+
// ```
46+
// unsafe { STATIC_MUT } += 1;
47+
// ```
48+
//
49+
// Or this example:
50+
// ```
51+
// let z = STATIC_MUT.a;
52+
// ```
53+
// We can't add an unsafe block like this:
54+
// ```
55+
// let z = unsafe { STATIC_MUT } .a;
56+
// ```
57+
fn pick_best_node_to_add_unsafe_block(
58+
ctx: &DiagnosticsContext<'_>,
59+
expr: &ast::Expr,
60+
) -> SyntaxNode {
61+
let Some(let_or_expr_stmt) = ctx.sema.ancestors_with_macros(expr.syntax().clone()).find(|node| {
62+
LetStmt::can_cast(node.kind()) || ExprStmt::can_cast(node.kind())
63+
}) else {
64+
// Is this reachable?
65+
return expr.syntax().clone();
66+
};
67+
let_or_expr_stmt
1268
}
1369

1470
#[cfg(test)]
1571
mod tests {
16-
use crate::tests::check_diagnostics;
72+
use crate::tests::{check_diagnostics, check_fix};
1773

1874
#[test]
1975
fn missing_unsafe_diagnostic_with_raw_ptr() {
@@ -23,7 +79,7 @@ fn main() {
2379
let x = &5 as *const usize;
2480
unsafe { let y = *x; }
2581
let z = *x;
26-
} //^^ error: this operation is unsafe and requires an unsafe function or block
82+
} //^^💡 error: this operation is unsafe and requires an unsafe function or block
2783
"#,
2884
)
2985
}
@@ -48,9 +104,9 @@ unsafe fn unsafe_fn() {
48104
49105
fn main() {
50106
unsafe_fn();
51-
//^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
107+
//^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
52108
HasUnsafe.unsafe_fn();
53-
//^^^^^^^^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
109+
//^^^^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
54110
unsafe {
55111
unsafe_fn();
56112
HasUnsafe.unsafe_fn();
@@ -72,7 +128,7 @@ static mut STATIC_MUT: Ty = Ty { a: 0 };
72128
73129
fn main() {
74130
let x = STATIC_MUT.a;
75-
//^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
131+
//^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
76132
unsafe {
77133
let x = STATIC_MUT.a;
78134
}
@@ -94,9 +150,135 @@ extern "rust-intrinsic" {
94150
fn main() {
95151
let _ = bitreverse(12);
96152
let _ = floorf32(12.0);
97-
//^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
153+
//^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
98154
}
99155
"#,
100156
);
101157
}
158+
159+
#[test]
160+
fn add_unsafe_block_when_dereferencing_a_raw_pointer() {
161+
check_fix(
162+
r#"
163+
fn main() {
164+
let x = &5 as *const usize;
165+
let z = *x$0;
166+
}
167+
"#,
168+
r#"
169+
fn main() {
170+
let x = &5 as *const usize;
171+
unsafe { let z = *x; }
172+
}
173+
"#,
174+
);
175+
}
176+
177+
#[test]
178+
fn add_unsafe_block_when_calling_unsafe_function() {
179+
check_fix(
180+
r#"
181+
unsafe fn func() {
182+
let x = &5 as *const usize;
183+
let z = *x;
184+
}
185+
fn main() {
186+
func$0();
187+
}
188+
"#,
189+
r#"
190+
unsafe fn func() {
191+
let x = &5 as *const usize;
192+
let z = *x;
193+
}
194+
fn main() {
195+
unsafe { func(); }
196+
}
197+
"#,
198+
)
199+
}
200+
201+
#[test]
202+
fn add_unsafe_block_when_calling_unsafe_method() {
203+
check_fix(
204+
r#"
205+
struct S(usize);
206+
impl S {
207+
unsafe fn func(&self) {
208+
let x = &self.0 as *const usize;
209+
let z = *x;
210+
}
211+
}
212+
fn main() {
213+
let s = S(5);
214+
s.func$0();
215+
}
216+
"#,
217+
r#"
218+
struct S(usize);
219+
impl S {
220+
unsafe fn func(&self) {
221+
let x = &self.0 as *const usize;
222+
let z = *x;
223+
}
224+
}
225+
fn main() {
226+
let s = S(5);
227+
unsafe { s.func(); }
228+
}
229+
"#,
230+
)
231+
}
232+
233+
#[test]
234+
fn add_unsafe_block_when_accessing_mutable_static() {
235+
check_fix(
236+
r#"
237+
struct Ty {
238+
a: u8,
239+
}
240+
241+
static mut STATIC_MUT: Ty = Ty { a: 0 };
242+
243+
fn main() {
244+
let x = STATIC_MUT$0.a;
245+
}
246+
"#,
247+
r#"
248+
struct Ty {
249+
a: u8,
250+
}
251+
252+
static mut STATIC_MUT: Ty = Ty { a: 0 };
253+
254+
fn main() {
255+
unsafe { let x = STATIC_MUT.a; }
256+
}
257+
"#,
258+
)
259+
}
260+
261+
#[test]
262+
fn add_unsafe_block_when_calling_unsafe_intrinsic() {
263+
check_fix(
264+
r#"
265+
extern "rust-intrinsic" {
266+
pub fn floorf32(x: f32) -> f32;
267+
}
268+
269+
fn main() {
270+
let _ = floorf32$0(12.0);
271+
}
272+
"#,
273+
r#"
274+
extern "rust-intrinsic" {
275+
pub fn floorf32(x: f32) -> f32;
276+
}
277+
278+
fn main() {
279+
unsafe { let _ = floorf32(12.0); }
280+
}
281+
"#,
282+
)
283+
}
102284
}

0 commit comments

Comments
 (0)