src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs RUST 1,767 lines View on github.com → Search inside
1//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.23mod format_like;45use base_db::SourceDatabase;6use hir::{ItemInNs, Semantics};7use ide_db::{8    RootDatabase, SnippetCap,9    documentation::{Documentation, HasDocs},10    imports::insert_use::ImportScope,11    source_change::SnippetEdit,12    syntax_helpers::suggest_name::NameGenerator,13    text_edit::TextEdit,14    ty_filter::TryEnum,15};16use itertools::{Either, Itertools};17use stdx::never;18use syntax::{19    SmolStr,20    SyntaxKind::{CLOSURE_EXPR, EXPR_STMT, MATCH_ARM, STMT_LIST},21    T, TextRange, TextSize, ToSmolStr,22    ast::{self, AstNode, AstToken},23    format_smolstr, match_ast,24};2526use crate::{27    CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,28    completions::postfix::format_like::add_format_like_completions,29    context::{BreakableKind, CompletionContext, DotAccess, DotAccessKind},30    item::{Builder, CompletionRelevancePostfixMatch},31};3233pub(crate) fn complete_postfix(34    acc: &mut Completions,35    ctx: &CompletionContext<'_, '_>,36    dot_access: &DotAccess<'_>,37) {38    if !ctx.config.enable_postfix_completions {39        return;40    }4142    let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {43        DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (44            it,45            &ty.original,46            match *kind {47                DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {48                    receiver_is_ambiguous_float_literal49                }50                DotAccessKind::Method => false,51            },52        ),53        _ => return,54    };55    let expr_ctx = &dot_access.ctx;56    let receiver_accessor = receiver_accessor(dot_receiver);5758    let receiver_text =59        get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);6061    let cap = match ctx.config.snippet_cap {62        Some(it) => it,63        None => return,64    };6566    let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {67        Some(it) => it,68        None => return,69    };70    let semi =71        if expr_ctx.in_block_expr && ctx.token.next_token().is_none_or(|it| it.kind() != T![;]) {72            ";"73        } else {74            ""75        };7677    let cfg = ctx.config.find_path_config(ctx.is_nightly);7879    if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop()80        && receiver_ty.impls_trait(ctx.db, drop_trait, &[])81        && let Some(drop_fn) = ctx.famous_defs().core_mem_drop()82        && let Some(path) = ctx.module.find_path(ctx.db, ItemInNs::Values(drop_fn.into()), cfg)83    {84        cov_mark::hit!(postfix_drop_completion);85        let mut item = postfix_snippet(86            "drop",87            "fn drop(&mut self)",88            format!("{path}($0{receiver_text})", path = path.display(ctx.db, ctx.edition)),89        );90        item.set_documentation(drop_fn.docs(ctx.db));91        item.add_to(acc, ctx.db);92    }9394    postfix_snippet("ref", "&expr", format!("&{receiver_text}")).add_to(acc, ctx.db);95    postfix_snippet("refm", "&mut expr", format!("&mut {receiver_text}")).add_to(acc, ctx.db);96    postfix_snippet("deref", "*expr", format!("*{receiver_text}")).add_to(acc, ctx.db);9798    // The rest of the postfix completions create an expression that moves an argument,99    // so it's better to consider references now to avoid breaking the compilation100101    let (dot_receiver_including_refs, prefix) = include_references(&receiver_accessor);102    let mut receiver_text = receiver_text;103    receiver_text.insert_str(0, &prefix);104    let postfix_snippet =105        match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {106            Some(it) => it,107            None => return,108        };109110    if !ctx.config.snippets.is_empty() {111        add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);112    }113114    postfix_snippet("box", "Box::new(expr)", format!("Box::new({receiver_text})"))115        .add_to(acc, ctx.db);116    postfix_snippet("dbg", "dbg!(expr)", format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme117    postfix_snippet("dbgr", "dbg!(&expr)", format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);118    postfix_snippet("call", "function(expr)", format!("${{1}}({receiver_text})"))119        .add_to(acc, ctx.db);120121    if let Some(expected_ty) = ctx.expected_type.as_ref()122        && let Some(adt) = expected_ty.as_adt()123    {124        let is_valid_new = expected_ty125            .iterate_assoc_items(ctx.db, |item| {126                if let hir::AssocItem::Function(func) = item127                    && func.name(ctx.db) == hir::sym::new128                    && !func.has_self_param(ctx.db)129                {130                    let params = func.params_without_self(ctx.db);131                    if params.len() == 1 {132                        return Some(());133                    }134                }135                None136            })137            .is_some();138139        let adt = hir::ModuleDef::from(adt);140        if is_valid_new && let Some(path) = ctx.module.find_path(ctx.db, adt, cfg) {141            let ty_name = path.display(ctx.db, ctx.display_target.edition).to_smolstr();142143            postfix_snippet(144                "new",145                &format_smolstr!("{}::new(expr)", ty_name),146                format!("{}::new({}$0)", ty_name, receiver_text),147            )148            .add_to(acc, ctx.db);149        }150    }151152    let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);153    let is_in_cond = is_in_condition(&dot_receiver_including_refs);154    let is_in_value = is_in_value(&dot_receiver_including_refs);155    if let Some(parent) = dot_receiver_including_refs.syntax().parent() {156        let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);157        match &try_enum {158            Some(try_enum) if is_in_cond => match try_enum {159                TryEnum::Result => {160                    postfix_snippet(161                        "let",162                        "let Ok(_)",163                        format!("let Ok({placeholder}) = {receiver_text}"),164                    )165                    .add_to(acc, ctx.db);166                    postfix_snippet(167                        "letm",168                        "let Ok(mut _)",169                        format!("let Ok(mut {placeholder}) = {receiver_text}"),170                    )171                    .add_to(acc, ctx.db);172                }173                TryEnum::Option => {174                    postfix_snippet(175                        "let",176                        "let Some(_)",177                        format!("let Some({placeholder}) = {receiver_text}"),178                    )179                    .add_to(acc, ctx.db);180                    postfix_snippet(181                        "letm",182                        "let Some(mut _)",183                        format!("let Some(mut {placeholder}) = {receiver_text}"),184                    )185                    .add_to(acc, ctx.db);186                }187            },188            _ if is_in_cond => {189                postfix_snippet("let", "let", format!("let $1 = {receiver_text}"))190                    .add_to(acc, ctx.db);191            }192            _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {193                postfix_snippet("let", "let", format!("let $0 = {receiver_text}{semi}"))194                    .add_to(acc, ctx.db);195                postfix_snippet("letm", "let mut", format!("let mut $0 = {receiver_text}{semi}"))196                    .add_to(acc, ctx.db);197            }198            _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => {199                postfix_snippet(200                    "let",201                    "let",202                    format!("{{\n    let $1 = {receiver_text};\n    $0\n}}"),203                )204                .add_to(acc, ctx.db);205                postfix_snippet(206                    "letm",207                    "let mut",208                    format!("{{\n    let mut $1 = {receiver_text};\n    $0\n}}"),209                )210                .add_to(acc, ctx.db);211            }212            _ => (),213        }214    }215216    if !is_in_cond {217        match try_enum {218            Some(try_enum) => match try_enum {219                TryEnum::Result => {220                    postfix_snippet(221                    "match",222                    "match expr {}",223                    format!("match {receiver_text} {{\n    Ok(${{1:_}}) => {{$2}},\n    Err(${{3:_}}) => {{$0}},\n}}"),224                )225                .add_to(acc, ctx.db);226                }227                TryEnum::Option => {228                    postfix_snippet(229                    "match",230                    "match expr {}",231                    format!(232                        "match {receiver_text} {{\n    Some(${{1:_}}) => {{$2}},\n    None => {{$0}},\n}}"233                    ),234                )235                .add_to(acc, ctx.db);236                }237            },238            None => {239                postfix_snippet(240                    "match",241                    "match expr {}",242                    format!("match {receiver_text} {{\n    ${{1:_}} => {{$0}},\n}}"),243                )244                .add_to(acc, ctx.db);245            }246        }247        if let Some(try_enum) = &try_enum {248            let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema);249            let if_then_snip =250                if is_in_value { "{\n    $2\n} else {\n    $0\n}" } else { "{\n    $0\n}" };251            match try_enum {252                TryEnum::Result => {253                    postfix_snippet(254                        "ifl",255                        "if let Ok {}",256                        format!("if let Ok({placeholder}) = {receiver_text} {if_then_snip}"),257                    )258                    .add_to(acc, ctx.db);259260                    postfix_snippet(261                        "lete",262                        "let Ok else {}",263                        format!("let Ok({placeholder}) = {receiver_text} else {{\n    $2\n}};$0"),264                    )265                    .add_to(acc, ctx.db);266267                    postfix_snippet(268                        "while",269                        "while let Ok {}",270                        format!("while let Ok({placeholder}) = {receiver_text} {{\n    $0\n}}"),271                    )272                    .add_to(acc, ctx.db);273                }274                TryEnum::Option => {275                    postfix_snippet(276                        "ifl",277                        "if let Some {}",278                        format!("if let Some({placeholder}) = {receiver_text} {if_then_snip}"),279                    )280                    .add_to(acc, ctx.db);281282                    postfix_snippet(283                        "lete",284                        "let Some else {}",285                        format!("let Some({placeholder}) = {receiver_text} else {{\n    $2\n}};$0"),286                    )287                    .add_to(acc, ctx.db);288289                    postfix_snippet(290                        "while",291                        "while let Some {}",292                        format!("while let Some({placeholder}) = {receiver_text} {{\n    $0\n}}"),293                    )294                    .add_to(acc, ctx.db);295                }296            }297        } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {298            let if_then_snip =299                if is_in_value { "{\n    $1\n} else {\n    $0\n}" } else { "{\n    $0\n}" };300            postfix_snippet("if", "if expr {}", format!("if {receiver_text} {if_then_snip}"))301                .add_to(acc, ctx.db);302            postfix_snippet(303                "while",304                "while expr {}",305                format!("while {receiver_text} {{\n    $0\n}}"),306            )307            .add_to(acc, ctx.db);308        } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()309            && receiver_ty.impls_trait(ctx.db, trait_, &[])310        {311            postfix_snippet(312                "for",313                "for ele in expr {}",314                format!("for ele in {receiver_text} {{\n    $0\n}}"),315            )316            .add_to(acc, ctx.db);317        }318    }319320    if receiver_ty.is_bool() || receiver_ty.is_unknown() {321        postfix_snippet("not", "!expr", format!("!{receiver_text}")).add_to(acc, ctx.db);322    }323324    let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver {325        block.modifier().is_some() || !block.is_standalone()326    } else {327        true328    };329    {330        let (open_brace, close_brace) =331            if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };332        // FIXME: Why add parentheses333        let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };334        let unsafe_completion_string =335            format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}");336        postfix_snippet("unsafe", "unsafe {}", unsafe_completion_string).add_to(acc, ctx.db);337338        let const_completion_string =339            format!("{open_paren}const {open_brace}{receiver_text}{close_brace}{close_paren}");340        postfix_snippet("const", "const {}", const_completion_string).add_to(acc, ctx.db);341    }342343    if let ast::Expr::Literal(literal) = dot_receiver.clone()344        && let Some(literal_text) = ast::String::cast(literal.token())345    {346        add_format_like_completions(acc, ctx, dot_receiver, cap, &literal_text, semi);347    }348349    postfix_snippet("return", "return expr", format!("return {receiver_text}{semi}"))350        .add_to(acc, ctx.db);351352    if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {353        postfix_snippet("break", "break expr", format!("break {receiver_text}{semi}"))354            .add_to(acc, ctx.db);355    }356}357358fn suggest_receiver_name(359    receiver: &ast::Expr,360    n: &str,361    sema: &Semantics<'_, RootDatabase>,362) -> SmolStr {363    let placeholder = |name| format_smolstr!("${{{n}:{name}}}");364365    match receiver {366        ast::Expr::PathExpr(path) => {367            if let Some(name) = path.path().and_then(|it| it.as_single_name_ref()) {368                return placeholder(name.text());369            }370        }371        ast::Expr::RefExpr(it) => {372            if let Some(receiver) = it.expr() {373                return suggest_receiver_name(&receiver, n, sema);374            }375        }376        _ => {}377    }378379    let name = NameGenerator::new_with_names([].into_iter()).try_for_variable(receiver, sema);380    match name {381        Some(name) => placeholder(&name),382        None => format_smolstr!("${n}"),383    }384}385386fn get_receiver_text(387    sema: &Semantics<'_, RootDatabase>,388    receiver: &ast::Expr,389    receiver_is_ambiguous_float_literal: bool,390) -> String {391    // Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.392    let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {393        return receiver.to_string();394    };395    if receiver_is_ambiguous_float_literal {396        range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))397    }398    let file_text = sema.db.file_text(range.file_id.file_id(sema.db));399    let text = file_text.text(sema.db);400    let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.end())]);401    let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);402403    // The receiver texts should be interpreted as-is, as they are expected to be404    // normal Rust expressions.405    SnippetEdit::escape_snippet_bits(&mut text);406    return text;407408    fn indent_of_tail_line(text: &str) -> usize {409        let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s);410        let trimmed = tail_line.trim_start_matches(' ');411        tail_line.len() - trimmed.len()412    }413}414415fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {416    receiver417        .syntax()418        .parent()419        .and_then(ast::Expr::cast)420        .filter(|it| {421            matches!(422                it,423                ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_)424            )425        })426        .unwrap_or_else(|| receiver.clone())427}428429/// Given an `initial_element`, tries to expand it to include deref(s), not(s), and then references.430/// Returns the expanded expressions, and the added prefix as a string431///432/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`.433fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {434    let mut resulting_element = initial_element.clone();435    let mut prefix = String::new();436437    while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)438        && parent.op_kind() == Some(ast::UnaryOp::Deref)439    {440        resulting_element = ast::Expr::from(parent);441        prefix.insert(0, '*');442    }443444    while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)445        && parent.op_kind() == Some(ast::UnaryOp::Not)446    {447        resulting_element = ast::Expr::from(parent);448        prefix.insert(0, '!');449    }450451    while let Some(parent_ref_element) =452        resulting_element.syntax().parent().and_then(ast::RefExpr::cast)453    {454        let last_child_or_token = parent_ref_element.syntax().last_child_or_token();455        prefix.insert_str(456            0,457            parent_ref_element458                .syntax()459                .children_with_tokens()460                .filter(|it| Some(it) != last_child_or_token.as_ref())461                .flat_map(|it| {462                    let has_ws = it.next_sibling_or_token().is_some_and(|it| it.kind().is_trivia());463                    let need_ws = !has_ws && it.kind().is_any_identifier();464                    itertools::chain([Either::Left(it)], need_ws.then_some(Either::Right(" ")))465                })466                .format("")467                .to_smolstr()468                .as_str(),469        );470        resulting_element = ast::Expr::from(parent_ref_element);471    }472473    (resulting_element, prefix)474}475476fn build_postfix_snippet_builder<'ctx>(477    ctx: &'ctx CompletionContext<'_, '_>,478    cap: SnippetCap,479    receiver: &'ctx ast::Expr,480) -> Option<impl Fn(&str, &str, String) -> Builder + 'ctx> {481    let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;482    if ctx.source_range().end() < receiver_range.start() {483        // This shouldn't happen, yet it does. I assume this might be due to an incorrect token484        // mapping.485        never!();486        return None;487    }488    let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());489490    // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that491    // can't be annotated for the closure, hence fix it by constructing it without the Option first492    fn build<'ctx>(493        ctx: &'ctx CompletionContext<'_, '_>,494        cap: SnippetCap,495        delete_range: TextRange,496    ) -> impl Fn(&str, &str, String) -> Builder + 'ctx {497        move |label, detail, snippet| {498            let edit = TextEdit::replace(delete_range, snippet);499            let mut item = CompletionItem::new(500                CompletionItemKind::Snippet,501                ctx.source_range(),502                label,503                ctx.edition,504            );505            item.detail(detail).snippet_edit(cap, edit);506            let postfix_match = if ctx.original_token.text() == label {507                cov_mark::hit!(postfix_exact_match_is_high_priority);508                Some(CompletionRelevancePostfixMatch::Exact)509            } else {510                cov_mark::hit!(postfix_inexact_match_is_low_priority);511                Some(CompletionRelevancePostfixMatch::NonExact)512            };513            let relevance = CompletionRelevance { postfix_match, ..Default::default() };514            item.set_relevance(relevance);515            item516        }517    }518    Some(build(ctx, cap, delete_range))519}520521fn add_custom_postfix_completions(522    acc: &mut Completions,523    ctx: &CompletionContext<'_, '_>,524    postfix_snippet: impl Fn(&str, &str, String) -> Builder,525    receiver_text: &str,526) -> Option<()> {527    ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;528    ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(529        |(trigger, snippet)| {530            let imports = match snippet.imports(ctx) {531                Some(imports) => imports,532                None => return,533            };534            let body = snippet.postfix_snippet(receiver_text);535            let document = Documentation::new_owned(format!("```rust\n{body}\n```"));536            let mut builder =537                postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), body);538            builder.documentation(document);539            for import in imports.into_iter() {540                builder.add_import(import);541            }542            builder.add_to(acc, ctx.db);543        },544    );545    None546}547548pub(crate) fn is_in_condition(it: &ast::Expr) -> bool {549    it.syntax()550        .parent()551        .and_then(|parent| {552            Some(match_ast! { match parent {553                ast::IfExpr(expr) => expr.condition()? == *it,554                ast::WhileExpr(expr) => expr.condition()? == *it,555                ast::MatchGuard(guard) => guard.condition()? == *it,556                ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&])557                    .then(|| is_in_condition(&bin_expr.into()))?,558                ast::Expr(expr) => (expr.syntax().text_range().start() == it.syntax().text_range().start())559                    .then(|| is_in_condition(&expr))?,560                _ => return None,561            } })562        })563        .unwrap_or(false)564}565566pub(crate) fn is_in_value(it: &ast::Expr) -> bool {567    let Some(node) = it.syntax().parent() else { return false };568    let kind = node.kind();569    ast::LetStmt::can_cast(kind)570        || ast::ArgList::can_cast(kind)571        || ast::ArrayExpr::can_cast(kind)572        || ast::ParenExpr::can_cast(kind)573        || ast::BreakExpr::can_cast(kind)574        || ast::ReturnExpr::can_cast(kind)575        || ast::PrefixExpr::can_cast(kind)576        || ast::FormatArgsArg::can_cast(kind)577        || ast::RecordExprField::can_cast(kind)578        || ast::BinExpr::cast(node.clone()).is_some_and(|expr| expr.rhs().as_ref() == Some(it))579        || ast::IndexExpr::cast(node).is_some_and(|expr| expr.index().as_ref() == Some(it))580}581582#[cfg(test)]583mod tests {584    use expect_test::expect;585586    use crate::{587        CompletionConfig, Snippet,588        tests::{TEST_CONFIG, check, check_edit, check_edit_with_config},589    };590591    #[test]592    fn postfix_completion_works_for_trivial_path_expression() {593        check(594            r#"595fn main() {596    let bar = true;597    bar.$0598}599"#,600            expect![[r#"601                sn box  Box::new(expr)602                sn call function(expr)603                sn const      const {}604                sn dbg      dbg!(expr)605                sn dbgr    dbg!(&expr)606                sn deref         *expr607                sn if       if expr {}608                sn let             let609                sn letm        let mut610                sn match match expr {}611                sn not           !expr612                sn ref           &expr613                sn refm      &mut expr614                sn return  return expr615                sn unsafe    unsafe {}616                sn while while expr {}617            "#]],618        );619    }620621    #[test]622    fn postfix_completion_works_for_function_calln() {623        check(624            r#"625fn foo(elt: bool) -> bool {626    !elt627}628629fn main() {630    let bar = true;631    foo(bar.$0)632}633"#,634            expect![[r#"635                sn box  Box::new(expr)636                sn call function(expr)637                sn const      const {}638                sn dbg      dbg!(expr)639                sn dbgr    dbg!(&expr)640                sn deref         *expr641                sn if       if expr {}642                sn match match expr {}643                sn not           !expr644                sn ref           &expr645                sn refm      &mut expr646                sn return  return expr647                sn unsafe    unsafe {}648                sn while while expr {}649            "#]],650        );651    }652653    #[test]654    fn postfix_completion_works_in_if_condition() {655        check(656            r#"657fn foo(cond: bool) {658    if cond.$0659}660"#,661            expect![[r#"662                sn box  Box::new(expr)663                sn call function(expr)664                sn const      const {}665                sn dbg      dbg!(expr)666                sn dbgr    dbg!(&expr)667                sn deref         *expr668                sn let             let669                sn not           !expr670                sn ref           &expr671                sn refm      &mut expr672                sn return  return expr673                sn unsafe    unsafe {}674            "#]],675        );676    }677678    #[test]679    fn postfix_type_filtering() {680        check(681            r#"682fn main() {683    let bar: u8 = 12;684    bar.$0685}686"#,687            expect![[r#"688                sn box  Box::new(expr)689                sn call function(expr)690                sn const      const {}691                sn dbg      dbg!(expr)692                sn dbgr    dbg!(&expr)693                sn deref         *expr694                sn let             let695                sn letm        let mut696                sn match match expr {}697                sn ref           &expr698                sn refm      &mut expr699                sn return  return expr700                sn unsafe    unsafe {}701            "#]],702        )703    }704705    #[test]706    fn let_middle_block() {707        check_edit(708            "let",709            r#"710fn main() {711    baz.l$0712    res713}714"#,715            r#"716fn main() {717    let $0 = baz;718    res719}720"#,721        );722723        check(724            r#"725fn main() {726    baz.l$0727    res728}729"#,730            expect![[r#"731                sn box  Box::new(expr)732                sn call function(expr)733                sn const      const {}734                sn dbg      dbg!(expr)735                sn dbgr    dbg!(&expr)736                sn deref         *expr737                sn if       if expr {}738                sn let             let739                sn letm        let mut740                sn match match expr {}741                sn not           !expr742                sn ref           &expr743                sn refm      &mut expr744                sn return  return expr745                sn unsafe    unsafe {}746                sn while while expr {}747            "#]],748        );749        check(750            r#"751fn main() {752    &baz.l$0753    res754}755"#,756            expect![[r#"757                sn box  Box::new(expr)758                sn call function(expr)759                sn const      const {}760                sn dbg      dbg!(expr)761                sn dbgr    dbg!(&expr)762                sn deref         *expr763                sn if       if expr {}764                sn let             let765                sn letm        let mut766                sn match match expr {}767                sn not           !expr768                sn ref           &expr769                sn refm      &mut expr770                sn return  return expr771                sn unsafe    unsafe {}772                sn while while expr {}773            "#]],774        );775    }776777    #[test]778    fn let_tail_block() {779        check_edit(780            "let",781            r#"782fn main() {783    baz.l$0784}785"#,786            r#"787fn main() {788    let $0 = baz;789}790"#,791        );792793        check(794            r#"795fn main() {796    baz.l$0797}798"#,799            expect![[r#"800                sn box  Box::new(expr)801                sn call function(expr)802                sn const      const {}803                sn dbg      dbg!(expr)804                sn dbgr    dbg!(&expr)805                sn deref         *expr806                sn if       if expr {}807                sn let             let808                sn letm        let mut809                sn match match expr {}810                sn not           !expr811                sn ref           &expr812                sn refm      &mut expr813                sn return  return expr814                sn unsafe    unsafe {}815                sn while while expr {}816            "#]],817        );818819        check(820            r#"821fn main() {822    &baz.l$0823}824"#,825            expect![[r#"826                sn box  Box::new(expr)827                sn call function(expr)828                sn const      const {}829                sn dbg      dbg!(expr)830                sn dbgr    dbg!(&expr)831                sn deref         *expr832                sn if       if expr {}833                sn let             let834                sn letm        let mut835                sn match match expr {}836                sn not           !expr837                sn ref           &expr838                sn refm      &mut expr839                sn return  return expr840                sn unsafe    unsafe {}841                sn while while expr {}842            "#]],843        );844    }845846    #[test]847    fn let_before_semicolon() {848        check_edit(849            "let",850            r#"851fn main() {852    baz.l$0;853}854"#,855            r#"856fn main() {857    let $0 = baz;858}859"#,860        );861    }862863    #[test]864    fn option_iflet() {865        check_edit(866            "ifl",867            r#"868//- minicore: option869fn main() {870    let bar = Some(true);871    bar.$0872}873"#,874            r#"875fn main() {876    let bar = Some(true);877    if let Some(${1:bar}) = bar {878    $0879}880}881"#,882        );883    }884885    #[test]886    fn option_iflet_cond() {887        check(888            r#"889//- minicore: option890fn main() {891    let bar = Some(true);892    if bar.$0893}894"#,895            expect![[r#"896                me and(…)    fn(self, Option<U>) -> Option<U>897                me as_ref()     const fn(&self) -> Option<&T>898                me ok_or(…) const fn(self, E) -> Result<T, E>899                me unwrap()               const fn(self) -> T900                me unwrap_or(…)              fn(self, T) -> T901                sn box                         Box::new(expr)902                sn call                        function(expr)903                sn const                             const {}904                sn dbg                             dbg!(expr)905                sn dbgr                           dbg!(&expr)906                sn deref                                *expr907                sn let                            let Some(_)908                sn letm                       let Some(mut _)909                sn ref                                  &expr910                sn refm                             &mut expr911                sn return                         return expr912                sn unsafe                           unsafe {}913            "#]],914        );915        check_edit(916            "let",917            r#"918//- minicore: option919fn main() {920    let bar = Some(true);921    if bar.$0922}923"#,924            r#"925fn main() {926    let bar = Some(true);927    if let Some(${0:bar}) = bar928}929"#,930        );931        check_edit(932            "let",933            r#"934//- minicore: option935fn main() {936    let bar = Some(true);937    if true && bar.$0938}939"#,940            r#"941fn main() {942    let bar = Some(true);943    if true && let Some(${0:bar}) = bar944}945"#,946        );947        check_edit(948            "let",949            r#"950//- minicore: option951fn main() {952    let bar = Some(true);953    if true && true && bar.$0954}955"#,956            r#"957fn main() {958    let bar = Some(true);959    if true && true && let Some(${0:bar}) = bar960}961"#,962        );963    }964965    #[test]966    fn iflet_fallback_cond() {967        check_edit(968            "let",969            r#"970fn main() {971    let bar = 2;972    if bar.$0973}974"#,975            r#"976fn main() {977    let bar = 2;978    if let $1 = bar979}980"#,981        );982    }983984    #[test]985    fn match_arm_let_block() {986        check(987            r#"988fn main() {989    match 2 {990        bar => bar.$0991    }992}993"#,994            expect![[r#"995                sn box  Box::new(expr)996                sn call function(expr)997                sn const      const {}998                sn dbg      dbg!(expr)999                sn dbgr    dbg!(&expr)1000                sn deref         *expr1001                sn let             let1002                sn letm        let mut1003                sn match match expr {}1004                sn ref           &expr1005                sn refm      &mut expr1006                sn return  return expr1007                sn unsafe    unsafe {}1008            "#]],1009        );1010        check(1011            r#"1012fn main() {1013    match 2 {1014        bar => &bar.l$01015    }1016}1017"#,1018            expect![[r#"1019                sn box  Box::new(expr)1020                sn call function(expr)1021                sn const      const {}1022                sn dbg      dbg!(expr)1023                sn dbgr    dbg!(&expr)1024                sn deref         *expr1025                sn let             let1026                sn letm        let mut1027                sn match match expr {}1028                sn ref           &expr1029                sn refm      &mut expr1030                sn return  return expr1031                sn unsafe    unsafe {}1032            "#]],1033        );1034        check_edit(1035            "let",1036            r#"1037fn main() {1038    match 2 {1039        bar => bar.$01040    }1041}1042"#,1043            r#"1044fn main() {1045    match 2 {1046        bar => {1047    let $1 = bar;1048    $01049}1050    }1051}1052"#,1053        );1054    }10551056    #[test]1057    fn closure_let_block() {1058        check_edit(1059            "let",1060            r#"1061fn main() {1062    let bar = 2;1063    let f = || bar.$0;1064}1065"#,1066            r#"1067fn main() {1068    let bar = 2;1069    let f = || {1070    let $1 = bar;1071    $01072};1073}1074"#,1075        );1076    }10771078    #[test]1079    fn option_letelse() {1080        check_edit(1081            "lete",1082            r#"1083//- minicore: option1084fn main() {1085    let bar = Some(true);1086    bar.$01087}1088"#,1089            r#"1090fn main() {1091    let bar = Some(true);1092    let Some(${1:bar}) = bar else {1093    $21094};$01095}1096"#,1097        );10981099        check_edit(1100            "lete",1101            r#"1102//- minicore: option1103fn main() {1104    let bar = Some(true);1105    bar.$01106    other();1107}1108"#,1109            r#"1110fn main() {1111    let bar = Some(true);1112    let Some(${1:bar}) = bar else {1113    $21114};$01115    other();1116}1117"#,1118        );1119    }11201121    #[test]1122    fn result_match() {1123        check_edit(1124            "match",1125            r#"1126//- minicore: result1127fn main() {1128    let bar = Ok(true);1129    bar.$01130}1131"#,1132            r#"1133fn main() {1134    let bar = Ok(true);1135    match bar {1136    Ok(${1:_}) => {$2},1137    Err(${3:_}) => {$0},1138}1139}1140"#,1141        );1142    }11431144    #[test]1145    fn postfix_completion_works_for_ambiguous_float_literal() {1146        check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)1147    }11481149    #[test]1150    fn works_in_simple_macro() {1151        check_edit(1152            "dbg",1153            r#"1154macro_rules! m { ($e:expr) => { $e } }1155fn main() {1156    let bar: u8 = 12;1157    m!(bar.d$0)1158}1159"#,1160            r#"1161macro_rules! m { ($e:expr) => { $e } }1162fn main() {1163    let bar: u8 = 12;1164    m!(dbg!(bar))1165}1166"#,1167        );1168    }11691170    #[test]1171    fn postfix_completion_for_references() {1172        check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);1173        check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);1174        check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);1175        check_edit(1176            "ifl",1177            r#"1178//- minicore: option1179fn main() {1180    let bar = &Some(true);1181    bar.$01182}1183"#,1184            r#"1185fn main() {1186    let bar = &Some(true);1187    if let Some(${1:bar}) = bar {1188    $01189}1190}1191"#,1192        )1193    }11941195    #[test]1196    fn postfix_completion_for_nots() {1197        check_edit(1198            "if",1199            r#"1200fn main() {1201    let is_foo = true;1202    !is_foo.$01203}1204"#,1205            r#"1206fn main() {1207    let is_foo = true;1208    if !is_foo {1209    $01210}1211}1212"#,1213        )1214    }12151216    #[test]1217    fn postfix_completion_if_else_in_value() {1218        check_edit(1219            "if",1220            r#"1221fn main() {1222    let s = cond.is_some().$0;1223}1224"#,1225            r#"1226fn main() {1227    let s = if cond.is_some() {1228    $11229} else {1230    $01231};1232}1233"#,1234        );12351236        check_edit(1237            "ifl",1238            r#"1239//- minicore: option1240fn main() {1241    let cond = Some("x");1242    let s = cond.$0;1243}1244"#,1245            r#"1246fn main() {1247    let cond = Some("x");1248    let s = if let Some(${1:cond}) = cond {1249    $21250} else {1251    $01252};1253}1254"#,1255        );12561257        check_edit(1258            "if",1259            r#"1260fn main() {1261    2 + true.$0;1262}1263"#,1264            r#"1265fn main() {1266    2 + if true {1267    $11268} else {1269    $01270};1271}1272"#,1273        );1274    }12751276    #[test]1277    fn postfix_completion_for_unsafe() {1278        postfix_completion_for_block("unsafe");1279    }12801281    #[test]1282    fn postfix_completion_for_const() {1283        postfix_completion_for_block("const");1284    }12851286    fn postfix_completion_for_block(kind: &str) {1287        check_edit(kind, r#"fn main() { foo.$0 }"#, &format!("fn main() {{ {kind} {{ foo }} }}"));1288        check_edit(1289            kind,1290            r#"fn main() { { foo }.$0 }"#,1291            &format!("fn main() {{ {kind} {{ foo }} }}"),1292        );1293        check_edit(1294            kind,1295            r#"fn main() { if x { foo }.$0 }"#,1296            &format!("fn main() {{ {kind} {{ if x {{ foo }} }} }}"),1297        );1298        check_edit(1299            kind,1300            r#"fn main() { loop { foo }.$0 }"#,1301            &format!("fn main() {{ {kind} {{ loop {{ foo }} }} }}"),1302        );1303        check_edit(1304            kind,1305            r#"fn main() { if true {}.$0 }"#,1306            &format!("fn main() {{ {kind} {{ if true {{}} }} }}"),1307        );1308        check_edit(1309            kind,1310            r#"fn main() { while true {}.$0 }"#,1311            &format!("fn main() {{ {kind} {{ while true {{}} }} }}"),1312        );1313        check_edit(1314            kind,1315            r#"1316//- minicore: iterator1317fn main() { for i in 0..10 {}.$0 }"#,1318            &format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),1319        );1320        check_edit(1321            kind,1322            r#"fn main() { let x = if true {1} else {2}.$0 }"#,1323            &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"),1324        );13251326        if kind == "const" {1327            check_edit(1328                kind,1329                r#"fn main() { unsafe {1}.$0 }"#,1330                &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),1331            );1332        } else {1333            check_edit(1334                kind,1335                r#"fn main() { const {1}.$0 }"#,1336                &format!("fn main() {{ {kind} {{ const {{1}} }} }}"),1337            );1338        }13391340        // completion will not be triggered1341        check_edit(1342            kind,1343            r#"fn main() { let x = true else {panic!()}.$0}"#,1344            &format!("fn main() {{ let x = true else {{panic!()}}.{kind} $0}}"),1345        );1346    }13471348    #[test]1349    fn custom_postfix_completion() {1350        let config = CompletionConfig {1351            snippets: vec![1352                Snippet::new(1353                    &[],1354                    &["break".into()],1355                    &["ControlFlow::Break(${receiver})".into()],1356                    "",1357                    &["core::ops::ControlFlow".into()],1358                    crate::SnippetScope::Expr,1359                )1360                .unwrap(),1361            ],1362            ..TEST_CONFIG1363        };13641365        check_edit_with_config(1366            config.clone(),1367            "break",1368            r#"1369//- minicore: try1370fn main() { 42.$0 }1371"#,1372            r#"1373use core::ops::ControlFlow;13741375fn main() { ControlFlow::Break(42) }1376"#,1377        );13781379        // The receiver texts should be escaped, see comments in `get_receiver_text()`1380        // for detail.1381        //1382        // Note that the last argument is what *lsp clients would see* rather than1383        // what users would see. Unescaping happens thereafter.1384        check_edit_with_config(1385            config.clone(),1386            "break",1387            r#"1388//- minicore: try1389fn main() { '\\'.$0 }1390"#,1391            r#"1392use core::ops::ControlFlow;13931394fn main() { ControlFlow::Break('\\\\') }1395"#,1396        );13971398        check_edit_with_config(1399            config,1400            "break",1401            r#"1402//- minicore: try1403fn main() {1404    match true {1405        true => "${1:placeholder}",1406        false => "\$",1407    }.$01408}1409"#,1410            r#"1411use core::ops::ControlFlow;14121413fn main() {1414    ControlFlow::Break(match true {1415    true => "\${1:placeholder}",1416    false => "\\\$",1417})1418}1419"#,1420        );1421    }14221423    #[test]1424    fn postfix_completion_for_format_like_strings() {1425        check_edit(1426            "format",1427            r#"fn main() { "{some_var:?}".$0 }"#,1428            r#"fn main() { format!("{some_var:?}") }"#,1429        );1430        check_edit(1431            "panic",1432            r#"fn main() { "Panic with {a}".$0 }"#,1433            r#"fn main() { panic!("Panic with {a}"); }"#,1434        );1435        check_edit(1436            "println",1437            r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,1438            r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#,1439        );1440        check_edit(1441            "loge",1442            r#"fn main() { "{2+2}".$0 }"#,1443            r#"fn main() { log::error!("{}", 2+2); }"#,1444        );1445        check_edit(1446            "logt",1447            r#"fn main() { "{2+2}".$0 }"#,1448            r#"fn main() { log::trace!("{}", 2+2); }"#,1449        );1450        check_edit(1451            "logd",1452            r#"fn main() { "{2+2}".$0 }"#,1453            r#"fn main() { log::debug!("{}", 2+2); }"#,1454        );1455        check_edit(1456            "logi",1457            r#"fn main() { "{2+2}".$0 }"#,1458            r#"fn main() { log::info!("{}", 2+2); }"#,1459        );1460        check_edit(1461            "logw",1462            r#"fn main() { "{2+2}".$0 }"#,1463            r#"fn main() { log::warn!("{}", 2+2); }"#,1464        );1465        check_edit(1466            "loge",1467            r#"fn main() { "{2+2}".$0 }"#,1468            r#"fn main() { log::error!("{}", 2+2); }"#,1469        );1470    }14711472    #[test]1473    fn postfix_custom_snippets_completion_for_references() {1474        // https://github.com/rust-lang/rust-analyzer/issues/792914751476        let snippet = Snippet::new(1477            &[],1478            &["ok".into()],1479            &["Ok(${receiver})".into()],1480            "",1481            &[],1482            crate::SnippetScope::Expr,1483        )1484        .unwrap();14851486        check_edit_with_config(1487            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1488            "ok",1489            r#"fn main() { &&42.o$0 }"#,1490            r#"fn main() { Ok(&&42) }"#,1491        );14921493        check_edit_with_config(1494            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1495            "ok",1496            r#"fn main() { &&42.$0 }"#,1497            r#"fn main() { Ok(&&42) }"#,1498        );14991500        check_edit_with_config(1501            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1502            "ok",1503            r#"fn main() { &raw mut 42.$0 }"#,1504            r#"fn main() { Ok(&raw mut 42) }"#,1505        );15061507        check_edit_with_config(1508            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1509            "ok",1510            r#"fn main() { &raw const 42.$0 }"#,1511            r#"fn main() { Ok(&raw const 42) }"#,1512        );15131514        check_edit_with_config(1515            CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },1516            "ok",1517            r#"1518struct A {1519    a: i32,1520}15211522fn main() {1523    let a = A {a :1};1524    &a.a.$01525}1526            "#,1527            r#"1528struct A {1529    a: i32,1530}15311532fn main() {1533    let a = A {a :1};1534    Ok(&a.a)1535}1536            "#,1537        );1538    }15391540    #[test]1541    fn postfix_custom_snippets_completion_for_reference_expr() {1542        // https://github.com/rust-lang/rust-analyzer/issues/210351543        let snippet = Snippet::new(1544            &[],1545            &["group".into()],1546            &["(${receiver})".into()],1547            "",1548            &[],1549            crate::SnippetScope::Expr,1550        )1551        .unwrap();15521553        check_edit_with_config(1554            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1555            "group",1556            r#"fn main() { &[1, 2, 3].g$0 }"#,1557            r#"fn main() { (&[1, 2, 3]) }"#,1558        );15591560        check_edit_with_config(1561            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1562            "group",1563            r#"fn main() { &&foo(a, b, 1+1).$0 }"#,1564            r#"fn main() { (&&foo(a, b, 1+1)) }"#,1565        );15661567        check_edit_with_config(1568            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1569            "group",1570            r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#,1571            r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#,1572        );15731574        check_edit_with_config(1575            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1576            "group",1577            r#"fn main() { &raw mut Foo::new().$0 }"#,1578            r#"fn main() { (&raw mut Foo::new()) }"#,1579        );15801581        check_edit_with_config(1582            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1583            "group",1584            r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,1585            r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,1586        );15871588        check_edit_with_config(1589            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1590            "group",1591            r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1592fn main() { id!(&raw const Foo::bar::SOME_CONST.$0) }"#,1593            r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1594fn main() { id!((&raw const Foo::bar::SOME_CONST)) }"#,1595        );1596    }15971598    #[test]1599    fn no_postfix_completions_in_if_block_that_has_an_else() {1600        check(1601            r#"1602fn test() {1603    if true {}.$0 else {}1604}1605"#,1606            expect![[r#""#]],1607        );1608    }16091610    #[test]1611    fn mut_ref_consuming() {1612        check_edit(1613            "call",1614            r#"1615fn main() {1616    let mut x = &mut 2;1617    &mut x.$0;1618}1619"#,1620            r#"1621fn main() {1622    let mut x = &mut 2;1623    ${1}(&mut x);1624}1625"#,1626        );1627    }16281629    #[test]1630    fn deref_consuming() {1631        check_edit(1632            "call",1633            r#"1634fn main() {1635    let mut x = &mut 2;1636    &mut *x.$0;1637}1638"#,1639            r#"1640fn main() {1641    let mut x = &mut 2;1642    ${1}(&mut *x);1643}1644"#,1645        );1646    }16471648    #[test]1649    fn inside_macro() {1650        check_edit(1651            "box",1652            r#"1653macro_rules! assert {1654    ( $it:expr $(,)? ) => { $it };1655}16561657fn foo() {1658    let a = true;1659    assert!(if a == false { true } else { false }.$0);1660}1661        "#,1662            r#"1663macro_rules! assert {1664    ( $it:expr $(,)? ) => { $it };1665}16661667fn foo() {1668    let a = true;1669    assert!(Box::new(if a == false { true } else { false }));1670}1671        "#,1672        );1673    }16741675    #[test]1676    fn snippet_dedent() {1677        check_edit(1678            "let",1679            r#"1680//- minicore: option1681fn foo(x: Option<i32>, y: Option<i32>) {1682    let _f = || {1683        x1684            .and(y)1685            .map(|it| {1686                it+21687            })1688            .$01689    };1690}1691"#,1692            r#"1693fn foo(x: Option<i32>, y: Option<i32>) {1694    let _f = || {1695        let $0 = x1696.and(y)1697.map(|it| {1698    it+21699});1700    };1701}1702"#,1703        );1704    }17051706    #[test]1707    fn postfix_new() {1708        check_edit(1709            "new",1710            r#"1711struct OtherThing;1712struct RefCell<T>(T);1713impl<T> RefCell<T> {1714    fn new(t: T) -> Self { RefCell(t) }1715}17161717fn main() {1718    let other_thing = OtherThing;1719    let thing: RefCell<OtherThing> = other_thing.$0;1720}1721"#,1722            r#"1723struct OtherThing;1724struct RefCell<T>(T);1725impl<T> RefCell<T> {1726    fn new(t: T) -> Self { RefCell(t) }1727}17281729fn main() {1730    let other_thing = OtherThing;1731    let thing: RefCell<OtherThing> = RefCell::new(other_thing$0);1732}1733"#,1734        );17351736        check_edit(1737            "new",1738            r#"1739mod foo {1740    pub struct OtherThing;1741    pub struct RefCell<T>(T);1742    impl<T> RefCell<T> {1743        pub fn new(t: T) -> Self { RefCell(t) }1744    }1745}17461747fn main() {1748    let thing: foo::RefCell<foo::OtherThing> = foo::OtherThing.$0;1749}1750"#,1751            r#"1752mod foo {1753    pub struct OtherThing;1754    pub struct RefCell<T>(T);1755    impl<T> RefCell<T> {1756        pub fn new(t: T) -> Self { RefCell(t) }1757    }1758}17591760fn main() {1761    let thing: foo::RefCell<foo::OtherThing> = foo::RefCell::new(foo::OtherThing$0);1762}1763"#,1764        );1765    }1766}

Code quality findings 48

Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}");
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
postfix_snippet("unsafe", "unsafe {}", unsafe_completion_string).add_to(acc, ctx.db);
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
sn unsafe unsafe {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
r#"fn main() { unsafe {1}.$0 }"#,
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
&format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.end())]);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap(),
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap();
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match *kind {
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
postfix_snippet("dbg", "dbg!(expr)", format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
postfix_snippet("dbgr", "dbg!(&expr)", format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
let mut resulting_element = initial_element.clone();
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
Some(match_ast! { match parent {
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbg dbg!(expr)
Info: `dbg!` macro is intended for temporary debugging. Remove before committing production code. Use proper logging instead.
info maintainability dbg-macro
sn dbgr dbg!(&expr)
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#,

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.