Skip to content

Something went wrong with the snippet implementation #5419

@A4-Tacks

Description

@A4-Tacks

Result from CocInfo

## versions

vim version: VIM - Vi IMproved 9.1 9011362
node version: v23.9.0
coc.nvim version: 0.0.82-911d33a 2025-08-20 20:30:00 +0800
coc.nvim directory: /home/lrne/.vim/plugged/coc.nvim
term: dumb
platform: linux

## Log of coc.nvim

2025-09-03T20:41:21.287 INFO (pid:30821) [plugin] - coc.nvim initialized with node: v23.9.0 after 971
2025-09-03T20:41:21.297 INFO (pid:30821) [services] - LanguageClient Rust Analyzer Language Server state change: stopped => starting
2025-09-03T20:41:21.327 INFO (pid:30821) [language-client-index] - Language server "rust-analyzer" started with 30877
2025-09-03T20:41:21.561 INFO (pid:30821) [services] - LanguageClient Rust Analyzer Language Server state change: starting => running
2025-09-03T20:41:21.580 INFO (pid:30821) [services] - service rust-analyzer started
2025-09-03T20:42:04.805 INFO (pid:30821) [attach] - receive notification: codeAction [ 'V' ]
2025-09-03T20:42:20.004 ERROR (pid:30821) [extension:coc-rust-analyzer] - Applying snippet edit: {
  "range": {
    "start": {
      "line": 154,
      "character": 0
    },
    "end": {
      "line": 154,
      "character": 0
    }
  },
  "newText": "fn $0fun_name(ctx: &AssistContext<'_>) -> Option<(ast::BinExpr, ast::BinaryOp, syntax::TextRange)> {\n    let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;\n    let op = bin_expr.op_kind()?;\n    let op_range = bin_expr.op_token()?.text_range();\n    if !op_range.contains_range(ctx.selection_trimmed()) {\n        return None;\n    \\}\n    Some((bin_expr, op, op_range))\n\\}\n\n",
  "insertTextFormat": 2
}
2025-09-03T20:42:26.008 INFO (pid:30821) [attach] - receive notification: showInfo []
2025-09-03T20:42:41.672 INFO (pid:30821) [attach] - receive notification: codeAction [ 'V' ]
2025-09-03T20:42:42.638 ERROR (pid:30821) [extension:coc-rust-analyzer] - Applying snippet edit: {
  "range": {
    "start": {
      "line": 154,
      "character": 0
    },
    "end": {
      "line": 154,
      "character": 0
    }
  },
  "newText": "fn $0fun_name(ctx: &AssistContext<'_>) -> Option<(ast::BinExpr, ast::BinaryOp, syntax::TextRange)> {\n    let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;\n    let op = bin_expr.op_kind()?;\n    let op_range = bin_expr.op_token()?.text_range();\n    if !op_range.contains_range(ctx.selection_trimmed()) {\n        return None;\n    \\}\n    Some((bin_expr, op, op_range))\n\\}\n\n",
  "insertTextFormat": 2
}
2025-09-03T20:42:43.022 ERROR (pid:30821) [snippets-session] - Something went wrong with the snippet implementation {
  range: {
    start: { line: 40, character: 13 },
    end: { line: 40, character: 21 }
  },
  text: 'op, op_range)',
  rangeLength: 8
} (mut op, op_range), op, op_range) = fun_name(ctx)?;

    editor.add_mappings(make.finish_with_mappings());
    while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) {
        match parent_expr.op_kind() {
            Some(parent_op) if parent_op == op => {
                bin_expr = parent_expr;
            }
            _ => break,
        }
    }

    let op = bin_expr.op_kind()?;
    let (inv_token, prec) = match op {
        ast::BinaryOp::LogicOp(ast::LogicOp::And) => (SyntaxKind::PIPE2, ExprPrecedence::LOr),
        ast::BinaryOp::LogicOp(ast::LogicOp::Or) => (SyntaxKind::AMP2, ExprPrecedence::LAnd),
        _ => return None,
    };

    let make = SyntaxFactory::with_mappings();

    let demorganed = bin_expr.clone_subtree();
    let mut editor = SyntaxEditor::new(demorganed.syntax().clone());
    editor.replace(demorganed.op_token()?, make.token(inv_token));

    let mut exprs = VecDeque::from([
        (bin_expr.lhs()?, demorganed.lhs()?, prec),
        (bin_expr.rhs()?, demorganed.rhs()?, prec),
    ]);

    while let Some((expr, demorganed, prec)) = exprs.pop_front() {
        if let BinExpr(bin_expr) = &expr {
            if let BinExpr(cbin_expr) = &demorganed {
                if op == bin_expr.op_kind()? {
                    editor.replace(cbin_expr.op_token()?, make.token(inv_token));
                    exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?, prec));
                    exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?, prec));
                } else {
                    let mut inv = invert_boolean_expression(&make, expr);
                    if precedence(&inv).needs_parentheses_in(prec) {
                        inv = make.expr_paren(inv).into();
                    }
                    editor.replace(demorganed.syntax(), inv.syntax());
                }
            } else {
                return None;
            }
        } else {
            let mut inv = invert_boolean_expression(&make, demorganed.clone());
            if precedence(&inv).needs_parentheses_in(prec) {
                inv = make.expr_paren(inv).into();
            }
            editor.replace(demorganed.syntax(), inv.syntax());
        }
    }

    editor.add_mappings(make.finish_with_mappings());
    let edit = editor.finish();
    let demorganed = ast::Expr::cast(edit.new_root().clone())?;

    acc.add_group(
        &GroupLabel("Apply De Morgan's law".to_owned()),
        AssistId::refactor_rewrite("apply_demorgan"),
        "Apply De Morgan's law",
        op_range,
        |builder| {
            let make = SyntaxFactory::with_mappings();
            let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast);
            let neg_expr = paren_expr
                .clone()
                .and_then(|paren_expr| paren_expr.syntax().parent())
                .and_then(ast::PrefixExpr::cast)
                .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
                .map(ast::Expr::PrefixExpr);

            let mut editor;
            if let Some(paren_expr) = paren_expr {
                if let Some(neg_expr) = neg_expr {
                    cov_mark::hit!(demorgan_double_negation);
                    let parent = neg_expr.syntax().parent();
                    editor = builder.make_editor(neg_expr.syntax());

                    if parent.is_some_and(|parent| {
                        demorganed.needs_parens_in_place_of(&parent, neg_expr.syntax())
                    }) {
                        cov_mark::hit!(demorgan_keep_parens_for_op_precedence2);
                        editor.replace(neg_expr.syntax(), make.expr_paren(demorganed).syntax());
                    } else {
                        editor.replace(neg_expr.syntax(), demorganed.syntax());
                    };
                } else {
                    cov_mark::hit!(demorgan_double_parens);
                    editor = builder.make_editor(paren_expr.syntax());

                    editor.replace(paren_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
                }
            } else {
                editor = builder.make_editor(bin_expr.syntax());
                editor.replace(bin_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
            }

            editor.add_mappings(make.finish_with_mappings());
            builder.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

fn fun_name(ctx: &AssistContext<'_>) -> Option<(ast::BinExpr, ast::BinaryOp, syntax::TextRange)> {
    let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
    let op = bin_expr.op_kind()?;
    let op_range = bin_expr.op_token()?.text_range();
    if !op_range.contains_range(ctx.selection_trimmed()) {
        return None;
    }
    Some((bin_expr, op, op_range))
}

 (mut op, op_range), bin_expr = fun_name(ctx)?;

    editor.add_mappings(make.finish_with_mappings());
    while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) {
        match parent_expr.op_kind() {
            Some(parent_op) if parent_op == op => {
                bin_expr = parent_expr;
            }
            _ => break,
        }
    }

    let op = bin_expr.op_kind()?;
    let (inv_token, prec) = match op {
        ast::BinaryOp::LogicOp(ast::LogicOp::And) => (SyntaxKind::PIPE2, ExprPrecedence::LOr),
        ast::BinaryOp::LogicOp(ast::LogicOp::Or) => (SyntaxKind::AMP2, ExprPrecedence::LAnd),
        _ => return None,
    };

    let make = SyntaxFactory::with_mappings();

    let demorganed = bin_expr.clone_subtree();
    let mut editor = SyntaxEditor::new(demorganed.syntax().clone());
    editor.replace(demorganed.op_token()?, make.token(inv_token));

    // Walk up the tree while we have the same binary operator
    let edit = editor.finish();
    let demorganed = ast::Expr::cast(edit.new_root().clone())?;

    acc.add_group(
        &GroupLabel("Apply De Morgan's law".to_owned()),
        AssistId::refactor_rewrite("apply_demorgan"),
        "Apply De Morgan's law",
        op_range,
        |builder| {
            let make = SyntaxFactory::with_mappings();
            let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast);
            let neg_expr = paren_expr
                .clone()
                .and_then(|paren_expr| paren_expr.syntax().parent())
                .and_then(ast::PrefixExpr::cast)
                .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
                .map(ast::Expr::PrefixExpr);

            let mut editor;
            if let Some(paren_expr) = paren_expr {
                if let Some(neg_expr) = neg_expr {
                    cov_mark::hit!(demorgan_double_negation);
                    let parent = neg_expr.syntax().parent();
                    editor = builder.make_editor(neg_expr.syntax());

                    if parent.is_some_and(|parent| {
                        demorganed.needs_parens_in_place_of(&parent, neg_expr.syntax())
                    }) {
                        cov_mark::hit!(demorgan_keep_parens_for_op_precedence2);
                        editor.replace(neg_expr.syntax(), make.expr_paren(demorganed).syntax());
                    } else {
                        editor.replace(neg_expr.syntax(), demorganed.syntax());
                    };
                } else {
                    cov_mark::hit!(demorgan_double_parens);
                    editor = builder.make_editor(paren_expr.syntax());

                    editor.replace(paren_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
                }
            } else {
                editor = builder.make_editor(bin_expr.syntax());
                editor.replace(bin_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
            }

            editor.add_mappings(make.finish_with_mappings());
            builder.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

fn fun_name(ctx: &AssistContext<'_>) -> Option<(ast::BinExpr, ast::BinaryOp, syntax::TextRange)> {
    let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
    let op = bin_expr.op_kind()?;
    let op_range = bin_expr.op_token()?.text_range();
    if !op_range.contains_range(ctx.selection_trimmed()) {
        return None;
    }
    Some((bin_expr, op, op_range))
}

// Assist: apply_demorgan_iterator
//
// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) to
// `Iterator::all` and `Iterator::any`.
//
// This transforms expressions of the form `!iter.any(|x| predicate(x))` into
// `iter.all(|x| !predicate(x))` and vice versa. This also works the other way for
// `Iterator::all` into `Iterator::any`.
//
// ```
// # //- minicore: iterator
// fn main() {
//     let arr = [1, 2, 3];
//     if !arr.into_iter().$0any(|num| num == 4) {
//         println!("foo");
//     }
// }
// ```
// ->
// ```
// fn main() {
//     let arr = [1, 2, 3];
//     if arr.into_iter().all(|num| num != 4) {
//         println!("foo");
//     }
// }
// ```
pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
    let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?;


2025-09-03T20:42:47.829 INFO (pid:30821) [attach] - receive notification: showInfo []

Describe the bug

Code-Action snippets damaged

This may be caused by out of order application code snippets when the device performance is low

Reproduce the bug

  • Clone repo rust-lang/rust-analyzer

  • Create file mini.vim with:

    colorscheme habamax
    
    set nu
    set laststatus=2
    set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
    set statusline^=%(%1*[%{coc#status()}%{get(b:,'coc_current_function','')}]%*\ %)
    
    call plug#begin('~/.vim/plugged')
    Plug 'neoclide/coc.nvim'
    call plug#end()
    
    inoremap <silent><expr> <cr>
                \   coc#pum#visible() ? coc#pum#confirm()
                \ : "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"
    inoremap <silent><expr> <tab> coc#pum#visible()
                \   ? coc#pum#next(0)
                \   : coc#refresh()
    xmap <F3> <Plug>(coc-codeaction-selected)
    nmap <C-k> <Plug>(coc-diagnostic-prev)
    nmap <C-j> <Plug>(coc-diagnostic-next)
  • Create file coc-settings.json with:

    {
        "rust-analyzer.updates.checkOnStartup": false,
        "rust-analyzer.check.workspace": false,
        "rust-analyzer.cachePriming.enable": false
    }
  • Run cd rust-analyzer/crates/ide-assists

  • Start vim with command: vim -Nu mini.vim src/handlers/apply_demorgan.rs

  • Operate vim.

    • Line-Visual-mode select line 41,48
    • Input <F3> choose 2. Extract into function
    • Try a few more times ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions