mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 14:13:58 +00:00
do not offer fixit for macro expansions and refactor the code
This commit is contained in:
parent
bf0322cd0c
commit
0c935732bc
1 changed files with 55 additions and 56 deletions
|
@ -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());
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue