Extract call_info and completion into separate crates

This commit is contained in:
Igor Aleksanov 2020-10-18 13:09:00 +03:00
parent 2067a410f3
commit 9e7c952bbd
34 changed files with 336 additions and 226 deletions

View file

@ -0,0 +1,657 @@
//! 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_context::CompletionContext,
completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
generated_features::FEATURES,
};
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
if ctx.mod_declaration_under_caret.is_some() {
return None;
}
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 path.to_string() == "feature" => {
complete_lint(acc, ctx, token_tree, FEATURES)
}
(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, DEFAULT_LINT_COMPLETIONS)
}
(_, 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,
lints_completions: &[LintCompletion],
) {
if let Ok(existing_lints) = parse_comma_sep_input(derive_input) {
for lint_completion in lints_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<FxHashSet<String>, ()> {
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<String> {
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"] },
];
pub(super) struct LintCompletion {
pub(super) label: &'static str,
pub(super) 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_test::{expect, Expect};
use crate::{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 = ""
"#]],
);
}
}

View file

@ -0,0 +1,431 @@
//! Completes references after dot (fields and method calls).
use hir::{HasVisibility, Type};
use rustc_hash::FxHashSet;
use test_utils::mark;
use crate::{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.self_param(ctx.db).is_some()
&& 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_test::{expect, Expect};
use test_utils::mark;
use crate::{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<T> {}
impl A<u32> {
fn the_method(&self) {}
}
impl A<i32> {
fn the_other_method(&self) {}
}
fn foo(a: A<u32>) { 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<T> 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<T> {}
impl<T> HashSet<T> {
pub fn the_method(&self) {}
}
fn foo() {
let s: HashSet<_>;
s.<|>
}
"#,
expect![[r#"
me the_method() pub fn the_method(&self)
"#]],
);
}
#[test]
fn completes_method_call_when_receiver_is_a_macro_call() {
check(
r#"
struct S;
impl S { fn foo(&self) {} }
macro_rules! make_s { () => { S }; }
fn main() { make_s!().f<|>; }
"#,
expect![[r#"
me foo() fn foo(&self)
"#]],
)
}
}

View file

@ -0,0 +1,135 @@
//! See `complete_fn_param`.
use rustc_hash::FxHashMap;
use syntax::{
ast::{self, ModuleItemOwner},
match_ast, AstNode,
};
use crate::{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_test::{expect, Expect};
use crate::{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
"#]],
)
}
}

View file

@ -0,0 +1,566 @@
//! Completes keywords.
use syntax::{ast, SyntaxKind};
use test_utils::mark;
use crate::{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();
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);
}
// 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 | ctx.has_field_list_parent {
add_keyword(ctx, acc, "pub(crate)", "pub(crate) ");
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<CompletionItem> {
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_test::{expect, Expect};
use crate::{
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 pub(crate)
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 pub(crate)
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 crate:main deps:std
use std::future::*;
struct A {}
impl Future for A {}
fn foo(a: A) { a.<|> }
//- /std/lib.rs crate:std
pub mod future {
#[lang = "future_trait"]
pub trait Future {}
}
"#,
expect![[r#"
kw await expr.await
"#]],
);
check(
r#"
//- /main.rs crate:main deps:std
use std::future::*;
fn foo() {
let a = async {};
a.<|>
}
//- /std/lib.rs crate:std
pub mod future {
#[lang = "future_trait"]
pub trait Future {
type Output;
}
}
"#,
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
"#]],
)
}
#[test]
fn before_field() {
check(
r#"
struct Foo {
<|>
pub f: i32,
}
"#,
expect![[r#"
kw pub
kw pub(crate)
"#]],
)
}
}

View file

@ -0,0 +1,41 @@
//! Completes macro invocations used in item position.
use crate::{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_test::{expect, Expect};
use crate::{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
"#]],
)
}
}

View file

@ -0,0 +1,324 @@
//! Completes mod declarations.
use base_db::{SourceDatabaseExt, VfsPath};
use hir::{Module, ModuleSource};
use ide_db::RootDatabase;
use rustc_hash::FxHashSet;
use crate::{CompletionItem, CompletionItemKind};
use super::{
completion_context::CompletionContext, completion_item::CompletionKind,
completion_item::Completions,
};
/// Complete mod declaration, i.e. `mod <|> ;`
pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let mod_under_caret = match &ctx.mod_declaration_under_caret {
Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
Some(mod_under_caret) => mod_under_caret,
None => return None,
};
let _p = profile::span("completion::complete_mod");
let current_module = ctx.scope.module()?;
let module_definition_file =
current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
let directory_to_look_for_submodules = directory_to_look_for_submodules(
current_module,
ctx.db,
source_root.path_for_file(&module_definition_file)?,
)?;
let existing_mod_declarations = current_module
.children(ctx.db)
.filter_map(|module| Some(module.name(ctx.db)?.to_string()))
.collect::<FxHashSet<_>>();
let module_declaration_file =
current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
module_declaration_source_file.file_id.original_file(ctx.db)
});
source_root
.iter()
.filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
.filter(|submodule_candidate_file| {
Some(submodule_candidate_file) != module_declaration_file.as_ref()
})
.filter_map(|submodule_file| {
let submodule_path = source_root.path_for_file(&submodule_file)?;
let directory_with_submodule = submodule_path.parent()?;
match submodule_path.name_and_extension()? {
("lib", Some("rs")) | ("main", Some("rs")) => None,
("mod", Some("rs")) => {
if directory_with_submodule.parent()? == directory_to_look_for_submodules {
match directory_with_submodule.name_and_extension()? {
(directory_name, None) => Some(directory_name.to_owned()),
_ => None,
}
} else {
None
}
}
(file_name, Some("rs"))
if directory_with_submodule == directory_to_look_for_submodules =>
{
Some(file_name.to_owned())
}
_ => None,
}
})
.filter(|name| !existing_mod_declarations.contains(name))
.for_each(|submodule_name| {
let mut label = submodule_name;
if mod_under_caret.semicolon_token().is_none() {
label.push(';')
}
acc.add(
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
.kind(CompletionItemKind::Module),
)
});
Some(())
}
fn directory_to_look_for_submodules(
module: Module,
db: &RootDatabase,
module_file_path: &VfsPath,
) -> Option<VfsPath> {
let directory_with_module_path = module_file_path.parent()?;
let base_directory = match module_file_path.name_and_extension()? {
("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => {
Some(directory_with_module_path)
}
(regular_rust_file_name, Some("rs")) => {
if matches!(
(
directory_with_module_path
.parent()
.as_ref()
.and_then(|path| path.name_and_extension()),
directory_with_module_path.name_and_extension(),
),
(Some(("src", None)), Some(("bin", None)))
) {
// files in /src/bin/ can import each other directly
Some(directory_with_module_path)
} else {
directory_with_module_path.join(regular_rust_file_name)
}
}
_ => None,
}?;
let mut resulting_path = base_directory;
for module in module_chain_to_containing_module_file(module, db) {
if let Some(name) = module.name(db) {
resulting_path = resulting_path.join(&name.to_string())?;
}
}
Some(resulting_path)
}
fn module_chain_to_containing_module_file(
current_module: Module,
db: &RootDatabase,
) -> Vec<Module> {
let mut path = Vec::new();
let mut current_module = Some(current_module);
while let Some(ModuleSource::Module(_)) =
current_module.map(|module| module.definition_source(db).value)
{
if let Some(module) = current_module {
path.insert(0, module);
current_module = module.parent(db);
} else {
current_module = None;
}
}
path
}
#[cfg(test)]
mod tests {
use crate::{test_utils::completion_list, CompletionKind};
use expect_test::{expect, Expect};
fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list(ra_fixture, CompletionKind::Magic);
expect.assert_eq(&actual);
}
#[test]
fn lib_module_completion() {
check(
r#"
//- /lib.rs
mod <|>
//- /foo.rs
fn foo() {}
//- /foo/ignored_foo.rs
fn ignored_foo() {}
//- /bar/mod.rs
fn bar() {}
//- /bar/ignored_bar.rs
fn ignored_bar() {}
"#,
expect![[r#"
md bar;
md foo;
"#]],
);
}
#[test]
fn no_module_completion_with_module_body() {
check(
r#"
//- /lib.rs
mod <|> {
}
//- /foo.rs
fn foo() {}
"#,
expect![[r#""#]],
);
}
#[test]
fn main_module_completion() {
check(
r#"
//- /main.rs
mod <|>
//- /foo.rs
fn foo() {}
//- /foo/ignored_foo.rs
fn ignored_foo() {}
//- /bar/mod.rs
fn bar() {}
//- /bar/ignored_bar.rs
fn ignored_bar() {}
"#,
expect![[r#"
md bar;
md foo;
"#]],
);
}
#[test]
fn main_test_module_completion() {
check(
r#"
//- /main.rs
mod tests {
mod <|>;
}
//- /tests/foo.rs
fn foo() {}
"#,
expect![[r#"
md foo
"#]],
);
}
#[test]
fn directly_nested_module_completion() {
check(
r#"
//- /lib.rs
mod foo;
//- /foo.rs
mod <|>;
//- /foo/bar.rs
fn bar() {}
//- /foo/bar/ignored_bar.rs
fn ignored_bar() {}
//- /foo/baz/mod.rs
fn baz() {}
//- /foo/moar/ignored_moar.rs
fn ignored_moar() {}
"#,
expect![[r#"
md bar
md baz
"#]],
);
}
#[test]
fn nested_in_source_module_completion() {
check(
r#"
//- /lib.rs
mod foo;
//- /foo.rs
mod bar {
mod <|>
}
//- /foo/bar/baz.rs
fn baz() {}
"#,
expect![[r#"
md baz;
"#]],
);
}
// FIXME binary modules are not supported in tests properly
// Binary modules are a bit special, they allow importing the modules from `/src/bin`
// and that's why are good to test two things:
// * no cycles are allowed in mod declarations
// * no modules from the parent directory are proposed
// Unfortunately, binary modules support is in cargo not rustc,
// hence the test does not work now
//
// #[test]
// fn regular_bin_module_completion() {
// check(
// r#"
// //- /src/bin.rs
// fn main() {}
// //- /src/bin/foo.rs
// mod <|>
// //- /src/bin/bar.rs
// fn bar() {}
// //- /src/bin/bar/bar_ignored.rs
// fn bar_ignored() {}
// "#,
// expect![[r#"
// md bar;
// "#]],foo
// );
// }
#[test]
fn already_declared_bin_module_completion_omitted() {
check(
r#"
//- /src/bin.rs crate:main
fn main() {}
//- /src/bin/foo.rs
mod <|>
//- /src/bin/bar.rs
mod foo;
fn bar() {}
//- /src/bin/bar/bar_ignored.rs
fn bar_ignored() {}
"#,
expect![[r#""#]],
);
}
}

View file

@ -0,0 +1,88 @@
//! Completes constats and paths in patterns.
use crate::{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_test::{expect, Expect};
use crate::{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
"#]],
);
}
}

View file

@ -0,0 +1,452 @@
//! Postfix completions, like `Ok(10).ifl<|>` => `if let Ok() = Ok(10) { <|> }`.
mod format_like;
use assists::utils::TryEnum;
use syntax::{
ast::{self, AstNode, AstToken},
TextRange, TextSize,
};
use text_edit::TextEdit;
use self::format_like::add_format_like_completions;
use crate::{
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, "ok", "Ok(expr)", &format!("Ok({})", 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,
"dbgr",
"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);
if let ast::Expr::Literal(literal) = dot_receiver.clone() {
if let Some(literal_text) = ast::String::cast(literal.token()) {
add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
}
}
}
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_test::{expect, Expect};
use crate::{
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 dbgr dbg!(&expr)
sn if if expr {}
sn match match expr {}
sn not !expr
sn ok Ok(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 dbgr dbg!(&expr)
sn match match expr {}
sn ok Ok(expr)
sn ref &expr
sn refm &mut expr
"#]],
)
}
#[test]
fn option_iflet() {
check_edit(
"ifl",
r#"
enum Option<T> { Some(T), None }
fn main() {
let bar = Option::Some(true);
bar.<|>
}
"#,
r#"
enum Option<T> { 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<T, E> { Ok(T), Err(E) }
fn main() {
let bar = Result::Ok(true);
bar.<|>
}
"#,
r#"
enum Result<T, E> { 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 }"#);
}
#[test]
fn postfix_completion_for_format_like_strings() {
check_edit(
"fmt",
r#"fn main() { "{some_var:?}".<|> }"#,
r#"fn main() { format!("{:?}", some_var) }"#,
);
check_edit(
"panic",
r#"fn main() { "Panic with {a}".<|> }"#,
r#"fn main() { panic!("Panic with {}", a) }"#,
);
check_edit(
"println",
r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".<|> }"#,
r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
);
check_edit(
"loge",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::error!("{}", 2+2) }"#,
);
check_edit(
"logt",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::trace!("{}", 2+2) }"#,
);
check_edit(
"logd",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::debug!("{}", 2+2) }"#,
);
check_edit(
"logi",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::info!("{}", 2+2) }"#,
);
check_edit(
"logw",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::warn!("{}", 2+2) }"#,
);
check_edit(
"loge",
r#"fn main() { "{2+2}".<|> }"#,
r#"fn main() { log::error!("{}", 2+2) }"#,
);
}
}

View file

@ -0,0 +1,279 @@
// Feature: Format String Completion.
//
// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
//
// The following postfix snippets are available:
//
// - `format` -> `format!(...)`
// - `panic` -> `panic!(...)`
// - `println` -> `println!(...)`
// - `log`:
// + `logd` -> `log::debug!(...)`
// + `logt` -> `log::trace!(...)`
// + `logi` -> `log::info!(...)`
// + `logw` -> `log::warn!(...)`
// + `loge` -> `log::error!(...)`
use crate::{
complete_postfix::postfix_snippet, completion_config::SnippetCap,
completion_context::CompletionContext, completion_item::Completions,
};
use syntax::ast::{self, AstToken};
/// Mapping ("postfix completion item" => "macro to use")
static KINDS: &[(&str, &str)] = &[
("fmt", "format!"),
("panic", "panic!"),
("println", "println!"),
("eprintln", "eprintln!"),
("logd", "log::debug!"),
("logt", "log::trace!"),
("logi", "log::info!"),
("logw", "log::warn!"),
("loge", "log::error!"),
];
pub(super) fn add_format_like_completions(
acc: &mut Completions,
ctx: &CompletionContext,
dot_receiver: &ast::Expr,
cap: SnippetCap,
receiver_text: &ast::String,
) {
let input = match string_literal_contents(receiver_text) {
// It's not a string literal, do not parse input.
Some(input) => input,
None => return,
};
let mut parser = FormatStrParser::new(input);
if parser.parse().is_ok() {
for (label, macro_name) in KINDS {
let snippet = parser.into_suggestion(macro_name);
postfix_snippet(ctx, cap, &dot_receiver, label, macro_name, &snippet).add_to(acc);
}
}
}
/// Checks whether provided item is a string literal.
fn string_literal_contents(item: &ast::String) -> Option<String> {
let item = item.text();
if item.len() >= 2 && item.starts_with("\"") && item.ends_with("\"") {
return Some(item[1..item.len() - 1].to_owned());
}
None
}
/// Parser for a format-like string. It is more allowing in terms of string contents,
/// as we expect variable placeholders to be filled with expressions.
#[derive(Debug)]
pub struct FormatStrParser {
input: String,
output: String,
extracted_expressions: Vec<String>,
state: State,
parsed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum State {
NotExpr,
MaybeExpr,
Expr,
MaybeIncorrect,
FormatOpts,
}
impl FormatStrParser {
pub fn new(input: String) -> Self {
Self {
input: input.into(),
output: String::new(),
extracted_expressions: Vec::new(),
state: State::NotExpr,
parsed: false,
}
}
pub fn parse(&mut self) -> Result<(), ()> {
let mut current_expr = String::new();
let mut placeholder_id = 1;
// Count of open braces inside of an expression.
// We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
// "{MyStruct { val_a: 0, val_b: 1 }}".
let mut inexpr_open_count = 0;
for chr in self.input.chars() {
match (self.state, chr) {
(State::NotExpr, '{') => {
self.output.push(chr);
self.state = State::MaybeExpr;
}
(State::NotExpr, '}') => {
self.output.push(chr);
self.state = State::MaybeIncorrect;
}
(State::NotExpr, _) => {
self.output.push(chr);
}
(State::MaybeIncorrect, '}') => {
// It's okay, we met "}}".
self.output.push(chr);
self.state = State::NotExpr;
}
(State::MaybeIncorrect, _) => {
// Error in the string.
return Err(());
}
(State::MaybeExpr, '{') => {
self.output.push(chr);
self.state = State::NotExpr;
}
(State::MaybeExpr, '}') => {
// This is an empty sequence '{}'. Replace it with placeholder.
self.output.push(chr);
self.extracted_expressions.push(format!("${}", placeholder_id));
placeholder_id += 1;
self.state = State::NotExpr;
}
(State::MaybeExpr, _) => {
current_expr.push(chr);
self.state = State::Expr;
}
(State::Expr, '}') => {
if inexpr_open_count == 0 {
self.output.push(chr);
self.extracted_expressions.push(current_expr.trim().into());
current_expr = String::new();
self.state = State::NotExpr;
} else {
// We're closing one brace met before inside of the expression.
current_expr.push(chr);
inexpr_open_count -= 1;
}
}
(State::Expr, ':') => {
if inexpr_open_count == 0 {
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
self.output.push(chr);
self.extracted_expressions.push(current_expr.trim().into());
current_expr = String::new();
self.state = State::FormatOpts;
} else {
// We're inside of braced expression, assume that it's a struct field name/value delimeter.
current_expr.push(chr);
}
}
(State::Expr, '{') => {
current_expr.push(chr);
inexpr_open_count += 1;
}
(State::Expr, _) => {
current_expr.push(chr);
}
(State::FormatOpts, '}') => {
self.output.push(chr);
self.state = State::NotExpr;
}
(State::FormatOpts, _) => {
self.output.push(chr);
}
}
}
if self.state != State::NotExpr {
return Err(());
}
self.parsed = true;
Ok(())
}
pub fn into_suggestion(&self, macro_name: &str) -> String {
assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
let expressions_as_string = self.extracted_expressions.join(", ");
format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
}
}
#[cfg(test)]
mod tests {
use super::*;
use expect_test::{expect, Expect};
fn check(input: &str, expect: &Expect) {
let mut parser = FormatStrParser::new((*input).to_owned());
let outcome_repr = if parser.parse().is_ok() {
// Parsing should be OK, expected repr is "string; expr_1, expr_2".
if parser.extracted_expressions.is_empty() {
parser.output
} else {
format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
}
} else {
// Parsing should fail, expected repr is "-".
"-".to_owned()
};
expect.assert_eq(&outcome_repr);
}
#[test]
fn format_str_parser() {
let test_vector = &[
("no expressions", expect![["no expressions"]]),
("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
("{expr:?}", expect![["{:?}; expr"]]),
("{malformed", expect![["-"]]),
("malformed}", expect![["-"]]),
("{{correct", expect![["{{correct"]]),
("correct}}", expect![["correct}}"]]),
("{correct}}}", expect![["{}}}; correct"]]),
("{correct}}}}}", expect![["{}}}}}; correct"]]),
("{incorrect}}", expect![["-"]]),
("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
(
"{SomeStruct { val_a: 0, val_b: 1 }}",
expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
),
("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
(
"{SomeStruct { val_a: 0, val_b: 1 }:?}",
expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
),
("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
];
for (input, output) in test_vector {
check(input, output)
}
}
#[test]
fn test_into_suggestion() {
let test_vector = &[
("println!", "{}", r#"println!("{}", $1)"#),
("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
(
"log::info!",
"{} {expr} {} {2 + 2}",
r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
),
("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
];
for (kind, input, output) in test_vector {
let mut parser = FormatStrParser::new((*input).to_owned());
parser.parse().expect("Parsing must succeed");
assert_eq!(&parser.into_suggestion(*kind), output);
}
}
}

View file

@ -0,0 +1,755 @@
//! 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::{CompletionContext, Completions};
pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
let path = match &ctx.path_qual {
Some(path) => path.clone(),
None => return,
};
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
return;
}
let context_module = ctx.scope.module();
let resolution = match ctx.sema.resolve_path(&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 `<Ty as Trait>::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_test::{expect, Expect};
use test_utils::mark;
use crate::{
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 crate:main deps:foo
use foo::<|>;
//- /foo/lib.rs crate:foo
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 _ = <S as Trait>::<|> }
"#,
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: Sub>() { 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>(T);
impl<T> Super for Wrap<T> {}
impl<T> Sub for Wrap<T> {
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<K, V, S = RandomState> {}
impl<K, V> HashMap<K, V, RandomState> {
pub fn new() -> HashMap<K, V, RandomState> { }
}
fn foo() {
HashMap::<|>
}
"#,
expect![[r#"
fn new() pub fn new() -> HashMap<K, V, RandomState>
"#]],
);
}
#[test]
fn dont_complete_attr() {
check(
r#"
mod foo { pub struct Foo; }
#[foo::<|>]
fn f() {}
"#,
expect![[""]],
);
}
#[test]
fn completes_function() {
check(
r#"
fn foo(
a: i32,
b: i32
) {
}
fn main() {
fo<|>
}
"#,
expect![[r#"
fn foo() fn foo(a: i32, b: i32)
fn main() fn main()
"#]],
);
}
}

View file

@ -0,0 +1,226 @@
//! Complete fields in record literals and patterns.
use crate::{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_test::{expect, Expect};
use crate::{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<T> { a: T }
fn foo() {
let _: A<u32> = 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
"#]],
);
}
}

View file

@ -0,0 +1,114 @@
//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
use crate::{
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);
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use crate::{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 tfn (Test function)
sn tmod (Test module)
"#]],
)
}
}

View file

@ -0,0 +1,736 @@
//! 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, HasAttrs, HasSource};
use syntax::{
ast::{self, edit, Impl},
display::function_declaration,
AstNode, SyntaxKind, SyntaxNode, TextRange, T,
};
use text_edit::TextEdit;
use crate::{
CompletionContext,
CompletionItem,
CompletionItemKind,
CompletionKind,
Completions,
// display::function_declaration,
};
#[derive(Debug, PartialEq, Eq)]
enum ImplCompletionKind {
All,
Fn,
TypeAlias,
Const,
}
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
if let Some((kind, trigger, impl_def)) = completion_match(ctx) {
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item {
hir::AssocItem::Function(fn_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn =>
{
add_function_impl(&trigger, acc, ctx, fn_item)
}
hir::AssocItem::TypeAlias(type_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias =>
{
add_type_alias_impl(&trigger, acc, ctx, type_item)
}
hir::AssocItem::Const(const_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const =>
{
add_const_impl(&trigger, acc, ctx, const_item)
}
_ => {}
});
}
}
fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, SyntaxNode, Impl)> {
let mut token = ctx.token.clone();
// For keywork without name like `impl .. { fn <|> }`, the current position is inside
// the whitespace token, which is outside `FN` syntax node.
// We need to follow the previous token in this case.
if token.kind() == SyntaxKind::WHITESPACE {
token = token.prev_token()?;
}
let impl_item_offset = match token.kind() {
// `impl .. { const <|> }`
// ERROR 0
// CONST_KW <- *
SyntaxKind::CONST_KW => 0,
// `impl .. { fn/type <|> }`
// FN/TYPE_ALIAS 0
// FN_KW <- *
SyntaxKind::FN_KW | SyntaxKind::TYPE_KW => 0,
// `impl .. { fn/type/const foo<|> }`
// FN/TYPE_ALIAS/CONST 1
// NAME 0
// IDENT <- *
SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME => 1,
// `impl .. { foo<|> }`
// MACRO_CALL 3
// PATH 2
// PATH_SEGMENT 1
// NAME_REF 0
// IDENT <- *
SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME_REF => 3,
_ => return None,
};
let impl_item = token.ancestors().nth(impl_item_offset)?;
// Must directly belong to an impl block.
// IMPL
// ASSOC_ITEM_LIST
// <item>
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
let kind = match impl_item.kind() {
// `impl ... { const <|> fn/type/const }`
_ if token.kind() == SyntaxKind::CONST_KW => ImplCompletionKind::Const,
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
SyntaxKind::FN => ImplCompletionKind::Fn,
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
_ => return None,
};
Some((kind, impl_item, 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.self_param(ctx.db).is_some() {
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_test::{expect, Expect};
use crate::{
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_completion_inside_fn() {
check(
r"
trait Test { fn test(); fn test2(); }
struct T;
impl Test for T {
fn test() {
t<|>
}
}
",
expect![[""]],
);
check(
r"
trait Test { fn test(); fn test2(); }
struct T;
impl Test for T {
fn test() {
fn t<|>
}
}
",
expect![[""]],
);
check(
r"
trait Test { fn test(); fn test2(); }
struct T;
impl Test for T {
fn test() {
fn <|>
}
}
",
expect![[""]],
);
// https://github.com/rust-analyzer/rust-analyzer/pull/5976#issuecomment-692332191
check(
r"
trait Test { fn test(); fn test2(); }
struct T;
impl Test for T {
fn test() {
foo.<|>
}
}
",
expect![[""]],
);
check(
r"
trait Test { fn test(_: i32); fn test2(); }
struct T;
impl Test for T {
fn test(t<|>)
}
",
expect![[""]],
);
check(
r"
trait Test { fn test(_: fn()); fn test2(); }
struct T;
impl Test for T {
fn test(f: fn <|>)
}
",
expect![[""]],
);
}
#[test]
fn no_completion_inside_const() {
check(
r"
trait Test { const TEST: fn(); const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: fn <|>
}
",
expect![[""]],
);
check(
r"
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: T<|>
}
",
expect![[""]],
);
check(
r"
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: u32 = f<|>
}
",
expect![[""]],
);
check(
r"
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: u32 = {
t<|>
};
}
",
expect![[""]],
);
check(
r"
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: u32 = {
fn <|>
};
}
",
expect![[""]],
);
check(
r"
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
struct T;
impl Test for T {
const TEST: u32 = {
fn t<|>
};
}
",
expect![[""]],
);
}
#[test]
fn no_completion_inside_type() {
check(
r"
trait Test { type Test; type Test2; fn test(); }
struct T;
impl Test for T {
type Test = T<|>;
}
",
expect![[""]],
);
check(
r"
trait Test { type Test; type Test2; fn test(); }
struct T;
impl Test for T {
type Test = fn <|>;
}
",
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<T>();
}
struct T;
impl Test for T {
fn f<|>
}
"#,
r#"
trait Test {
fn foo<T>();
}
struct T;
impl Test for T {
fn foo<T>() {
$0
}
}
"#,
);
check_edit(
"foo",
r#"
trait Test {
fn foo<T>() where T: Into<String>;
}
struct T;
impl Test for T {
fn f<|>
}
"#,
r#"
trait Test {
fn foo<T>() where T: Into<String>;
}
struct T;
impl Test for T {
fn foo<T>()
where T: Into<String> {
$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\
}
",
);
}
#[test]
fn complete_without_name() {
let test = |completion: &str, hint: &str, completed: &str, next_sibling: &str| {
println!(
"completion='{}', hint='{}', next_sibling='{}'",
completion, hint, next_sibling
);
check_edit(
completion,
&format!(
r#"
trait Test {{
type Foo;
const CONST: u16;
fn bar();
}}
struct T;
impl Test for T {{
{}
{}
}}
"#,
hint, next_sibling
),
&format!(
r#"
trait Test {{
type Foo;
const CONST: u16;
fn bar();
}}
struct T;
impl Test for T {{
{}
{}
}}
"#,
completed, next_sibling
),
)
};
// Enumerate some possible next siblings.
for next_sibling in &[
"",
"fn other_fn() {}", // `const <|> fn` -> `const fn`
"type OtherType = i32;",
"const OTHER_CONST: i32 = 0;",
"async fn other_fn() {}",
"unsafe fn other_fn() {}",
"default fn other_fn() {}",
"default type OtherType = i32;",
"default const OTHER_CONST: i32 = 0;",
] {
test("bar", "fn <|>", "fn bar() {\n $0\n}", next_sibling);
test("Foo", "type <|>", "type Foo = ", next_sibling);
test("CONST", "const <|>", "const CONST: u16 = ", next_sibling);
}
}
}

View file

@ -0,0 +1,679 @@
//! 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::{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()
|| ctx.mod_declaration_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_test::{expect, Expect};
use test_utils::mark;
use crate::{
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<Enum>) {
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<Enum>) {
match x {
None => (),
Some(ref en<|>) => (),
}
}
"#,
expect![[""]],
);
}
#[test]
fn bind_pat_and_path() {
check(
r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
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<T>() { <|> }"#,
expect![[r#"
tp T
fn quux() fn quux<T>()
"#]],
);
}
#[test]
fn completes_generic_params_in_struct() {
check(
r#"struct S<T> { 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()
"#]],
);
}
/// Regression test for issue #6091.
#[test]
fn correctly_completes_module_items_prefixed_with_underscore() {
check_edit(
"_alpha",
r#"
fn main() {
_<|>
}
fn _alpha() {}
"#,
r#"
fn main() {
_alpha()$0
}
fn _alpha() {}
"#,
)
}
#[test]
fn completes_extern_prelude() {
check(
r#"
//- /lib.rs crate:main deps:other_crate
use <|>;
//- /other_crate/lib.rs crate:other_crate
// 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 crate:main deps:std
fn foo() { let x: <|> }
//- /std/lib.rs crate:std
#[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 crate:main deps:core,std
fn foo() { let x: <|> }
//- /core/lib.rs crate:core
#[prelude_import]
use prelude::*;
mod prelude { struct Option; }
//- /std/lib.rs crate:std deps:core
#[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
"#]],
)
}
}

View file

@ -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<SnippetCap>,
}
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: () }),
}
}
}

View file

@ -0,0 +1,520 @@
//! See `CompletionContext` structure.
use base_db::{FilePosition, SourceDatabase};
use call_info::ActiveParameter;
use hir::{Local, ScopeDef, 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 test_utils::mark;
use text_edit::Indel;
use crate::{
patterns::{
fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
has_field_list_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, inside_impl_trait_block, is_in_loop_body, is_match_arm,
unsafe_is_prev,
},
CompletionConfig,
};
/// `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<hir::Crate>,
pub(super) expected_type: Option<Type>,
pub(super) name_ref_syntax: Option<ast::NameRef>,
pub(super) function_syntax: Option<ast::Fn>,
pub(super) use_item_syntax: Option<ast::Use>,
pub(super) record_lit_syntax: Option<ast::RecordExpr>,
pub(super) record_pat_syntax: Option<ast::RecordPat>,
pub(super) record_field_syntax: Option<ast::RecordExprField>,
pub(super) impl_def: Option<ast::Impl>,
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
pub(super) active_parameter: Option<ActiveParameter>,
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_qual: Option<ast::Path>,
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<ast::Expr>,
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<ast::Attr>,
pub(super) mod_declaration_under_caret: Option<ast::Module>,
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) inside_impl_trait_block: bool,
pub(super) has_field_list_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,
pub(super) for_is_prev2: bool,
pub(super) fn_is_prev: bool,
pub(super) locals: Vec<(String, Local)>,
}
impl<'a> CompletionContext<'a> {
pub(super) fn new(
db: &'a RootDatabase,
position: FilePosition,
config: &'a CompletionConfig,
) -> Option<CompletionContext<'a>> {
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 locals = vec![];
scope.process_all_names(&mut |name, scope| {
if let ScopeDef::Local(local) = scope {
locals.push((name.to_string(), local));
}
});
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_qual: 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,
mod_declaration_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,
inside_impl_trait_block: false,
has_field_list_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,
for_is_prev2: false,
fn_is_prev: false,
locals,
};
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::<ast::MacroCall>(&original_file, offset),
find_node_at_offset::<ast::MacroCall>(&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.speculative_expand(
&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)
}
/// Checks whether completions in that particular case don't make much sense.
/// Examples:
/// - `fn <|>` -- we expect function name, it's unlikely that "hint" will be helpful.
/// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
/// - `for _ i<|>` -- obviously, it'll be "in" keyword.
pub(crate) fn no_completion_required(&self) -> bool {
(self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2
}
/// 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
let kind = self.token.kind();
if kind == IDENT || kind == UNDERSCORE || 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.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
self.has_field_list_parent = has_field_list_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.clone());
self.mod_declaration_under_caret =
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
.filter(|module| module.item_list().is_none());
self.for_is_prev2 = for_is_prev2(syntax_element.clone());
self.fn_is_prev = fn_is_prev(syntax_element.clone());
}
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::<ast::NameRef>(&file_with_fake_ident, offset) {
// Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
// See RFC#1685.
if is_node::<ast::Param>(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::<ast::Name>(&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::<ast::Param>(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();
if let Some(path) = path_or_use_tree_qualifier(&path) {
self.path_qual = path
.segment()
.and_then(|it| {
find_node_with_range::<ast::PathSegment>(
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;
}
}
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::<ast::IfExpr>(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
.receiver()
.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<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
find_covering_element(syntax, range).ancestors().find_map(N::cast)
}
fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
match node.ancestors().find_map(N::cast) {
None => false,
Some(n) => n.syntax().text_range() == node.text_range(),
}
}
fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> {
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()
}

View file

@ -0,0 +1,384 @@
//! See `CompletionItem` structure.
use std::fmt;
use hir::Documentation;
use syntax::TextRange;
use text_edit::TextEdit;
use crate::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<CompletionItemKind>,
/// 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<String>,
/// Additional info to show in the UI pop up.
detail: Option<String>,
documentation: Option<Documentation>,
/// 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<CompletionScore>,
}
// 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<String>,
) -> 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<Documentation> {
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<CompletionItemKind> {
self.kind
}
pub fn deprecated(&self) -> bool {
self.deprecated
}
pub fn score(&self) -> Option<CompletionScore> {
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<String>,
insert_text_format: InsertTextFormat,
detail: Option<String>,
documentation: Option<Documentation>,
lookup: Option<String>,
kind: Option<CompletionItemKind>,
text_edit: Option<TextEdit>,
deprecated: Option<bool>,
trigger_call_info: Option<bool>,
score: Option<CompletionScore>,
}
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<String>) -> Builder {
self.lookup = Some(lookup.into());
self
}
pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
self.label = label.into();
self
}
pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
self.insert_text = Some(insert_text.into());
self
}
pub(crate) fn insert_snippet(
mut self,
_cap: SnippetCap,
snippet: impl Into<String>,
) -> 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<String>) -> Builder {
self.set_detail(Some(detail))
}
pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> 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<Documentation>) -> 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<CompletionItem> for Builder {
fn into(self) -> CompletionItem {
self.build()
}
}
/// Represents an in-progress set of completions being built.
#[derive(Debug, Default)]
pub struct Completions {
buf: Vec<CompletionItem>,
}
impl Completions {
pub fn add(&mut self, item: impl Into<CompletionItem>) {
self.buf.push(item.into())
}
pub fn add_all<I>(&mut self, items: I)
where
I: IntoIterator,
I::Item: Into<CompletionItem>,
{
items.into_iter().for_each(|item| self.add(item.into()))
}
}
impl Into<Vec<CompletionItem>> for Completions {
fn into(self) -> Vec<CompletionItem> {
self.buf
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,264 @@
//! `completions` crate provides utilities for generating completions of user input.
mod completion_config;
mod completion_item;
mod completion_context;
mod presentation;
mod patterns;
mod generated_features;
#[cfg(test)]
mod test_utils;
mod complete_attribute;
mod complete_dot;
mod complete_record;
mod complete_pattern;
mod complete_fn_param;
mod complete_keyword;
mod complete_snippet;
mod complete_qualified_path;
mod complete_unqualified_path;
mod complete_postfix;
mod complete_macro_in_item_position;
mod complete_trait_impl;
mod complete_mod;
use base_db::FilePosition;
use ide_db::RootDatabase;
use crate::{
completion_context::CompletionContext,
completion_item::{CompletionKind, Completions},
};
pub use crate::{
completion_config::CompletionConfig,
completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
};
//FIXME: split the following feature into fine-grained features.
// Feature: Magic Completions
//
// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
// completions as well:
//
// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
// is placed at the appropriate position. Even though `if` is easy to type, you
// still want to complete it, to get ` { }` for free! `return` is inserted with a
// space or `;` depending on the return type of the function.
//
// When completing a function call, `()` are automatically inserted. If a function
// takes arguments, the cursor is positioned inside the parenthesis.
//
// There are postfix completions, which can be triggered by typing something like
// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
//
// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
// - `expr.match` -> `match expr {}`
// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
// - `expr.ref` -> `&expr`
// - `expr.refm` -> `&mut expr`
// - `expr.not` -> `!expr`
// - `expr.dbg` -> `dbg!(expr)`
// - `expr.dbgr` -> `dbg!(&expr)`
// - `expr.call` -> `(expr)`
//
// There also snippet completions:
//
// .Expressions
// - `pd` -> `eprintln!(" = {:?}", );`
// - `ppd` -> `eprintln!(" = {:#?}", );`
//
// .Items
// - `tfn` -> `#[test] fn feature(){}`
// - `tmod` ->
// ```rust
// #[cfg(test)]
// mod tests {
// use super::*;
//
// #[test]
// fn test_name() {}
// }
// ```
/// Main entry point for completion. We run completion as a two-phase process.
///
/// First, we look at the position and collect a so-called `CompletionContext.
/// This is a somewhat messy process, because, during completion, syntax tree is
/// incomplete and can look really weird.
///
/// Once the context is collected, we run a series of completion routines which
/// look at the context and produce completion items. One subtlety about this
/// phase is that completion engine should not filter by the substring which is
/// already present, it should give all possible variants for the identifier at
/// the caret. In other words, for
///
/// ```no_run
/// fn f() {
/// let foo = 92;
/// let _ = bar<|>
/// }
/// ```
///
/// `foo` *should* be present among the completion variants. Filtering by
/// identifier prefix/fuzzy match should be done higher in the stack, together
/// with ordering of completions (currently this is done by the client).
pub fn completions(
db: &RootDatabase,
config: &CompletionConfig,
position: FilePosition,
) -> Option<Completions> {
let ctx = CompletionContext::new(db, position, config)?;
if ctx.no_completion_required() {
// No work required here.
return None;
}
let mut acc = Completions::default();
complete_attribute::complete_attribute(&mut acc, &ctx);
complete_fn_param::complete_fn_param(&mut acc, &ctx);
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
complete_snippet::complete_item_snippet(&mut acc, &ctx);
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
complete_dot::complete_dot(&mut acc, &ctx);
complete_record::complete_record(&mut acc, &ctx);
complete_pattern::complete_pattern(&mut acc, &ctx);
complete_postfix::complete_postfix(&mut acc, &ctx);
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
complete_mod::complete_mod(&mut acc, &ctx);
Some(acc)
}
#[cfg(test)]
mod tests {
use crate::completion_config::CompletionConfig;
use crate::test_utils;
struct DetailAndDocumentation<'a> {
detail: &'a str,
documentation: &'a str,
}
fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
let (db, position) = test_utils::position(ra_fixture);
let config = CompletionConfig::default();
let completions: Vec<_> = crate::completions(&db, &config, position).unwrap().into();
for item in completions {
if item.detail() == Some(expected.detail) {
let opt = item.documentation();
let doc = opt.as_ref().map(|it| it.as_str());
assert_eq!(doc, Some(expected.documentation));
return;
}
}
panic!("completion detail not found: {}", expected.detail)
}
fn check_no_completion(ra_fixture: &str) {
let (db, position) = test_utils::position(ra_fixture);
let config = CompletionConfig::default();
let completions: Option<Vec<String>> = crate::completions(&db, &config, position)
.and_then(|completions| {
let completions: Vec<_> = completions.into();
if completions.is_empty() {
None
} else {
Some(completions)
}
})
.map(|completions| {
completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
});
// `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
assert_eq!(completions, None, "Completions were generated, but weren't expected");
}
#[test]
fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
check_detail_and_documentation(
r#"
//- /lib.rs
macro_rules! bar {
() => {
struct Bar;
impl Bar {
#[doc = "Do the foo"]
fn foo(&self) {}
}
}
}
bar!();
fn foo() {
let bar = Bar;
bar.fo<|>;
}
"#,
DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
);
}
#[test]
fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
check_detail_and_documentation(
r#"
//- /lib.rs
macro_rules! bar {
() => {
struct Bar;
impl Bar {
/// Do the foo
fn foo(&self) {}
}
}
}
bar!();
fn foo() {
let bar = Bar;
bar.fo<|>;
}
"#,
DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
);
}
#[test]
fn test_no_completions_required() {
// There must be no hint for 'in' keyword.
check_no_completion(
r#"
fn foo() {
for i i<|>
}
"#,
);
// After 'in' keyword hints may be spawned.
check_detail_and_documentation(
r#"
/// Do the foo
fn foo() -> &'static str { "foo" }
fn bar() {
for c in fo<|>
}
"#,
DetailAndDocumentation {
detail: "fn foo() -> &'static str",
documentation: "Do the foo",
},
);
}
}

View file

@ -0,0 +1,249 @@
//! 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::test_utils::{check_pattern_is_applicable, check_pattern_is_not_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 inside_impl_trait_block(element: SyntaxElement) -> bool {
// Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
// where we only check the first parent with different text range.
element
.ancestors()
.find(|it| it.kind() == IMPL)
.map(|it| ast::Impl::cast(it).unwrap())
.map(|it| it.trait_().is_some())
.unwrap_or(false)
}
#[test]
fn test_inside_impl_trait_block() {
check_pattern_is_applicable(r"impl Foo for Bar { f<|> }", inside_impl_trait_block);
check_pattern_is_applicable(r"impl Foo for Bar { fn f<|> }", inside_impl_trait_block);
check_pattern_is_not_applicable(r"impl A { f<|> }", inside_impl_trait_block);
check_pattern_is_not_applicable(r"impl A { fn f<|> }", inside_impl_trait_block);
}
pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool {
not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some()
}
#[test]
fn test_has_field_list_parent() {
check_pattern_is_applicable(r"struct Foo { f<|> }", has_field_list_parent);
check_pattern_is_applicable(r"struct Foo { f<|> pub f: i32}", has_field_list_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()
}
pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
element
.into_token()
.and_then(|it| previous_non_trivia_token(it))
.filter(|it| it.kind() == FN_KW)
.is_some()
}
#[test]
fn test_fn_is_prev() {
check_pattern_is_applicable(r"fn l<|>", fn_is_prev);
}
/// Check if the token previous to the previous one is `for`.
/// For example, `for _ i<|>` => true.
pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
element
.into_token()
.and_then(|it| previous_non_trivia_token(it))
.and_then(|it| previous_non_trivia_token(it))
.filter(|it| it.kind() == FOR_KW)
.is_some()
}
#[test]
fn test_for_is_prev2() {
check_pattern_is_applicable(r"for i i<|>", for_is_prev2);
}
#[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<SyntaxNode> {
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<SyntaxToken> {
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<SyntaxElement> {
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)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,130 @@
//! Runs completion for testing purposes.
use base_db::{fixture::ChangeFixture, FileLoader, FilePosition};
use hir::Semantics;
use ide_db::RootDatabase;
use itertools::Itertools;
use stdx::{format_to, trim_indent};
use syntax::{AstNode, NodeOrToken, SyntaxElement};
use test_utils::{assert_eq_text, RangeOrOffset};
use crate::{completion_item::CompletionKind, CompletionConfig, CompletionItem};
/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
let change_fixture = ChangeFixture::parse(ra_fixture);
let mut database = RootDatabase::default();
database.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
let offset = match range_or_offset {
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
(database, FilePosition { file_id, offset })
}
pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
do_completion_with_config(CompletionConfig::default(), code, kind)
}
pub(crate) fn do_completion_with_config(
config: CompletionConfig,
code: &str,
kind: CompletionKind,
) -> Vec<CompletionItem> {
let mut kind_completions: Vec<CompletionItem> = 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<CompletionItem> = 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 (db, position) = position(ra_fixture_before);
let completions: Vec<CompletionItem> =
crate::completions(&db, &config, position).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 = db.file_text(position.file_id).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 (db, pos) = position(code);
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)));
}
pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
let (db, pos) = position(code);
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)));
}
pub(crate) fn get_all_completion_items(
config: CompletionConfig,
code: &str,
) -> Vec<CompletionItem> {
let (db, position) = position(code);
crate::completions(&db, &config, position).unwrap().into()
}