Implement basic completion for fields
This commit is contained in:
parent
0d724ea572
commit
ab0b63992b
@ -1,6 +1,7 @@
|
|||||||
mod completion_item;
|
mod completion_item;
|
||||||
mod completion_context;
|
mod completion_context;
|
||||||
|
|
||||||
|
mod complete_dot;
|
||||||
mod complete_fn_param;
|
mod complete_fn_param;
|
||||||
mod complete_keyword;
|
mod complete_keyword;
|
||||||
mod complete_snippet;
|
mod complete_snippet;
|
||||||
@ -20,13 +21,13 @@ use crate::{
|
|||||||
|
|
||||||
pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind};
|
pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind};
|
||||||
|
|
||||||
/// Main entry point for copmletion. We run comletion as a two-phase process.
|
/// 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.
|
/// First, we look at the position and collect a so-called `CompletionContext.
|
||||||
/// This is a somewhat messy process, because, during completion, syntax tree is
|
/// This is a somewhat messy process, because, during completion, syntax tree is
|
||||||
/// incomplete and can look readlly weired.
|
/// incomplete and can look really weird.
|
||||||
///
|
///
|
||||||
/// Once the context is collected, we run a series of completion routines whihc
|
/// Once the context is collected, we run a series of completion routines which
|
||||||
/// look at the context and produce completion items.
|
/// look at the context and produce completion items.
|
||||||
pub(crate) fn completions(
|
pub(crate) fn completions(
|
||||||
db: &db::RootDatabase,
|
db: &db::RootDatabase,
|
||||||
@ -43,6 +44,7 @@ pub(crate) fn completions(
|
|||||||
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
||||||
complete_path::complete_path(&mut acc, &ctx)?;
|
complete_path::complete_path(&mut acc, &ctx)?;
|
||||||
complete_scope::complete_scope(&mut acc, &ctx)?;
|
complete_scope::complete_scope(&mut acc, &ctx)?;
|
||||||
|
complete_dot::complete_dot(&mut acc, &ctx)?;
|
||||||
|
|
||||||
Ok(Some(acc))
|
Ok(Some(acc))
|
||||||
}
|
}
|
||||||
|
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,
|
pub(super) is_stmt: bool,
|
||||||
/// Something is typed at the "top" level, in module or impl/trait.
|
/// Something is typed at the "top" level, in module or impl/trait.
|
||||||
pub(super) is_new_item: bool,
|
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> {
|
impl<'a> CompletionContext<'a> {
|
||||||
@ -54,6 +58,8 @@ impl<'a> CompletionContext<'a> {
|
|||||||
after_if: false,
|
after_if: false,
|
||||||
is_stmt: false,
|
is_stmt: false,
|
||||||
is_new_item: false,
|
is_new_item: false,
|
||||||
|
dot_receiver: None,
|
||||||
|
is_method_call: false,
|
||||||
};
|
};
|
||||||
ctx.fill(original_file, position.offset);
|
ctx.fill(original_file, position.offset);
|
||||||
Ok(Some(ctx))
|
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() {
|
let parent = match name_ref.syntax().parent() {
|
||||||
Some(it) => it,
|
Some(it) => it,
|
||||||
None => return,
|
None => return,
|
||||||
@ -120,11 +132,6 @@ impl<'a> CompletionContext<'a> {
|
|||||||
}
|
}
|
||||||
if path.qualifier().is_none() {
|
if path.qualifier().is_none() {
|
||||||
self.is_trivial_path = true;
|
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
|
self.is_stmt = match name_ref
|
||||||
.syntax()
|
.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,
|
Struct,
|
||||||
Enum,
|
Enum,
|
||||||
Binding,
|
Binding,
|
||||||
|
Field,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -124,6 +124,15 @@ pub struct StructField {
|
|||||||
ty: Ty,
|
ty: Ty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StructField {
|
||||||
|
pub fn name(&self) -> SmolStr {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
pub fn ty(&self) -> Ty {
|
||||||
|
self.ty.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fields of an enum variant or struct
|
/// Fields of an enum variant or struct
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum VariantData {
|
pub enum VariantData {
|
||||||
@ -168,7 +177,10 @@ impl VariantData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_field_ty(&self, field_name: &str) -> Option<Ty> {
|
pub(crate) fn get_field_ty(&self, field_name: &str) -> Option<Ty> {
|
||||||
self.fields().iter().find(|f| f.name == field_name).map(|f| f.ty.clone())
|
self.fields()
|
||||||
|
.iter()
|
||||||
|
.find(|f| f.name == field_name)
|
||||||
|
.map(|f| f.ty.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fields(&self) -> &[StructField] {
|
pub fn fields(&self) -> &[StructField] {
|
||||||
|
@ -44,6 +44,7 @@ pub use self::{
|
|||||||
module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
|
module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
|
||||||
function::{Function, FnScopes},
|
function::{Function, FnScopes},
|
||||||
adt::{Struct, Enum},
|
adt::{Struct, Enum},
|
||||||
|
ty::Ty,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::function::FnSignatureInfo;
|
pub use self::function::FnSignatureInfo;
|
||||||
|
@ -574,7 +574,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
|||||||
match receiver_ty {
|
match receiver_ty {
|
||||||
Ty::Tuple(fields) => {
|
Ty::Tuple(fields) => {
|
||||||
let i = text.parse::<usize>().ok();
|
let i = text.parse::<usize>().ok();
|
||||||
i.and_then(|i| fields.get(i).cloned()).unwrap_or(Ty::Unknown)
|
i.and_then(|i| fields.get(i).cloned())
|
||||||
|
.unwrap_or(Ty::Unknown)
|
||||||
}
|
}
|
||||||
Ty::Adt { def_id, .. } => {
|
Ty::Adt { def_id, .. } => {
|
||||||
let field_ty = match def_id.resolve(self.db)? {
|
let field_ty = match def_id.resolve(self.db)? {
|
||||||
@ -589,7 +590,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
|||||||
} else {
|
} else {
|
||||||
Ty::Unknown
|
Ty::Unknown
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ast::Expr::TryExpr(e) => {
|
ast::Expr::TryExpr(e) => {
|
||||||
let _inner_ty = if let Some(e) = e.expr() {
|
let _inner_ty = if let Some(e) = e.expr() {
|
||||||
self.infer_expr(e)?
|
self.infer_expr(e)?
|
||||||
|
@ -58,6 +58,7 @@ impl Conv for CompletionItemKind {
|
|||||||
CompletionItemKind::Struct => Struct,
|
CompletionItemKind::Struct => Struct,
|
||||||
CompletionItemKind::Enum => Enum,
|
CompletionItemKind::Enum => Enum,
|
||||||
CompletionItemKind::Binding => Variable,
|
CompletionItemKind::Binding => Variable,
|
||||||
|
CompletionItemKind::Field => Field,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2030,6 +2030,10 @@ impl<'a> MethodCallExpr<'a> {
|
|||||||
pub fn expr(self) -> Option<Expr<'a>> {
|
pub fn expr(self) -> Option<Expr<'a>> {
|
||||||
super::child_opt(self)
|
super::child_opt(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name_ref(self) -> Option<NameRef<'a>> {
|
||||||
|
super::child_opt(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module
|
// Module
|
||||||
|
@ -403,7 +403,7 @@ Grammar(
|
|||||||
),
|
),
|
||||||
"MethodCallExpr": (
|
"MethodCallExpr": (
|
||||||
traits: ["ArgListOwner"],
|
traits: ["ArgListOwner"],
|
||||||
options: [ "Expr" ],
|
options: [ "Expr", "NameRef" ],
|
||||||
),
|
),
|
||||||
"IndexExpr": (),
|
"IndexExpr": (),
|
||||||
"FieldExpr": (options: ["Expr", "NameRef"]),
|
"FieldExpr": (options: ["Expr", "NameRef"]),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user