Skip to content

Commit 319bc52

Browse files
Add diagnostics for unsafe_op_in_unsafe_fn
Turns out it's pretty easy, but I did have to add support for allowed-by-default lints.
1 parent b9be0f2 commit 319bc52

File tree

5 files changed

+55
-10
lines changed

5 files changed

+55
-10
lines changed

src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use crate::{
1313
db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TyExt, TyKind,
1414
};
1515

16-
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
16+
/// Returns `(unsafe_exprs, fn_is_unsafe)`.
17+
///
18+
/// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
19+
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprId>, bool) {
1720
let _p = tracing::info_span!("missing_unsafe").entered();
1821

1922
let mut res = Vec::new();
@@ -24,9 +27,6 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
2427
| DefWithBodyId::VariantId(_)
2528
| DefWithBodyId::InTypeConstId(_) => false,
2629
};
27-
if is_unsafe {
28-
return res;
29-
}
3030

3131
let body = db.body(def);
3232
let infer = db.infer(def);
@@ -36,7 +36,7 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
3636
}
3737
});
3838

39-
res
39+
(res, is_unsafe)
4040
}
4141

4242
pub struct UnsafeExpr {

src/tools/rust-analyzer/crates/hir/src/diagnostics.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ pub struct PrivateField {
258258
#[derive(Debug)]
259259
pub struct MissingUnsafe {
260260
pub expr: InFile<AstPtr<ast::Expr>>,
261+
/// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error.
262+
pub only_lint: bool,
261263
}
262264

263265
#[derive(Debug)]

src/tools/rust-analyzer/crates/hir/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,9 +1884,10 @@ impl DefWithBody {
18841884
);
18851885
}
18861886

1887-
for expr in hir_ty::diagnostics::missing_unsafe(db, self.into()) {
1887+
let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into());
1888+
for expr in unafe_exprs {
18881889
match source_map.expr_syntax(expr) {
1889-
Ok(expr) => acc.push(MissingUnsafe { expr }.into()),
1890+
Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()),
18901891
Err(SyntheticSyntax) => {
18911892
// FIXME: Here and elsewhere in this file, the `expr` was
18921893
// desugared, report or assert that this doesn't happen.

src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
1111
//
1212
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
1313
pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
14+
let code = if d.only_lint {
15+
DiagnosticCode::RustcLint("unsafe_op_in_unsafe_fn")
16+
} else {
17+
DiagnosticCode::RustcHardError("E0133")
18+
};
1419
Diagnostic::new_with_syntax_node_ptr(
1520
ctx,
16-
DiagnosticCode::RustcHardError("E0133"),
21+
code,
1722
"this operation is unsafe and requires an unsafe function or block",
1823
d.expr.map(|it| it.into()),
1924
)
@@ -562,6 +567,30 @@ fn main() {
562567
ed2021::safe();
563568
ed2024::not_safe();
564569
//^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
570+
}
571+
"#,
572+
)
573+
}
574+
575+
#[test]
576+
fn unsafe_op_in_unsafe_fn_allowed_by_default() {
577+
check_diagnostics(
578+
r#"
579+
unsafe fn foo(p: *mut i32) {
580+
*p = 123;
581+
}
582+
"#,
583+
)
584+
}
585+
586+
#[test]
587+
fn unsafe_op_in_unsafe_fn() {
588+
check_diagnostics(
589+
r#"
590+
#![warn(unsafe_op_in_unsafe_fn)]
591+
unsafe fn foo(p: *mut i32) {
592+
*p = 123;
593+
//^^💡 warn: this operation is unsafe and requires an unsafe function or block
565594
}
566595
"#,
567596
)

src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ pub fn semantic_diagnostics(
491491

492492
// The edition isn't accurate (each diagnostics may have its own edition due to macros),
493493
// but it's okay as it's only being used for error recovery.
494-
handle_lint_attributes(
494+
handle_lints(
495495
&ctx.sema,
496496
&mut FxHashMap::default(),
497497
&mut lints,
@@ -551,14 +551,27 @@ fn build_group_dict(
551551
map_with_prefixes.into_iter().map(|(k, v)| (k.strip_prefix(prefix).unwrap(), v)).collect()
552552
}
553553

554-
fn handle_lint_attributes(
554+
/// Thd default severity for lints that are not warn by default.
555+
// FIXME: Autogenerate this instead of write manually.
556+
static LINTS_DEFAULT_SEVERITY: LazyLock<FxHashMap<&str, Severity>> =
557+
LazyLock::new(|| FxHashMap::from_iter([("unsafe_op_in_unsafe_fn", Severity::Allow)]));
558+
559+
fn handle_lints(
555560
sema: &Semantics<'_, RootDatabase>,
556561
cache: &mut FxHashMap<HirFileId, FxHashMap<SmolStr, SeverityAttr>>,
557562
diagnostics: &mut [(InFile<SyntaxNode>, &mut Diagnostic)],
558563
cache_stack: &mut Vec<HirFileId>,
559564
edition: Edition,
560565
) {
561566
for (node, diag) in diagnostics {
567+
let lint = match diag.code {
568+
DiagnosticCode::RustcLint(lint) | DiagnosticCode::Clippy(lint) => lint,
569+
_ => panic!("non-lint passed to `handle_lints()`"),
570+
};
571+
if let Some(&default_severity) = LINTS_DEFAULT_SEVERITY.get(lint) {
572+
diag.severity = default_severity;
573+
}
574+
562575
let mut diag_severity = fill_lint_attrs(sema, node, cache, cache_stack, diag, edition);
563576

564577
if let outline_diag_severity @ Some(_) =

0 commit comments

Comments
 (0)