From 1b0c7701cc97cd7bef8bb9729011d4cf291a60c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 17:42:52 +0200 Subject: Rename ra_ide -> ide --- crates/ide/src/completion/complete_attribute.rs | 644 ++++++++++ crates/ide/src/completion/complete_dot.rs | 416 +++++++ crates/ide/src/completion/complete_fn_param.rs | 135 +++ crates/ide/src/completion/complete_keyword.rs | 536 +++++++++ .../completion/complete_macro_in_item_position.rs | 41 + crates/ide/src/completion/complete_pattern.rs | 88 ++ crates/ide/src/completion/complete_postfix.rs | 378 ++++++ .../ide/src/completion/complete_qualified_path.rs | 733 ++++++++++++ crates/ide/src/completion/complete_record.rs | 226 ++++ crates/ide/src/completion/complete_snippet.rs | 116 ++ crates/ide/src/completion/complete_trait_impl.rs | 488 ++++++++ .../src/completion/complete_unqualified_path.rs | 658 +++++++++++ crates/ide/src/completion/completion_config.rs | 35 + crates/ide/src/completion/completion_context.rs | 465 ++++++++ crates/ide/src/completion/completion_item.rs | 384 ++++++ crates/ide/src/completion/patterns.rs | 194 +++ crates/ide/src/completion/presentation.rs | 1229 ++++++++++++++++++++ crates/ide/src/completion/test_utils.rs | 114 ++ 18 files changed, 6880 insertions(+) create mode 100644 crates/ide/src/completion/complete_attribute.rs create mode 100644 crates/ide/src/completion/complete_dot.rs create mode 100644 crates/ide/src/completion/complete_fn_param.rs create mode 100644 crates/ide/src/completion/complete_keyword.rs create mode 100644 crates/ide/src/completion/complete_macro_in_item_position.rs create mode 100644 crates/ide/src/completion/complete_pattern.rs create mode 100644 crates/ide/src/completion/complete_postfix.rs create mode 100644 crates/ide/src/completion/complete_qualified_path.rs create mode 100644 crates/ide/src/completion/complete_record.rs create mode 100644 crates/ide/src/completion/complete_snippet.rs create mode 100644 crates/ide/src/completion/complete_trait_impl.rs create mode 100644 crates/ide/src/completion/complete_unqualified_path.rs create mode 100644 crates/ide/src/completion/completion_config.rs create mode 100644 crates/ide/src/completion/completion_context.rs create mode 100644 crates/ide/src/completion/completion_item.rs create mode 100644 crates/ide/src/completion/patterns.rs create mode 100644 crates/ide/src/completion/presentation.rs create mode 100644 crates/ide/src/completion/test_utils.rs (limited to 'crates/ide/src/completion') diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs new file mode 100644 index 000000000..603d935de --- /dev/null +++ b/crates/ide/src/completion/complete_attribute.rs @@ -0,0 +1,644 @@ +//! Completion for attributes +//! +//! This module uses a bit of static metadata to provide completions +//! for built-in attributes. + +use rustc_hash::FxHashSet; +use syntax::{ast, AstNode, SyntaxKind}; + +use crate::completion::{ + completion_context::CompletionContext, + completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, +}; + +pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let attribute = ctx.attribute_under_caret.as_ref()?; + match (attribute.path(), attribute.token_tree()) { + (Some(path), Some(token_tree)) if path.to_string() == "derive" => { + complete_derive(acc, ctx, token_tree) + } + (Some(path), Some(token_tree)) + if ["allow", "warn", "deny", "forbid"] + .iter() + .any(|lint_level| lint_level == &path.to_string()) => + { + complete_lint(acc, ctx, token_tree) + } + (_, Some(_token_tree)) => {} + _ => complete_attribute_start(acc, ctx, attribute), + } + Some(()) +} + +fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { + for attr_completion in ATTRIBUTES { + let mut item = CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + attr_completion.label, + ) + .kind(CompletionItemKind::Attribute); + + if let Some(lookup) = attr_completion.lookup { + item = item.lookup_by(lookup); + } + + match (attr_completion.snippet, ctx.config.snippet_cap) { + (Some(snippet), Some(cap)) => { + item = item.insert_snippet(cap, snippet); + } + _ => {} + } + + if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { + acc.add(item); + } + } +} + +struct AttrCompletion { + label: &'static str, + lookup: Option<&'static str>, + snippet: Option<&'static str>, + prefer_inner: bool, +} + +impl AttrCompletion { + const fn prefer_inner(self) -> AttrCompletion { + AttrCompletion { prefer_inner: true, ..self } + } +} + +const fn attr( + label: &'static str, + lookup: Option<&'static str>, + snippet: Option<&'static str>, +) -> AttrCompletion { + AttrCompletion { label, lookup, snippet, prefer_inner: false } +} + +const ATTRIBUTES: &[AttrCompletion] = &[ + attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), + attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), + attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), + attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), + attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)), + attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), + attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), + attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), + attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), + // FIXME: resolve through macro resolution? + attr("global_allocator", None, None).prefer_inner(), + attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), + attr("inline(…)", Some("inline"), Some("inline(${0:lint})")), + attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)), + attr("link", None, None), + attr("macro_export", None, None), + attr("macro_use", None, None), + attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)), + attr("no_mangle", None, None), + attr("no_std", None, None).prefer_inner(), + attr("non_exhaustive", None, None), + attr("panic_handler", None, None).prefer_inner(), + attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")), + attr("proc_macro", None, None), + attr("proc_macro_attribute", None, None), + attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")), + attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}")) + .prefer_inner(), + attr("repr(…)", Some("repr"), Some("repr(${0:C})")), + attr( + "should_panic(…)", + Some("should_panic"), + Some(r#"should_panic(expected = "${0:reason}")"#), + ), + attr( + r#"target_feature = "…""#, + Some("target_feature"), + Some("target_feature = \"${0:feature}\""), + ), + attr("test", None, None), + attr("used", None, None), + attr("warn(…)", Some("warn"), Some("warn(${0:lint})")), + attr( + r#"windows_subsystem = "…""#, + Some("windows_subsystem"), + Some(r#"windows_subsystem = "${0:subsystem}""#), + ) + .prefer_inner(), +]; + +fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { + if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { + for derive_completion in DEFAULT_DERIVE_COMPLETIONS + .into_iter() + .filter(|completion| !existing_derives.contains(completion.label)) + { + let mut label = derive_completion.label.to_owned(); + for dependency in derive_completion + .dependencies + .into_iter() + .filter(|&&dependency| !existing_derives.contains(dependency)) + { + label.push_str(", "); + label.push_str(dependency); + } + acc.add( + CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) + .kind(CompletionItemKind::Attribute), + ); + } + + for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { + acc.add( + CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + custom_derive_name, + ) + .kind(CompletionItemKind::Attribute), + ); + } + } +} + +fn complete_lint(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { + if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { + for lint_completion in DEFAULT_LINT_COMPLETIONS + .into_iter() + .filter(|completion| !existing_lints.contains(completion.label)) + { + acc.add( + CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + lint_completion.label, + ) + .kind(CompletionItemKind::Attribute) + .detail(lint_completion.description), + ); + } + } +} + +fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result, ()> { + match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { + (Some(left_paren), Some(right_paren)) + if left_paren.kind() == SyntaxKind::L_PAREN + && right_paren.kind() == SyntaxKind::R_PAREN => + { + let mut input_derives = FxHashSet::default(); + let mut current_derive = String::new(); + for token in derive_input + .syntax() + .children_with_tokens() + .filter_map(|token| token.into_token()) + .skip_while(|token| token != &left_paren) + .skip(1) + .take_while(|token| token != &right_paren) + { + if SyntaxKind::COMMA == token.kind() { + if !current_derive.is_empty() { + input_derives.insert(current_derive); + current_derive = String::new(); + } + } else { + current_derive.push_str(token.to_string().trim()); + } + } + + if !current_derive.is_empty() { + input_derives.insert(current_derive); + } + Ok(input_derives) + } + _ => Err(()), + } +} + +fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet { + let mut result = FxHashSet::default(); + ctx.scope.process_all_names(&mut |name, scope_def| { + if let hir::ScopeDef::MacroDef(mac) = scope_def { + if mac.is_derive_macro() { + result.insert(name.to_string()); + } + } + }); + result +} + +struct DeriveCompletion { + label: &'static str, + dependencies: &'static [&'static str], +} + +/// Standard Rust derives and the information about their dependencies +/// (the dependencies are needed so that the main derive don't break the compilation when added) +#[rustfmt::skip] +const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ + DeriveCompletion { label: "Clone", dependencies: &[] }, + DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, + DeriveCompletion { label: "Debug", dependencies: &[] }, + DeriveCompletion { label: "Default", dependencies: &[] }, + DeriveCompletion { label: "Hash", dependencies: &[] }, + DeriveCompletion { label: "PartialEq", dependencies: &[] }, + DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, +]; + +struct LintCompletion { + label: &'static str, + description: &'static str, +} + +#[rustfmt::skip] +const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ + LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, + LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, + LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, + LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, + LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, + LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, + LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, + LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, + LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, + LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, + LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, + LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, + LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, + LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, + LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, + LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, + LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, + LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, + LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, + LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, + LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, + LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, + LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, + LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, + LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, + LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, + LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, + LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, + LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, + LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, + LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, + LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, + LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, + LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, + LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, + LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, + LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, + LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, + LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, + LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, + LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, + LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, + LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, + LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, + LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, + LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, + LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, + LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, + LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, + LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, + LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, + LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, + LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, + LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, + LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, + LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, + LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, + LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, + LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, + LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, + LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, + LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, + LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, + LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, + LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, + LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, + LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, + LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, + LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, + LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, + LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, + LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, + LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, + LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, + LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, + LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, + LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, + LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, + LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, + LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, + LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, + LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, + LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, + LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, + LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, + LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, + LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, + LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, + LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, + LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, + LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, + LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, + LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, + LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, + LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, + LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, + LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, + LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, + LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, + LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, + LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, + LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, + LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, + LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, + LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, + LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, + LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, + LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, + LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, + LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, + LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, + LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, + LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, + LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, + LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, +]; + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Attribute); + expect.assert_eq(&actual); + } + + #[test] + fn empty_derive_completion() { + check( + r#" +#[derive(<|>)] +struct Test {} + "#, + expect![[r#" + at Clone + at Copy, Clone + at Debug + at Default + at Eq, PartialEq + at Hash + at Ord, PartialOrd, Eq, PartialEq + at PartialEq + at PartialOrd, PartialEq + "#]], + ); + } + + #[test] + fn empty_lint_completion() { + check( + r#"#[allow(<|>)]"#, + expect![[r#" + at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name + at ambiguous_associated_items ambiguous associated items + at anonymous_parameters detects anonymous parameters + at arithmetic_overflow arithmetic operation overflows + at array_into_iter detects calling `into_iter` on arrays + at asm_sub_register using only a subset of a register for inline asm inputs + at bare_trait_objects suggest using `dyn Trait` for trait objects + at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants + at box_pointers use of owned (Box type) heap memory + at cenum_impl_drop_cast a C-like enum implementing Drop is cast + at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types + at coherence_leak_check distinct impls distinguished only by the leak-check code + at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice + at confusable_idents detects visually confusable pairs between identifiers + at const_err constant evaluation detected erroneous expression + at dead_code detect unused, unexported items + at deprecated detects use of deprecated items + at deprecated_in_future detects use of items that will be deprecated in a future version + at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated + at ellipsis_inclusive_range_patterns `...` range patterns are deprecated + at explicit_outlives_requirements outlives requirements can be inferred + at exported_private_dependencies public interface leaks type from a private dependency + at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice + at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns + at improper_ctypes proper use of libc types in foreign modules + at improper_ctypes_definitions proper use of libc types in foreign item definitions + at incomplete_features incomplete features that may function improperly in some or all cases + at incomplete_include trailing content in included file + at indirect_structural_match pattern with const indirectly referencing non-structural-match type + at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]` + at intra_doc_link_resolution_failure failures in resolving intra-doc link targets + at invalid_codeblock_attributes codeblock attribute looks a lot like a known one + at invalid_type_param_default type parameter default erroneously allowed in invalid location + at invalid_value an invalid value is being created (such as a NULL reference) + at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements + at keyword_idents detects edition keywords being used as an identifier + at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters + at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths + at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system + at meta_variable_misuse possible meta-variable misuse at macro definition + at missing_copy_implementations detects potentially-forgotten implementations of `Copy` + at missing_crate_level_docs detects crates with no crate-level documentation + at missing_debug_implementations detects missing implementations of Debug + at missing_doc_code_examples detects publicly-exported items without code samples in their documentation + at missing_docs detects missing documentation for public members + at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns + at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used + at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows + at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior + at no_mangle_const_items const items will not have their symbols exported + at no_mangle_generic_items generic items must be mangled + at non_ascii_idents detects non-ASCII identifiers + at non_camel_case_types types, variants, traits and type parameters should have camel case names + at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern + at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names + at non_upper_case_globals static constants should have uppercase identifiers + at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order + at overflowing_literals literal out of range for its type + at overlapping_patterns detects overlapping patterns + at path_statements path statements with no effect + at patterns_in_fns_without_body patterns in functions without body were erroneously allowed + at private_doc_tests detects code samples in docs of private items not documented by rustdoc + at private_in_public detect private items in public interfaces not caught by the old implementation + at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules + at pub_use_of_private_extern_crate detect public re-exports of private extern crates + at redundant_semicolons detects unnecessary trailing semicolons + at renamed_and_removed_lints lints that have been renamed or removed + at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed + at single_use_lifetimes detects lifetime parameters that are only used once + at soft_unstable a feature gate that doesn't break dependent crates + at stable_features stable features found in `#[feature]` directive + at trivial_bounds these bounds don't depend on an type parameters + at trivial_casts detects trivial casts which could be removed + at trivial_numeric_casts detects trivial casts of numeric types which could be removed + at type_alias_bounds bounds in type aliases are not enforced + at tyvar_behind_raw_pointer raw pointer to an inference variable + at unaligned_references detects unaligned references to fields of packed structs + at uncommon_codepoints detects uncommon Unicode codepoints in identifiers + at unconditional_panic operation will cause a panic at runtime + at unconditional_recursion functions that cannot return without calling themselves + at unknown_crate_types unknown crate type found in `#[crate_type]` directive + at unknown_lints unrecognized lint attribute + at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]` + at unreachable_code detects unreachable code paths + at unreachable_patterns detects unreachable patterns + at unreachable_pub `pub` items not reachable from crate root + at unsafe_code usage of `unsafe` code + at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated + at unstable_features enabling unstable features (deprecated. do not use) + at unstable_name_collisions detects name collision with an existing but unstable method + at unused_allocation detects unnecessary allocations that can be eliminated + at unused_assignments detect assignments that will never be read + at unused_attributes detects attributes that were not used by the compiler + at unused_braces unnecessary braces around an expression + at unused_comparisons comparisons made useless by limits of the types involved + at unused_crate_dependencies crate dependencies that are never used + at unused_doc_comments detects doc comments that aren't used by rustdoc + at unused_extern_crates extern crates that are never used + at unused_features unused features found in crate-level `#[feature]` directives + at unused_import_braces unnecessary braces around an imported item + at unused_imports imports that are never used + at unused_labels detects labels that are never used + at unused_lifetimes detects lifetime parameters that are never used + at unused_macros detects macros that were not used + at unused_must_use unused result of a type flagged as `#[must_use]` + at unused_mut detect mut variables which don't need to be mutable + at unused_parens `if`, `match`, `while` and `return` do not need parentheses + at unused_qualifications detects unnecessarily qualified names + at unused_results unused result of an expression in a statement + at unused_unsafe unnecessary use of an `unsafe` block + at unused_variables detect variables which are not used in any way + at variant_size_differences detects enums with widely varying variant sizes + at warnings mass-change the level for lints which produce warnings + at where_clauses_object_safety checks the object safety of where clauses + at while_true suggest using `loop { }` instead of `while true { }` + "#]], + ) + } + + #[test] + fn no_completion_for_incorrect_derive() { + check( + r#" +#[derive{<|>)] +struct Test {} +"#, + expect![[r#""#]], + ) + } + + #[test] + fn derive_with_input_completion() { + check( + r#" +#[derive(serde::Serialize, PartialEq, <|>)] +struct Test {} +"#, + expect![[r#" + at Clone + at Copy, Clone + at Debug + at Default + at Eq + at Hash + at Ord, PartialOrd, Eq + at PartialOrd + "#]], + ) + } + + #[test] + fn test_attribute_completion() { + check( + r#"#[<|>]"#, + expect![[r#" + at allow(…) + at cfg(…) + at cfg_attr(…) + at deny(…) + at deprecated = "…" + at derive(…) + at doc = "…" + at forbid(…) + at ignore = "…" + at inline(…) + at link + at link_name = "…" + at macro_export + at macro_use + at must_use = "…" + at no_mangle + at non_exhaustive + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at repr(…) + at should_panic(…) + at target_feature = "…" + at test + at used + at warn(…) + "#]], + ) + } + + #[test] + fn test_attribute_completion_inside_nested_attr() { + check(r#"#[cfg(<|>)]"#, expect![[]]) + } + + #[test] + fn test_inner_attribute_completion() { + check( + r"#![<|>]", + expect![[r#" + at allow(…) + at cfg(…) + at cfg_attr(…) + at deny(…) + at deprecated = "…" + at derive(…) + at doc = "…" + at feature(…) + at forbid(…) + at global_allocator + at ignore = "…" + at inline(…) + at link + at link_name = "…" + at macro_export + at macro_use + at must_use = "…" + at no_mangle + at no_std + at non_exhaustive + at panic_handler + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at recursion_limit = … + at repr(…) + at should_panic(…) + at target_feature = "…" + at test + at used + at warn(…) + at windows_subsystem = "…" + "#]], + ); + } +} diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/ide/src/completion/complete_dot.rs new file mode 100644 index 000000000..532665285 --- /dev/null +++ b/crates/ide/src/completion/complete_dot.rs @@ -0,0 +1,416 @@ +//! Completes references after dot (fields and method calls). + +use hir::{HasVisibility, Type}; +use rustc_hash::FxHashSet; +use test_utils::mark; + +use crate::completion::{completion_context::CompletionContext, completion_item::Completions}; + +/// Complete dot accesses, i.e. fields or methods. +pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { + let dot_receiver = match &ctx.dot_receiver { + Some(expr) => expr, + _ => return, + }; + + let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { + Some(ty) => ty, + _ => return, + }; + + if ctx.is_call { + mark::hit!(test_no_struct_field_completion_for_method_call); + } else { + complete_fields(acc, ctx, &receiver_ty); + } + complete_methods(acc, ctx, &receiver_ty); +} + +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { + for receiver in receiver.autoderef(ctx.db) { + for (field, ty) in receiver.fields(ctx.db) { + if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { + // Skip private field. FIXME: If the definition location of the + // field is editable, we should show the completion + continue; + } + acc.add_field(ctx, field, &ty); + } + for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { + // FIXME: Handle visibility + acc.add_tuple_field(ctx, i, &ty); + } + } +} + +fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { + if let Some(krate) = ctx.krate { + let mut seen_methods = FxHashSet::default(); + let traits_in_scope = ctx.scope.traits_in_scope(); + receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { + if func.has_self_param(ctx.db) + && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) + && seen_methods.insert(func.name(ctx.db)) + { + acc.add_function(ctx, func, None); + } + None::<()> + }); + } +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + use test_utils::mark; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual); + } + + #[test] + fn test_struct_field_and_method_completion() { + check( + r#" +struct S { foo: u32 } +impl S { + fn bar(&self) {} +} +fn foo(s: S) { s.<|> } +"#, + expect![[r#" + me bar() fn bar(&self) + fd foo u32 + "#]], + ); + } + + #[test] + fn test_struct_field_completion_self() { + check( + r#" +struct S { the_field: (u32,) } +impl S { + fn foo(self) { self.<|> } +} +"#, + expect![[r#" + me foo() fn foo(self) + fd the_field (u32,) + "#]], + ) + } + + #[test] + fn test_struct_field_completion_autoderef() { + check( + r#" +struct A { the_field: (u32, i32) } +impl A { + fn foo(&self) { self.<|> } +} +"#, + expect![[r#" + me foo() fn foo(&self) + fd the_field (u32, i32) + "#]], + ) + } + + #[test] + fn test_no_struct_field_completion_for_method_call() { + mark::check!(test_no_struct_field_completion_for_method_call); + check( + r#" +struct A { the_field: u32 } +fn foo(a: A) { a.<|>() } +"#, + expect![[""]], + ); + } + + #[test] + fn test_visibility_filtering() { + check( + r#" +mod inner { + pub struct A { + private_field: u32, + pub pub_field: u32, + pub(crate) crate_field: u32, + pub(super) super_field: u32, + } +} +fn foo(a: inner::A) { a.<|> } +"#, + expect![[r#" + fd crate_field u32 + fd pub_field u32 + fd super_field u32 + "#]], + ); + + check( + r#" +struct A {} +mod m { + impl super::A { + fn private_method(&self) {} + pub(super) fn the_method(&self) {} + } +} +fn foo(a: A) { a.<|> } +"#, + expect![[r#" + me the_method() pub(super) fn the_method(&self) + "#]], + ); + } + + #[test] + fn test_union_field_completion() { + check( + r#" +union U { field: u8, other: u16 } +fn foo(u: U) { u.<|> } +"#, + expect![[r#" + fd field u8 + fd other u16 + "#]], + ); + } + + #[test] + fn test_method_completion_only_fitting_impls() { + check( + r#" +struct A {} +impl A { + fn the_method(&self) {} +} +impl A { + fn the_other_method(&self) {} +} +fn foo(a: A) { a.<|> } +"#, + expect![[r#" + me the_method() fn the_method(&self) + "#]], + ) + } + + #[test] + fn test_trait_method_completion() { + check( + r#" +struct A {} +trait Trait { fn the_method(&self); } +impl Trait for A {} +fn foo(a: A) { a.<|> } +"#, + expect![[r#" + me the_method() fn the_method(&self) + "#]], + ); + } + + #[test] + fn test_trait_method_completion_deduplicated() { + check( + r" +struct A {} +trait Trait { fn the_method(&self); } +impl Trait for T {} +fn foo(a: &A) { a.<|> } +", + expect![[r#" + me the_method() fn the_method(&self) + "#]], + ); + } + + #[test] + fn completes_trait_method_from_other_module() { + check( + r" +struct A {} +mod m { + pub trait Trait { fn the_method(&self); } +} +use m::Trait; +impl Trait for A {} +fn foo(a: A) { a.<|> } +", + expect![[r#" + me the_method() fn the_method(&self) + "#]], + ); + } + + #[test] + fn test_no_non_self_method() { + check( + r#" +struct A {} +impl A { + fn the_method() {} +} +fn foo(a: A) { + a.<|> +} +"#, + expect![[""]], + ); + } + + #[test] + fn test_tuple_field_completion() { + check( + r#" +fn foo() { + let b = (0, 3.14); + b.<|> +} +"#, + expect![[r#" + fd 0 i32 + fd 1 f64 + "#]], + ) + } + + #[test] + fn test_tuple_field_inference() { + check( + r#" +pub struct S; +impl S { pub fn blah(&self) {} } + +struct T(S); + +impl T { + fn foo(&self) { + // FIXME: This doesn't work without the trailing `a` as `0.` is a float + self.0.a<|> + } +} +"#, + expect![[r#" + me blah() pub fn blah(&self) + "#]], + ); + } + + #[test] + fn test_completion_works_in_consts() { + check( + r#" +struct A { the_field: u32 } +const X: u32 = { + A { the_field: 92 }.<|> +}; +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn works_in_simple_macro_1() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +struct A { the_field: u32 } +fn foo(a: A) { + m!(a.x<|>) +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn works_in_simple_macro_2() { + // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +struct A { the_field: u32 } +fn foo(a: A) { + m!(a.<|>) +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn works_in_simple_macro_recursive_1() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +struct A { the_field: u32 } +fn foo(a: A) { + m!(m!(m!(a.x<|>))) +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn macro_expansion_resilient() { + check( + r#" +macro_rules! dbg { + () => {}; + ($val:expr) => { + match $val { tmp => { tmp } } + }; + // Trailing comma with single argument is ignored + ($val:expr,) => { $crate::dbg!($val) }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} +struct A { the_field: u32 } +fn foo(a: A) { + dbg!(a.<|>) +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn test_method_completion_issue_3547() { + check( + r#" +struct HashSet {} +impl HashSet { + pub fn the_method(&self) {} +} +fn foo() { + let s: HashSet<_>; + s.<|> +} +"#, + expect![[r#" + me the_method() pub fn the_method(&self) + "#]], + ); + } +} diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/ide/src/completion/complete_fn_param.rs new file mode 100644 index 000000000..7c63ce58f --- /dev/null +++ b/crates/ide/src/completion/complete_fn_param.rs @@ -0,0 +1,135 @@ +//! See `complete_fn_param`. + +use rustc_hash::FxHashMap; +use syntax::{ + ast::{self, ModuleItemOwner}, + match_ast, AstNode, +}; + +use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; + +/// Complete repeated parameters, both name and type. For example, if all +/// functions in a file have a `spam: &mut Spam` parameter, a completion with +/// `spam: &mut Spam` insert text/label and `spam` lookup string will be +/// suggested. +pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_param { + return; + } + + let mut params = FxHashMap::default(); + + let me = ctx.token.ancestors().find_map(ast::Fn::cast); + let mut process_fn = |func: ast::Fn| { + if Some(&func) == me.as_ref() { + return; + } + func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { + let text = param.syntax().text().to_string(); + params.entry(text).or_insert(param); + }) + }; + + for node in ctx.token.parent().ancestors() { + match_ast! { + match node { + ast::SourceFile(it) => it.items().filter_map(|item| match item { + ast::Item::Fn(it) => Some(it), + _ => None, + }).for_each(&mut process_fn), + ast::ItemList(it) => it.items().filter_map(|item| match item { + ast::Item::Fn(it) => Some(it), + _ => None, + }).for_each(&mut process_fn), + ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item { + ast::AssocItem::Fn(it) => Some(it), + _ => None, + }).for_each(&mut process_fn), + _ => continue, + } + }; + } + + params + .into_iter() + .filter_map(|(label, param)| { + let lookup = param.pat()?.syntax().text().to_string(); + Some((label, lookup)) + }) + .for_each(|(label, lookup)| { + CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) + .kind(crate::CompletionItemKind::Binding) + .lookup_by(lookup) + .add_to(acc) + }); +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Magic); + expect.assert_eq(&actual); + } + + #[test] + fn test_param_completion_last_param() { + check( + r#" +fn foo(file_id: FileId) {} +fn bar(file_id: FileId) {} +fn baz(file<|>) {} +"#, + expect![[r#" + bn file_id: FileId + "#]], + ); + } + + #[test] + fn test_param_completion_nth_param() { + check( + r#" +fn foo(file_id: FileId) {} +fn baz(file<|>, x: i32) {} +"#, + expect![[r#" + bn file_id: FileId + "#]], + ); + } + + #[test] + fn test_param_completion_trait_param() { + check( + r#" +pub(crate) trait SourceRoot { + pub fn contains(&self, file_id: FileId) -> bool; + pub fn module_map(&self) -> &ModuleMap; + pub fn lines(&self, file_id: FileId) -> &LineIndex; + pub fn syntax(&self, file<|>) +} +"#, + expect![[r#" + bn file_id: FileId + "#]], + ); + } + + #[test] + fn completes_param_in_inner_function() { + check( + r#" +fn outer(text: String) { + fn inner(<|>) +} +"#, + expect![[r#" + bn text: String + "#]], + ) + } +} diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs new file mode 100644 index 000000000..a80708935 --- /dev/null +++ b/crates/ide/src/completion/complete_keyword.rs @@ -0,0 +1,536 @@ +//! FIXME: write short doc here + +use syntax::{ast, SyntaxKind}; +use test_utils::mark; + +use crate::completion::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, +}; + +pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { + // complete keyword "crate" in use stmt + let source_range = ctx.source_range(); + match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { + (Some(_), None) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") + .kind(CompletionItemKind::Keyword) + .insert_text("crate::") + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super::") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + (Some(_), Some(_)) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super::") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + _ => {} + } + + // Suggest .await syntax for types that implement Future trait + if let Some(receiver) = &ctx.dot_receiver { + if let Some(ty) = ctx.sema.type_of_expr(receiver) { + if ty.impls_future(ctx.db) { + CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") + .kind(CompletionItemKind::Keyword) + .detail("expr.await") + .insert_text("await") + .add_to(acc); + } + }; + } +} + +pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.token.kind() == SyntaxKind::COMMENT { + mark::hit!(no_keyword_completion_in_comments); + return; + } + + let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; + if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { + add_keyword(ctx, acc, "where", "where "); + return; + } + if ctx.unsafe_is_prev { + if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { + add_keyword(ctx, acc, "fn", "fn $0() {}") + } + + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "trait", "trait $0 {}"); + add_keyword(ctx, acc, "impl", "impl $0 {}"); + } + + return; + } + if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent + { + add_keyword(ctx, acc, "fn", "fn $0() {}"); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "use", "use "); + add_keyword(ctx, acc, "impl", "impl $0 {}"); + add_keyword(ctx, acc, "trait", "trait $0 {}"); + } + + if ctx.has_item_list_or_source_file_parent { + add_keyword(ctx, acc, "enum", "enum $0 {}"); + add_keyword(ctx, acc, "struct", "struct $0"); + add_keyword(ctx, acc, "union", "union $0 {}"); + } + + if ctx.is_expr { + add_keyword(ctx, acc, "match", "match $0 {}"); + add_keyword(ctx, acc, "while", "while $0 {}"); + add_keyword(ctx, acc, "loop", "loop {$0}"); + add_keyword(ctx, acc, "if", "if "); + add_keyword(ctx, acc, "if let", "if let "); + } + + if ctx.if_is_prev || ctx.block_expr_parent { + add_keyword(ctx, acc, "let", "let "); + } + + if ctx.after_if { + add_keyword(ctx, acc, "else", "else {$0}"); + add_keyword(ctx, acc, "else if", "else if $0 {}"); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "mod", "mod $0 {}"); + } + if ctx.bind_pat_parent || ctx.ref_pat_parent { + add_keyword(ctx, acc, "mut", "mut "); + } + if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent + { + add_keyword(ctx, acc, "const", "const "); + add_keyword(ctx, acc, "type", "type "); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "static", "static "); + }; + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "extern", "extern "); + } + if ctx.has_item_list_or_source_file_parent + || has_trait_or_impl_parent + || ctx.block_expr_parent + || ctx.is_match_arm + { + add_keyword(ctx, acc, "unsafe", "unsafe "); + } + if ctx.in_loop_body { + if ctx.can_be_stmt { + add_keyword(ctx, acc, "continue", "continue;"); + add_keyword(ctx, acc, "break", "break;"); + } else { + add_keyword(ctx, acc, "continue", "continue"); + add_keyword(ctx, acc, "break", "break"); + } + } + if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent { + add_keyword(ctx, acc, "pub", "pub ") + } + + if !ctx.is_trivial_path { + return; + } + let fn_def = match &ctx.function_syntax { + Some(it) => it, + None => return, + }; + acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); +} + +fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { + let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) + .kind(CompletionItemKind::Keyword); + + match ctx.config.snippet_cap { + Some(cap) => res.insert_snippet(cap, snippet), + _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), + } + .build() +} + +fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { + acc.add(keyword(ctx, kw, snippet)); +} + +fn complete_return( + ctx: &CompletionContext, + fn_def: &ast::Fn, + can_be_stmt: bool, +) -> Option { + let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { + (true, true) => "return $0;", + (true, false) => "return;", + (false, true) => "return $0", + (false, false) => "return", + }; + Some(keyword(ctx, "return", snip)) +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + use test_utils::mark; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Keyword); + expect.assert_eq(&actual) + } + + #[test] + fn test_keywords_in_use_stmt() { + check( + r"use <|>", + expect![[r#" + kw crate:: + kw self + kw super:: + "#]], + ); + + check( + r"use a::<|>", + expect![[r#" + kw self + kw super:: + "#]], + ); + + check( + r"use a::{b, <|>}", + expect![[r#" + kw self + kw super:: + "#]], + ); + } + + #[test] + fn test_keywords_at_source_file_level() { + check( + r"m<|>", + expect![[r#" + kw const + kw enum + kw extern + kw fn + kw impl + kw mod + kw pub + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); + } + + #[test] + fn test_keywords_in_function() { + check( + r"fn quux() { <|> }", + expect![[r#" + kw const + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_inside_block() { + check( + r"fn quux() { if true { <|> } }", + expect![[r#" + kw const + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_after_if() { + check( + r#"fn quux() { if true { () } <|> }"#, + expect![[r#" + kw const + kw else + kw else if + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + check_edit( + "else", + r#"fn quux() { if true { () } <|> }"#, + r#"fn quux() { if true { () } else {$0} }"#, + ); + } + + #[test] + fn test_keywords_in_match_arm() { + check( + r#" +fn quux() -> i32 { + match () { () => <|> } +} +"#, + expect![[r#" + kw if + kw if let + kw loop + kw match + kw return + kw unsafe + kw while + "#]], + ); + } + + #[test] + fn test_keywords_in_trait_def() { + check( + r"trait My { <|> }", + expect![[r#" + kw const + kw fn + kw type + kw unsafe + "#]], + ); + } + + #[test] + fn test_keywords_in_impl_def() { + check( + r"impl My { <|> }", + expect![[r#" + kw const + kw fn + kw pub + kw type + kw unsafe + "#]], + ); + } + + #[test] + fn test_keywords_in_loop() { + check( + r"fn my() { loop { <|> } }", + expect![[r#" + kw break + kw const + kw continue + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_after_unsafe_in_item_list() { + check( + r"unsafe <|>", + expect![[r#" + kw fn + kw impl + kw trait + "#]], + ); + } + + #[test] + fn test_keywords_after_unsafe_in_block_expr() { + check( + r"fn my_fn() { unsafe <|> }", + expect![[r#" + kw fn + kw impl + kw trait + "#]], + ); + } + + #[test] + fn test_mut_in_ref_and_in_fn_parameters_list() { + check( + r"fn my_fn(&<|>) {}", + expect![[r#" + kw mut + "#]], + ); + check( + r"fn my_fn(<|>) {}", + expect![[r#" + kw mut + "#]], + ); + check( + r"fn my_fn() { let &<|> }", + expect![[r#" + kw mut + "#]], + ); + } + + #[test] + fn test_where_keyword() { + check( + r"trait A <|>", + expect![[r#" + kw where + "#]], + ); + check( + r"impl A <|>", + expect![[r#" + kw where + "#]], + ); + } + + #[test] + fn no_keyword_completion_in_comments() { + mark::check!(no_keyword_completion_in_comments); + check( + r#" +fn test() { + let x = 2; // A comment<|> +} +"#, + expect![[""]], + ); + check( + r#" +/* +Some multi-line comment<|> +*/ +"#, + expect![[""]], + ); + check( + r#" +/// Some doc comment +/// let test<|> = 1 +"#, + expect![[""]], + ); + } + + #[test] + fn test_completion_await_impls_future() { + check( + r#" +//- /main.rs +use std::future::*; +struct A {} +impl Future for A {} +fn foo(a: A) { a.<|> } + +//- /std/lib.rs +pub mod future { + #[lang = "future_trait"] + pub trait Future {} +} +"#, + expect![[r#" + kw await expr.await + "#]], + ) + } + + #[test] + fn after_let() { + check( + r#"fn main() { let _ = <|> }"#, + expect![[r#" + kw if + kw if let + kw loop + kw match + kw return + kw while + "#]], + ) + } +} diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/ide/src/completion/complete_macro_in_item_position.rs new file mode 100644 index 000000000..0447f0511 --- /dev/null +++ b/crates/ide/src/completion/complete_macro_in_item_position.rs @@ -0,0 +1,41 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { + // Show only macros in top level. + if ctx.is_new_item { + ctx.scope.process_all_names(&mut |name, res| { + if let hir::ScopeDef::MacroDef(mac) = res { + acc.add_macro(ctx, Some(name.to_string()), mac); + } + }) + } +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual) + } + + #[test] + fn completes_macros_as_item() { + check( + r#" +macro_rules! foo { () => {} } +fn foo() {} + +<|> +"#, + expect![[r#" + ma foo!(…) macro_rules! foo + "#]], + ) + } +} diff --git a/crates/ide/src/completion/complete_pattern.rs b/crates/ide/src/completion/complete_pattern.rs new file mode 100644 index 000000000..aceb77cb5 --- /dev/null +++ b/crates/ide/src/completion/complete_pattern.rs @@ -0,0 +1,88 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +/// Completes constats and paths in patterns. +pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_pat_binding_or_const { + return; + } + if ctx.record_pat_syntax.is_some() { + return; + } + + // FIXME: ideally, we should look at the type we are matching against and + // suggest variants + auto-imports + ctx.scope.process_all_names(&mut |name, res| { + match &res { + hir::ScopeDef::ModuleDef(def) => match def { + hir::ModuleDef::Adt(hir::Adt::Enum(..)) + | hir::ModuleDef::Adt(hir::Adt::Struct(..)) + | hir::ModuleDef::EnumVariant(..) + | hir::ModuleDef::Const(..) + | hir::ModuleDef::Module(..) => (), + _ => return, + }, + hir::ScopeDef::MacroDef(_) => (), + _ => return, + }; + + acc.add_resolution(ctx, name.to_string(), &res) + }); +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual) + } + + #[test] + fn completes_enum_variants_and_modules() { + check( + r#" +enum E { X } +use self::E::X; +const Z: E = E::X; +mod m {} + +static FOO: E = E::X; +struct Bar { f: u32 } + +fn foo() { + match E::X { <|> } +} +"#, + expect![[r#" + st Bar + en E + ev X () + ct Z + md m + "#]], + ); + } + + #[test] + fn completes_in_simple_macro_call() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +enum E { X } + +fn foo() { + m!(match E::X { <|> }) +} +"#, + expect![[r#" + en E + ma m!(…) macro_rules! m + "#]], + ); + } +} diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs new file mode 100644 index 000000000..d50b13c52 --- /dev/null +++ b/crates/ide/src/completion/complete_postfix.rs @@ -0,0 +1,378 @@ +//! FIXME: write short doc here +use assists::utils::TryEnum; +use syntax::{ + ast::{self, AstNode}, + TextRange, TextSize, +}; +use text_edit::TextEdit; + +use crate::{ + completion::{ + completion_config::SnippetCap, + completion_context::CompletionContext, + completion_item::{Builder, CompletionKind, Completions}, + }, + CompletionItem, CompletionItemKind, +}; + +pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.config.enable_postfix_completions { + return; + } + + let dot_receiver = match &ctx.dot_receiver { + Some(it) => it, + None => return, + }; + + let receiver_text = + get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); + + let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { + Some(it) => it, + None => return, + }; + + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return, + }; + let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); + if let Some(try_enum) = &try_enum { + match try_enum { + TryEnum::Result => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "ifl", + "if let Ok {}", + &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "while", + "while let Ok {}", + &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + } + TryEnum::Option => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "ifl", + "if let Some {}", + &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "while", + "while let Some {}", + &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + } + } + } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "if", + "if expr {}", + &format!("if {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + postfix_snippet( + ctx, + cap, + &dot_receiver, + "while", + "while expr {}", + &format!("while {} {{\n $0\n}}", receiver_text), + ) + .add_to(acc); + postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) + .add_to(acc); + } + + postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)) + .add_to(acc); + postfix_snippet( + ctx, + cap, + &dot_receiver, + "refm", + "&mut expr", + &format!("&mut {}", receiver_text), + ) + .add_to(acc); + + // The rest of the postfix completions create an expression that moves an argument, + // so it's better to consider references now to avoid breaking the compilation + let dot_receiver = include_references(dot_receiver); + let receiver_text = + get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); + + match try_enum { + Some(try_enum) => match try_enum { + TryEnum::Result => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text), + ) + .add_to(acc); + } + TryEnum::Option => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!( + "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}", + receiver_text + ), + ) + .add_to(acc); + } + }, + None => { + postfix_snippet( + ctx, + cap, + &dot_receiver, + "match", + "match expr {}", + &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text), + ) + .add_to(acc); + } + } + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "box", + "Box::new(expr)", + &format!("Box::new({})", receiver_text), + ) + .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "dbg", + "dbg!(expr)", + &format!("dbg!({})", receiver_text), + ) + .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "call", + "function(expr)", + &format!("${{1}}({})", receiver_text), + ) + .add_to(acc); +} + +fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { + if receiver_is_ambiguous_float_literal { + let text = receiver.syntax().text(); + let without_dot = ..text.len() - TextSize::of('.'); + text.slice(without_dot).to_string() + } else { + receiver.to_string() + } +} + +fn include_references(initial_element: &ast::Expr) -> ast::Expr { + let mut resulting_element = initial_element.clone(); + while let Some(parent_ref_element) = + resulting_element.syntax().parent().and_then(ast::RefExpr::cast) + { + resulting_element = ast::Expr::from(parent_ref_element); + } + resulting_element +} + +fn postfix_snippet( + ctx: &CompletionContext, + cap: SnippetCap, + receiver: &ast::Expr, + label: &str, + detail: &str, + snippet: &str, +) -> Builder { + let edit = { + let receiver_syntax = receiver.syntax(); + let receiver_range = ctx.sema.original_range(receiver_syntax).range; + let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); + TextEdit::replace(delete_range, snippet.to_string()) + }; + CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) + .detail(detail) + .kind(CompletionItemKind::Snippet) + .snippet_edit(cap, edit) +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Postfix); + expect.assert_eq(&actual) + } + + #[test] + fn postfix_completion_works_for_trivial_path_expression() { + check( + r#" +fn main() { + let bar = true; + bar.<|> +} +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn if if expr {} + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn while while expr {} + "#]], + ); + } + + #[test] + fn postfix_type_filtering() { + check( + r#" +fn main() { + let bar: u8 = 12; + bar.<|> +} +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn match match expr {} + sn ref &expr + sn refm &mut expr + "#]], + ) + } + + #[test] + fn option_iflet() { + check_edit( + "ifl", + r#" +enum Option { Some(T), None } + +fn main() { + let bar = Option::Some(true); + bar.<|> +} +"#, + r#" +enum Option { Some(T), None } + +fn main() { + let bar = Option::Some(true); + if let Some($1) = bar { + $0 +} +} +"#, + ); + } + + #[test] + fn result_match() { + check_edit( + "match", + r#" +enum Result { Ok(T), Err(E) } + +fn main() { + let bar = Result::Ok(true); + bar.<|> +} +"#, + r#" +enum Result { Ok(T), Err(E) } + +fn main() { + let bar = Result::Ok(true); + match bar { + Ok(${1:_}) => {$2}, + Err(${3:_}) => {$0}, +} +} +"#, + ); + } + + #[test] + fn postfix_completion_works_for_ambiguous_float_literal() { + check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#) + } + + #[test] + fn works_in_simple_macro() { + check_edit( + "dbg", + r#" +macro_rules! m { ($e:expr) => { $e } } +fn main() { + let bar: u8 = 12; + m!(bar.d<|>) +} +"#, + r#" +macro_rules! m { ($e:expr) => { $e } } +fn main() { + let bar: u8 = 12; + m!(dbg!(bar)) +} +"#, + ); + } + + #[test] + fn postfix_completion_for_references() { + check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#); + check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#); + } +} diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs new file mode 100644 index 000000000..cb7dd23c1 --- /dev/null +++ b/crates/ide/src/completion/complete_qualified_path.rs @@ -0,0 +1,733 @@ +//! Completion of paths, i.e. `some::prefix::<|>`. + +use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; +use rustc_hash::FxHashSet; +use syntax::AstNode; +use test_utils::mark; + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { + let path = match &ctx.path_prefix { + Some(path) => path.clone(), + None => return, + }; + + if ctx.attribute_under_caret.is_some() { + return; + } + + let context_module = ctx.scope.module(); + + let resolution = match ctx.scope.resolve_hir_path_qualifier(&path) { + Some(res) => res, + None => return, + }; + + // Add associated types on type parameters and `Self`. + resolution.assoc_type_shorthand_candidates(ctx.db, |alias| { + acc.add_type_alias(ctx, alias); + None::<()> + }); + + match resolution { + PathResolution::Def(hir::ModuleDef::Module(module)) => { + let module_scope = module.scope(ctx.db, context_module); + for (name, def) in module_scope { + if ctx.use_item_syntax.is_some() { + if let ScopeDef::Unknown = def { + if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { + if name_ref.syntax().text() == name.to_string().as_str() { + // for `use self::foo<|>`, don't suggest `foo` as a completion + mark::hit!(dont_complete_current_use); + continue; + } + } + } + } + + acc.add_resolution(ctx, name.to_string(), &def); + } + } + PathResolution::Def(def @ hir::ModuleDef::Adt(_)) + | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => { + if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { + for variant in e.variants(ctx.db) { + acc.add_enum_variant(ctx, variant, None); + } + } + let ty = match def { + hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), + hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), + _ => unreachable!(), + }; + + // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType. + // (where AssocType is defined on a trait, not an inherent impl) + + let krate = ctx.krate; + if let Some(krate) = krate { + let traits_in_scope = ctx.scope.traits_in_scope(); + ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { + if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { + return None; + } + match item { + hir::AssocItem::Function(func) => { + acc.add_function(ctx, func, None); + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + None::<()> + }); + + // Iterate assoc types separately + ty.iterate_assoc_items(ctx.db, krate, |item| { + if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { + return None; + } + match item { + hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {} + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + None::<()> + }); + } + } + PathResolution::Def(hir::ModuleDef::Trait(t)) => { + // Handles `Trait::assoc` as well as `::assoc`. + for item in t.items(ctx.db) { + if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { + continue; + } + match item { + hir::AssocItem::Function(func) => { + acc.add_function(ctx, func, None); + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + } + } + PathResolution::TypeParam(_) | PathResolution::SelfType(_) => { + if let Some(krate) = ctx.krate { + let ty = match resolution { + PathResolution::TypeParam(param) => param.ty(ctx.db), + PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db), + _ => return, + }; + + let traits_in_scope = ctx.scope.traits_in_scope(); + let mut seen = FxHashSet::default(); + ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { + if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { + return None; + } + + // We might iterate candidates of a trait multiple times here, so deduplicate + // them. + if seen.insert(item) { + match item { + hir::AssocItem::Function(func) => { + acc.add_function(ctx, func, None); + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + } + None::<()> + }); + } + } + _ => {} + } +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + use test_utils::mark; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual); + } + + fn check_builtin(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::BuiltinType); + expect.assert_eq(&actual); + } + + #[test] + fn dont_complete_current_use() { + mark::check!(dont_complete_current_use); + check(r#"use self::foo<|>;"#, expect![[""]]); + } + + #[test] + fn dont_complete_current_use_in_braces_with_glob() { + check( + r#" +mod foo { pub struct S; } +use self::{foo::*, bar<|>}; +"#, + expect![[r#" + st S + md foo + "#]], + ); + } + + #[test] + fn dont_complete_primitive_in_use() { + check_builtin(r#"use self::<|>;"#, expect![[""]]); + } + + #[test] + fn dont_complete_primitive_in_module_scope() { + check_builtin(r#"fn foo() { self::<|> }"#, expect![[""]]); + } + + #[test] + fn completes_primitives() { + check_builtin( + r#"fn main() { let _: <|> = 92; }"#, + expect![[r#" + bt bool + bt char + bt f32 + bt f64 + bt i128 + bt i16 + bt i32 + bt i64 + bt i8 + bt isize + bt str + bt u128 + bt u16 + bt u32 + bt u64 + bt u8 + bt usize + "#]], + ); + } + + #[test] + fn completes_mod_with_same_name_as_function() { + check( + r#" +use self::my::<|>; + +mod my { pub struct Bar; } +fn my() {} +"#, + expect![[r#" + st Bar + "#]], + ); + } + + #[test] + fn filters_visibility() { + check( + r#" +use self::my::<|>; + +mod my { + struct Bar; + pub struct Foo; + pub use Bar as PublicBar; +} +"#, + expect![[r#" + st Foo + st PublicBar + "#]], + ); + } + + #[test] + fn completes_use_item_starting_with_self() { + check( + r#" +use self::m::<|>; + +mod m { pub struct Bar; } +"#, + expect![[r#" + st Bar + "#]], + ); + } + + #[test] + fn completes_use_item_starting_with_crate() { + check( + r#" +//- /lib.rs +mod foo; +struct Spam; +//- /foo.rs +use crate::Sp<|> +"#, + expect![[r#" + st Spam + md foo + "#]], + ); + } + + #[test] + fn completes_nested_use_tree() { + check( + r#" +//- /lib.rs +mod foo; +struct Spam; +//- /foo.rs +use crate::{Sp<|>}; +"#, + expect![[r#" + st Spam + md foo + "#]], + ); + } + + #[test] + fn completes_deeply_nested_use_tree() { + check( + r#" +//- /lib.rs +mod foo; +pub mod bar { + pub mod baz { + pub struct Spam; + } +} +//- /foo.rs +use crate::{bar::{baz::Sp<|>}}; +"#, + expect![[r#" + st Spam + "#]], + ); + } + + #[test] + fn completes_enum_variant() { + check( + r#" +enum E { Foo, Bar(i32) } +fn foo() { let _ = E::<|> } +"#, + expect![[r#" + ev Bar(…) (i32) + ev Foo () + "#]], + ); + } + + #[test] + fn completes_struct_associated_items() { + check( + r#" +//- /lib.rs +struct S; + +impl S { + fn a() {} + fn b(&self) {} + const C: i32 = 42; + type T = i32; +} + +fn foo() { let _ = S::<|> } +"#, + expect![[r#" + ct C const C: i32 = 42; + ta T type T = i32; + fn a() fn a() + me b() fn b(&self) + "#]], + ); + } + + #[test] + fn associated_item_visibility() { + check( + r#" +struct S; + +mod m { + impl super::S { + pub(super) fn public_method() { } + fn private_method() { } + pub(super) type PublicType = u32; + type PrivateType = u32; + pub(super) const PUBLIC_CONST: u32 = 1; + const PRIVATE_CONST: u32 = 1; + } +} + +fn foo() { let _ = S::<|> } +"#, + expect![[r#" + ct PUBLIC_CONST pub(super) const PUBLIC_CONST: u32 = 1; + ta PublicType pub(super) type PublicType = u32; + fn public_method() pub(super) fn public_method() + "#]], + ); + } + + #[test] + fn completes_enum_associated_method() { + check( + r#" +enum E {}; +impl E { fn m() { } } + +fn foo() { let _ = E::<|> } + "#, + expect![[r#" + fn m() fn m() + "#]], + ); + } + + #[test] + fn completes_union_associated_method() { + check( + r#" +union U {}; +impl U { fn m() { } } + +fn foo() { let _ = U::<|> } +"#, + expect![[r#" + fn m() fn m() + "#]], + ); + } + + #[test] + fn completes_use_paths_across_crates() { + check( + r#" +//- /main.rs +use foo::<|>; + +//- /foo/lib.rs +pub mod bar { pub struct S; } +"#, + expect![[r#" + md bar + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_1() { + check( + r#" +trait Trait { fn m(); } + +fn foo() { let _ = Trait::<|> } +"#, + expect![[r#" + fn m() fn m() + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_2() { + check( + r#" +trait Trait { fn m(); } + +struct S; +impl Trait for S {} + +fn foo() { let _ = S::<|> } +"#, + expect![[r#" + fn m() fn m() + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_3() { + check( + r#" +trait Trait { fn m(); } + +struct S; +impl Trait for S {} + +fn foo() { let _ = ::<|> } +"#, + expect![[r#" + fn m() fn m() + "#]], + ); + } + + #[test] + fn completes_ty_param_assoc_ty() { + check( + r#" +trait Super { + type Ty; + const CONST: u8; + fn func() {} + fn method(&self) {} +} + +trait Sub: Super { + type SubTy; + const C2: (); + fn subfunc() {} + fn submethod(&self) {} +} + +fn foo() { T::<|> } +"#, + expect![[r#" + ct C2 const C2: (); + ct CONST const CONST: u8; + ta SubTy type SubTy; + ta Ty type Ty; + fn func() fn func() + me method() fn method(&self) + fn subfunc() fn subfunc() + me submethod() fn submethod(&self) + "#]], + ); + } + + #[test] + fn completes_self_param_assoc_ty() { + check( + r#" +trait Super { + type Ty; + const CONST: u8 = 0; + fn func() {} + fn method(&self) {} +} + +trait Sub: Super { + type SubTy; + const C2: () = (); + fn subfunc() {} + fn submethod(&self) {} +} + +struct Wrap(T); +impl Super for Wrap {} +impl Sub for Wrap { + fn subfunc() { + // Should be able to assume `Self: Sub + Super` + Self::<|> + } +} +"#, + expect![[r#" + ct C2 const C2: () = (); + ct CONST const CONST: u8 = 0; + ta SubTy type SubTy; + ta Ty type Ty; + fn func() fn func() + me method() fn method(&self) + fn subfunc() fn subfunc() + me submethod() fn submethod(&self) + "#]], + ); + } + + #[test] + fn completes_type_alias() { + check( + r#" +struct S; +impl S { fn foo() {} } +type T = S; +impl T { fn bar() {} } + +fn main() { T::<|>; } +"#, + expect![[r#" + fn bar() fn bar() + fn foo() fn foo() + "#]], + ); + } + + #[test] + fn completes_qualified_macros() { + check( + r#" +#[macro_export] +macro_rules! foo { () => {} } + +fn main() { let _ = crate::<|> } + "#, + expect![[r##" + ma foo!(…) #[macro_export] + macro_rules! foo + fn main() fn main() + "##]], + ); + } + + #[test] + fn test_super_super_completion() { + check( + r#" +mod a { + const A: usize = 0; + mod b { + const B: usize = 0; + mod c { use super::super::<|> } + } +} +"#, + expect![[r#" + ct A + md b + "#]], + ); + } + + #[test] + fn completes_reexported_items_under_correct_name() { + check( + r#" +fn foo() { self::m::<|> } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + expect![[r#" + ct RIGHT_CONST + st RightType + fn right_fn() fn wrong_fn() + "#]], + ); + + check_edit( + "RightType", + r#" +fn foo() { self::m::<|> } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + r#" +fn foo() { self::m::RightType } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + ); + } + + #[test] + fn completes_in_simple_macro_call() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +fn main() { m!(self::f<|>); } +fn foo() {} +"#, + expect![[r#" + fn foo() fn foo() + fn main() fn main() + "#]], + ); + } + + #[test] + fn function_mod_share_name() { + check( + r#" +fn foo() { self::m::<|> } + +mod m { + pub mod z {} + pub fn z() {} +} +"#, + expect![[r#" + md z + fn z() pub fn z() + "#]], + ); + } + + #[test] + fn completes_hashmap_new() { + check( + r#" +struct RandomState; +struct HashMap {} + +impl HashMap { + pub fn new() -> HashMap { } +} +fn foo() { + HashMap::<|> +} +"#, + expect![[r#" + fn new() pub fn new() -> HashMap + "#]], + ); + } + + #[test] + fn dont_complete_attr() { + check( + r#" +mod foo { pub struct Foo; } +#[foo::<|>] +fn f() {} +"#, + expect![[""]], + ); + } +} diff --git a/crates/ide/src/completion/complete_record.rs b/crates/ide/src/completion/complete_record.rs new file mode 100644 index 000000000..74b94594d --- /dev/null +++ b/crates/ide/src/completion/complete_record.rs @@ -0,0 +1,226 @@ +//! Complete fields in record literals and patterns. +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { + (None, None) => return None, + (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), + (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), + (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit), + }; + + for (field, ty) in missing_fields { + acc.add_field(ctx, field, &ty) + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual); + } + + #[test] + fn test_record_pattern_field() { + check( + r#" +struct S { foo: u32 } + +fn process(f: S) { + match f { + S { f<|>: 92 } => (), + } +} +"#, + expect![[r#" + fd foo u32 + "#]], + ); + } + + #[test] + fn test_record_pattern_enum_variant() { + check( + r#" +enum E { S { foo: u32, bar: () } } + +fn process(e: E) { + match e { + E::S { <|> } => (), + } +} +"#, + expect![[r#" + fd bar () + fd foo u32 + "#]], + ); + } + + #[test] + fn test_record_pattern_field_in_simple_macro() { + check( + r" +macro_rules! m { ($e:expr) => { $e } } +struct S { foo: u32 } + +fn process(f: S) { + m!(match f { + S { f<|>: 92 } => (), + }) +} +", + expect![[r#" + fd foo u32 + "#]], + ); + } + + #[test] + fn only_missing_fields_are_completed_in_destruct_pats() { + check( + 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 {} +} +"#, + expect![[r#" + fd bar u32 + fd baz u32 + "#]], + ); + } + + #[test] + fn test_record_literal_field() { + check( + r#" +struct A { the_field: u32 } +fn foo() { + A { the<|> } +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn test_record_literal_enum_variant() { + check( + r#" +enum E { A { a: u32 } } +fn foo() { + let _ = E::A { <|> } +} +"#, + expect![[r#" + fd a u32 + "#]], + ); + } + + #[test] + fn test_record_literal_two_structs() { + check( + r#" +struct A { a: u32 } +struct B { b: u32 } + +fn foo() { + let _: A = B { <|> } +} +"#, + expect![[r#" + fd b u32 + "#]], + ); + } + + #[test] + fn test_record_literal_generic_struct() { + check( + r#" +struct A { a: T } + +fn foo() { + let _: A = A { <|> } +} +"#, + expect![[r#" + fd a u32 + "#]], + ); + } + + #[test] + fn test_record_literal_field_in_simple_macro() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +struct A { the_field: u32 } +fn foo() { + m!(A { the<|> }) +} +"#, + expect![[r#" + fd the_field u32 + "#]], + ); + } + + #[test] + fn only_missing_fields_are_completed() { + check( + r#" +struct S { + foo1: u32, foo2: u32, + bar: u32, baz: u32, +} + +fn main() { + let foo1 = 1; + let s = S { foo1, foo2: 5, <|> } +} +"#, + expect![[r#" + fd bar u32 + fd baz u32 + "#]], + ); + } + + #[test] + fn completes_functional_update() { + check( + r#" +struct S { foo1: u32, foo2: u32 } + +fn main() { + let foo1 = 1; + let s = S { foo1, <|> .. loop {} } +} +"#, + expect![[r#" + fd foo2 u32 + "#]], + ); + } +} diff --git a/crates/ide/src/completion/complete_snippet.rs b/crates/ide/src/completion/complete_snippet.rs new file mode 100644 index 000000000..4368e4eec --- /dev/null +++ b/crates/ide/src/completion/complete_snippet.rs @@ -0,0 +1,116 @@ +//! FIXME: write short doc here + +use crate::completion::{ + completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem, + CompletionItemKind, CompletionKind, Completions, +}; + +fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder { + CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label) + .insert_snippet(cap, snippet) + .kind(CompletionItemKind::Snippet) +} + +pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { + return; + } + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return, + }; + + snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); + snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); +} + +pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_new_item { + return; + } + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return, + }; + + snippet( + ctx, + cap, + "tmod (Test module)", + "\ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ${1:test_name}() { + $0 + } +}", + ) + .lookup_by("tmod") + .add_to(acc); + + snippet( + ctx, + cap, + "tfn (Test function)", + "\ +#[test] +fn ${1:feature}() { + $0 +}", + ) + .lookup_by("tfn") + .add_to(acc); + + snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc); + snippet(ctx, cap, "pub(crate)", "pub(crate) $0").add_to(acc); +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{test_utils::completion_list, CompletionKind}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Snippet); + expect.assert_eq(&actual) + } + + #[test] + fn completes_snippets_in_expressions() { + check( + r#"fn foo(x: i32) { <|> }"#, + expect![[r#" + sn pd + sn ppd + "#]], + ); + } + + #[test] + fn should_not_complete_snippets_in_path() { + check(r#"fn foo(x: i32) { ::foo<|> }"#, expect![[""]]); + check(r#"fn foo(x: i32) { ::<|> }"#, expect![[""]]); + } + + #[test] + fn completes_snippets_in_items() { + check( + r#" +#[cfg(test)] +mod tests { + <|> +} +"#, + expect![[r#" + sn macro_rules + sn pub(crate) + sn tfn (Test function) + sn tmod (Test module) + "#]], + ) + } +} diff --git a/crates/ide/src/completion/complete_trait_impl.rs b/crates/ide/src/completion/complete_trait_impl.rs new file mode 100644 index 000000000..478e31262 --- /dev/null +++ b/crates/ide/src/completion/complete_trait_impl.rs @@ -0,0 +1,488 @@ +//! Completion for associated items in a trait implementation. +//! +//! This module adds the completion items related to implementing associated +//! items within a `impl Trait for Struct` block. The current context node +//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node +//! and an direct child of an `IMPL`. +//! +//! # Examples +//! +//! Considering the following trait `impl`: +//! +//! ```ignore +//! trait SomeTrait { +//! fn foo(); +//! } +//! +//! impl SomeTrait for () { +//! fn f<|> +//! } +//! ``` +//! +//! may result in the completion of the following method: +//! +//! ```ignore +//! # trait SomeTrait { +//! # fn foo(); +//! # } +//! +//! impl SomeTrait for () { +//! fn foo() {}<|> +//! } +//! ``` + +use assists::utils::get_missing_assoc_items; +use hir::{self, Docs, HasSource}; +use syntax::{ + ast::{self, edit, Impl}, + AstNode, SyntaxKind, SyntaxNode, TextRange, T, +}; +use text_edit::TextEdit; + +use crate::{ + completion::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, + }, + display::function_declaration, +}; + +pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { + if let Some((trigger, impl_def)) = completion_match(ctx) { + match trigger.kind() { + SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .for_each(|item| match item { + hir::AssocItem::Function(fn_item) => { + add_function_impl(&trigger, acc, ctx, fn_item) + } + hir::AssocItem::TypeAlias(type_item) => { + add_type_alias_impl(&trigger, acc, ctx, type_item) + } + hir::AssocItem::Const(const_item) => { + add_const_impl(&trigger, acc, ctx, const_item) + } + }), + + SyntaxKind::FN => { + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::Function(fn_item) => Some(fn_item), + _ => None, + }) + { + add_function_impl(&trigger, acc, ctx, missing_fn); + } + } + + SyntaxKind::TYPE_ALIAS => { + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::TypeAlias(type_item) => Some(type_item), + _ => None, + }) + { + add_type_alias_impl(&trigger, acc, ctx, missing_fn); + } + } + + SyntaxKind::CONST => { + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::Const(const_item) => Some(const_item), + _ => None, + }) + { + add_const_impl(&trigger, acc, ctx, missing_fn); + } + } + + _ => {} + } + } +} + +fn completion_match(ctx: &CompletionContext) -> Option<(SyntaxNode, Impl)> { + let (trigger, impl_def_offset) = ctx.token.ancestors().find_map(|p| match p.kind() { + SyntaxKind::FN | SyntaxKind::TYPE_ALIAS | SyntaxKind::CONST | SyntaxKind::BLOCK_EXPR => { + Some((p, 2)) + } + SyntaxKind::NAME_REF => Some((p, 5)), + _ => None, + })?; + let impl_def = (0..impl_def_offset - 1) + .try_fold(trigger.parent()?, |t, _| t.parent()) + .and_then(ast::Impl::cast)?; + Some((trigger, impl_def)) +} + +fn add_function_impl( + fn_def_node: &SyntaxNode, + acc: &mut Completions, + ctx: &CompletionContext, + func: hir::Function, +) { + let fn_name = func.name(ctx.db).to_string(); + + let label = if !func.params(ctx.db).is_empty() { + format!("fn {}(..)", fn_name) + } else { + format!("fn {}()", fn_name) + }; + + let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) + .lookup_by(fn_name) + .set_documentation(func.docs(ctx.db)); + + let completion_kind = if func.has_self_param(ctx.db) { + CompletionItemKind::Method + } else { + CompletionItemKind::Function + }; + let range = TextRange::new(fn_def_node.text_range().start(), ctx.source_range().end()); + + let function_decl = function_declaration(&func.source(ctx.db).value); + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = format!("{} {{\n $0\n}}", function_decl); + builder.snippet_edit(cap, TextEdit::replace(range, snippet)) + } + None => { + let header = format!("{} {{", function_decl); + builder.text_edit(TextEdit::replace(range, header)) + } + } + .kind(completion_kind) + .add_to(acc); +} + +fn add_type_alias_impl( + type_def_node: &SyntaxNode, + acc: &mut Completions, + ctx: &CompletionContext, + type_alias: hir::TypeAlias, +) { + let alias_name = type_alias.name(ctx.db).to_string(); + + let snippet = format!("type {} = ", alias_name); + + let range = TextRange::new(type_def_node.text_range().start(), ctx.source_range().end()); + + CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) + .text_edit(TextEdit::replace(range, snippet)) + .lookup_by(alias_name) + .kind(CompletionItemKind::TypeAlias) + .set_documentation(type_alias.docs(ctx.db)) + .add_to(acc); +} + +fn add_const_impl( + const_def_node: &SyntaxNode, + acc: &mut Completions, + ctx: &CompletionContext, + const_: hir::Const, +) { + let const_name = const_.name(ctx.db).map(|n| n.to_string()); + + if let Some(const_name) = const_name { + let snippet = make_const_compl_syntax(&const_.source(ctx.db).value); + + let range = TextRange::new(const_def_node.text_range().start(), ctx.source_range().end()); + + CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) + .text_edit(TextEdit::replace(range, snippet)) + .lookup_by(const_name) + .kind(CompletionItemKind::Const) + .set_documentation(const_.docs(ctx.db)) + .add_to(acc); + } +} + +fn make_const_compl_syntax(const_: &ast::Const) -> String { + let const_ = edit::remove_attrs_and_docs(const_); + + let const_start = const_.syntax().text_range().start(); + let const_end = const_.syntax().text_range().end(); + + let start = + const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start()); + + let end = const_ + .syntax() + .children_with_tokens() + .find(|s| s.kind() == T![;] || s.kind() == T![=]) + .map_or(const_end, |f| f.text_range().start()); + + let len = end - start; + let range = TextRange::new(0.into(), len); + + let syntax = const_.syntax().text().slice(range).to_string(); + + format!("{} = ", syntax.trim_end()) +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Magic); + expect.assert_eq(&actual) + } + + #[test] + fn name_ref_function_type_const() { + check( + r#" +trait Test { + type TestType; + const TEST_CONST: u16; + fn test(); +} +struct T; + +impl Test for T { + t<|> +} +"#, + expect![[" +ct const TEST_CONST: u16 = \n\ +fn fn test() +ta type TestType = \n\ + "]], + ); + } + + #[test] + fn no_nested_fn_completions() { + check( + r" +trait Test { + fn test(); + fn test2(); +} +struct T; + +impl Test for T { + fn test() { + t<|> + } +} +", + expect![[""]], + ); + } + + #[test] + fn name_ref_single_function() { + check_edit( + "test", + r#" +trait Test { + fn test(); +} +struct T; + +impl Test for T { + t<|> +} +"#, + r#" +trait Test { + fn test(); +} +struct T; + +impl Test for T { + fn test() { + $0 +} +} +"#, + ); + } + + #[test] + fn single_function() { + check_edit( + "test", + r#" +trait Test { + fn test(); +} +struct T; + +impl Test for T { + fn t<|> +} +"#, + r#" +trait Test { + fn test(); +} +struct T; + +impl Test for T { + fn test() { + $0 +} +} +"#, + ); + } + + #[test] + fn hide_implemented_fn() { + check( + r#" +trait Test { + fn foo(); + fn foo_bar(); +} +struct T; + +impl Test for T { + fn foo() {} + fn f<|> +} +"#, + expect![[r#" + fn fn foo_bar() + "#]], + ); + } + + #[test] + fn generic_fn() { + check_edit( + "foo", + r#" +trait Test { + fn foo(); +} +struct T; + +impl Test for T { + fn f<|> +} +"#, + r#" +trait Test { + fn foo(); +} +struct T; + +impl Test for T { + fn foo() { + $0 +} +} +"#, + ); + check_edit( + "foo", + r#" +trait Test { + fn foo() where T: Into; +} +struct T; + +impl Test for T { + fn f<|> +} +"#, + r#" +trait Test { + fn foo() where T: Into; +} +struct T; + +impl Test for T { + fn foo() +where T: Into { + $0 +} +} +"#, + ); + } + + #[test] + fn associated_type() { + check_edit( + "SomeType", + r#" +trait Test { + type SomeType; +} + +impl Test for () { + type S<|> +} +"#, + " +trait Test { + type SomeType; +} + +impl Test for () { + type SomeType = \n\ +} +", + ); + } + + #[test] + fn associated_const() { + check_edit( + "SOME_CONST", + r#" +trait Test { + const SOME_CONST: u16; +} + +impl Test for () { + const S<|> +} +"#, + " +trait Test { + const SOME_CONST: u16; +} + +impl Test for () { + const SOME_CONST: u16 = \n\ +} +", + ); + + check_edit( + "SOME_CONST", + r#" +trait Test { + const SOME_CONST: u16 = 92; +} + +impl Test for () { + const S<|> +} +"#, + " +trait Test { + const SOME_CONST: u16 = 92; +} + +impl Test for () { + const SOME_CONST: u16 = \n\ +} +", + ); + } +} diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs new file mode 100644 index 000000000..824227f31 --- /dev/null +++ b/crates/ide/src/completion/complete_unqualified_path.rs @@ -0,0 +1,658 @@ +//! Completion of names from the current scope, e.g. locals and imported items. + +use hir::{Adt, ModuleDef, ScopeDef, Type}; +use syntax::AstNode; +use test_utils::mark; + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { + if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { + return; + } + if ctx.record_lit_syntax.is_some() + || ctx.record_pat_syntax.is_some() + || ctx.attribute_under_caret.is_some() + { + return; + } + + if let Some(ty) = &ctx.expected_type { + complete_enum_variants(acc, ctx, ty); + } + + if ctx.is_pat_binding_or_const { + return; + } + + ctx.scope.process_all_names(&mut |name, res| { + if ctx.use_item_syntax.is_some() { + if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { + if name_ref.syntax().text() == name.to_string().as_str() { + mark::hit!(self_fulfilling_completion); + return; + } + } + } + acc.add_resolution(ctx, name.to_string(), &res) + }); +} + +fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { + if let Some(Adt::Enum(enum_data)) = ty.as_adt() { + let variants = enum_data.variants(ctx.db); + + let module = if let Some(module) = ctx.scope.module() { + // Compute path from the completion site if available. + module + } else { + // Otherwise fall back to the enum's definition site. + enum_data.module(ctx.db) + }; + + for variant in variants { + if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { + // Variants with trivial paths are already added by the existing completion logic, + // so we should avoid adding these twice + if path.segments.len() > 1 { + acc.add_qualified_enum_variant(ctx, variant, path); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + use test_utils::mark; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Reference); + expect.assert_eq(&actual) + } + + #[test] + fn self_fulfilling_completion() { + mark::check!(self_fulfilling_completion); + check( + r#" +use foo<|> +use std::collections; +"#, + expect![[r#" + ?? collections + "#]], + ); + } + + #[test] + fn bind_pat_and_path_ignore_at() { + check( + r#" +enum Enum { A, B } +fn quux(x: Option) { + match x { + None => (), + Some(en<|> @ Enum::A) => (), + } +} +"#, + expect![[""]], + ); + } + + #[test] + fn bind_pat_and_path_ignore_ref() { + check( + r#" +enum Enum { A, B } +fn quux(x: Option) { + match x { + None => (), + Some(ref en<|>) => (), + } +} +"#, + expect![[""]], + ); + } + + #[test] + fn bind_pat_and_path() { + check( + r#" +enum Enum { A, B } +fn quux(x: Option) { + match x { + None => (), + Some(En<|>) => (), + } +} +"#, + expect![[r#" + en Enum + "#]], + ); + } + + #[test] + fn completes_bindings_from_let() { + check( + r#" +fn quux(x: i32) { + let y = 92; + 1 + <|>; + let z = (); +} +"#, + expect![[r#" + fn quux(…) fn quux(x: i32) + bn x i32 + bn y i32 + "#]], + ); + } + + #[test] + fn completes_bindings_from_if_let() { + check( + r#" +fn quux() { + if let Some(x) = foo() { + let y = 92; + }; + if let Some(a) = bar() { + let b = 62; + 1 + <|> + } +} +"#, + expect![[r#" + bn a + bn b i32 + fn quux() fn quux() + "#]], + ); + } + + #[test] + fn completes_bindings_from_for() { + check( + r#" +fn quux() { + for x in &[1, 2, 3] { <|> } +} +"#, + expect![[r#" + fn quux() fn quux() + bn x + "#]], + ); + } + + #[test] + fn completes_if_prefix_is_keyword() { + mark::check!(completes_if_prefix_is_keyword); + check_edit( + "wherewolf", + r#" +fn main() { + let wherewolf = 92; + drop(where<|>) +} +"#, + r#" +fn main() { + let wherewolf = 92; + drop(wherewolf) +} +"#, + ) + } + + #[test] + fn completes_generic_params() { + check( + r#"fn quux() { <|> }"#, + expect![[r#" + tp T + fn quux() fn quux() + "#]], + ); + } + + #[test] + fn completes_generic_params_in_struct() { + check( + r#"struct S { x: <|>}"#, + expect![[r#" + st S<…> + tp Self + tp T + "#]], + ); + } + + #[test] + fn completes_self_in_enum() { + check( + r#"enum X { Y(<|>) }"#, + expect![[r#" + tp Self + en X + "#]], + ); + } + + #[test] + fn completes_module_items() { + check( + r#" +struct S; +enum E {} +fn quux() { <|> } +"#, + expect![[r#" + en E + st S + fn quux() fn quux() + "#]], + ); + } + + #[test] + fn completes_extern_prelude() { + check( + r#" +//- /lib.rs +use <|>; + +//- /other_crate/lib.rs +// nothing here +"#, + expect![[r#" + md other_crate + "#]], + ); + } + + #[test] + fn completes_module_items_in_nested_modules() { + check( + r#" +struct Foo; +mod m { + struct Bar; + fn quux() { <|> } +} +"#, + expect![[r#" + st Bar + fn quux() fn quux() + "#]], + ); + } + + #[test] + fn completes_return_type() { + check( + r#" +struct Foo; +fn x() -> <|> +"#, + expect![[r#" + st Foo + fn x() fn x() + "#]], + ); + } + + #[test] + fn dont_show_both_completions_for_shadowing() { + check( + r#" +fn foo() { + let bar = 92; + { + let bar = 62; + drop(<|>) + } +} +"#, + // FIXME: should be only one bar here + expect![[r#" + bn bar i32 + bn bar i32 + fn foo() fn foo() + "#]], + ); + } + + #[test] + fn completes_self_in_methods() { + check( + r#"impl S { fn foo(&self) { <|> } }"#, + expect![[r#" + tp Self + bn self &{unknown} + "#]], + ); + } + + #[test] + fn completes_prelude() { + check( + r#" +//- /main.rs +fn foo() { let x: <|> } + +//- /std/lib.rs +#[prelude_import] +use prelude::*; + +mod prelude { struct Option; } +"#, + expect![[r#" + st Option + fn foo() fn foo() + md std + "#]], + ); + } + + #[test] + fn completes_std_prelude_if_core_is_defined() { + check( + r#" +//- /main.rs +fn foo() { let x: <|> } + +//- /core/lib.rs +#[prelude_import] +use prelude::*; + +mod prelude { struct Option; } + +//- /std/lib.rs +#[prelude_import] +use prelude::*; + +mod prelude { struct String; } +"#, + expect![[r#" + st String + md core + fn foo() fn foo() + md std + "#]], + ); + } + + #[test] + fn completes_macros_as_value() { + check( + r#" +macro_rules! foo { () => {} } + +#[macro_use] +mod m1 { + macro_rules! bar { () => {} } +} + +mod m2 { + macro_rules! nope { () => {} } + + #[macro_export] + macro_rules! baz { () => {} } +} + +fn main() { let v = <|> } +"#, + expect![[r##" + ma bar!(…) macro_rules! bar + ma baz!(…) #[macro_export] + macro_rules! baz + ma foo!(…) macro_rules! foo + md m1 + md m2 + fn main() fn main() + "##]], + ); + } + + #[test] + fn completes_both_macro_and_value() { + check( + r#" +macro_rules! foo { () => {} } +fn foo() { <|> } +"#, + expect![[r#" + ma foo!(…) macro_rules! foo + fn foo() fn foo() + "#]], + ); + } + + #[test] + fn completes_macros_as_type() { + check( + r#" +macro_rules! foo { () => {} } +fn main() { let x: <|> } +"#, + expect![[r#" + ma foo!(…) macro_rules! foo + fn main() fn main() + "#]], + ); + } + + #[test] + fn completes_macros_as_stmt() { + check( + r#" +macro_rules! foo { () => {} } +fn main() { <|> } +"#, + expect![[r#" + ma foo!(…) macro_rules! foo + fn main() fn main() + "#]], + ); + } + + #[test] + fn completes_local_item() { + check( + r#" +fn main() { + return f<|>; + fn frobnicate() {} +} +"#, + expect![[r#" + fn frobnicate() fn frobnicate() + fn main() fn main() + "#]], + ); + } + + #[test] + fn completes_in_simple_macro_1() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +fn quux(x: i32) { + let y = 92; + m!(<|>); +} +"#, + expect![[r#" + ma m!(…) macro_rules! m + fn quux(…) fn quux(x: i32) + bn x i32 + bn y i32 + "#]], + ); + } + + #[test] + fn completes_in_simple_macro_2() { + check( + r" +macro_rules! m { ($e:expr) => { $e } } +fn quux(x: i32) { + let y = 92; + m!(x<|>); +} +", + expect![[r#" + ma m!(…) macro_rules! m + fn quux(…) fn quux(x: i32) + bn x i32 + bn y i32 + "#]], + ); + } + + #[test] + fn completes_in_simple_macro_without_closing_parens() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +fn quux(x: i32) { + let y = 92; + m!(x<|> +} +"#, + expect![[r#" + ma m!(…) macro_rules! m + fn quux(…) fn quux(x: i32) + bn x i32 + bn y i32 + "#]], + ); + } + + #[test] + fn completes_unresolved_uses() { + check( + r#" +use spam::Quux; + +fn main() { <|> } +"#, + expect![[r#" + ?? Quux + fn main() fn main() + "#]], + ); + } + #[test] + fn completes_enum_variant_matcharm() { + check( + r#" +enum Foo { Bar, Baz, Quux } + +fn main() { + let foo = Foo::Quux; + match foo { Qu<|> } +} +"#, + expect![[r#" + en Foo + ev Foo::Bar () + ev Foo::Baz () + ev Foo::Quux () + "#]], + ) + } + + #[test] + fn completes_enum_variant_iflet() { + check( + r#" +enum Foo { Bar, Baz, Quux } + +fn main() { + let foo = Foo::Quux; + if let Qu<|> = foo { } +} +"#, + expect![[r#" + en Foo + ev Foo::Bar () + ev Foo::Baz () + ev Foo::Quux () + "#]], + ) + } + + #[test] + fn completes_enum_variant_basic_expr() { + check( + r#" +enum Foo { Bar, Baz, Quux } +fn main() { let foo: Foo = Q<|> } +"#, + expect![[r#" + en Foo + ev Foo::Bar () + ev Foo::Baz () + ev Foo::Quux () + fn main() fn main() + "#]], + ) + } + + #[test] + fn completes_enum_variant_from_module() { + check( + r#" +mod m { pub enum E { V } } +fn f() -> m::E { V<|> } +"#, + expect![[r#" + fn f() fn f() -> m::E + md m + ev m::E::V () + "#]], + ) + } + + #[test] + fn dont_complete_attr() { + check( + r#" +struct Foo; +#[<|>] +fn f() {} +"#, + expect![[""]], + ) + } + + #[test] + fn completes_type_or_trait_in_impl_block() { + check( + r#" +trait MyTrait {} +struct MyStruct {} + +impl My<|> +"#, + expect![[r#" + st MyStruct + tt MyTrait + tp Self + "#]], + ) + } +} diff --git a/crates/ide/src/completion/completion_config.rs b/crates/ide/src/completion/completion_config.rs new file mode 100644 index 000000000..71b49ace8 --- /dev/null +++ b/crates/ide/src/completion/completion_config.rs @@ -0,0 +1,35 @@ +//! Settings for tweaking completion. +//! +//! The fun thing here is `SnippetCap` -- this type can only be created in this +//! module, and we use to statically check that we only produce snippet +//! completions if we are allowed to. + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CompletionConfig { + pub enable_postfix_completions: bool, + pub add_call_parenthesis: bool, + pub add_call_argument_snippets: bool, + pub snippet_cap: Option, +} + +impl CompletionConfig { + pub fn allow_snippets(&mut self, yes: bool) { + self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SnippetCap { + _private: (), +} + +impl Default for CompletionConfig { + fn default() -> Self { + CompletionConfig { + enable_postfix_completions: true, + add_call_parenthesis: true, + add_call_argument_snippets: true, + snippet_cap: Some(SnippetCap { _private: () }), + } + } +} diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs new file mode 100644 index 000000000..047ecd9d7 --- /dev/null +++ b/crates/ide/src/completion/completion_context.rs @@ -0,0 +1,465 @@ +//! FIXME: write short doc here + +use base_db::SourceDatabase; +use hir::{Semantics, SemanticsScope, Type}; +use ide_db::RootDatabase; +use syntax::{ + algo::{find_covering_element, find_node_at_offset}, + ast, match_ast, AstNode, NodeOrToken, + SyntaxKind::*, + SyntaxNode, SyntaxToken, TextRange, TextSize, +}; +use text_edit::Indel; + +use super::patterns::{ + has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, + has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, + has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, +}; +use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; +use test_utils::mark; + +/// `CompletionContext` is created early during completion to figure out, where +/// exactly is the cursor, syntax-wise. +#[derive(Debug)] +pub(crate) struct CompletionContext<'a> { + pub(super) sema: Semantics<'a, RootDatabase>, + pub(super) scope: SemanticsScope<'a>, + pub(super) db: &'a RootDatabase, + pub(super) config: &'a CompletionConfig, + pub(super) position: FilePosition, + /// The token before the cursor, in the original file. + pub(super) original_token: SyntaxToken, + /// The token before the cursor, in the macro-expanded file. + pub(super) token: SyntaxToken, + pub(super) krate: Option, + pub(super) expected_type: Option, + pub(super) name_ref_syntax: Option, + pub(super) function_syntax: Option, + pub(super) use_item_syntax: Option, + pub(super) record_lit_syntax: Option, + pub(super) record_pat_syntax: Option, + pub(super) record_field_syntax: Option, + pub(super) impl_def: Option, + /// FIXME: `ActiveParameter` is string-based, which is very very wrong + pub(super) active_parameter: Option, + pub(super) is_param: bool, + /// If a name-binding or reference to a const in a pattern. + /// Irrefutable patterns (like let) are excluded. + pub(super) is_pat_binding_or_const: bool, + /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. + pub(super) is_trivial_path: bool, + /// If not a trivial path, the prefix (qualifier). + pub(super) path_prefix: Option, + pub(super) after_if: bool, + /// `true` if we are a statement or a last expr in the block. + pub(super) can_be_stmt: bool, + /// `true` if we expect an expression at the cursor position. + pub(super) is_expr: bool, + /// Something is typed at the "top" level, in module or impl/trait. + pub(super) is_new_item: bool, + /// The receiver if this is a field or method access, i.e. writing something.<|> + pub(super) dot_receiver: Option, + pub(super) dot_receiver_is_ambiguous_float_literal: bool, + /// If this is a call (method or function) in particular, i.e. the () are already there. + pub(super) is_call: bool, + /// Like `is_call`, but for tuple patterns. + pub(super) is_pattern_call: bool, + /// If this is a macro call, i.e. the () are already there. + pub(super) is_macro_call: bool, + pub(super) is_path_type: bool, + pub(super) has_type_args: bool, + pub(super) attribute_under_caret: Option, + pub(super) unsafe_is_prev: bool, + pub(super) if_is_prev: bool, + pub(super) block_expr_parent: bool, + pub(super) bind_pat_parent: bool, + pub(super) ref_pat_parent: bool, + pub(super) in_loop_body: bool, + pub(super) has_trait_parent: bool, + pub(super) has_impl_parent: bool, + pub(super) trait_as_prev_sibling: bool, + pub(super) impl_as_prev_sibling: bool, + pub(super) is_match_arm: bool, + pub(super) has_item_list_or_source_file_parent: bool, +} + +impl<'a> CompletionContext<'a> { + pub(super) fn new( + db: &'a RootDatabase, + position: FilePosition, + config: &'a CompletionConfig, + ) -> Option> { + let sema = Semantics::new(db); + + let original_file = sema.parse(position.file_id); + + // Insert a fake ident to get a valid parse tree. We will use this file + // to determine context, though the original_file will be used for + // actual completion. + let file_with_fake_ident = { + let parse = db.parse(position.file_id); + let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); + parse.reparse(&edit).tree() + }; + let fake_ident_token = + file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); + + let krate = sema.to_module_def(position.file_id).map(|m| m.krate()); + let original_token = + original_file.syntax().token_at_offset(position.offset).left_biased()?; + let token = sema.descend_into_macros(original_token.clone()); + let scope = sema.scope_at_offset(&token.parent(), position.offset); + let mut ctx = CompletionContext { + sema, + scope, + db, + config, + original_token, + token, + position, + krate, + expected_type: None, + name_ref_syntax: None, + function_syntax: None, + use_item_syntax: None, + record_lit_syntax: None, + record_pat_syntax: None, + record_field_syntax: None, + impl_def: None, + active_parameter: ActiveParameter::at(db, position), + is_param: false, + is_pat_binding_or_const: false, + is_trivial_path: false, + path_prefix: None, + after_if: false, + can_be_stmt: false, + is_expr: false, + is_new_item: false, + dot_receiver: None, + is_call: false, + is_pattern_call: false, + is_macro_call: false, + is_path_type: false, + has_type_args: false, + dot_receiver_is_ambiguous_float_literal: false, + attribute_under_caret: None, + unsafe_is_prev: false, + in_loop_body: false, + ref_pat_parent: false, + bind_pat_parent: false, + block_expr_parent: false, + has_trait_parent: false, + has_impl_parent: false, + trait_as_prev_sibling: false, + impl_as_prev_sibling: false, + if_is_prev: false, + is_match_arm: false, + has_item_list_or_source_file_parent: false, + }; + + let mut original_file = original_file.syntax().clone(); + let mut hypothetical_file = file_with_fake_ident.syntax().clone(); + let mut offset = position.offset; + let mut fake_ident_token = fake_ident_token; + + // Are we inside a macro call? + while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( + find_node_at_offset::(&original_file, offset), + find_node_at_offset::(&hypothetical_file, offset), + ) { + if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) + != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) + { + break; + } + let hypothetical_args = match macro_call_with_fake_ident.token_tree() { + Some(tt) => tt, + None => break, + }; + if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( + ctx.sema.expand(&actual_macro_call), + ctx.sema.expand_hypothetical( + &actual_macro_call, + &hypothetical_args, + fake_ident_token, + ), + ) { + let new_offset = hypothetical_expansion.1.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + break; + } + original_file = actual_expansion; + hypothetical_file = hypothetical_expansion.0; + fake_ident_token = hypothetical_expansion.1; + offset = new_offset; + } else { + break; + } + } + ctx.fill_keyword_patterns(&hypothetical_file, offset); + ctx.fill(&original_file, hypothetical_file, offset); + Some(ctx) + } + + // The range of the identifier that is being completed. + pub(crate) fn source_range(&self) -> TextRange { + // check kind of macro-expanded token, but use range of original token + if self.token.kind() == IDENT || self.token.kind().is_keyword() { + mark::hit!(completes_if_prefix_is_keyword); + self.original_token.text_range() + } else { + TextRange::empty(self.position.offset) + } + } + + fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { + let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); + let syntax_element = NodeOrToken::Token(fake_ident_token); + self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); + self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); + self.if_is_prev = if_is_prev(syntax_element.clone()); + self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); + self.ref_pat_parent = has_ref_parent(syntax_element.clone()); + self.in_loop_body = is_in_loop_body(syntax_element.clone()); + self.has_trait_parent = has_trait_parent(syntax_element.clone()); + self.has_impl_parent = has_impl_parent(syntax_element.clone()); + self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); + self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); + self.is_match_arm = is_match_arm(syntax_element.clone()); + self.has_item_list_or_source_file_parent = + has_item_list_or_source_file_parent(syntax_element); + } + + fn fill( + &mut self, + original_file: &SyntaxNode, + file_with_fake_ident: SyntaxNode, + offset: TextSize, + ) { + // FIXME: this is wrong in at least two cases: + // * when there's no token `foo(<|>)` + // * when there is a token, but it happens to have type of it's own + self.expected_type = self + .token + .ancestors() + .find_map(|node| { + let ty = match_ast! { + match node { + ast::Pat(it) => self.sema.type_of_pat(&it), + ast::Expr(it) => self.sema.type_of_expr(&it), + _ => return None, + } + }; + Some(ty) + }) + .flatten(); + self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); + + // First, let's try to complete a reference to some declaration. + if let Some(name_ref) = find_node_at_offset::(&file_with_fake_ident, offset) { + // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. + // See RFC#1685. + if is_node::(name_ref.syntax()) { + self.is_param = true; + return; + } + // FIXME: remove this (V) duplication and make the check more precise + if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { + self.record_pat_syntax = + self.sema.find_node_at_offset_with_macros(&original_file, offset); + } + self.classify_name_ref(original_file, name_ref, offset); + } + + // Otherwise, see if this is a declaration. We can use heuristics to + // suggest declaration names, see `CompletionKind::Magic`. + if let Some(name) = find_node_at_offset::(&file_with_fake_ident, offset) { + if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { + self.is_pat_binding_or_const = true; + if bind_pat.at_token().is_some() + || bind_pat.ref_token().is_some() + || bind_pat.mut_token().is_some() + { + self.is_pat_binding_or_const = false; + } + if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { + self.is_pat_binding_or_const = false; + } + if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { + if let Some(pat) = let_stmt.pat() { + if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) + { + self.is_pat_binding_or_const = false; + } + } + } + } + if is_node::(name.syntax()) { + self.is_param = true; + return; + } + // FIXME: remove this (^) duplication and make the check more precise + if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { + self.record_pat_syntax = + self.sema.find_node_at_offset_with_macros(&original_file, offset); + } + } + } + + fn classify_name_ref( + &mut self, + original_file: &SyntaxNode, + name_ref: ast::NameRef, + offset: TextSize, + ) { + self.name_ref_syntax = + find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); + let name_range = name_ref.syntax().text_range(); + if ast::RecordExprField::for_field_name(&name_ref).is_some() { + self.record_lit_syntax = + self.sema.find_node_at_offset_with_macros(&original_file, offset); + } + + self.impl_def = self + .sema + .ancestors_with_macros(self.token.parent()) + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::Impl::cast); + + let top_node = name_ref + .syntax() + .ancestors() + .take_while(|it| it.text_range() == name_range) + .last() + .unwrap(); + + match top_node.parent().map(|it| it.kind()) { + Some(SOURCE_FILE) | Some(ITEM_LIST) => { + self.is_new_item = true; + return; + } + _ => (), + } + + self.use_item_syntax = + self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast); + + self.function_syntax = self + .sema + .ancestors_with_macros(self.token.parent()) + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::Fn::cast); + + self.record_field_syntax = self + .sema + .ancestors_with_macros(self.token.parent()) + .take_while(|it| { + it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR + }) + .find_map(ast::RecordExprField::cast); + + let parent = match name_ref.syntax().parent() { + Some(it) => it, + None => return, + }; + + if let Some(segment) = ast::PathSegment::cast(parent.clone()) { + let path = segment.parent_path(); + self.is_call = path + .syntax() + .parent() + .and_then(ast::PathExpr::cast) + .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) + .is_some(); + self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); + self.is_pattern_call = + path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some(); + + self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); + self.has_type_args = segment.generic_arg_list().is_some(); + + let hygiene = hir::Hygiene::new(self.db, self.position.file_id.into()); + if let Some(path) = hir::Path::from_src(path.clone(), &hygiene) { + if let Some(path_prefix) = path.qualifier() { + self.path_prefix = Some(path_prefix); + return; + } + } + + if path.qualifier().is_none() { + self.is_trivial_path = true; + + // Find either enclosing expr statement (thing with `;`) or a + // block. If block, check that we are the last expr. + self.can_be_stmt = name_ref + .syntax() + .ancestors() + .find_map(|node| { + if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { + return Some( + stmt.syntax().text_range() == name_ref.syntax().text_range(), + ); + } + if let Some(block) = ast::BlockExpr::cast(node) { + return Some( + block.expr().map(|e| e.syntax().text_range()) + == Some(name_ref.syntax().text_range()), + ); + } + None + }) + .unwrap_or(false); + self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); + + if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { + if let Some(if_expr) = + self.sema.find_node_at_offset_with_macros::(original_file, off) + { + if if_expr.syntax().text_range().end() + < name_ref.syntax().text_range().start() + { + self.after_if = true; + } + } + } + } + } + if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { + // The receiver comes before the point of insertion of the fake + // ident, so it should have the same range in the non-modified file + self.dot_receiver = field_expr + .expr() + .map(|e| e.syntax().text_range()) + .and_then(|r| find_node_with_range(original_file, r)); + self.dot_receiver_is_ambiguous_float_literal = + if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { + match l.kind() { + ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), + _ => false, + } + } else { + false + } + } + if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { + // As above + self.dot_receiver = method_call_expr + .expr() + .map(|e| e.syntax().text_range()) + .and_then(|r| find_node_with_range(original_file, r)); + self.is_call = true; + } + } +} + +fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { + find_covering_element(syntax, range).ancestors().find_map(N::cast) +} + +fn is_node(node: &SyntaxNode) -> bool { + match node.ancestors().find_map(N::cast) { + None => false, + Some(n) => n.syntax().text_range() == node.text_range(), + } +} diff --git a/crates/ide/src/completion/completion_item.rs b/crates/ide/src/completion/completion_item.rs new file mode 100644 index 000000000..9377cdc57 --- /dev/null +++ b/crates/ide/src/completion/completion_item.rs @@ -0,0 +1,384 @@ +//! FIXME: write short doc here + +use std::fmt; + +use hir::Documentation; +use syntax::TextRange; +use text_edit::TextEdit; + +use crate::completion::completion_config::SnippetCap; + +/// `CompletionItem` describes a single completion variant in the editor pop-up. +/// It is basically a POD with various properties. To construct a +/// `CompletionItem`, use `new` method and the `Builder` struct. +pub struct CompletionItem { + /// Used only internally in tests, to check only specific kind of + /// completion (postfix, keyword, reference, etc). + #[allow(unused)] + pub(crate) completion_kind: CompletionKind, + /// Label in the completion pop up which identifies completion. + label: String, + /// Range of identifier that is being completed. + /// + /// It should be used primarily for UI, but we also use this to convert + /// genetic TextEdit into LSP's completion edit (see conv.rs). + /// + /// `source_range` must contain the completion offset. `insert_text` should + /// start with what `source_range` points to, or VSCode will filter out the + /// completion silently. + source_range: TextRange, + /// What happens when user selects this item. + /// + /// Typically, replaces `source_range` with new identifier. + text_edit: TextEdit, + insert_text_format: InsertTextFormat, + + /// What item (struct, function, etc) are we completing. + kind: Option, + + /// Lookup is used to check if completion item indeed can complete current + /// ident. + /// + /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it + /// contains `bar` sub sequence), and `quux` will rejected. + lookup: Option, + + /// Additional info to show in the UI pop up. + detail: Option, + documentation: Option, + + /// Whether this item is marked as deprecated + deprecated: bool, + + /// If completing a function call, ask the editor to show parameter popup + /// after completion. + trigger_call_info: bool, + + /// Score is useful to pre select or display in better order completion items + score: Option, +} + +// We use custom debug for CompletionItem to make snapshot tests more readable. +impl fmt::Debug for CompletionItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut s = f.debug_struct("CompletionItem"); + s.field("label", &self.label()).field("source_range", &self.source_range()); + if self.text_edit().len() == 1 { + let atom = &self.text_edit().iter().next().unwrap(); + s.field("delete", &atom.delete); + s.field("insert", &atom.insert); + } else { + s.field("text_edit", &self.text_edit); + } + if let Some(kind) = self.kind().as_ref() { + s.field("kind", kind); + } + if self.lookup() != self.label() { + s.field("lookup", &self.lookup()); + } + if let Some(detail) = self.detail() { + s.field("detail", &detail); + } + if let Some(documentation) = self.documentation() { + s.field("documentation", &documentation); + } + if self.deprecated { + s.field("deprecated", &true); + } + if let Some(score) = &self.score { + s.field("score", score); + } + if self.trigger_call_info { + s.field("trigger_call_info", &true); + } + s.finish() + } +} + +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] +pub enum CompletionScore { + /// If only type match + TypeMatch, + /// If type and name match + TypeAndNameMatch, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompletionItemKind { + Snippet, + Keyword, + Module, + Function, + BuiltinType, + Struct, + Enum, + EnumVariant, + Binding, + Field, + Static, + Const, + Trait, + TypeAlias, + Method, + TypeParam, + Macro, + Attribute, + UnresolvedReference, +} + +impl CompletionItemKind { + #[cfg(test)] + pub(crate) fn tag(&self) -> &'static str { + match self { + CompletionItemKind::Attribute => "at", + CompletionItemKind::Binding => "bn", + CompletionItemKind::BuiltinType => "bt", + CompletionItemKind::Const => "ct", + CompletionItemKind::Enum => "en", + CompletionItemKind::EnumVariant => "ev", + CompletionItemKind::Field => "fd", + CompletionItemKind::Function => "fn", + CompletionItemKind::Keyword => "kw", + CompletionItemKind::Macro => "ma", + CompletionItemKind::Method => "me", + CompletionItemKind::Module => "md", + CompletionItemKind::Snippet => "sn", + CompletionItemKind::Static => "sc", + CompletionItemKind::Struct => "st", + CompletionItemKind::Trait => "tt", + CompletionItemKind::TypeAlias => "ta", + CompletionItemKind::TypeParam => "tp", + CompletionItemKind::UnresolvedReference => "??", + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub(crate) enum CompletionKind { + /// Parser-based keyword completion. + Keyword, + /// Your usual "complete all valid identifiers". + Reference, + /// "Secret sauce" completions. + Magic, + Snippet, + Postfix, + BuiltinType, + Attribute, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum InsertTextFormat { + PlainText, + Snippet, +} + +impl CompletionItem { + pub(crate) fn new( + completion_kind: CompletionKind, + source_range: TextRange, + label: impl Into, + ) -> Builder { + let label = label.into(); + Builder { + source_range, + completion_kind, + label, + insert_text: None, + insert_text_format: InsertTextFormat::PlainText, + detail: None, + documentation: None, + lookup: None, + kind: None, + text_edit: None, + deprecated: None, + trigger_call_info: None, + score: None, + } + } + /// What user sees in pop-up in the UI. + pub fn label(&self) -> &str { + &self.label + } + pub fn source_range(&self) -> TextRange { + self.source_range + } + + pub fn insert_text_format(&self) -> InsertTextFormat { + self.insert_text_format + } + + pub fn text_edit(&self) -> &TextEdit { + &self.text_edit + } + + /// Short one-line additional information, like a type + pub fn detail(&self) -> Option<&str> { + self.detail.as_deref() + } + /// A doc-comment + pub fn documentation(&self) -> Option { + self.documentation.clone() + } + /// What string is used for filtering. + pub fn lookup(&self) -> &str { + self.lookup.as_deref().unwrap_or(&self.label) + } + + pub fn kind(&self) -> Option { + self.kind + } + + pub fn deprecated(&self) -> bool { + self.deprecated + } + + pub fn score(&self) -> Option { + self.score + } + + pub fn trigger_call_info(&self) -> bool { + self.trigger_call_info + } +} + +/// A helper to make `CompletionItem`s. +#[must_use] +pub(crate) struct Builder { + source_range: TextRange, + completion_kind: CompletionKind, + label: String, + insert_text: Option, + insert_text_format: InsertTextFormat, + detail: Option, + documentation: Option, + lookup: Option, + kind: Option, + text_edit: Option, + deprecated: Option, + trigger_call_info: Option, + score: Option, +} + +impl Builder { + pub(crate) fn add_to(self, acc: &mut Completions) { + acc.add(self.build()) + } + + pub(crate) fn build(self) -> CompletionItem { + let label = self.label; + let text_edit = match self.text_edit { + Some(it) => it, + None => TextEdit::replace( + self.source_range, + self.insert_text.unwrap_or_else(|| label.clone()), + ), + }; + + CompletionItem { + source_range: self.source_range, + label, + insert_text_format: self.insert_text_format, + text_edit, + detail: self.detail, + documentation: self.documentation, + lookup: self.lookup, + kind: self.kind, + completion_kind: self.completion_kind, + deprecated: self.deprecated.unwrap_or(false), + trigger_call_info: self.trigger_call_info.unwrap_or(false), + score: self.score, + } + } + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { + self.lookup = Some(lookup.into()); + self + } + pub(crate) fn label(mut self, label: impl Into) -> Builder { + self.label = label.into(); + self + } + pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder { + self.insert_text = Some(insert_text.into()); + self + } + pub(crate) fn insert_snippet( + mut self, + _cap: SnippetCap, + snippet: impl Into, + ) -> Builder { + self.insert_text_format = InsertTextFormat::Snippet; + self.insert_text(snippet) + } + pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { + self.kind = Some(kind); + self + } + pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { + self.text_edit = Some(edit); + self + } + pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { + self.insert_text_format = InsertTextFormat::Snippet; + self.text_edit(edit) + } + #[allow(unused)] + pub(crate) fn detail(self, detail: impl Into) -> Builder { + self.set_detail(Some(detail)) + } + pub(crate) fn set_detail(mut self, detail: Option>) -> Builder { + self.detail = detail.map(Into::into); + self + } + #[allow(unused)] + pub(crate) fn documentation(self, docs: Documentation) -> Builder { + self.set_documentation(Some(docs)) + } + pub(crate) fn set_documentation(mut self, docs: Option) -> Builder { + self.documentation = docs.map(Into::into); + self + } + pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { + self.deprecated = Some(deprecated); + self + } + pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { + self.score = Some(score); + self + } + pub(crate) fn trigger_call_info(mut self) -> Builder { + self.trigger_call_info = Some(true); + self + } +} + +impl<'a> Into for Builder { + fn into(self) -> CompletionItem { + self.build() + } +} + +/// Represents an in-progress set of completions being built. +#[derive(Debug, Default)] +pub(crate) struct Completions { + buf: Vec, +} + +impl Completions { + pub(crate) fn add(&mut self, item: impl Into) { + self.buf.push(item.into()) + } + pub(crate) fn add_all(&mut self, items: I) + where + I: IntoIterator, + I::Item: Into, + { + items.into_iter().for_each(|item| self.add(item.into())) + } +} + +impl Into> for Completions { + fn into(self) -> Vec { + self.buf + } +} diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs new file mode 100644 index 000000000..ffc97c076 --- /dev/null +++ b/crates/ide/src/completion/patterns.rs @@ -0,0 +1,194 @@ +//! Patterns telling us certain facts about current syntax element, they are used in completion context + +use syntax::{ + algo::non_trivia_sibling, + ast::{self, LoopBodyOwner}, + match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, + SyntaxKind::*, + SyntaxNode, SyntaxToken, +}; + +#[cfg(test)] +use crate::completion::test_utils::check_pattern_is_applicable; + +pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == ASSOC_ITEM_LIST) + .and_then(|it| it.parent()) + .filter(|it| it.kind() == TRAIT) + .is_some() +} +#[test] +fn test_has_trait_parent() { + check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); +} + +pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == ASSOC_ITEM_LIST) + .and_then(|it| it.parent()) + .filter(|it| it.kind() == IMPL) + .is_some() +} +#[test] +fn test_has_impl_parent() { + check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); +} + +pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() +} +#[test] +fn test_has_block_expr_parent() { + check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); +} + +pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { + element.ancestors().find(|it| it.kind() == IDENT_PAT).is_some() +} +#[test] +fn test_has_bind_pat_parent() { + check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); + check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); +} + +pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) + .is_some() +} +#[test] +fn test_has_ref_parent() { + check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); + check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); +} + +pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { + let ancestor = not_same_range_ancestor(element); + if !ancestor.is_some() { + return true; + } + ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() +} +#[test] +fn test_has_item_list_or_source_file_parent() { + check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); + check_pattern_is_applicable(r"mod foo { f<|> }", has_item_list_or_source_file_parent); +} + +pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { + not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() + && previous_sibling_or_ancestor_sibling(element) + .and_then(|it| it.into_token()) + .filter(|it| it.kind() == FAT_ARROW) + .is_some() +} +#[test] +fn test_is_match_arm() { + check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); +} + +pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { + element + .into_token() + .and_then(|it| previous_non_trivia_token(it)) + .filter(|it| it.kind() == UNSAFE_KW) + .is_some() +} +#[test] +fn test_unsafe_is_prev() { + check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); +} + +pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { + element + .into_token() + .and_then(|it| previous_non_trivia_token(it)) + .filter(|it| it.kind() == IF_KW) + .is_some() +} +#[test] +fn test_if_is_prev() { + check_pattern_is_applicable(r"if l<|>", if_is_prev); +} + +pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { + previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some() +} +#[test] +fn test_has_trait_as_prev_sibling() { + check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); +} + +pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { + previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some() +} +#[test] +fn test_has_impl_as_prev_sibling() { + check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); +} + +pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { + let leaf = match element { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => token.parent(), + }; + for node in leaf.ancestors() { + if node.kind() == FN || node.kind() == CLOSURE_EXPR { + break; + } + let loop_body = match_ast! { + match node { + ast::ForExpr(it) => it.loop_body(), + ast::WhileExpr(it) => it.loop_body(), + ast::LoopExpr(it) => it.loop_body(), + _ => None, + } + }; + if let Some(body) = loop_body { + if body.syntax().text_range().contains_range(leaf.text_range()) { + return true; + } + } + } + false +} + +fn not_same_range_ancestor(element: SyntaxElement) -> Option { + element + .ancestors() + .take_while(|it| it.text_range() == element.text_range()) + .last() + .and_then(|it| it.parent()) +} + +fn previous_non_trivia_token(token: SyntaxToken) -> Option { + let mut token = token.prev_token(); + while let Some(inner) = token.clone() { + if !inner.kind().is_trivia() { + return Some(inner); + } else { + token = inner.prev_token(); + } + } + None +} + +fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option { + let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); + if let Some(sibling) = token_sibling { + Some(sibling) + } else { + // if not trying to find first ancestor which has such a sibling + let node = match element { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => token.parent(), + }; + let range = node.text_range(); + let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; + let prev_sibling_node = top_node.ancestors().find(|it| { + non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() + })?; + non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) + } +} diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs new file mode 100644 index 000000000..e1b1ea4ce --- /dev/null +++ b/crates/ide/src/completion/presentation.rs @@ -0,0 +1,1229 @@ +//! This modules takes care of rendering various definitions as completion items. +//! It also handles scoring (sorting) completions. + +use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; +use itertools::Itertools; +use syntax::ast::NameOwner; +use test_utils::mark; + +use crate::{ + completion::{ + completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind, + CompletionKind, Completions, + }, + display::{const_label, function_declaration, macro_label, type_label}, + CompletionScore, RootDatabase, +}; + +impl Completions { + pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { + let is_deprecated = is_deprecated(field, ctx.db); + let name = field.name(ctx.db); + let mut completion_item = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) + .kind(CompletionItemKind::Field) + .detail(ty.display(ctx.db).to_string()) + .set_documentation(field.docs(ctx.db)) + .set_deprecated(is_deprecated); + + if let Some(score) = compute_score(ctx, &ty, &name.to_string()) { + completion_item = completion_item.set_score(score); + } + + completion_item.add_to(self); + } + + pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) + .kind(CompletionItemKind::Field) + .detail(ty.display(ctx.db).to_string()) + .add_to(self); + } + + pub(crate) fn add_resolution( + &mut self, + ctx: &CompletionContext, + local_name: String, + resolution: &ScopeDef, + ) { + use hir::ModuleDef::*; + + let completion_kind = match resolution { + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, + _ => CompletionKind::Reference, + }; + + let kind = match resolution { + ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, + ScopeDef::ModuleDef(Function(func)) => { + return self.add_function(ctx, *func, Some(local_name)); + } + ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, + // FIXME: add CompletionItemKind::Union + ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, + ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, + + ScopeDef::ModuleDef(EnumVariant(var)) => { + return self.add_enum_variant(ctx, *var, Some(local_name)); + } + ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, + ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, + ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, + ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, + ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, + ScopeDef::Local(..) => CompletionItemKind::Binding, + // (does this need its own kind?) + ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, + ScopeDef::MacroDef(mac) => { + return self.add_macro(ctx, Some(local_name), *mac); + } + ScopeDef::Unknown => { + return self.add( + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name) + .kind(CompletionItemKind::UnresolvedReference), + ); + } + }; + + let docs = match resolution { + ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), + _ => None, + }; + + let mut completion_item = + CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); + if let ScopeDef::Local(local) = resolution { + let ty = local.ty(ctx.db); + if !ty.is_unknown() { + completion_item = completion_item.detail(ty.display(ctx.db).to_string()); + } + }; + + if let ScopeDef::Local(local) = resolution { + if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { + completion_item = completion_item.set_score(score); + } + } + + // Add `<>` for generic types + if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { + if let Some(cap) = ctx.config.snippet_cap { + let has_non_default_type_params = match resolution { + ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), + ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), + _ => false, + }; + if has_non_default_type_params { + mark::hit!(inserts_angle_brackets_for_generics); + completion_item = completion_item + .lookup_by(local_name.clone()) + .label(format!("{}<…>", local_name)) + .insert_snippet(cap, format!("{}<$0>", local_name)); + } + } + } + + completion_item.kind(kind).set_documentation(docs).add_to(self) + } + + pub(crate) fn add_macro( + &mut self, + ctx: &CompletionContext, + name: Option, + macro_: hir::MacroDef, + ) { + // FIXME: Currently proc-macro do not have ast-node, + // such that it does not have source + if macro_.is_proc_macro() { + return; + } + + let name = match name { + Some(it) => it, + None => return, + }; + + let ast_node = macro_.source(ctx.db).value; + let detail = macro_label(&ast_node); + + let docs = macro_.docs(ctx.db); + + let mut builder = CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + &format!("{}!", name), + ) + .kind(CompletionItemKind::Macro) + .set_documentation(docs.clone()) + .set_deprecated(is_deprecated(macro_, ctx.db)) + .detail(detail); + + let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call; + builder = match ctx.config.snippet_cap { + Some(cap) if needs_bang => { + let docs = docs.as_ref().map_or("", |s| s.as_str()); + let (bra, ket) = guess_macro_braces(&name, docs); + builder + .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket)) + .label(format!("{}!{}…{}", name, bra, ket)) + .lookup_by(format!("{}!", name)) + } + None if needs_bang => builder.insert_text(format!("{}!", name)), + _ => { + mark::hit!(dont_insert_macro_call_parens_unncessary); + builder.insert_text(name) + } + }; + + self.add(builder); + } + + pub(crate) fn add_function( + &mut self, + ctx: &CompletionContext, + func: hir::Function, + local_name: Option, + ) { + let has_self_param = func.has_self_param(ctx.db); + + let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); + let ast_node = func.source(ctx.db).value; + + let mut builder = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) + .kind(if has_self_param { + CompletionItemKind::Method + } else { + CompletionItemKind::Function + }) + .set_documentation(func.docs(ctx.db)) + .set_deprecated(is_deprecated(func, ctx.db)) + .detail(function_declaration(&ast_node)); + + let params = ast_node + .param_list() + .into_iter() + .flat_map(|it| it.params()) + .flat_map(|it| it.pat()) + .map(|pat| pat.to_string().trim_start_matches('_').into()) + .collect(); + + builder = builder.add_call_parens(ctx, name, Params::Named(params)); + + self.add(builder) + } + + pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { + let ast_node = constant.source(ctx.db).value; + let name = match ast_node.name() { + Some(name) => name, + _ => return, + }; + let detail = const_label(&ast_node); + + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) + .kind(CompletionItemKind::Const) + .set_documentation(constant.docs(ctx.db)) + .set_deprecated(is_deprecated(constant, ctx.db)) + .detail(detail) + .add_to(self); + } + + pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { + let type_def = type_alias.source(ctx.db).value; + let name = match type_def.name() { + Some(name) => name, + _ => return, + }; + let detail = type_label(&type_def); + + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) + .kind(CompletionItemKind::TypeAlias) + .set_documentation(type_alias.docs(ctx.db)) + .set_deprecated(is_deprecated(type_alias, ctx.db)) + .detail(detail) + .add_to(self); + } + + pub(crate) fn add_qualified_enum_variant( + &mut self, + ctx: &CompletionContext, + variant: hir::EnumVariant, + path: ModPath, + ) { + self.add_enum_variant_impl(ctx, variant, None, Some(path)) + } + + pub(crate) fn add_enum_variant( + &mut self, + ctx: &CompletionContext, + variant: hir::EnumVariant, + local_name: Option, + ) { + self.add_enum_variant_impl(ctx, variant, local_name, None) + } + + fn add_enum_variant_impl( + &mut self, + ctx: &CompletionContext, + variant: hir::EnumVariant, + local_name: Option, + path: Option, + ) { + let is_deprecated = is_deprecated(variant, ctx.db); + let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); + let qualified_name = match &path { + Some(it) => it.to_string(), + None => name.to_string(), + }; + let detail_types = variant + .fields(ctx.db) + .into_iter() + .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); + let variant_kind = variant.kind(ctx.db); + let detail = match variant_kind { + StructKind::Tuple | StructKind::Unit => format!( + "({})", + detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") + ), + StructKind::Record => format!( + "{{ {} }}", + detail_types + .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) + .format(", ") + ), + }; + let mut res = CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + qualified_name.clone(), + ) + .kind(CompletionItemKind::EnumVariant) + .set_documentation(variant.docs(ctx.db)) + .set_deprecated(is_deprecated) + .detail(detail); + + if path.is_some() { + res = res.lookup_by(name); + } + + if variant_kind == StructKind::Tuple { + mark::hit!(inserts_parens_for_tuple_enums); + let params = Params::Anonymous(variant.fields(ctx.db).len()); + res = res.add_call_parens(ctx, qualified_name, params) + } + + res.add_to(self); + } +} + +pub(crate) fn compute_score( + ctx: &CompletionContext, + ty: &Type, + name: &str, +) -> Option { + let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { + mark::hit!(record_field_type_match); + let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; + (struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db)) + } else if let Some(active_parameter) = &ctx.active_parameter { + mark::hit!(active_param_type_match); + (active_parameter.name.clone(), active_parameter.ty.clone()) + } else { + return None; + }; + + // Compute score + // For the same type + if &active_type != ty { + return None; + } + + let mut res = CompletionScore::TypeMatch; + + // If same type + same name then go top position + if active_name == name { + res = CompletionScore::TypeAndNameMatch + } + + Some(res) +} + +enum Params { + Named(Vec), + Anonymous(usize), +} + +impl Params { + fn len(&self) -> usize { + match self { + Params::Named(xs) => xs.len(), + Params::Anonymous(len) => *len, + } + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Builder { + fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder { + if !ctx.config.add_call_parenthesis { + return self; + } + if ctx.use_item_syntax.is_some() { + mark::hit!(no_parens_in_use_item); + return self; + } + if ctx.is_pattern_call { + mark::hit!(dont_duplicate_pattern_parens); + return self; + } + if ctx.is_call { + return self; + } + + // Don't add parentheses if the expected type is some function reference. + if let Some(ty) = &ctx.expected_type { + if ty.is_fn() { + mark::hit!(no_call_parens_if_fn_ptr_needed); + return self; + } + } + + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return self, + }; + // If not an import, add parenthesis automatically. + mark::hit!(inserts_parens_for_function_calls); + + let (snippet, label) = if params.is_empty() { + (format!("{}()$0", name), format!("{}()", name)) + } else { + self = self.trigger_call_info(); + let snippet = match (ctx.config.add_call_argument_snippets, params) { + (true, Params::Named(params)) => { + let function_params_snippet = + params.iter().enumerate().format_with(", ", |(index, param_name), f| { + f(&format_args!("${{{}:{}}}", index + 1, param_name)) + }); + format!("{}({})$0", name, function_params_snippet) + } + _ => { + mark::hit!(suppress_arg_snippets); + format!("{}($0)", name) + } + }; + + (snippet, format!("{}(…)", name)) + }; + self.lookup_by(name).label(label).insert_snippet(cap, snippet) + } +} + +fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { + node.attrs(db).by_key("deprecated").exists() +} + +fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { + let mut votes = [0, 0, 0]; + for (idx, s) in docs.match_indices(¯o_name) { + let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + +#[cfg(test)] +mod tests { + use std::cmp::Reverse; + + use expect::{expect, Expect}; + use test_utils::mark; + + use crate::{ + completion::{ + test_utils::{ + check_edit, check_edit_with_config, do_completion, get_all_completion_items, + }, + CompletionConfig, CompletionKind, + }, + CompletionScore, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = do_completion(ra_fixture, CompletionKind::Reference); + expect.assert_debug_eq(&actual); + } + + fn check_scores(ra_fixture: &str, expect: Expect) { + fn display_score(score: Option) -> &'static str { + match score { + Some(CompletionScore::TypeMatch) => "[type]", + Some(CompletionScore::TypeAndNameMatch) => "[type+name]", + None => "[]".into(), + } + } + + let mut completions = get_all_completion_items(CompletionConfig::default(), ra_fixture); + completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); + let actual = completions + .into_iter() + .filter(|it| it.completion_kind == CompletionKind::Reference) + .map(|it| { + let tag = it.kind().unwrap().tag(); + let score = display_score(it.score()); + format!("{} {} {}\n", tag, it.label(), score) + }) + .collect::(); + expect.assert_eq(&actual); + } + + #[test] + fn enum_detail_includes_record_fields() { + check( + r#" +enum Foo { Foo { x: i32, y: i32 } } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo", + source_range: 54..56, + delete: 54..56, + insert: "Foo", + kind: EnumVariant, + detail: "{ x: i32, y: i32 }", + }, + ] + "#]], + ); + } + + #[test] + fn enum_detail_doesnt_include_tuple_fields() { + check( + r#" +enum Foo { Foo (i32, i32) } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo(…)", + source_range: 46..48, + delete: 46..48, + insert: "Foo($0)", + kind: EnumVariant, + lookup: "Foo", + detail: "(i32, i32)", + trigger_call_info: true, + }, + ] + "#]], + ); + } + + #[test] + fn enum_detail_just_parentheses_for_unit() { + check( + r#" +enum Foo { Foo } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo", + source_range: 35..37, + delete: 35..37, + insert: "Foo", + kind: EnumVariant, + detail: "()", + }, + ] + "#]], + ); + } + + #[test] + fn sets_deprecated_flag_in_completion_items() { + check( + r#" +#[deprecated] +fn something_deprecated() {} +#[deprecated(since = "1.0.0")] +fn something_else_deprecated() {} + +fn main() { som<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "main()", + source_range: 121..124, + delete: 121..124, + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "something_deprecated()", + source_range: 121..124, + delete: 121..124, + insert: "something_deprecated()$0", + kind: Function, + lookup: "something_deprecated", + detail: "fn something_deprecated()", + deprecated: true, + }, + CompletionItem { + label: "something_else_deprecated()", + source_range: 121..124, + delete: 121..124, + insert: "something_else_deprecated()$0", + kind: Function, + lookup: "something_else_deprecated", + detail: "fn something_else_deprecated()", + deprecated: true, + }, + ] + "#]], + ); + + check( + r#" +struct A { #[deprecated] the_field: u32 } +fn foo() { A { the<|> } } +"#, + expect![[r#" + [ + CompletionItem { + label: "the_field", + source_range: 57..60, + delete: 57..60, + insert: "the_field", + kind: Field, + detail: "u32", + deprecated: true, + }, + ] + "#]], + ); + } + + #[test] + fn renders_docs() { + check( + r#" +struct S { + /// Field docs + foo: +} +impl S { + /// Method docs + fn bar(self) { self.<|> } +}"#, + expect![[r#" + [ + CompletionItem { + label: "bar()", + source_range: 94..94, + delete: 94..94, + insert: "bar()$0", + kind: Method, + lookup: "bar", + detail: "fn bar(self)", + documentation: Documentation( + "Method docs", + ), + }, + CompletionItem { + label: "foo", + source_range: 94..94, + delete: 94..94, + insert: "foo", + kind: Field, + detail: "{unknown}", + documentation: Documentation( + "Field docs", + ), + }, + ] + "#]], + ); + + check( + r#" +use self::my<|>; + +/// mod docs +mod my { } + +/// enum docs +enum E { + /// variant docs + V +} +use self::E::*; +"#, + expect![[r#" + [ + CompletionItem { + label: "E", + source_range: 10..12, + delete: 10..12, + insert: "E", + kind: Enum, + documentation: Documentation( + "enum docs", + ), + }, + CompletionItem { + label: "V", + source_range: 10..12, + delete: 10..12, + insert: "V", + kind: EnumVariant, + detail: "()", + documentation: Documentation( + "variant docs", + ), + }, + CompletionItem { + label: "my", + source_range: 10..12, + delete: 10..12, + insert: "my", + kind: Module, + documentation: Documentation( + "mod docs", + ), + }, + ] + "#]], + ) + } + + #[test] + fn dont_render_attrs() { + check( + r#" +struct S; +impl S { + #[inline] + fn the_method(&self) { } +} +fn foo(s: S) { s.<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "the_method()", + source_range: 81..81, + delete: 81..81, + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "#]], + ) + } + + #[test] + fn inserts_parens_for_function_calls() { + mark::check!(inserts_parens_for_function_calls); + check_edit( + "no_args", + r#" +fn no_args() {} +fn main() { no_<|> } +"#, + r#" +fn no_args() {} +fn main() { no_args()$0 } +"#, + ); + + check_edit( + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_<|> } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args(${1:x}, ${2:y})$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.f<|> } +"#, + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.foo()$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.f<|> +} +"#, + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.foo(${1:x})$0 +} +"#, + ); + } + + #[test] + fn suppress_arg_snippets() { + mark::check!(suppress_arg_snippets); + check_edit_with_config( + CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_<|> } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args($0) } +"#, + ); + } + + #[test] + fn strips_underscores_from_args() { + check_edit( + "foo", + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { f<|> } +"#, + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } +"#, + ); + } + + #[test] + fn inserts_parens_for_tuple_enums() { + mark::check!(inserts_parens_for_tuple_enums); + check_edit( + "Some", + r#" +enum Option { Some(T), None } +use Option::*; +fn main() -> Option { + Som<|> +} +"#, + r#" +enum Option { Some(T), None } +use Option::*; +fn main() -> Option { + Some($0) +} +"#, + ); + check_edit( + "Some", + r#" +enum Option { Some(T), None } +use Option::*; +fn main(value: Option) { + match value { + Som<|> + } +} +"#, + r#" +enum Option { Some(T), None } +use Option::*; +fn main(value: Option) { + match value { + Some($0) + } +} +"#, + ); + } + + #[test] + fn dont_duplicate_pattern_parens() { + mark::check!(dont_duplicate_pattern_parens); + check_edit( + "Var", + r#" +enum E { Var(i32) } +fn main() { + match E::Var(92) { + E::<|>(92) => (), + } +} +"#, + r#" +enum E { Var(i32) } +fn main() { + match E::Var(92) { + E::Var(92) => (), + } +} +"#, + ); + } + + #[test] + fn no_call_parens_if_fn_ptr_needed() { + mark::check!(no_call_parens_if_fn_ptr_needed); + check_edit( + "foo", + r#" +fn foo(foo: u8, bar: u8) {} +struct ManualVtable { f: fn(u8, u8) } + +fn main() -> ManualVtable { + ManualVtable { f: f<|> } +} +"#, + r#" +fn foo(foo: u8, bar: u8) {} +struct ManualVtable { f: fn(u8, u8) } + +fn main() -> ManualVtable { + ManualVtable { f: foo } +} +"#, + ); + } + + #[test] + fn no_parens_in_use_item() { + mark::check!(no_parens_in_use_item); + check_edit( + "foo", + r#" +mod m { pub fn foo() {} } +use crate::m::f<|>; +"#, + r#" +mod m { pub fn foo() {} } +use crate::m::foo; +"#, + ); + } + + #[test] + fn no_parens_in_call() { + check_edit( + "foo", + r#" +fn foo(x: i32) {} +fn main() { f<|>(); } +"#, + r#" +fn foo(x: i32) {} +fn main() { foo(); } +"#, + ); + check_edit( + "foo", + r#" +struct Foo; +impl Foo { fn foo(&self){} } +fn f(foo: &Foo) { foo.f<|>(); } +"#, + r#" +struct Foo; +impl Foo { fn foo(&self){} } +fn f(foo: &Foo) { foo.foo(); } +"#, + ); + } + + #[test] + fn inserts_angle_brackets_for_generics() { + mark::check!(inserts_angle_brackets_for_generics); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +type Vec = (T,); +fn foo(xs: Ve<|>) +"#, + r#" +type Vec = (T,); +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + } + + #[test] + fn dont_insert_macro_call_parens_unncessary() { + mark::check!(dont_insert_macro_call_parens_unncessary); + check_edit( + "frobnicate!", + r#" +//- /main.rs +use foo::<|>; +//- /foo/lib.rs +#[macro_export] +macro_rules frobnicate { () => () } +"#, + r#" +use foo::frobnicate; +"#, + ); + + check_edit( + "frobnicate!", + r#" +macro_rules frobnicate { () => () } +fn main() { frob<|>!(); } +"#, + r#" +macro_rules frobnicate { () => () } +fn main() { frobnicate!(); } +"#, + ); + } + + #[test] + fn active_param_score() { + mark::check!(active_param_type_match); + check_scores( + r#" +struct S { foo: i64, bar: u32, baz: u32 } +fn test(bar: u32) { } +fn foo(s: S) { test(s.<|>) } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ); + } + + #[test] + fn record_field_scores() { + mark::check!(record_field_type_match); + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn foo(a: A) { B { bar: a.<|> }; } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ) + } + + #[test] + fn record_field_and_call_scores() { + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn f(foo: i64) { } +fn foo(a: A) { B { bar: f(a.<|>) }; } +"#, + expect![[r#" + fd foo [type+name] + fd bar [] + fd baz [] + "#]], + ); + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn f(foo: i64) { } +fn foo(a: A) { f(B { bar: a.<|> }); } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ); + } + + #[test] + fn prioritize_exact_ref_match() { + check_scores( + r#" +struct WorldSnapshot { _f: () }; +fn go(world: &WorldSnapshot) { go(w<|>) } +"#, + expect![[r#" + bn world [type+name] + st WorldSnapshot [] + fn go(…) [] + "#]], + ); + } + + #[test] + fn too_many_arguments() { + mark::check!(too_many_arguments); + check_scores( + r#" +struct Foo; +fn f(foo: &Foo) { f(foo, w<|>) } +"#, + expect![[r#" + st Foo [] + fn f(…) [] + bn foo [] + "#]], + ); + } + + #[test] + fn guesses_macro_braces() { + check_edit( + "vec!", + r#" +/// Creates a [`Vec`] containing the arguments. +/// +/// ``` +/// let v = vec![1, 2, 3]; +/// assert_eq!(v[0], 1); +/// assert_eq!(v[1], 2); +/// assert_eq!(v[2], 3); +/// ``` +macro_rules! vec { () => {} } + +fn fn main() { v<|> } +"#, + r#" +/// Creates a [`Vec`] containing the arguments. +/// +/// ``` +/// let v = vec![1, 2, 3]; +/// assert_eq!(v[0], 1); +/// assert_eq!(v[1], 2); +/// assert_eq!(v[2], 3); +/// ``` +macro_rules! vec { () => {} } + +fn fn main() { vec![$0] } +"#, + ); + + check_edit( + "foo!", + r#" +/// Foo +/// +/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, +/// call as `let _=foo! { hello world };` +macro_rules! foo { () => {} } +fn main() { <|> } +"#, + r#" +/// Foo +/// +/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, +/// call as `let _=foo! { hello world };` +macro_rules! foo { () => {} } +fn main() { foo! {$0} } +"#, + ) + } +} diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs new file mode 100644 index 000000000..1452d7e9e --- /dev/null +++ b/crates/ide/src/completion/test_utils.rs @@ -0,0 +1,114 @@ +//! Runs completion for testing purposes. + +use hir::Semantics; +use itertools::Itertools; +use stdx::{format_to, trim_indent}; +use syntax::{AstNode, NodeOrToken, SyntaxElement}; +use test_utils::assert_eq_text; + +use crate::{ + completion::{completion_item::CompletionKind, CompletionConfig}, + mock_analysis::analysis_and_position, + CompletionItem, +}; + +pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec { + do_completion_with_config(CompletionConfig::default(), code, kind) +} + +pub(crate) fn do_completion_with_config( + config: CompletionConfig, + code: &str, + kind: CompletionKind, +) -> Vec { + let mut kind_completions: Vec = get_all_completion_items(config, code) + .into_iter() + .filter(|c| c.completion_kind == kind) + .collect(); + kind_completions.sort_by(|l, r| l.label().cmp(r.label())); + kind_completions +} + +pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { + completion_list_with_config(CompletionConfig::default(), code, kind) +} + +pub(crate) fn completion_list_with_config( + config: CompletionConfig, + code: &str, + kind: CompletionKind, +) -> String { + let mut kind_completions: Vec = get_all_completion_items(config, code) + .into_iter() + .filter(|c| c.completion_kind == kind) + .collect(); + kind_completions.sort_by_key(|c| c.label().to_owned()); + let label_width = kind_completions + .iter() + .map(|it| monospace_width(it.label())) + .max() + .unwrap_or_default() + .min(16); + kind_completions + .into_iter() + .map(|it| { + let tag = it.kind().unwrap().tag(); + let var_name = format!("{} {}", tag, it.label()); + let mut buf = var_name; + if let Some(detail) = it.detail() { + let width = label_width.saturating_sub(monospace_width(it.label())); + format_to!(buf, "{:width$} {}", "", detail, width = width); + } + format_to!(buf, "\n"); + buf + }) + .collect() +} + +fn monospace_width(s: &str) -> usize { + s.chars().count() +} + +pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + check_edit_with_config(CompletionConfig::default(), what, ra_fixture_before, ra_fixture_after) +} + +pub(crate) fn check_edit_with_config( + config: CompletionConfig, + what: &str, + ra_fixture_before: &str, + ra_fixture_after: &str, +) { + let ra_fixture_after = trim_indent(ra_fixture_after); + let (analysis, position) = analysis_and_position(ra_fixture_before); + let completions: Vec = + analysis.completions(&config, position).unwrap().unwrap().into(); + let (completion,) = completions + .iter() + .filter(|it| it.lookup() == what) + .collect_tuple() + .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); + let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); + completion.text_edit().apply(&mut actual); + assert_eq_text!(&ra_fixture_after, &actual) +} + +pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { + let (analysis, pos) = analysis_and_position(code); + analysis + .with_db(|db| { + let sema = Semantics::new(db); + let original_file = sema.parse(pos.file_id); + let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); + assert!(check(NodeOrToken::Token(token))); + }) + .unwrap(); +} + +pub(crate) fn get_all_completion_items( + config: CompletionConfig, + code: &str, +) -> Vec { + let (analysis, position) = analysis_and_position(code); + analysis.completions(&config, position).unwrap().unwrap().into() +} -- cgit v1.2.3 From 0e1cda3079eb9936dcf8acec3e47bad48c5ccc58 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 18:06:14 +0200 Subject: Minor --- crates/ide/src/completion/completion_context.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'crates/ide/src/completion') diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 047ecd9d7..09440334d 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs @@ -9,15 +9,21 @@ use syntax::{ SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, }; +use test_utils::mark; use text_edit::Indel; -use super::patterns::{ - has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, - has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, - has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, +use crate::{ + call_info::ActiveParameter, + completion::{ + patterns::{ + has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, + has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, + has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, + }, + CompletionConfig, + }, + FilePosition, }; -use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; -use test_utils::mark; /// `CompletionContext` is created early during completion to figure out, where /// exactly is the cursor, syntax-wise. -- cgit v1.2.3 From 86f89d9b31fc7e4fd9300570e6b6304c749caa6c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 22:41:55 +0200 Subject: Remove Hygiene from completion --- crates/ide/src/completion/complete_keyword.rs | 29 +++---- .../ide/src/completion/complete_qualified_path.rs | 4 +- crates/ide/src/completion/completion_context.rs | 91 +++++++++++++--------- 3 files changed, 65 insertions(+), 59 deletions(-) (limited to 'crates/ide/src/completion') diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs index a80708935..22ada3cf2 100644 --- a/crates/ide/src/completion/complete_keyword.rs +++ b/crates/ide/src/completion/complete_keyword.rs @@ -10,30 +10,21 @@ use crate::completion::{ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { // complete keyword "crate" in use stmt let source_range = ctx.source_range(); - match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { - (Some(_), None) => { + + if ctx.use_item_syntax.is_some() { + if ctx.path_qual.is_none() { CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") .kind(CompletionItemKind::Keyword) .insert_text("crate::") .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "self") - .kind(CompletionItemKind::Keyword) - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "super::") - .kind(CompletionItemKind::Keyword) - .insert_text("super::") - .add_to(acc); - } - (Some(_), Some(_)) => { - CompletionItem::new(CompletionKind::Keyword, source_range, "self") - .kind(CompletionItemKind::Keyword) - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "super::") - .kind(CompletionItemKind::Keyword) - .insert_text("super::") - .add_to(acc); } - _ => {} + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super::") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); } // Suggest .await syntax for types that implement Future trait diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs index cb7dd23c1..74794dc88 100644 --- a/crates/ide/src/completion/complete_qualified_path.rs +++ b/crates/ide/src/completion/complete_qualified_path.rs @@ -8,7 +8,7 @@ use test_utils::mark; use crate::completion::{CompletionContext, Completions}; pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { - let path = match &ctx.path_prefix { + let path = match &ctx.path_qual { Some(path) => path.clone(), None => return, }; @@ -19,7 +19,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon let context_module = ctx.scope.module(); - let resolution = match ctx.scope.resolve_hir_path_qualifier(&path) { + let resolution = match ctx.sema.resolve_path(&path) { Some(res) => res, None => return, }; diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 09440334d..3857dce67 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs @@ -56,7 +56,7 @@ pub(crate) struct CompletionContext<'a> { /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. pub(super) is_trivial_path: bool, /// If not a trivial path, the prefix (qualifier). - pub(super) path_prefix: Option, + pub(super) path_qual: Option, pub(super) after_if: bool, /// `true` if we are a statement or a last expr in the block. pub(super) can_be_stmt: bool, @@ -137,7 +137,7 @@ impl<'a> CompletionContext<'a> { is_param: false, is_pat_binding_or_const: false, is_trivial_path: false, - path_prefix: None, + path_qual: None, after_if: false, can_be_stmt: false, is_expr: false, @@ -385,48 +385,54 @@ impl<'a> CompletionContext<'a> { self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); self.has_type_args = segment.generic_arg_list().is_some(); - let hygiene = hir::Hygiene::new(self.db, self.position.file_id.into()); - if let Some(path) = hir::Path::from_src(path.clone(), &hygiene) { - if let Some(path_prefix) = path.qualifier() { - self.path_prefix = Some(path_prefix); + if let Some(path) = path_or_use_tree_qualifier(&path) { + self.path_qual = path + .segment() + .and_then(|it| { + find_node_with_range::( + original_file, + it.syntax().text_range(), + ) + }) + .map(|it| it.parent_path()); + return; + } + + if let Some(segment) = path.segment() { + if segment.coloncolon_token().is_some() { return; } } - if path.qualifier().is_none() { - self.is_trivial_path = true; - - // Find either enclosing expr statement (thing with `;`) or a - // block. If block, check that we are the last expr. - self.can_be_stmt = name_ref - .syntax() - .ancestors() - .find_map(|node| { - if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { - return Some( - stmt.syntax().text_range() == name_ref.syntax().text_range(), - ); - } - if let Some(block) = ast::BlockExpr::cast(node) { - return Some( - block.expr().map(|e| e.syntax().text_range()) - == Some(name_ref.syntax().text_range()), - ); - } - None - }) - .unwrap_or(false); - self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); + self.is_trivial_path = true; - if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { - if let Some(if_expr) = - self.sema.find_node_at_offset_with_macros::(original_file, off) + // Find either enclosing expr statement (thing with `;`) or a + // block. If block, check that we are the last expr. + self.can_be_stmt = name_ref + .syntax() + .ancestors() + .find_map(|node| { + if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { + return Some(stmt.syntax().text_range() == name_ref.syntax().text_range()); + } + if let Some(block) = ast::BlockExpr::cast(node) { + return Some( + block.expr().map(|e| e.syntax().text_range()) + == Some(name_ref.syntax().text_range()), + ); + } + None + }) + .unwrap_or(false); + self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); + + if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { + if let Some(if_expr) = + self.sema.find_node_at_offset_with_macros::(original_file, off) + { + if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() { - if if_expr.syntax().text_range().end() - < name_ref.syntax().text_range().start() - { - self.after_if = true; - } + self.after_if = true; } } } @@ -469,3 +475,12 @@ fn is_node(node: &SyntaxNode) -> bool { Some(n) => n.syntax().text_range() == node.text_range(), } } + +fn path_or_use_tree_qualifier(path: &ast::Path) -> Option { + if let Some(qual) = path.qualifier() { + return Some(qual); + } + let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; + let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; + use_tree.path() +} -- cgit v1.2.3