fix: breaking snippets on typed incomplete suggestions

Fix the case where if a user types `&&42.o`, snippet completion
was still applying &&Ok(42). Note this was fixed previously
on `&&42.` but this still remained a problem for this case
This commit is contained in:
feniljain 2022-12-03 08:10:54 +05:30
parent 81d26e730e
commit ec268c0d6c

View file

@ -5,7 +5,7 @@ mod format_like;
use hir::{Documentation, HasAttrs}; use hir::{Documentation, HasAttrs};
use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap}; use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
use syntax::{ use syntax::{
ast::{self, AstNode, AstToken}, ast::{self, make, AstNode, AstToken},
SyntaxKind::{EXPR_STMT, STMT_LIST}, SyntaxKind::{EXPR_STMT, STMT_LIST},
TextRange, TextSize, TextRange, TextSize,
}; };
@ -129,8 +129,10 @@ pub(crate) fn complete_postfix(
// The rest of the postfix completions create an expression that moves an argument, // The rest of the postfix completions create an expression that moves an argument,
// so it's better to consider references now to avoid breaking the compilation // so it's better to consider references now to avoid breaking the compilation
let dot_receiver = include_references(dot_receiver);
let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal); let (dot_receiver, node_to_replace_with) = include_references(dot_receiver);
let receiver_text =
get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) { let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
Some(it) => it, Some(it) => it,
None => return, None => return,
@ -210,14 +212,35 @@ fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal:
text.replace('\\', "\\\\").replace('$', "\\$") text.replace('\\', "\\\\").replace('$', "\\$")
} }
fn include_references(initial_element: &ast::Expr) -> ast::Expr { fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
let mut resulting_element = initial_element.clone(); let mut resulting_element = initial_element.clone();
while let Some(parent_ref_element) =
resulting_element.syntax().parent().and_then(ast::RefExpr::cast) while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
{ {
resulting_element = ast::Expr::from(parent_ref_element); resulting_element = ast::Expr::from(field_expr);
} }
resulting_element
let mut new_element_opt = initial_element.clone();
if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
if let Some(expr) = first_ref_expr.expr() {
resulting_element = expr.clone();
}
while let Some(parent_ref_element) =
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
{
resulting_element = ast::Expr::from(parent_ref_element);
new_element_opt = make::expr_ref(new_element_opt, false);
}
} else {
// If we do not find any ref expressions, restore
// all the progress of tree climbing
resulting_element = initial_element.clone();
}
(resulting_element, new_element_opt)
} }
fn build_postfix_snippet_builder<'ctx>( fn build_postfix_snippet_builder<'ctx>(
@ -225,8 +248,7 @@ fn build_postfix_snippet_builder<'ctx>(
cap: SnippetCap, cap: SnippetCap,
receiver: &'ctx ast::Expr, receiver: &'ctx ast::Expr,
) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> { ) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
let receiver_syntax = receiver.syntax(); let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
let receiver_range = ctx.sema.original_range_opt(receiver_syntax)?.range;
if ctx.source_range().end() < receiver_range.start() { if ctx.source_range().end() < receiver_range.start() {
// This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping. // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
return None; return None;
@ -616,22 +638,55 @@ fn main() {
#[test] #[test]
fn postfix_custom_snippets_completion_for_references() { fn postfix_custom_snippets_completion_for_references() {
// https://github.com/rust-lang/rust-analyzer/issues/7929
let snippet = Snippet::new(
&[],
&["ok".into()],
&["Ok(${receiver})".into()],
"",
&[],
crate::SnippetScope::Expr,
)
.unwrap();
check_edit_with_config( check_edit_with_config(
CompletionConfig { CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
snippets: vec![Snippet::new( "ok",
&[], r#"fn main() { &&42.o$0 }"#,
&["ok".into()], r#"fn main() { Ok(&&42) }"#,
&["Ok(${receiver})".into()], );
"",
&[], check_edit_with_config(
crate::SnippetScope::Expr, CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
)
.unwrap()],
..TEST_CONFIG
},
"ok", "ok",
r#"fn main() { &&42.$0 }"#, r#"fn main() { &&42.$0 }"#,
r#"fn main() { Ok(&&42) }"#, r#"fn main() { Ok(&&42) }"#,
); );
check_edit_with_config(
CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },
"ok",
r#"
struct A {
a: i32,
}
fn main() {
let a = A {a :1};
&a.a.$0
}
"#,
r#"
struct A {
a: i32,
}
fn main() {
let a = A {a :1};
Ok(&a.a)
}
"#,
);
} }
} }