diff options
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#" | ||
1252 | enum Foo<T> { A(T) } | ||
1253 | fn 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 | ||
301 | impl<'a> MatchCheckCtx<'a> { | 301 | impl<'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 | ||
155 | impl InferenceResult { | 157 | impl 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 { | |||
28 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 28 | pub(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 | ||
43 | fn expand_macro_recur( | 43 | fn 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 | }; |
14 | use itertools::Itertools; | ||
14 | use rustc_hash::FxHashSet; | 15 | use rustc_hash::FxHashSet; |
15 | use syntax::{ | 16 | use 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 | ||
21 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 26 | use 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 | |||
149 | fn create_struct_def( | 152 | fn 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 | ||
190 | fn update_variant(variant: &ast::Variant) -> Option<()> { | 203 | fn 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 | ||
219 | fn process_references( | 247 | fn 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 | ||
252 | fn reference_to_node( | 281 | fn 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 | |||
355 | enum 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)] | ||
365 | enum Enum { Variant{ field: u32$0 } }"#, | ||
366 | r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ pub field: u32 } | ||
367 | |||
368 | #[derive(Debug)] | ||
369 | #[derive(Clone)] | ||
370 | enum 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 | ||
498 | fn f() { | 558 | fn 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 | ||
509 | fn f() { | 569 | fn 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#" | ||
581 | enum E { | ||
582 | $0V(i32, i32) | ||
583 | } | ||
584 | |||
585 | fn f() { | ||
586 | let E::V(i, j) = E::V(9, 2); | ||
587 | } | ||
588 | "#, | ||
589 | r#" | ||
590 | struct V(pub i32, pub i32); | ||
591 | |||
592 | enum E { | ||
593 | V(V) | ||
594 | } | ||
595 | |||
596 | fn 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 @@ | |||
1 | use syntax::ast::{self, AstNode}; | 1 | use syntax::{ |
2 | ast::{self, edit::IndentLevel, AstNode}, | ||
3 | match_ast, | ||
4 | }; | ||
2 | 5 | ||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use 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#" | ||
172 | impl S { | ||
173 | fn f() -> $0(u8, u8)$0 {} | ||
174 | } | ||
175 | "#, | ||
176 | r#" | ||
177 | type $0Type = (u8, u8); | ||
178 | |||
179 | impl S { | ||
180 | fn f() -> Type {} | ||
181 | } | ||
182 | "#, | ||
183 | ); | ||
184 | check_assist( | ||
185 | extract_type_alias, | ||
186 | r#" | ||
187 | trait Tr { | ||
188 | fn f() -> $0(u8, u8)$0 {} | ||
189 | } | ||
190 | "#, | ||
191 | r#" | ||
192 | type $0Type = (u8, u8); | ||
193 | |||
194 | trait Tr { | ||
195 | fn f() -> Type {} | ||
196 | } | ||
197 | "#, | ||
198 | ); | ||
199 | } | ||
200 | |||
201 | #[test] | ||
202 | fn indentation() { | ||
203 | check_assist( | ||
204 | extract_type_alias, | ||
205 | r#" | ||
206 | mod m { | ||
207 | fn f() -> $0u8$0 {} | ||
208 | } | ||
209 | "#, | ||
210 | r#" | ||
211 | mod 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#" |
278 | fn foo() { | 283 | fn foo() -> i32 { |
279 | $0bar(1 + 1)$0 | 284 | $0bar(1 + 1)$0 |
280 | } | 285 | } |
286 | |||
287 | fn bar(i: i32) -> i32 { | ||
288 | i | ||
289 | } | ||
281 | "#, | 290 | "#, |
282 | r#" | 291 | r#" |
283 | fn foo() { | 292 | fn foo() -> i32 { |
284 | let $0bar = bar(1 + 1); | 293 | let $0bar = bar(1 + 1); |
285 | bar | 294 | bar |
286 | } | 295 | } |
296 | |||
297 | fn 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#" | ||
817 | fn 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. |
10 | pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(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 | ||
32 | fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { | 32 | fn 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 | ||
175 | fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> { | 175 | fn 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 |
6 | pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(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 | ||
31 | pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { | 31 | pub(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)] |
14 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 14 | use 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)] |
18 | pub(crate) enum ImmediatePrevSibling { | 18 | pub(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)] |
26 | pub(crate) enum ImmediateLocation { | 26 | pub(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 | ||
232 | fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { | ||
233 | syntax.covering_element(range).ancestors().find_map(N::cast) | ||
234 | } | ||
235 | |||
197 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 236 | pub(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_( | |||
580 | pub fn struct_( | 580 | pub 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 | -- |
121 | Whether to add argument snippets when completing functions. | 121 | Whether to add argument snippets when completing functions. |
122 | Only 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 | |||
196 | If 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 | ||
192 | Note this excellent https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/[guide] from https://github.com/rksm[@rksm]. | 206 | Note 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 | }, |