mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
Merge #332
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:
commit
1d6dcef5c5
30 changed files with 1519 additions and 319 deletions
|
@ -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))
|
||||
}
|
||||
|
|
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, 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#""#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(()),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue