do not offer fixit for macro expansions and refactor the code

This commit is contained in:
XFFXFF 2023-03-15 09:06:36 +08:00
parent bf0322cd0c
commit 0c935732bc

View file

@ -1,7 +1,7 @@
use hir::db::AstDatabase; use hir::db::ExpandDatabase;
use ide_db::{assists::Assist, source_change::SourceChange}; use ide_db::{assists::Assist, source_change::SourceChange};
use syntax::AstNode;
use syntax::{ast, SyntaxNode}; use syntax::{ast, SyntaxNode};
use syntax::{match_ast, AstNode};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsContext}; use crate::{fix, Diagnostic, DiagnosticsContext};
@ -19,10 +19,15 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf
} }
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> { fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> {
// The fixit will not work correctly for macro expansions, so we don't offer it in that case.
if d.expr.file_id.is_macro() {
return None;
}
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
let expr = d.expr.value.to_node(&root); let expr = d.expr.value.to_node(&root);
let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr); let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?;
let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text()); let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text());
let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement); let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement);
@ -42,72 +47,51 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Ass
// - `unsafe_expr += 1` -> `unsafe { unsafe_expr += 1 }` // - `unsafe_expr += 1` -> `unsafe { unsafe_expr += 1 }`
// - `&unsafe_expr` -> `unsafe { &unsafe_expr }` // - `&unsafe_expr` -> `unsafe { &unsafe_expr }`
// - `&&unsafe_expr` -> `unsafe { &&unsafe_expr }` // - `&&unsafe_expr` -> `unsafe { &&unsafe_expr }`
fn pick_best_node_to_add_unsafe_block(unsafe_expr: &ast::Expr) -> SyntaxNode { fn pick_best_node_to_add_unsafe_block(unsafe_expr: &ast::Expr) -> Option<SyntaxNode> {
// The `unsafe_expr` might be: // The `unsafe_expr` might be:
// - `ast::CallExpr`: call an unsafe function // - `ast::CallExpr`: call an unsafe function
// - `ast::MethodCallExpr`: call an unsafe method // - `ast::MethodCallExpr`: call an unsafe method
// - `ast::PrefixExpr`: dereference a raw pointer // - `ast::PrefixExpr`: dereference a raw pointer
// - `ast::PathExpr`: access a static mut variable // - `ast::PathExpr`: access a static mut variable
for node in unsafe_expr.syntax().ancestors() { for (node, parent) in
let Some(parent) = node.parent() else { unsafe_expr.syntax().ancestors().zip(unsafe_expr.syntax().ancestors().skip(1))
return node; {
}; match_ast! {
match parent.kind() { match parent {
syntax::SyntaxKind::METHOD_CALL_EXPR => { // If the `parent` is a `MethodCallExpr`, that means the `node`
// Check if the `node` is the receiver of the method call // is the receiver of the method call, because only the receiver
let method_call_expr = ast::MethodCallExpr::cast(parent.clone()).unwrap(); // can be a direct child of a method call. The method name
if method_call_expr // itself is not an expression but a `NameRef`, and an argument
.receiver() // is a direct child of an `ArgList`.
.map(|receiver| { ast::MethodCallExpr(_) => continue,
receiver.syntax().text_range().contains_range(node.text_range()) ast::FieldExpr(_) => continue,
}) ast::RefExpr(_) => continue,
.unwrap_or(false) ast::BinExpr(it) => {
{ // Check if the `node` is the left-hand side of an
// Actually, I think it's not necessary to check whether the // assignment, if so, we don't want to wrap it in an unsafe
// text range of the `node` (which is the ancestor of the // block, e.g. `unsafe_expr += 1`
// `unsafe_expr`) is contained in the text range of the let is_left_hand_side_of_assignment = {
// receiver. The `node` could potentially be the receiver, the if let Some(ast::BinaryOp::Assignment { .. }) = it.op_kind() {
// method name, or the argument list. Since the `node` is the it.lhs().map(|lhs| lhs.syntax().text_range().contains_range(node.text_range())).unwrap_or(false)
// ancestor of the unsafe_expr, it cannot be the method name. } else {
// Additionally, if the `node` is the argument list, the loop false
// would break at least when `parent` reaches the argument list. }
// };
// Dispite this, I still check the text range because I think it if !is_left_hand_side_of_assignment {
// makes the code easier to understand. return Some(node);
continue;
}
return node;
}
syntax::SyntaxKind::FIELD_EXPR | syntax::SyntaxKind::REF_EXPR => continue,
syntax::SyntaxKind::BIN_EXPR => {
// Check if the `node` is the left-hand side of an assignment
let is_left_hand_side_of_assignment = {
let bin_expr = ast::BinExpr::cast(parent.clone()).unwrap();
if let Some(ast::BinaryOp::Assignment { .. }) = bin_expr.op_kind() {
let is_left_hand_side = bin_expr
.lhs()
.map(|lhs| lhs.syntax().text_range().contains_range(node.text_range()))
.unwrap_or(false);
is_left_hand_side
} else {
false
} }
}; },
if !is_left_hand_side_of_assignment { _ => { return Some(node); }
return node;
}
}
_ => {
return node;
} }
} }
} }
unsafe_expr.syntax().clone() None
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::{check_diagnostics, check_fix}; use crate::tests::{check_diagnostics, check_fix, check_no_fix};
#[test] #[test]
fn missing_unsafe_diagnostic_with_raw_ptr() { fn missing_unsafe_diagnostic_with_raw_ptr() {
@ -467,4 +451,19 @@ fn main() {
"#, "#,
) )
} }
#[test]
fn unsafe_expr_in_macro_call() {
check_no_fix(
r#"
unsafe fn foo() -> u8 {
0
}
fn main() {
let x = format!("foo: {}", foo$0());
}
"#,
)
}
} }