aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs23
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs28
-rw-r--r--crates/hir_ty/src/diagnostics/match_check/usefulness.rs5
-rw-r--r--crates/hir_ty/src/infer.rs2
-rw-r--r--crates/hir_ty/src/infer/pat.rs11
-rw-r--r--crates/hir_ty/src/tests/patterns.rs47
-rw-r--r--crates/ide/src/expand_macro.rs6
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs169
-rw-r--r--crates/ide_assists/src/handlers/extract_type_alias.rs78
-rw-r--r--crates/ide_assists/src/handlers/extract_variable.rs33
-rw-r--r--crates/ide_completion/src/completions/dot.rs7
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs6
-rw-r--r--crates/ide_completion/src/completions/keyword.rs2
-rw-r--r--crates/ide_completion/src/completions/macro_in_item_position.rs2
-rw-r--r--crates/ide_completion/src/completions/postfix.rs17
-rw-r--r--crates/ide_completion/src/completions/snippet.rs2
-rw-r--r--crates/ide_completion/src/context.rs65
-rw-r--r--crates/ide_completion/src/patterns.rs49
-rw-r--r--crates/ide_completion/src/render/function.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs1
-rw-r--r--crates/syntax/src/ast/make.rs5
-rw-r--r--docs/user/generated_config.adoc1
-rw-r--r--docs/user/manual.adoc14
-rw-r--r--editors/code/package.json2
24 files changed, 420 insertions, 157 deletions
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 3efbce773..a2a4d61db 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -357,17 +357,20 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
357 infer: &infer, 357 infer: &infer,
358 db, 358 db,
359 pattern_arena: &pattern_arena, 359 pattern_arena: &pattern_arena,
360 eprint_panic_context: &|| { 360 panic_context: &|| {
361 use syntax::AstNode; 361 use syntax::AstNode;
362 if let Ok(scrutinee_sptr) = source_map.expr_syntax(match_expr) { 362 let match_expr_text = source_map
363 let root = scrutinee_sptr.file_syntax(db.upcast()); 363 .expr_syntax(match_expr)
364 if let Some(match_ast) = scrutinee_sptr.value.to_node(&root).syntax().parent() { 364 .ok()
365 eprintln!( 365 .and_then(|scrutinee_sptr| {
366 "Match checking is about to panic on this expression:\n{}", 366 let root = scrutinee_sptr.file_syntax(db.upcast());
367 match_ast.to_string(), 367 scrutinee_sptr.value.to_node(&root).syntax().parent()
368 ); 368 })
369 } 369 .map(|node| node.to_string());
370 } 370 format!(
371 "expression:\n{}",
372 match_expr_text.as_deref().unwrap_or("<synthesized expr>")
373 )
371 }, 374 },
372 }; 375 };
373 let report = compute_match_usefulness(&cx, &m_arms); 376 let report = compute_match_usefulness(&cx, &m_arms);
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index a9a99f57a..c8e1b23de 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -100,10 +100,19 @@ impl<'a> PatCtxt<'a> {
100 } 100 }
101 101
102 pub(crate) fn lower_pattern(&mut self, pat: hir_def::expr::PatId) -> Pat { 102 pub(crate) fn lower_pattern(&mut self, pat: hir_def::expr::PatId) -> Pat {
103 // FIXME: implement pattern adjustments (implicit pattern dereference; "RFC 2005-match-ergonomics") 103 // XXX(iDawer): Collecting pattern adjustments feels imprecise to me.
104 // When lowering of & and box patterns are implemented this should be tested
105 // in a manner of `match_ergonomics_issue_9095` test.
106 // Pattern adjustment is part of RFC 2005-match-ergonomics.
104 // More info https://github.com/rust-lang/rust/issues/42640#issuecomment-313535089 107 // More info https://github.com/rust-lang/rust/issues/42640#issuecomment-313535089
105 let unadjusted_pat = self.lower_pattern_unadjusted(pat); 108 let unadjusted_pat = self.lower_pattern_unadjusted(pat);
106 unadjusted_pat 109 self.infer.pat_adjustments.get(&pat).map(|it| &**it).unwrap_or_default().iter().rev().fold(
110 unadjusted_pat,
111 |subpattern, ref_ty| Pat {
112 ty: ref_ty.clone(),
113 kind: Box::new(PatKind::Deref { subpattern }),
114 },
115 )
107 } 116 }
108 117
109 fn lower_pattern_unadjusted(&mut self, pat: hir_def::expr::PatId) -> Pat { 118 fn lower_pattern_unadjusted(&mut self, pat: hir_def::expr::PatId) -> Pat {
@@ -1236,6 +1245,21 @@ fn main(f: Foo) {
1236 ); 1245 );
1237 } 1246 }
1238 1247
1248 #[test]
1249 fn match_ergonomics_issue_9095() {
1250 check_diagnostics(
1251 r#"
1252enum Foo<T> { A(T) }
1253fn main() {
1254 match &Foo::A(true) {
1255 _ => {}
1256 Foo::A(_) => {}
1257 }
1258}
1259"#,
1260 );
1261 }
1262
1239 mod false_negatives { 1263 mod false_negatives {
1240 //! The implementation of match checking here is a work in progress. As we roll this out, we 1264 //! The implementation of match checking here is a work in progress. As we roll this out, we
1241 //! prefer false negatives to false positives (ideally there would be no false positives). This 1265 //! prefer false negatives to false positives (ideally there would be no false positives). This
diff --git a/crates/hir_ty/src/diagnostics/match_check/usefulness.rs b/crates/hir_ty/src/diagnostics/match_check/usefulness.rs
index 83b094a89..bd76a606c 100644
--- a/crates/hir_ty/src/diagnostics/match_check/usefulness.rs
+++ b/crates/hir_ty/src/diagnostics/match_check/usefulness.rs
@@ -295,7 +295,7 @@ pub(crate) struct MatchCheckCtx<'a> {
295 pub(crate) db: &'a dyn HirDatabase, 295 pub(crate) db: &'a dyn HirDatabase,
296 /// Lowered patterns from arms plus generated by the check. 296 /// Lowered patterns from arms plus generated by the check.
297 pub(crate) pattern_arena: &'a RefCell<PatternArena>, 297 pub(crate) pattern_arena: &'a RefCell<PatternArena>,
298 pub(crate) eprint_panic_context: &'a dyn Fn(), 298 pub(crate) panic_context: &'a dyn Fn() -> String,
299} 299}
300 300
301impl<'a> MatchCheckCtx<'a> { 301impl<'a> MatchCheckCtx<'a> {
@@ -331,8 +331,7 @@ impl<'a> MatchCheckCtx<'a> {
331 331
332 #[track_caller] 332 #[track_caller]
333 pub(super) fn bug(&self, info: &str) -> ! { 333 pub(super) fn bug(&self, info: &str) -> ! {
334 (self.eprint_panic_context)(); 334 panic!("bug: {}\n{}", info, (self.panic_context)());
335 panic!("bug: {}", info);
336 } 335 }
337} 336}
338 337
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 7a4268819..0e9f777da 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -150,6 +150,8 @@ pub struct InferenceResult {
150 type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>, 150 type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
151 /// Interned Unknown to return references to. 151 /// Interned Unknown to return references to.
152 standard_types: InternedStandardTypes, 152 standard_types: InternedStandardTypes,
153 /// Stores the types which were implicitly dereferenced in pattern binding modes.
154 pub pat_adjustments: FxHashMap<PatId, Vec<Ty>>,
153} 155}
154 156
155impl InferenceResult { 157impl InferenceResult {
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index 83e0a7a9e..25dff7e49 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -101,7 +101,9 @@ impl<'a> InferenceContext<'a> {
101 let mut expected = self.resolve_ty_shallow(expected); 101 let mut expected = self.resolve_ty_shallow(expected);
102 102
103 if is_non_ref_pat(&body, pat) { 103 if is_non_ref_pat(&body, pat) {
104 let mut pat_adjustments = Vec::new();
104 while let Some((inner, _lifetime, mutability)) = expected.as_reference() { 105 while let Some((inner, _lifetime, mutability)) = expected.as_reference() {
106 pat_adjustments.push(expected.clone());
105 expected = self.resolve_ty_shallow(inner); 107 expected = self.resolve_ty_shallow(inner);
106 default_bm = match default_bm { 108 default_bm = match default_bm {
107 BindingMode::Move => BindingMode::Ref(mutability), 109 BindingMode::Move => BindingMode::Ref(mutability),
@@ -109,6 +111,11 @@ impl<'a> InferenceContext<'a> {
109 BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability), 111 BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability),
110 } 112 }
111 } 113 }
114
115 if !pat_adjustments.is_empty() {
116 pat_adjustments.shrink_to_fit();
117 self.result.pat_adjustments.insert(pat, pat_adjustments);
118 }
112 } else if let Pat::Ref { .. } = &body[pat] { 119 } else if let Pat::Ref { .. } = &body[pat] {
113 cov_mark::hit!(match_ergonomics_ref); 120 cov_mark::hit!(match_ergonomics_ref);
114 // When you encounter a `&pat` pattern, reset to Move. 121 // When you encounter a `&pat` pattern, reset to Move.
@@ -290,6 +297,10 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
290 Expr::Literal(Literal::String(..)) => false, 297 Expr::Literal(Literal::String(..)) => false,
291 _ => true, 298 _ => true,
292 }, 299 },
300 Pat::Bind { mode: BindingAnnotation::Mutable, subpat: Some(subpat), .. }
301 | Pat::Bind { mode: BindingAnnotation::Unannotated, subpat: Some(subpat), .. } => {
302 is_non_ref_pat(body, *subpat)
303 }
293 Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false, 304 Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
294 } 305 }
295} 306}
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index cd08b5c7a..7d00cee9b 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -20,6 +20,8 @@ fn infer_pattern() {
20 let h = val; 20 let h = val;
21 } 21 }
22 22
23 if let x @ true = &true {}
24
23 let lambda = |a: u64, b, c: i32| { a + b; c }; 25 let lambda = |a: u64, b, c: i32| { a + b; c };
24 26
25 let ref ref_to_x = x; 27 let ref ref_to_x = x;
@@ -30,7 +32,7 @@ fn infer_pattern() {
30 "#, 32 "#,
31 expect![[r#" 33 expect![[r#"
32 8..9 'x': &i32 34 8..9 'x': &i32
33 17..368 '{ ...o_x; }': () 35 17..400 '{ ...o_x; }': ()
34 27..28 'y': &i32 36 27..28 'y': &i32
35 31..32 'x': &i32 37 31..32 'x': &i32
36 42..44 '&z': &i32 38 42..44 '&z': &i32
@@ -59,24 +61,31 @@ fn infer_pattern() {
59 176..204 '{ ... }': () 61 176..204 '{ ... }': ()
60 190..191 'h': {unknown} 62 190..191 'h': {unknown}
61 194..197 'val': {unknown} 63 194..197 'val': {unknown}
62 214..220 'lambda': |u64, u64, i32| -> i32 64 210..236 'if let...rue {}': ()
63 223..255 '|a: u6...b; c }': |u64, u64, i32| -> i32 65 217..225 'x @ true': &bool
64 224..225 'a': u64 66 221..225 'true': bool
65 232..233 'b': u64 67 221..225 'true': bool
66 235..236 'c': i32 68 228..233 '&true': &bool
67 243..255 '{ a + b; c }': i32 69 229..233 'true': bool
68 245..246 'a': u64 70 234..236 '{}': ()
69 245..250 'a + b': u64 71 246..252 'lambda': |u64, u64, i32| -> i32
70 249..250 'b': u64 72 255..287 '|a: u6...b; c }': |u64, u64, i32| -> i32
71 252..253 'c': i32 73 256..257 'a': u64
72 266..278 'ref ref_to_x': &&i32 74 264..265 'b': u64
73 281..282 'x': &i32 75 267..268 'c': i32
74 292..301 'mut mut_x': &i32 76 275..287 '{ a + b; c }': i32
75 304..305 'x': &i32 77 277..278 'a': u64
76 315..335 'ref mu...f_to_x': &mut &i32 78 277..282 'a + b': u64
77 338..339 'x': &i32 79 281..282 'b': u64
78 349..350 'k': &mut &i32 80 284..285 'c': i32
79 353..365 'mut_ref_to_x': &mut &i32 81 298..310 'ref ref_to_x': &&i32
82 313..314 'x': &i32
83 324..333 'mut mut_x': &i32
84 336..337 'x': &i32
85 347..367 'ref mu...f_to_x': &mut &i32
86 370..371 'x': &i32
87 381..382 'k': &mut &i32
88 385..397 'mut_ref_to_x': &mut &i32
80 "#]], 89 "#]],
81 ); 90 );
82} 91}
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index eebae5ebe..e0d01fa96 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -28,8 +28,8 @@ pub struct ExpandedMacro {
28pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 28pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
29 let sema = Semantics::new(db); 29 let sema = Semantics::new(db);
30 let file = sema.parse(position.file_id); 30 let file = sema.parse(position.file_id);
31 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?; 31 let mac = find_node_at_offset::<ast::MacroCall>(file.syntax(), position.offset)?;
32 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; 32 let name = mac.path()?.segment()?.name_ref()?;
33 33
34 let expanded = expand_macro_recur(&sema, &mac)?; 34 let expanded = expand_macro_recur(&sema, &mac)?;
35 35
@@ -37,7 +37,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
37 // macro expansion may lose all white space information 37 // macro expansion may lose all white space information
38 // But we hope someday we can use ra_fmt for that 38 // But we hope someday we can use ra_fmt for that
39 let expansion = insert_whitespaces(expanded); 39 let expansion = insert_whitespaces(expanded);
40 Some(ExpandedMacro { name: name_ref.text().to_string(), expansion }) 40 Some(ExpandedMacro { name: name.to_string(), expansion })
41} 41}
42 42
43fn expand_macro_recur( 43fn expand_macro_recur(
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 007aba23d..d3ff7b65c 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -11,14 +11,19 @@ use ide_db::{
11 search::FileReference, 11 search::FileReference,
12 RootDatabase, 12 RootDatabase,
13}; 13};
14use itertools::Itertools;
14use rustc_hash::FxHashSet; 15use rustc_hash::FxHashSet;
15use syntax::{ 16use syntax::{
16 algo::find_node_at_offset, 17 ast::{
17 ast::{self, make, AstNode, NameOwner, VisibilityOwner}, 18 self, make, AstNode, AttrsOwner, GenericParamsOwner, NameOwner, TypeBoundsOwner,
18 ted, SyntaxNode, T, 19 VisibilityOwner,
20 },
21 match_ast,
22 ted::{self, Position},
23 SyntaxNode, T,
19}; 24};
20 25
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 26use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
22 27
23// Assist: extract_struct_from_enum_variant 28// Assist: extract_struct_from_enum_variant
24// 29//
@@ -70,11 +75,10 @@ pub(crate) fn extract_struct_from_enum_variant(
70 continue; 75 continue;
71 } 76 }
72 builder.edit_file(file_id); 77 builder.edit_file(file_id);
73 let source_file = builder.make_mut(ctx.sema.parse(file_id));
74 let processed = process_references( 78 let processed = process_references(
75 ctx, 79 ctx,
80 builder,
76 &mut visited_modules_set, 81 &mut visited_modules_set,
77 source_file.syntax(),
78 &enum_module_def, 82 &enum_module_def,
79 &variant_hir_name, 83 &variant_hir_name,
80 references, 84 references,
@@ -84,13 +88,12 @@ pub(crate) fn extract_struct_from_enum_variant(
84 }); 88 });
85 } 89 }
86 builder.edit_file(ctx.frange.file_id); 90 builder.edit_file(ctx.frange.file_id);
87 let source_file = builder.make_mut(ctx.sema.parse(ctx.frange.file_id));
88 let variant = builder.make_mut(variant.clone()); 91 let variant = builder.make_mut(variant.clone());
89 if let Some(references) = def_file_references { 92 if let Some(references) = def_file_references {
90 let processed = process_references( 93 let processed = process_references(
91 ctx, 94 ctx,
95 builder,
92 &mut visited_modules_set, 96 &mut visited_modules_set,
93 source_file.syntax(),
94 &enum_module_def, 97 &enum_module_def,
95 &variant_hir_name, 98 &variant_hir_name,
96 references, 99 references,
@@ -100,12 +103,12 @@ pub(crate) fn extract_struct_from_enum_variant(
100 }); 103 });
101 } 104 }
102 105
103 let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility()); 106 let def = create_struct_def(variant_name.clone(), &field_list, &enum_ast);
104 let start_offset = &variant.parent_enum().syntax().clone(); 107 let start_offset = &variant.parent_enum().syntax().clone();
105 ted::insert_raw(ted::Position::before(start_offset), def.syntax()); 108 ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line()); 109 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107 110
108 update_variant(&variant); 111 update_variant(&variant, enum_ast.generic_param_list());
109 }, 112 },
110 ) 113 )
111} 114}
@@ -149,7 +152,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
149fn create_struct_def( 152fn create_struct_def(
150 variant_name: ast::Name, 153 variant_name: ast::Name,
151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 154 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
152 visibility: Option<ast::Visibility>, 155 enum_: &ast::Enum,
153) -> ast::Struct { 156) -> ast::Struct {
154 let pub_vis = make::visibility_pub(); 157 let pub_vis = make::visibility_pub();
155 158
@@ -184,12 +187,38 @@ fn create_struct_def(
184 } 187 }
185 }; 188 };
186 189
187 make::struct_(visibility, variant_name, None, field_list).clone_for_update() 190 // FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
191 let strukt =
192 make::struct_(enum_.visibility(), variant_name, enum_.generic_param_list(), field_list)
193 .clone_for_update();
194
195 // copy attributes
196 ted::insert_all(
197 Position::first_child_of(strukt.syntax()),
198 enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(),
199 );
200 strukt
188} 201}
189 202
190fn update_variant(variant: &ast::Variant) -> Option<()> { 203fn update_variant(variant: &ast::Variant, generic: Option<ast::GenericParamList>) -> Option<()> {
191 let name = variant.name()?; 204 let name = variant.name()?;
192 let tuple_field = make::tuple_field(None, make::ty(&name.text())); 205 let ty = match generic {
206 // FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
207 Some(gpl) => {
208 let gpl = gpl.clone_for_update();
209 gpl.generic_params().for_each(|gp| {
210 match gp {
211 ast::GenericParam::LifetimeParam(it) => it.type_bound_list(),
212 ast::GenericParam::TypeParam(it) => it.type_bound_list(),
213 ast::GenericParam::ConstParam(_) => return,
214 }
215 .map(|it| it.remove());
216 });
217 make::ty(&format!("{}<{}>", name.text(), gpl.generic_params().join(", ")))
218 }
219 None => make::ty(&name.text()),
220 };
221 let tuple_field = make::tuple_field(None, ty);
193 let replacement = make::variant( 222 let replacement = make::variant(
194 name, 223 name,
195 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), 224 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
@@ -208,18 +237,17 @@ fn apply_references(
208 if let Some((scope, path)) = import { 237 if let Some((scope, path)) = import {
209 insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg); 238 insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
210 } 239 }
211 ted::insert_raw( 240 // deep clone to prevent cycle
212 ted::Position::before(segment.syntax()), 241 let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
213 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(), 242 ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax());
214 );
215 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['('])); 243 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
216 ted::insert_raw(ted::Position::after(&node), make::token(T![')'])); 244 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
217} 245}
218 246
219fn process_references( 247fn process_references(
220 ctx: &AssistContext, 248 ctx: &AssistContext,
249 builder: &mut AssistBuilder,
221 visited_modules: &mut FxHashSet<Module>, 250 visited_modules: &mut FxHashSet<Module>,
222 source_file: &SyntaxNode,
223 enum_module_def: &ModuleDef, 251 enum_module_def: &ModuleDef,
224 variant_hir_name: &Name, 252 variant_hir_name: &Name,
225 refs: Vec<FileReference>, 253 refs: Vec<FileReference>,
@@ -228,8 +256,9 @@ fn process_references(
228 // and corresponding nodes up front 256 // and corresponding nodes up front
229 refs.into_iter() 257 refs.into_iter()
230 .flat_map(|reference| { 258 .flat_map(|reference| {
231 let (segment, scope_node, module) = 259 let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
232 reference_to_node(&ctx.sema, source_file, reference)?; 260 let segment = builder.make_mut(segment);
261 let scope_node = builder.make_syntax_mut(scope_node);
233 if !visited_modules.contains(&module) { 262 if !visited_modules.contains(&module) {
234 let mod_path = module.find_use_path_prefixed( 263 let mod_path = module.find_use_path_prefixed(
235 ctx.sema.db, 264 ctx.sema.db,
@@ -251,23 +280,22 @@ fn process_references(
251 280
252fn reference_to_node( 281fn reference_to_node(
253 sema: &hir::Semantics<RootDatabase>, 282 sema: &hir::Semantics<RootDatabase>,
254 source_file: &SyntaxNode,
255 reference: FileReference, 283 reference: FileReference,
256) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> { 284) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
257 let offset = reference.range.start(); 285 let segment =
258 if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) { 286 reference.name.as_name_ref()?.syntax().parent().and_then(ast::PathSegment::cast)?;
259 // tuple variant 287 let parent = segment.parent_path().syntax().parent()?;
260 Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?)) 288 let expr_or_pat = match_ast! {
261 } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) { 289 match parent {
262 // record variant 290 ast::PathExpr(_it) => parent.parent()?,
263 Some((record_expr.path()?.segment()?, record_expr.syntax().clone())) 291 ast::RecordExpr(_it) => parent,
264 } else { 292 ast::TupleStructPat(_it) => parent,
265 None 293 ast::RecordPat(_it) => parent,
266 } 294 _ => return None,
267 .and_then(|(segment, expr)| { 295 }
268 let module = sema.scope(&expr).module()?; 296 };
269 Some((segment, expr, module)) 297 let module = sema.scope(&expr_or_pat).module()?;
270 }) 298 Some((segment, expr_or_pat, module))
271} 299}
272 300
273#[cfg(test)] 301#[cfg(test)]
@@ -278,6 +306,12 @@ mod tests {
278 306
279 use super::*; 307 use super::*;
280 308
309 fn check_not_applicable(ra_fixture: &str) {
310 let fixture =
311 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
312 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
313 }
314
281 #[test] 315 #[test]
282 fn test_extract_struct_several_fields_tuple() { 316 fn test_extract_struct_several_fields_tuple() {
283 check_assist( 317 check_assist(
@@ -312,6 +346,32 @@ enum A { One(One) }"#,
312 } 346 }
313 347
314 #[test] 348 #[test]
349 fn test_extract_struct_carries_over_generics() {
350 check_assist(
351 extract_struct_from_enum_variant,
352 r"enum En<T> { Var { a: T$0 } }",
353 r#"struct Var<T>{ pub a: T }
354
355enum En<T> { Var(Var<T>) }"#,
356 );
357 }
358
359 #[test]
360 fn test_extract_struct_carries_over_attributes() {
361 check_assist(
362 extract_struct_from_enum_variant,
363 r#"#[derive(Debug)]
364#[derive(Clone)]
365enum Enum { Variant{ field: u32$0 } }"#,
366 r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ pub field: u32 }
367
368#[derive(Debug)]
369#[derive(Clone)]
370enum Enum { Variant(Variant) }"#,
371 );
372 }
373
374 #[test]
315 fn test_extract_struct_keep_comments_and_attrs_one_field_named() { 375 fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
316 check_assist( 376 check_assist(
317 extract_struct_from_enum_variant, 377 extract_struct_from_enum_variant,
@@ -496,7 +556,7 @@ enum E {
496} 556}
497 557
498fn f() { 558fn f() {
499 let e = E::V { i: 9, j: 2 }; 559 let E::V { i, j } = E::V { i: 9, j: 2 };
500} 560}
501"#, 561"#,
502 r#" 562 r#"
@@ -507,7 +567,34 @@ enum E {
507} 567}
508 568
509fn f() { 569fn f() {
510 let e = E::V(V { i: 9, j: 2 }); 570 let E::V(V { i, j }) = E::V(V { i: 9, j: 2 });
571}
572"#,
573 )
574 }
575
576 #[test]
577 fn extract_record_fix_references2() {
578 check_assist(
579 extract_struct_from_enum_variant,
580 r#"
581enum E {
582 $0V(i32, i32)
583}
584
585fn f() {
586 let E::V(i, j) = E::V(9, 2);
587}
588"#,
589 r#"
590struct V(pub i32, pub i32);
591
592enum E {
593 V(V)
594}
595
596fn f() {
597 let E::V(V(i, j)) = E::V(V(9, 2));
511} 598}
512"#, 599"#,
513 ) 600 )
@@ -610,12 +697,6 @@ fn foo() {
610 ); 697 );
611 } 698 }
612 699
613 fn check_not_applicable(ra_fixture: &str) {
614 let fixture =
615 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
616 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
617 }
618
619 #[test] 700 #[test]
620 fn test_extract_enum_not_applicable_for_element_with_no_fields() { 701 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
621 check_not_applicable("enum A { $0One }"); 702 check_not_applicable("enum A { $0One }");
diff --git a/crates/ide_assists/src/handlers/extract_type_alias.rs b/crates/ide_assists/src/handlers/extract_type_alias.rs
index 442a209b9..4bccf5984 100644
--- a/crates/ide_assists/src/handlers/extract_type_alias.rs
+++ b/crates/ide_assists/src/handlers/extract_type_alias.rs
@@ -1,4 +1,7 @@
1use syntax::ast::{self, AstNode}; 1use syntax::{
2 ast::{self, edit::IndentLevel, AstNode},
3 match_ast,
4};
2 5
3use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
4 7
@@ -25,7 +28,15 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Opti
25 } 28 }
26 29
27 let node = ctx.find_node_at_range::<ast::Type>()?; 30 let node = ctx.find_node_at_range::<ast::Type>()?;
28 let insert = ctx.find_node_at_offset::<ast::Item>()?.syntax().text_range().start(); 31 let item = ctx.find_node_at_offset::<ast::Item>()?;
32 let insert = match_ast! {
33 match (item.syntax().parent()?) {
34 ast::AssocItemList(it) => it.syntax().parent()?.clone(),
35 _ => item.syntax().clone(),
36 }
37 };
38 let indent = IndentLevel::from_node(&insert);
39 let insert = insert.text_range().start();
29 let target = node.syntax().text_range(); 40 let target = node.syntax().text_range();
30 41
31 acc.add( 42 acc.add(
@@ -37,10 +48,14 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Opti
37 builder.replace(target, "Type"); 48 builder.replace(target, "Type");
38 match ctx.config.snippet_cap { 49 match ctx.config.snippet_cap {
39 Some(cap) => { 50 Some(cap) => {
40 builder.insert_snippet(cap, insert, format!("type $0Type = {};\n\n", node)); 51 builder.insert_snippet(
52 cap,
53 insert,
54 format!("type $0Type = {};\n\n{}", node, indent),
55 );
41 } 56 }
42 None => { 57 None => {
43 builder.insert(insert, format!("type Type = {};\n\n", node)); 58 builder.insert(insert, format!("type Type = {};\n\n{}", node, indent));
44 } 59 }
45 } 60 }
46 }, 61 },
@@ -146,4 +161,59 @@ struct S {
146 "#, 161 "#,
147 ); 162 );
148 } 163 }
164
165 #[test]
166 fn extract_from_impl_or_trait() {
167 // When invoked in an impl/trait, extracted type alias should be placed next to the
168 // impl/trait, not inside.
169 check_assist(
170 extract_type_alias,
171 r#"
172impl S {
173 fn f() -> $0(u8, u8)$0 {}
174}
175 "#,
176 r#"
177type $0Type = (u8, u8);
178
179impl S {
180 fn f() -> Type {}
181}
182 "#,
183 );
184 check_assist(
185 extract_type_alias,
186 r#"
187trait Tr {
188 fn f() -> $0(u8, u8)$0 {}
189}
190 "#,
191 r#"
192type $0Type = (u8, u8);
193
194trait Tr {
195 fn f() -> Type {}
196}
197 "#,
198 );
199 }
200
201 #[test]
202 fn indentation() {
203 check_assist(
204 extract_type_alias,
205 r#"
206mod m {
207 fn f() -> $0u8$0 {}
208}
209 "#,
210 r#"
211mod m {
212 type $0Type = u8;
213
214 fn f() -> Type {}
215}
216 "#,
217 );
218 }
149} 219}
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index ae084c86c..46b54a5f5 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -36,6 +36,11 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
36 return None; 36 return None;
37 } 37 }
38 let to_extract = node.ancestors().find_map(valid_target_expr)?; 38 let to_extract = node.ancestors().find_map(valid_target_expr)?;
39 if let Some(ty) = ctx.sema.type_of_expr(&to_extract) {
40 if ty.is_unit() {
41 return None;
42 }
43 }
39 let anchor = Anchor::from(&to_extract)?; 44 let anchor = Anchor::from(&to_extract)?;
40 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone(); 45 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
41 let target = to_extract.syntax().text_range(); 46 let target = to_extract.syntax().text_range();
@@ -275,15 +280,23 @@ fn foo() {
275 check_assist( 280 check_assist(
276 extract_variable, 281 extract_variable,
277 r#" 282 r#"
278fn foo() { 283fn foo() -> i32 {
279 $0bar(1 + 1)$0 284 $0bar(1 + 1)$0
280} 285}
286
287fn bar(i: i32) -> i32 {
288 i
289}
281"#, 290"#,
282 r#" 291 r#"
283fn foo() { 292fn foo() -> i32 {
284 let $0bar = bar(1 + 1); 293 let $0bar = bar(1 + 1);
285 bar 294 bar
286} 295}
296
297fn bar(i: i32) -> i32 {
298 i
299}
287"#, 300"#,
288 ) 301 )
289 } 302 }
@@ -796,6 +809,22 @@ fn foo() {
796 check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }"); 809 check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
797 } 810 }
798 811
812 #[test]
813 fn test_extract_var_unit_expr_not_applicable() {
814 check_assist_not_applicable(
815 extract_variable,
816 r#"
817fn foo() {
818 let mut i = 3;
819 $0if i >= 0 {
820 i += 1;
821 } else {
822 i -= 1;
823 }$0
824}"#,
825 );
826 }
827
799 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic 828 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
800 #[test] 829 #[test]
801 fn extract_var_target() { 830 fn extract_var_target() {
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs
index 302c9ccbd..e0a7021fd 100644
--- a/crates/ide_completion/src/completions/dot.rs
+++ b/crates/ide_completion/src/completions/dot.rs
@@ -8,7 +8,7 @@ use crate::{context::CompletionContext, Completions};
8 8
9/// Complete dot accesses, i.e. fields or methods. 9/// Complete dot accesses, i.e. fields or methods.
10pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { 10pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
11 let dot_receiver = match &ctx.dot_receiver { 11 let dot_receiver = match ctx.dot_receiver() {
12 Some(expr) => expr, 12 Some(expr) => expr,
13 _ => return complete_undotted_self(acc, ctx), 13 _ => return complete_undotted_self(acc, ctx),
14 }; 14 };
@@ -30,7 +30,10 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
30} 30}
31 31
32fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { 32fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) {
33 if !ctx.is_trivial_path || !ctx.config.enable_self_on_the_fly { 33 if !ctx.config.enable_self_on_the_fly {
34 return;
35 }
36 if !ctx.is_trivial_path || ctx.is_path_disallowed() {
34 return; 37 return;
35 } 38 }
36 ctx.scope.process_all_names(&mut |name, def| { 39 ctx.scope.process_all_names(&mut |name, def| {
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index df27e7a84..d72bf13d3 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -162,19 +162,19 @@ pub(crate) fn position_for_import<'a>(
162 Some(match import_candidate { 162 Some(match import_candidate {
163 Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(), 163 Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
164 Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(), 164 Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
165 Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(), 165 Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(),
166 None => ctx 166 None => ctx
167 .name_ref_syntax 167 .name_ref_syntax
168 .as_ref() 168 .as_ref()
169 .map(|name_ref| name_ref.syntax()) 169 .map(|name_ref| name_ref.syntax())
170 .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax())) 170 .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
171 .or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?, 171 .or_else(|| ctx.dot_receiver().map(|expr| expr.syntax()))?,
172 }) 172 })
173} 173}
174 174
175fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> { 175fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
176 let current_module = ctx.scope.module()?; 176 let current_module = ctx.scope.module()?;
177 if let Some(dot_receiver) = &ctx.dot_receiver { 177 if let Some(dot_receiver) = ctx.dot_receiver() {
178 ImportAssets::for_fuzzy_method_call( 178 ImportAssets::for_fuzzy_method_call(
179 current_module, 179 current_module,
180 ctx.sema.type_of_expr(dot_receiver)?, 180 ctx.sema.type_of_expr(dot_receiver)?,
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index 0d035c611..1a7a484a4 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -31,7 +31,7 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
31 } 31 }
32 32
33 // Suggest .await syntax for types that implement Future trait 33 // Suggest .await syntax for types that implement Future trait
34 if let Some(receiver) = &ctx.dot_receiver { 34 if let Some(receiver) = ctx.dot_receiver() {
35 if let Some(ty) = ctx.sema.type_of_expr(receiver) { 35 if let Some(ty) = ctx.sema.type_of_expr(receiver) {
36 if ty.impls_future(ctx.db) { 36 if ty.impls_future(ctx.db) {
37 let mut item = kw_completion("await"); 37 let mut item = kw_completion("await");
diff --git a/crates/ide_completion/src/completions/macro_in_item_position.rs b/crates/ide_completion/src/completions/macro_in_item_position.rs
index 202e71215..781b96ff1 100644
--- a/crates/ide_completion/src/completions/macro_in_item_position.rs
+++ b/crates/ide_completion/src/completions/macro_in_item_position.rs
@@ -5,7 +5,7 @@ use crate::{CompletionContext, Completions};
5// Ideally this should be removed and moved into `(un)qualified_path` respectively 5// Ideally this should be removed and moved into `(un)qualified_path` respectively
6pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { 6pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
7 // Show only macros in top level. 7 // Show only macros in top level.
8 if !ctx.is_new_item { 8 if !ctx.expects_item() {
9 return; 9 return;
10 } 10 }
11 11
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs
index 962aaf0df..86bbb58e2 100644
--- a/crates/ide_completion/src/completions/postfix.rs
+++ b/crates/ide_completion/src/completions/postfix.rs
@@ -14,6 +14,7 @@ use crate::{
14 completions::postfix::format_like::add_format_like_completions, 14 completions::postfix::format_like::add_format_like_completions,
15 context::CompletionContext, 15 context::CompletionContext,
16 item::{Builder, CompletionKind}, 16 item::{Builder, CompletionKind},
17 patterns::ImmediateLocation,
17 CompletionItem, CompletionItemKind, Completions, 18 CompletionItem, CompletionItemKind, Completions,
18}; 19};
19 20
@@ -22,13 +23,16 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
22 return; 23 return;
23 } 24 }
24 25
25 let dot_receiver = match &ctx.dot_receiver { 26 let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location {
26 Some(it) => it, 27 Some(ImmediateLocation::MethodCall { receiver: Some(it) }) => (it, false),
27 None => return, 28 Some(ImmediateLocation::FieldAccess {
29 receiver: Some(it),
30 receiver_is_ambiguous_float_literal,
31 }) => (it, *receiver_is_ambiguous_float_literal),
32 _ => return,
28 }; 33 };
29 34
30 let receiver_text = 35 let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
31 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
32 36
33 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { 37 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
34 Some(it) => it, 38 Some(it) => it,
@@ -123,8 +127,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
123 // The rest of the postfix completions create an expression that moves an argument, 127 // The rest of the postfix completions create an expression that moves an argument,
124 // so it's better to consider references now to avoid breaking the compilation 128 // so it's better to consider references now to avoid breaking the compilation
125 let dot_receiver = include_references(dot_receiver); 129 let dot_receiver = include_references(dot_receiver);
126 let receiver_text = 130 let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal);
127 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
128 131
129 match try_enum { 132 match try_enum {
130 Some(try_enum) => match try_enum { 133 Some(try_enum) => match try_enum {
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index defc25b00..6e6a6eb92 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -29,7 +29,7 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
29} 29}
30 30
31pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { 31pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
32 if !ctx.is_new_item { 32 if !ctx.expects_item() {
33 return; 33 return;
34 } 34 }
35 let cap = match ctx.config.snippet_cap { 35 let cap = match ctx.config.snippet_cap {
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 7c46c815d..6f685c02f 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -78,11 +78,6 @@ pub(crate) struct CompletionContext<'a> {
78 pub(super) can_be_stmt: bool, 78 pub(super) can_be_stmt: bool,
79 /// `true` if we expect an expression at the cursor position. 79 /// `true` if we expect an expression at the cursor position.
80 pub(super) is_expr: bool, 80 pub(super) is_expr: bool,
81 /// Something is typed at the "top" level, in module or impl/trait.
82 pub(super) is_new_item: bool,
83 /// The receiver if this is a field or method access, i.e. writing something.$0
84 pub(super) dot_receiver: Option<ast::Expr>,
85 pub(super) dot_receiver_is_ambiguous_float_literal: bool,
86 /// If this is a call (method or function) in particular, i.e. the () are already there. 81 /// If this is a call (method or function) in particular, i.e. the () are already there.
87 pub(super) is_call: bool, 82 pub(super) is_call: bool,
88 /// Like `is_call`, but for tuple patterns. 83 /// Like `is_call`, but for tuple patterns.
@@ -158,9 +153,6 @@ impl<'a> CompletionContext<'a> {
158 path_qual: None, 153 path_qual: None,
159 can_be_stmt: false, 154 can_be_stmt: false,
160 is_expr: false, 155 is_expr: false,
161 is_new_item: false,
162 dot_receiver: None,
163 dot_receiver_is_ambiguous_float_literal: false,
164 is_call: false, 156 is_call: false,
165 is_pattern_call: false, 157 is_pattern_call: false,
166 is_macro_call: false, 158 is_macro_call: false,
@@ -255,6 +247,22 @@ impl<'a> CompletionContext<'a> {
255 ) 247 )
256 } 248 }
257 249
250 pub(crate) fn has_dot_receiver(&self) -> bool {
251 matches!(
252 &self.completion_location,
253 Some(ImmediateLocation::FieldAccess { receiver, .. }) | Some(ImmediateLocation::MethodCall { receiver })
254 if receiver.is_some()
255 )
256 }
257
258 pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
259 match &self.completion_location {
260 Some(ImmediateLocation::MethodCall { receiver })
261 | Some(ImmediateLocation::FieldAccess { receiver, .. }) => receiver.as_ref(),
262 _ => None,
263 }
264 }
265
258 pub(crate) fn expects_use_tree(&self) -> bool { 266 pub(crate) fn expects_use_tree(&self) -> bool {
259 matches!(self.completion_location, Some(ImmediateLocation::Use)) 267 matches!(self.completion_location, Some(ImmediateLocation::Use))
260 } 268 }
@@ -267,6 +275,7 @@ impl<'a> CompletionContext<'a> {
267 matches!(self.completion_location, Some(ImmediateLocation::ItemList)) 275 matches!(self.completion_location, Some(ImmediateLocation::ItemList))
268 } 276 }
269 277
278 // fn expects_value(&self) -> bool {
270 pub(crate) fn expects_expression(&self) -> bool { 279 pub(crate) fn expects_expression(&self) -> bool {
271 self.is_expr 280 self.is_expr
272 } 281 }
@@ -540,16 +549,7 @@ impl<'a> CompletionContext<'a> {
540 self.name_ref_syntax = 549 self.name_ref_syntax =
541 find_node_at_offset(original_file, name_ref.syntax().text_range().start()); 550 find_node_at_offset(original_file, name_ref.syntax().text_range().start());
542 551
543 let name_range = name_ref.syntax().text_range(); 552 if matches!(self.completion_location, Some(ImmediateLocation::ItemList)) {
544 let top_node = name_ref
545 .syntax()
546 .ancestors()
547 .take_while(|it| it.text_range() == name_range)
548 .last()
549 .unwrap();
550
551 if matches!(top_node.parent().map(|it| it.kind()), Some(SOURCE_FILE) | Some(ITEM_LIST)) {
552 self.is_new_item = true;
553 return; 553 return;
554 } 554 }
555 555
@@ -623,33 +623,8 @@ impl<'a> CompletionContext<'a> {
623 .unwrap_or(false); 623 .unwrap_or(false);
624 self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); 624 self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some();
625 } 625 }
626 626 self.is_call |=
627 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { 627 matches!(self.completion_location, Some(ImmediateLocation::MethodCall { .. }));
628 // The receiver comes before the point of insertion of the fake
629 // ident, so it should have the same range in the non-modified file
630 self.dot_receiver = field_expr
631 .expr()
632 .map(|e| e.syntax().text_range())
633 .and_then(|r| find_node_with_range(original_file, r));
634 self.dot_receiver_is_ambiguous_float_literal =
635 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
636 match l.kind() {
637 ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
638 _ => false,
639 }
640 } else {
641 false
642 };
643 }
644
645 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
646 // As above
647 self.dot_receiver = method_call_expr
648 .receiver()
649 .map(|e| e.syntax().text_range())
650 .and_then(|r| find_node_with_range(original_file, r));
651 self.is_call = true;
652 }
653 } 628 }
654} 629}
655 630
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 26516046b..080898aef 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -7,13 +7,13 @@ use syntax::{
7 ast::{self, LoopBodyOwner}, 7 ast::{self, LoopBodyOwner},
8 match_ast, AstNode, Direction, SyntaxElement, 8 match_ast, AstNode, Direction, SyntaxElement,
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextSize, T, 10 SyntaxNode, SyntaxToken, TextRange, TextSize, T,
11}; 11};
12 12
13#[cfg(test)] 13#[cfg(test)]
14use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 14use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
15 15
16/// Direct parent container of the cursor position 16/// Immediate previous node to what we are completing.
17#[derive(Copy, Clone, Debug, PartialEq, Eq)] 17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
18pub(crate) enum ImmediatePrevSibling { 18pub(crate) enum ImmediatePrevSibling {
19 IfExpr, 19 IfExpr,
@@ -21,7 +21,7 @@ pub(crate) enum ImmediatePrevSibling {
21 ImplDefType, 21 ImplDefType,
22} 22}
23 23
24/// Direct parent container of the cursor position 24/// Direct parent "thing" of what we are currently completing.
25#[derive(Clone, Debug, PartialEq, Eq)] 25#[derive(Clone, Debug, PartialEq, Eq)]
26pub(crate) enum ImmediateLocation { 26pub(crate) enum ImmediateLocation {
27 Use, 27 Use,
@@ -37,6 +37,15 @@ pub(crate) enum ImmediateLocation {
37 // Fake file ast node 37 // Fake file ast node
38 ModDeclaration(ast::Module), 38 ModDeclaration(ast::Module),
39 // Original file ast node 39 // Original file ast node
40 MethodCall {
41 receiver: Option<ast::Expr>,
42 },
43 // Original file ast node
44 FieldAccess {
45 receiver: Option<ast::Expr>,
46 receiver_is_ambiguous_float_literal: bool,
47 },
48 // Original file ast node
40 /// The record expr of the field name we are completing 49 /// The record expr of the field name we are completing
41 RecordExpr(ast::RecordExpr), 50 RecordExpr(ast::RecordExpr),
42 // Original file ast node 51 // Original file ast node
@@ -164,12 +173,38 @@ pub(crate) fn determine_location(
164 Some(TRAIT) => ImmediateLocation::Trait, 173 Some(TRAIT) => ImmediateLocation::Trait,
165 _ => return None, 174 _ => return None,
166 }, 175 },
167 ast::Module(it) => if it.item_list().is_none() { 176 ast::Module(it) => {
177 if it.item_list().is_none() {
168 ImmediateLocation::ModDeclaration(it) 178 ImmediateLocation::ModDeclaration(it)
169 } else { 179 } else {
170 return None 180 return None;
181 }
171 }, 182 },
172 ast::Attr(it) => ImmediateLocation::Attribute(it), 183 ast::Attr(it) => ImmediateLocation::Attribute(it),
184 ast::FieldExpr(it) => {
185 let receiver = it
186 .expr()
187 .map(|e| e.syntax().text_range())
188 .and_then(|r| find_node_with_range(original_file, r));
189 let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver {
190 match l.kind() {
191 ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
192 _ => false,
193 }
194 } else {
195 false
196 };
197 ImmediateLocation::FieldAccess {
198 receiver,
199 receiver_is_ambiguous_float_literal,
200 }
201 },
202 ast::MethodCallExpr(it) => ImmediateLocation::MethodCall {
203 receiver: it
204 .receiver()
205 .map(|e| e.syntax().text_range())
206 .and_then(|r| find_node_with_range(original_file, r)),
207 },
173 _ => return None, 208 _ => return None,
174 } 209 }
175 }; 210 };
@@ -194,6 +229,10 @@ fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode {
194 name_ref.syntax().clone() 229 name_ref.syntax().clone()
195} 230}
196 231
232fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
233 syntax.covering_element(range).ancestors().find_map(N::cast)
234}
235
197pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 236pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
198 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`, 237 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
199 // where we only check the first parent with different text range. 238 // where we only check the first parent with different text range.
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs
index 3ec77ca0f..1abeed96d 100644
--- a/crates/ide_completion/src/render/function.rs
+++ b/crates/ide_completion/src/render/function.rs
@@ -154,7 +154,7 @@ impl<'a> FunctionRender<'a> {
154 }; 154 };
155 155
156 let mut params_pats = Vec::new(); 156 let mut params_pats = Vec::new();
157 let params_ty = if self.ctx.completion.dot_receiver.is_some() || self.receiver.is_some() { 157 let params_ty = if self.ctx.completion.has_dot_receiver() || self.receiver.is_some() {
158 self.func.method_params(self.ctx.db()).unwrap_or_default() 158 self.func.method_params(self.ctx.db()).unwrap_or_default()
159 } else { 159 } else {
160 if let Some(s) = ast_params.self_param() { 160 if let Some(s) = ast_params.self_param() {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index ae78fd4f6..c33cdb740 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -92,6 +92,7 @@ config_data! {
92 checkOnSave_overrideCommand: Option<Vec<String>> = "null", 92 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
93 93
94 /// Whether to add argument snippets when completing functions. 94 /// Whether to add argument snippets when completing functions.
95 /// Only applies when `#rust-analyzer.completion.addCallParenthesis#` is set.
95 completion_addCallArgumentSnippets: bool = "true", 96 completion_addCallArgumentSnippets: bool = "true",
96 /// Whether to add parenthesis when completing functions. 97 /// Whether to add parenthesis when completing functions.
97 completion_addCallParenthesis: bool = "true", 98 completion_addCallParenthesis: bool = "true",
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 0cf170626..4c3c9661d 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -580,12 +580,11 @@ pub fn fn_(
580pub fn struct_( 580pub fn struct_(
581 visibility: Option<ast::Visibility>, 581 visibility: Option<ast::Visibility>,
582 strukt_name: ast::Name, 582 strukt_name: ast::Name,
583 type_params: Option<ast::GenericParamList>, 583 generic_param_list: Option<ast::GenericParamList>,
584 field_list: ast::FieldList, 584 field_list: ast::FieldList,
585) -> ast::Struct { 585) -> ast::Struct {
586 let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" }; 586 let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" };
587 let type_params = 587 let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string());
588 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
589 let visibility = match visibility { 588 let visibility = match visibility {
590 None => String::new(), 589 None => String::new(),
591 Some(it) => format!("{} ", it), 590 Some(it) => format!("{} ", it),
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index dbd9a3503..7f405b4d7 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -119,6 +119,7 @@ similar option.
119+ 119+
120-- 120--
121Whether to add argument snippets when completing functions. 121Whether to add argument snippets when completing functions.
122Only applies when `#rust-analyzer.completion.addCallParenthesis#` is set.
122-- 123--
123[[rust-analyzer.completion.addCallParenthesis]]rust-analyzer.completion.addCallParenthesis (default: `true`):: 124[[rust-analyzer.completion.addCallParenthesis]]rust-analyzer.completion.addCallParenthesis (default: `true`)::
124+ 125+
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 1f95df56e..9a8d76700 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -187,6 +187,20 @@ Install it with pacman, for example:
187$ pacman -S rust-analyzer 187$ pacman -S rust-analyzer
188---- 188----
189 189
190==== Gentoo Linux
191
192`rust-analyzer` is available in the GURU repository:
193
194- https://gitweb.gentoo.org/repo/proj/guru.git/tree/dev-util/rust-analyzer-bin/rust-analyzer-bin-9999.ebuild[`dev-util/rust-analyzer-bin-9999`] (the https://github.com/rust-analyzer/rust-analyzer/releases/latest[latest release] as a live binary ebuild)
195
196If not already, GURU must be enabled (e.g. using `app-eselect/eselect-repository`) and sync'd before running `emerge`:
197
198[source,bash]
199----
200$ eselect repository enable guru && emaint sync -r guru
201$ emerge rust-analyzer-bin
202----
203
190=== Emacs 204=== Emacs
191 205
192Note this excellent https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/[guide] from https://github.com/rksm[@rksm]. 206Note this excellent https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/[guide] from https://github.com/rksm[@rksm].
diff --git a/editors/code/package.json b/editors/code/package.json
index 42a06e137..4a5070d02 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -553,7 +553,7 @@
553 } 553 }
554 }, 554 },
555 "rust-analyzer.completion.addCallArgumentSnippets": { 555 "rust-analyzer.completion.addCallArgumentSnippets": {
556 "markdownDescription": "Whether to add argument snippets when completing functions.", 556 "markdownDescription": "Whether to add argument snippets when completing functions.\nOnly applies when `#rust-analyzer.completion.addCallParenthesis#` is set.",
557 "default": true, 557 "default": true,
558 "type": "boolean" 558 "type": "boolean"
559 }, 559 },