diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index b683572fbf..93157bbba6 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -5,8 +5,7 @@ mod completion_context; mod presentation; mod complete_dot; -mod complete_record_literal; -mod complete_record_pattern; +mod complete_record; mod complete_pattern; mod complete_fn_param; mod complete_keyword; @@ -89,8 +88,7 @@ pub(crate) fn completions( complete_path::complete_path(&mut acc, &ctx); complete_scope::complete_scope(&mut acc, &ctx); complete_dot::complete_dot(&mut acc, &ctx); - complete_record_literal::complete_record_literal(&mut acc, &ctx); - complete_record_pattern::complete_record_pattern(&mut acc, &ctx); + complete_record::complete_record(&mut acc, &ctx); complete_pattern::complete_pattern(&mut acc, &ctx); complete_postfix::complete_postfix(&mut acc, &ctx); complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); diff --git a/crates/ra_ide/src/completion/complete_record.rs b/crates/ra_ide/src/completion/complete_record.rs new file mode 100644 index 0000000000..01dd8c6db7 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_record.rs @@ -0,0 +1,411 @@ +//! Complete fields in record literals and patterns. +use crate::completion::{CompletionContext, Completions}; +use ra_syntax::{ast, ast::NameOwner, SmolStr}; + +pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let (ty, variant, already_present_fields) = + match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) { + (None, None) => return None, + (Some(_), Some(_)) => panic!("A record cannot be both a literal and a pattern"), + (Some(record_pat), _) => ( + ctx.sema.type_of_pat(&record_pat.clone().into())?, + ctx.sema.resolve_record_pattern(record_pat)?, + pattern_ascribed_fields(record_pat), + ), + (_, Some(record_lit)) => ( + ctx.sema.type_of_expr(&record_lit.clone().into())?, + ctx.sema.resolve_record_literal(record_lit)?, + literal_ascribed_fields(record_lit), + ), + }; + + for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| { + // FIXME: already_present_names better be `Vec` + !already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string())) + }) { + acc.add_field(ctx, field, &field_ty); + } + Some(()) +} + +fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec { + record_lit + .record_field_list() + .map(|field_list| field_list.fields()) + .map(|fields| { + fields + .into_iter() + .filter_map(|field| field.name_ref()) + .map(|name_ref| name_ref.text().clone()) + .collect() + }) + .unwrap_or_default() +} + +fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec { + record_pat + .record_field_pat_list() + .map(|pat_list| { + pat_list + .record_field_pats() + .filter_map(|fild_pat| fild_pat.name()) + .chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name())) + .map(|name| name.text().clone()) + .collect() + }) + .unwrap_or_default() +} + +#[cfg(test)] +mod tests { + mod record_lit_tests { + use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn complete(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn test_record_pattern_field() { + let completions = complete( + r" + struct S { foo: u32 } + + fn process(f: S) { + match f { + S { f<|>: 92 } => (), + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "foo", + source_range: [117; 118), + delete: [117; 118), + insert: "foo", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_pattern_enum_variant() { + let completions = complete( + r" + enum E { + S { foo: u32, bar: () } + } + + fn process(e: E) { + match e { + E::S { <|> } => (), + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "bar", + source_range: [161; 161), + delete: [161; 161), + insert: "bar", + kind: Field, + detail: "()", + }, + CompletionItem { + label: "foo", + source_range: [161; 161), + delete: [161; 161), + insert: "foo", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_pattern_field_in_simple_macro() { + let completions = complete( + r" + macro_rules! m { ($e:expr) => { $e } } + struct S { foo: u32 } + + fn process(f: S) { + m!(match f { + S { f<|>: 92 } => (), + }) + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "foo", + source_range: [171; 172), + delete: [171; 172), + insert: "foo", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn only_missing_fields_are_completed_in_destruct_pats() { + let completions = complete( + r" + struct S { + foo1: u32, + foo2: u32, + bar: u32, + baz: u32, + } + + fn main() { + let s = S { + foo1: 1, + foo2: 2, + bar: 3, + baz: 4, + }; + if let S { foo1, foo2: a, <|> } = s {} + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "bar", + source_range: [372; 372), + delete: [372; 372), + insert: "bar", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "baz", + source_range: [372; 372), + delete: [372; 372), + insert: "baz", + kind: Field, + detail: "u32", + }, + ] + "###); + } + } + + mod record_pat_tests { + use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn complete(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn test_record_literal_deprecated_field() { + let completions = complete( + r" + struct A { + #[deprecated] + the_field: u32, + } + fn foo() { + A { the<|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "the_field", + source_range: [142; 145), + delete: [142; 145), + insert: "the_field", + kind: Field, + detail: "u32", + deprecated: true, + }, + ] + "###); + } + + #[test] + fn test_record_literal_field() { + let completions = complete( + r" + struct A { the_field: u32 } + fn foo() { + A { the<|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "the_field", + source_range: [83; 86), + delete: [83; 86), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_enum_variant() { + let completions = complete( + r" + enum E { + A { a: u32 } + } + fn foo() { + let _ = E::A { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "a", + source_range: [119; 119), + delete: [119; 119), + insert: "a", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_two_structs() { + let completions = complete( + r" + struct A { a: u32 } + struct B { b: u32 } + + fn foo() { + let _: A = B { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "b", + source_range: [119; 119), + delete: [119; 119), + insert: "b", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_generic_struct() { + let completions = complete( + r" + struct A { a: T } + + fn foo() { + let _: A = A { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "a", + source_range: [93; 93), + delete: [93; 93), + insert: "a", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_field_in_simple_macro() { + let completions = complete( + r" + macro_rules! m { ($e:expr) => { $e } } + struct A { the_field: u32 } + fn foo() { + m!(A { the<|> }) + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "the_field", + source_range: [137; 140), + delete: [137; 140), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn only_missing_fields_are_completed() { + let completions = complete( + r" + struct S { + foo1: u32, + foo2: u32, + bar: u32, + baz: u32, + } + + fn main() { + let foo1 = 1; + let s = S { + foo1, + foo2: 5, + <|> + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "bar", + source_range: [302; 302), + delete: [302; 302), + insert: "bar", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "baz", + source_range: [302; 302), + delete: [302; 302), + insert: "baz", + kind: Field, + detail: "u32", + }, + ] + "###); + } + } +} diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs deleted file mode 100644 index e4e764f582..0000000000 --- a/crates/ra_ide/src/completion/complete_record_literal.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; -use ra_syntax::SmolStr; - -/// Complete fields in fields literals. -pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { - let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { - Some((ctx.sema.type_of_expr(&it.clone().into())?, ctx.sema.resolve_record_literal(it)?)) - }) { - Some(it) => it, - _ => return, - }; - - let already_present_names: Vec = ctx - .record_lit_syntax - .as_ref() - .and_then(|record_literal| record_literal.record_field_list()) - .map(|field_list| field_list.fields()) - .map(|fields| { - fields - .into_iter() - .filter_map(|field| field.name_ref()) - .map(|name_ref| name_ref.text().clone()) - .collect() - }) - .unwrap_or_default(); - - for (field, field_ty) in ty.variant_fields(ctx.db, variant) { - if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) { - acc.add_field(ctx, field, &field_ty); - } - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn complete(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn test_record_literal_deprecated_field() { - let completions = complete( - r" - struct A { - #[deprecated] - the_field: u32, - } - fn foo() { - A { the<|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "the_field", - source_range: [142; 145), - delete: [142; 145), - insert: "the_field", - kind: Field, - detail: "u32", - deprecated: true, - }, - ] - "###); - } - - #[test] - fn test_record_literal_field() { - let completions = complete( - r" - struct A { the_field: u32 } - fn foo() { - A { the<|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "the_field", - source_range: [83; 86), - delete: [83; 86), - insert: "the_field", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_enum_variant() { - let completions = complete( - r" - enum E { - A { a: u32 } - } - fn foo() { - let _ = E::A { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "a", - source_range: [119; 119), - delete: [119; 119), - insert: "a", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_two_structs() { - let completions = complete( - r" - struct A { a: u32 } - struct B { b: u32 } - - fn foo() { - let _: A = B { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "b", - source_range: [119; 119), - delete: [119; 119), - insert: "b", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_generic_struct() { - let completions = complete( - r" - struct A { a: T } - - fn foo() { - let _: A = A { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "a", - source_range: [93; 93), - delete: [93; 93), - insert: "a", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_field_in_simple_macro() { - let completions = complete( - r" - macro_rules! m { ($e:expr) => { $e } } - struct A { the_field: u32 } - fn foo() { - m!(A { the<|> }) - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "the_field", - source_range: [137; 140), - delete: [137; 140), - insert: "the_field", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn only_missing_fields_are_completed() { - let completions = complete( - r" - struct S { - foo1: u32, - foo2: u32, - bar: u32, - baz: u32, - } - - fn main() { - let foo1 = 1; - let s = S { - foo1, - foo2: 5, - <|> - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "bar", - source_range: [302; 302), - delete: [302; 302), - insert: "bar", - kind: Field, - detail: "u32", - }, - CompletionItem { - label: "baz", - source_range: [302; 302), - delete: [302; 302), - insert: "baz", - kind: Field, - detail: "u32", - }, - ] - "###); - } -} diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs deleted file mode 100644 index 962376428c..0000000000 --- a/crates/ra_ide/src/completion/complete_record_pattern.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; - -pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { - let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { - Some((ctx.sema.type_of_pat(&it.clone().into())?, ctx.sema.resolve_record_pattern(it)?)) - }) { - Some(it) => it, - _ => return, - }; - - for (field, field_ty) in ty.variant_fields(ctx.db, variant) { - acc.add_field(ctx, field, &field_ty); - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn complete(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn test_record_pattern_field() { - let completions = complete( - r" - struct S { foo: u32 } - - fn process(f: S) { - match f { - S { f<|>: 92 } => (), - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "foo", - source_range: [117; 118), - delete: [117; 118), - insert: "foo", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_pattern_enum_variant() { - let completions = complete( - r" - enum E { - S { foo: u32, bar: () } - } - - fn process(e: E) { - match e { - E::S { <|> } => (), - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "bar", - source_range: [161; 161), - delete: [161; 161), - insert: "bar", - kind: Field, - detail: "()", - }, - CompletionItem { - label: "foo", - source_range: [161; 161), - delete: [161; 161), - insert: "foo", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_pattern_field_in_simple_macro() { - let completions = complete( - r" - macro_rules! m { ($e:expr) => { $e } } - struct S { foo: u32 } - - fn process(f: S) { - m!(match f { - S { f<|>: 92 } => (), - }) - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "foo", - source_range: [171; 172), - delete: [171; 172), - insert: "foo", - kind: Field, - detail: "u32", - }, - ] - "###); - } -}