4700: Add top level keywords completion r=matklad a=mcrakhman

This fixes the following issue: https://github.com/rust-analyzer/rust-analyzer/issues/4566. 

Also added simple logic which filters the keywords which can be used with unsafe on the top level.   

Co-authored-by: Mikhail Rakhmanov <rakhmanov.m@gmail.com>
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-06-13 12:02:59 +00:00 committed by GitHub
commit c87c4a0a40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 636 additions and 658 deletions

View File

@ -15,6 +15,7 @@ mod complete_unqualified_path;
mod complete_postfix;
mod complete_macro_in_item_position;
mod complete_trait_impl;
mod patterns;
#[cfg(test)]
mod test_utils;

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,17 @@ use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase;
use ra_syntax::{
algo::{find_covering_element, find_node_at_offset},
ast, match_ast, AstNode,
ast, match_ast, AstNode, NodeOrToken,
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize,
};
use ra_text_edit::Indel;
use super::patterns::{
has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent,
has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev,
};
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
use test_utils::mark;
@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> {
pub(super) is_path_type: bool,
pub(super) has_type_args: bool,
pub(super) attribute_under_caret: Option<ast::Attr>,
pub(super) unsafe_is_prev: bool,
pub(super) if_is_prev: bool,
pub(super) block_expr_parent: bool,
pub(super) bind_pat_parent: bool,
pub(super) ref_pat_parent: bool,
pub(super) in_loop_body: bool,
pub(super) has_trait_parent: bool,
pub(super) has_impl_parent: bool,
pub(super) trait_as_prev_sibling: bool,
pub(super) impl_as_prev_sibling: bool,
pub(super) is_match_arm: bool,
pub(super) has_item_list_or_source_file_parent: bool,
}
impl<'a> CompletionContext<'a> {
@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> {
has_type_args: false,
dot_receiver_is_ambiguous_float_literal: false,
attribute_under_caret: None,
unsafe_is_prev: false,
in_loop_body: false,
ref_pat_parent: false,
bind_pat_parent: false,
block_expr_parent: false,
has_trait_parent: false,
has_impl_parent: false,
trait_as_prev_sibling: false,
impl_as_prev_sibling: false,
if_is_prev: false,
is_match_arm: false,
has_item_list_or_source_file_parent: false,
};
let mut original_file = original_file.syntax().clone();
@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> {
break;
}
}
ctx.fill_keyword_patterns(&hypothetical_file, offset);
ctx.fill(&original_file, hypothetical_file, offset);
Some(ctx)
}
@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> {
self.sema.scope_at_offset(&self.token.parent(), self.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.clone());
self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
self.if_is_prev = if_is_prev(syntax_element.clone());
self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
self.ref_pat_parent = has_ref_parent(syntax_element.clone());
self.in_loop_body = is_in_loop_body(syntax_element.clone());
self.has_trait_parent = has_trait_parent(syntax_element.clone());
self.has_impl_parent = has_impl_parent(syntax_element.clone());
self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
self.is_match_arm = is_match_arm(syntax_element.clone());
self.has_item_list_or_source_file_parent =
has_item_list_or_source_file_parent(syntax_element.clone());
}
fn fill(
&mut self,
original_file: &SyntaxNode,

View File

@ -125,6 +125,32 @@ pub enum CompletionItemKind {
Attribute,
}
impl CompletionItemKind {
#[cfg(test)]
pub(crate) fn tag(&self) -> &'static str {
match self {
CompletionItemKind::Snippet => "sn",
CompletionItemKind::Keyword => "kw",
CompletionItemKind::Module => "md",
CompletionItemKind::Function => "fn",
CompletionItemKind::BuiltinType => "bt",
CompletionItemKind::Struct => "st",
CompletionItemKind::Enum => "en",
CompletionItemKind::EnumVariant => "ev",
CompletionItemKind::Binding => "bn",
CompletionItemKind::Field => "fd",
CompletionItemKind::Static => "sc",
CompletionItemKind::Const => "ct",
CompletionItemKind::Trait => "tt",
CompletionItemKind::TypeAlias => "ta",
CompletionItemKind::Method => "me",
CompletionItemKind::TypeParam => "tp",
CompletionItemKind::Macro => "ma",
CompletionItemKind::Attribute => "at",
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum CompletionKind {
/// Parser-based keyword completion.

View File

@ -0,0 +1,194 @@
//! Patterns telling us certain facts about current syntax element, they are used in completion context
use ra_syntax::{
algo::non_trivia_sibling,
ast::{self, LoopBodyOwner},
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
SyntaxKind::*,
SyntaxNode, SyntaxToken,
};
#[cfg(test)]
use crate::completion::test_utils::check_pattern_is_applicable;
pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
not_same_range_ancestor(element)
.filter(|it| it.kind() == ITEM_LIST)
.and_then(|it| it.parent())
.filter(|it| it.kind() == TRAIT_DEF)
.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() == ITEM_LIST)
.and_then(|it| it.parent())
.filter(|it| it.kind() == IMPL_DEF)
.is_some()
}
#[test]
fn test_has_impl_parent() {
check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
}
pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
}
#[test]
fn test_has_block_expr_parent() {
check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent);
}
pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
element.ancestors().find(|it| it.kind() == BIND_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"impl { f<|> }", has_item_list_or_source_file_parent);
}
pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
&& previous_sibling_or_ancestor_sibling(element)
.and_then(|it| it.into_token())
.filter(|it| it.kind() == FAT_ARROW)
.is_some()
}
#[test]
fn test_is_match_arm() {
check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm);
}
pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
element
.into_token()
.and_then(|it| previous_non_trivia_token(it))
.filter(|it| it.kind() == UNSAFE_KW)
.is_some()
}
#[test]
fn test_unsafe_is_prev() {
check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev);
}
pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
element
.into_token()
.and_then(|it| previous_non_trivia_token(it))
.filter(|it| it.kind() == IF_KW)
.is_some()
}
#[test]
fn test_if_is_prev() {
check_pattern_is_applicable(r"if l<|>", if_is_prev);
}
pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).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_DEF).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_DEF || node.kind() == LAMBDA_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)
}
}

