mod completion_item;
mod completion_context;
+mod complete_dot;
mod complete_fn_param;
mod complete_keyword;
mod complete_snippet;
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.
/// 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.
pub(crate) fn completions(
db: &db::RootDatabase,
complete_snippet::complete_item_snippet(&mut acc, &ctx);
complete_path::complete_path(&mut acc, &ctx)?;
complete_scope::complete_scope(&mut acc, &ctx)?;
+ complete_dot::complete_dot(&mut acc, &ctx)?;
Ok(Some(acc))
}
--- /dev/null
+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#""#,
+ );
+ }
+}
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> {
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))
_ => (),
}
+ 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,
}
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()
}
}
}
+ 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;
+ }
}
}
Struct,
Enum,
Binding,
+ Field,
}
#[derive(Debug, PartialEq, Eq)]
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
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VariantData {
}
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] {
module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
function::{Function, FnScopes},
adt::{Struct, Enum},
+ ty::Ty,
};
pub use self::function::FnSignatureInfo;
match receiver_ty {
Ty::Tuple(fields) => {
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, .. } => {
let field_ty = match def_id.resolve(self.db)? {
} else {
Ty::Unknown
}
- },
+ }
ast::Expr::TryExpr(e) => {
let _inner_ty = if let Some(e) = e.expr() {
self.infer_expr(e)?
CompletionItemKind::Struct => Struct,
CompletionItemKind::Enum => Enum,
CompletionItemKind::Binding => Variable,
+ CompletionItemKind::Field => Field,
}
}
}
pub fn expr(self) -> Option<Expr<'a>> {
super::child_opt(self)
}
+
+ pub fn name_ref(self) -> Option<NameRef<'a>> {
+ super::child_opt(self)
+ }
}
// Module
),
"MethodCallExpr": (
traits: ["ArgListOwner"],
- options: [ "Expr" ],
+ options: [ "Expr", "NameRef" ],
),
"IndexExpr": (),
"FieldExpr": (options: ["Expr", "NameRef"]),