mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 22:01:37 +00:00
Implement basic completion for fields
This commit is contained in:
parent
0d724ea572
commit
ab0b63992b
10 changed files with 156 additions and 12 deletions
98
crates/ra_analysis/src/completion/complete_dot.rs
Normal file
98
crates/ra_analysis/src/completion/complete_dot.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use ra_syntax::ast::AstNode;
|
||||
use hir::{Ty, Def};
|
||||
|
||||
use crate::Cancelable;
|
||||
use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind};
|
||||
|
||||
/// Complete dot accesses, i.e. fields or methods (currently only fields).
|
||||
pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
|
||||
let module = if let Some(module) = &ctx.module {
|
||||
module
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
let function = if let Some(fn_def) = ctx.enclosing_fn {
|
||||
hir::source_binder::function_from_module(ctx.db, module, fn_def)
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
let receiver = if let Some(receiver) = ctx.dot_receiver {
|
||||
receiver
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
let infer_result = function.infer(ctx.db)?;
|
||||
let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) {
|
||||
ty
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
if !ctx.is_method_call {
|
||||
complete_fields(acc, ctx, receiver_ty)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, ty: Ty) -> Cancelable<()> {
|
||||
// TODO: autoderef etc.
|
||||
match ty {
|
||||
Ty::Adt { def_id, .. } => {
|
||||
match def_id.resolve(ctx.db)? {
|
||||
Def::Struct(s) => {
|
||||
let variant_data = s.variant_data(ctx.db)?;
|
||||
for field in variant_data.fields() {
|
||||
CompletionItem::new(CompletionKind::Reference, field.name().to_string())
|
||||
.kind(CompletionItemKind::Field)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
// TODO unions
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ty::Tuple(fields) => {
|
||||
for (i, _ty) in fields.iter().enumerate() {
|
||||
CompletionItem::new(CompletionKind::Reference, i.to_string())
|
||||
.kind(CompletionItemKind::Field)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::completion::*;
|
||||
|
||||
fn check_ref_completion(code: &str, expected_completions: &str) {
|
||||
check_completion(code, expected_completions, CompletionKind::Reference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion() {
|
||||
check_ref_completion(
|
||||
r"
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
a.<|>
|
||||
}
|
||||
",
|
||||
r#"the_field"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_struct_field_completion_for_method_call() {
|
||||
check_ref_completion(
|
||||
r"
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
a.<|>()
|
||||
}
|
||||
",
|
||||
r#""#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,10 @@ pub(super) struct CompletionContext<'a> {
|
|||
pub(super) is_stmt: 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<'a>>,
|
||||
/// If this is a method call in particular, i.e. the () are already there.
|
||||
pub(super) is_method_call: bool,
|
||||
}
|
||||
|
||||
impl<'a> CompletionContext<'a> {
|
||||
|
@ -54,6 +58,8 @@ impl<'a> CompletionContext<'a> {
|
|||
after_if: false,
|
||||
is_stmt: false,
|
||||
is_new_item: false,
|
||||
dot_receiver: None,
|
||||
is_method_call: false,
|
||||
};
|
||||
ctx.fill(original_file, position.offset);
|
||||
Ok(Some(ctx))
|
||||
|
@ -105,6 +111,12 @@ impl<'a> CompletionContext<'a> {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
self.enclosing_fn = self
|
||||
.leaf
|
||||
.ancestors()
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::FnDef::cast);
|
||||
|
||||
let parent = match name_ref.syntax().parent() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
|
@ -120,11 +132,6 @@ impl<'a> CompletionContext<'a> {
|
|||
}
|
||||
if path.qualifier().is_none() {
|
||||
self.is_trivial_path = true;
|
||||
self.enclosing_fn = self
|
||||
.leaf
|
||||
.ancestors()
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::FnDef::cast);
|
||||
|
||||
self.is_stmt = match name_ref
|
||||
.syntax()
|
||||
|
@ -145,6 +152,23 @@ impl<'a> CompletionContext<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(_field_expr) = ast::FieldExpr::cast(parent) {
|
||||
self.dot_receiver = self
|
||||
.leaf
|
||||
.ancestors()
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::FieldExpr::cast)
|
||||
.and_then(ast::FieldExpr::expr);
|
||||
}
|
||||
if let Some(_method_call_expr) = ast::MethodCallExpr::cast(parent) {
|
||||
self.dot_receiver = self
|
||||
.leaf
|
||||
.ancestors()
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::MethodCallExpr::cast)
|
||||
.and_then(ast::MethodCallExpr::expr);
|
||||
self.is_method_call = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ pub enum CompletionItemKind {
|
|||
Struct,
|
||||
Enum,
|
||||
Binding,
|
||||
Field,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue