Skip to content

Commit 15ff8a5

Browse files
committed
Auto merge of rust-lang#13715 - feniljain:fix_completions, r=jonas-schievink
fix: breaking snippets on typed incomplete suggestions Possible fix for rust-lang#7929 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 Previous relevant PR: rust-lang#13517 ### Points to help in review: - The main problem why everything broke on adding an extra `o` was, earlier `dot_receiver` was `42.` which was a `LITERAL` but now `42.o` becomes a `FIELD_EXPR` - Till now `include_references` was just checking for parent of `LITERAL` and if it was a `REF_EXPR`, but now we consider `FIELD_EXPR` and traverse all of them, finally to reach `REF_EXPR`. If `REF_EXPR` is not found we just return the original `initial_element` - We are constructing a new node during `include_references` because if we rely on `dot_receiver` solely we would get `&&42.o` to be replaced with, but we want `&&42` to be replaced with ### Output Video: https://user-images.githubusercontent.com/49019259/205420166-efbdef78-5b3a-4aef-ab4b-d892dac056a0.mov Hope everything I wrote makes sense 😅 Also interestingly previous PR's number was `13517` and this PR's number is `13715`, nicee
2 parents e7dff74 + ec268c0 commit 15ff8a5

File tree

1 file changed

+77
-22
lines changed

1 file changed

+77
-22
lines changed

crates/ide-completion/src/completions/postfix.rs

+77-22
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod format_like;
55
use hir::{Documentation, HasAttrs};
66
use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
77
use syntax::{
8-
ast::{self, AstNode, AstToken},
8+
ast::{self, make, AstNode, AstToken},
99
SyntaxKind::{EXPR_STMT, STMT_LIST},
1010
TextRange, TextSize,
1111
};
@@ -129,8 +129,10 @@ pub(crate) fn complete_postfix(
129129

130130
// The rest of the postfix completions create an expression that moves an argument,
131131
// so it's better to consider references now to avoid breaking the compilation
132-
let dot_receiver = include_references(dot_receiver);
133-
let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal);
132+
133+
let (dot_receiver, node_to_replace_with) = include_references(dot_receiver);
134+
let receiver_text =
135+
get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
134136
let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
135137
Some(it) => it,
136138
None => return,
@@ -210,23 +212,43 @@ fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal:
210212
text.replace('\\', "\\\\").replace('$', "\\$")
211213
}
212214

213-
fn include_references(initial_element: &ast::Expr) -> ast::Expr {
215+
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
214216
let mut resulting_element = initial_element.clone();
215-
while let Some(parent_ref_element) =
216-
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
217+
218+
while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
217219
{
218-
resulting_element = ast::Expr::from(parent_ref_element);
220+
resulting_element = ast::Expr::from(field_expr);
219221
}
220-
resulting_element
222+
223+
let mut new_element_opt = initial_element.clone();
224+
225+
if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
226+
if let Some(expr) = first_ref_expr.expr() {
227+
resulting_element = expr.clone();
228+
}
229+
230+
while let Some(parent_ref_element) =
231+
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
232+
{
233+
resulting_element = ast::Expr::from(parent_ref_element);
234+
235+
new_element_opt = make::expr_ref(new_element_opt, false);
236+
}
237+
} else {
238+
// If we do not find any ref expressions, restore
239+
// all the progress of tree climbing
240+
resulting_element = initial_element.clone();
241+
}
242+
243+
(resulting_element, new_element_opt)
221244
}
222245

223246
fn build_postfix_snippet_builder<'ctx>(
224247
ctx: &'ctx CompletionContext<'_>,
225248
cap: SnippetCap,
226249
receiver: &'ctx ast::Expr,
227250
) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
228-
let receiver_syntax = receiver.syntax();
229-
let receiver_range = ctx.sema.original_range_opt(receiver_syntax)?.range;
251+
let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
230252
if ctx.source_range().end() < receiver_range.start() {
231253
// This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
232254
return None;
@@ -616,22 +638,55 @@ fn main() {
616638

617639
#[test]
618640
fn postfix_custom_snippets_completion_for_references() {
641+
// https://github.com/rust-lang/rust-analyzer/issues/7929
642+
643+
let snippet = Snippet::new(
644+
&[],
645+
&["ok".into()],
646+
&["Ok(${receiver})".into()],
647+
"",
648+
&[],
649+
crate::SnippetScope::Expr,
650+
)
651+
.unwrap();
652+
619653
check_edit_with_config(
620-
CompletionConfig {
621-
snippets: vec![Snippet::new(
622-
&[],
623-
&["ok".into()],
624-
&["Ok(${receiver})".into()],
625-
"",
626-
&[],
627-
crate::SnippetScope::Expr,
628-
)
629-
.unwrap()],
630-
..TEST_CONFIG
631-
},
654+
CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
655+
"ok",
656+
r#"fn main() { &&42.o$0 }"#,
657+
r#"fn main() { Ok(&&42) }"#,
658+
);
659+
660+
check_edit_with_config(
661+
CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
632662
"ok",
633663
r#"fn main() { &&42.$0 }"#,
634664
r#"fn main() { Ok(&&42) }"#,
635665
);
666+
667+
check_edit_with_config(
668+
CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },
669+
"ok",
670+
r#"
671+
struct A {
672+
a: i32,
673+
}
674+
675+
fn main() {
676+
let a = A {a :1};
677+
&a.a.$0
678+
}
679+
"#,
680+
r#"
681+
struct A {
682+
a: i32,
683+
}
684+
685+
fn main() {
686+
let a = A {a :1};
687+
Ok(&a.a)
688+
}
689+
"#,
690+
);
636691
}
637692
}

0 commit comments

Comments
 (0)