mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 13:51:31 +00:00
Extract call_info and completion into separate crates
This commit is contained in:
parent
2067a410f3
commit
9e7c952bbd
34 changed files with 336 additions and 226 deletions
657
crates/completion/src/complete_attribute.rs
Normal file
657
crates/completion/src/complete_attribute.rs
Normal 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 = "…"
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
431
crates/completion/src/complete_dot.rs
Normal file
431
crates/completion/src/complete_dot.rs
Normal 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)
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
135
crates/completion/src/complete_fn_param.rs
Normal file
135
crates/completion/src/complete_fn_param.rs
Normal 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
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
566
crates/completion/src/complete_keyword.rs
Normal file
566
crates/completion/src/complete_keyword.rs
Normal 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)
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
41
crates/completion/src/complete_macro_in_item_position.rs
Normal file
41
crates/completion/src/complete_macro_in_item_position.rs
Normal 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
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
324
crates/completion/src/complete_mod.rs
Normal file
324
crates/completion/src/complete_mod.rs
Normal 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#""#]],
|
||||
);
|
||||
}
|
||||
}
|
88
crates/completion/src/complete_pattern.rs
Normal file
88
crates/completion/src/complete_pattern.rs
Normal 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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
452
crates/completion/src/complete_postfix.rs
Normal file
452
crates/completion/src/complete_postfix.rs
Normal 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) }"#,
|
||||
);
|
||||
}
|
||||
}
|
279
crates/completion/src/complete_postfix/format_like.rs
Normal file
279
crates/completion/src/complete_postfix/format_like.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
755
crates/completion/src/complete_qualified_path.rs
Normal file
755
crates/completion/src/complete_qualified_path.rs
Normal 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()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
226
crates/completion/src/complete_record.rs
Normal file
226
crates/completion/src/complete_record.rs
Normal 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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
114
crates/completion/src/complete_snippet.rs
Normal file
114
crates/completion/src/complete_snippet.rs
Normal 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)
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
736
crates/completion/src/complete_trait_impl.rs
Normal file
736
crates/completion/src/complete_trait_impl.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
679
crates/completion/src/complete_unqualified_path.rs
Normal file
679
crates/completion/src/complete_unqualified_path.rs
Normal 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
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
35
crates/completion/src/completion_config.rs
Normal file
35
crates/completion/src/completion_config.rs
Normal 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: () }),
|
||||
}
|
||||
}
|
||||
}
|
520
crates/completion/src/completion_context.rs
Normal file
520
crates/completion/src/completion_context.rs
Normal 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()
|
||||
}
|
384
crates/completion/src/completion_item.rs
Normal file
384
crates/completion/src/completion_item.rs
Normal 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
|
||||
}
|
||||
}
|
4
crates/completion/src/generated_features.rs
Normal file
4
crates/completion/src/generated_features.rs
Normal file
File diff suppressed because one or more lines are too long
264
crates/completion/src/lib.rs
Normal file
264
crates/completion/src/lib.rs
Normal 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",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
249
crates/completion/src/patterns.rs
Normal file
249
crates/completion/src/patterns.rs
Normal 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)
|
||||
}
|
||||
}
|
1341
crates/completion/src/presentation.rs
Normal file
1341
crates/completion/src/presentation.rs
Normal file
File diff suppressed because it is too large
Load diff
130
crates/completion/src/test_utils.rs
Normal file
130
crates/completion/src/test_utils.rs
Normal 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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue