coc.nvim icon indicating copy to clipboard operation
coc.nvim copied to clipboard

Something went wrong with the snippet implementation

Open A4-Tacks opened this issue 4 months ago • 2 comments

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 ...

A4-Tacks avatar Sep 03 '25 12:09 A4-Tacks

I've tested several times with vim, couldn't reproduce the Something went wrong with the snippet implementation error log. Still debugging on this.

fannheyward avatar Sep 08 '25 06:09 fannheyward

Image

A4-Tacks avatar Sep 08 '25 15:09 A4-Tacks