332: Struct types r=matklad a=flodiebold

Infer types for struct fields, and add basic field completions. There's also some code for enums, but I focused on getting structs working.

There's still ways to go before this becomes useful: There's no autoderef (or even reference types) and no inference for `self`, for example.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2018-12-27 10:08:34 +00:00
commit 1d6dcef5c5
30 changed files with 1519 additions and 319 deletions

View file

@ -1,6 +1,7 @@
mod completion_item;
mod completion_context;
mod complete_dot;
mod complete_fn_param;
mod complete_keyword;
mod complete_snippet;
@ -20,13 +21,13 @@ use crate::{
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,
@ -43,6 +44,7 @@ pub(crate) fn completions(
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))
}

View 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, receiver: Ty) -> Cancelable<()> {
// TODO: autoderef etc.
match receiver {
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#""#,
);
}
}

View file

@ -8,7 +8,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> C
(Some(path), Some(module)) => (path.clone(), module),
_ => return Ok(()),
};
let def_id = match module.resolve_path(ctx.db, path)? {
let def_id = match module.resolve_path(ctx.db, &path)?.take_types() {
Some(it) => it,
None => return Ok(()),
};

View file

@ -1,12 +1,13 @@
use ra_editor::find_node_at_offset;
use ra_text_edit::AtomTextEdit;
use ra_syntax::{
algo::find_leaf_at_offset,
algo::{find_leaf_at_offset, find_covering_node},
ast,
AstNode,
SyntaxNodeRef,
SourceFileNode,
TextUnit,
TextRange,
SyntaxKind::*,
};
use hir::source_binder;
@ -31,6 +32,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,12 +59,14 @@ 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))
}
fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
fn fill(&mut self, original_file: &'a SourceFileNode, offset: TextUnit) {
// 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.
@ -76,7 +83,7 @@ impl<'a> CompletionContext<'a> {
self.is_param = true;
return;
}
self.classify_name_ref(&file, name_ref);
self.classify_name_ref(original_file, name_ref);
}
// Otherwise, see if this is a declaration. We can use heuristics to
@ -88,7 +95,7 @@ impl<'a> CompletionContext<'a> {
}
}
}
fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
fn classify_name_ref(&mut self, original_file: &'a SourceFileNode, name_ref: ast::NameRef) {
let name_range = name_ref.syntax().range();
let top_node = name_ref
.syntax()
@ -105,6 +112,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 +133,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()
@ -137,7 +145,9 @@ impl<'a> CompletionContext<'a> {
};
if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
if let Some(if_expr) =
find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off)
{
if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
self.after_if = true;
}
@ -145,9 +155,33 @@ impl<'a> CompletionContext<'a> {
}
}
}
if let Some(field_expr) = ast::FieldExpr::cast(parent) {
// 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().range())
.and_then(|r| find_node_with_range(original_file.syntax(), r));
}
if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
// As above
self.dot_receiver = method_call_expr
.expr()
.map(|e| e.syntax().range())
.and_then(|r| find_node_with_range(original_file.syntax(), r));
self.is_method_call = true;
}
}
}
fn find_node_with_range<'a, N: AstNode<'a>>(
syntax: SyntaxNodeRef<'a>,
range: TextRange,
) -> Option<N> {
let node = find_covering_node(syntax, range);
node.ancestors().find_map(N::cast)
}
fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
match node.ancestors().filter_map(N::cast).next() {
None => false,

View file

@ -1,5 +1,7 @@
use crate::db;
use hir::PerNs;
/// `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.
@ -25,7 +27,10 @@ pub enum CompletionItemKind {
Keyword,
Module,
Function,
Struct,
Enum,
Binding,
Field,
}
#[derive(Debug, PartialEq, Eq)]
@ -117,16 +122,27 @@ impl Builder {
db: &db::RootDatabase,
resolution: &hir::Resolution,
) -> Builder {
if let Some(def_id) = resolution.def_id {
if let Ok(def) = def_id.resolve(db) {
let kind = match def {
hir::Def::Module(..) => CompletionItemKind::Module,
hir::Def::Function(..) => CompletionItemKind::Function,
_ => return self,
};
self.kind = Some(kind);
}
}
let resolved = resolution.def_id.and_then(|d| d.resolve(db).ok());
let kind = match resolved {
PerNs {
types: Some(hir::Def::Module(..)),
..
} => CompletionItemKind::Module,
PerNs {
types: Some(hir::Def::Struct(..)),
..
} => CompletionItemKind::Struct,
PerNs {
types: Some(hir::Def::Enum(..)),
..
} => CompletionItemKind::Enum,
PerNs {
values: Some(hir::Def::Function(..)),
..
} => CompletionItemKind::Function,
_ => return self,
};
self.kind = Some(kind);
self
}
}

View file

@ -95,6 +95,9 @@ salsa::database_storage! {
fn submodules() for hir::db::SubmodulesQuery;
fn infer() for hir::db::InferQuery;
fn type_for_def() for hir::db::TypeForDefQuery;
fn type_for_field() for hir::db::TypeForFieldQuery;
fn struct_data() for hir::db::StructDataQuery;
fn enum_data() for hir::db::EnumDataQuery;
}
}
}