View File

@ -5,25 +5,63 @@ use crate::{
mock_analysis::{analysis_and_position, single_file_with_position},
CompletionItem,
};
use hir::Semantics;
use ra_syntax::{AstNode, NodeOrToken, SyntaxElement};
pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
do_completion_with_options(code, kind, &CompletionConfig::default())
}
pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
completion_list_with_options(code, kind, &CompletionConfig::default())
}
pub(crate) fn do_completion_with_options(
code: &str,
kind: CompletionKind,
options: &CompletionConfig,
) -> Vec<CompletionItem> {
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
.into_iter()
.filter(|c| c.completion_kind == kind)
.collect();
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
kind_completions
}
fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> {
let (analysis, position) = if code.contains("//-") {
analysis_and_position(code)
} else {
single_file_with_position(code)
};
let completions = analysis.completions(options, position).unwrap().unwrap();
let completion_items: Vec<CompletionItem> = completions.into();
let mut kind_completions: Vec<CompletionItem> =
completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
analysis.completions(options, position).unwrap().unwrap().into()
}
pub(crate) fn completion_list_with_options(
code: &str,
kind: CompletionKind,
options: &CompletionConfig,
) -> String {
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
.into_iter()
.filter(|c| c.completion_kind == kind)
.collect();
kind_completions.sort_by_key(|c| c.label().to_owned());
kind_completions
.into_iter()
.map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label()))
.collect()
}
pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
let (analysis, pos) = single_file_with_position(code);
analysis
.with_db(|db| {
let sema = Semantics::new(db);
let original_file = sema.parse(pos.file_id);
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
assert!(check(NodeOrToken::Token(token)));
})
.unwrap();
}