diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index d742d62955..fe580700ff 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -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)) } diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs new file mode 100644 index 0000000000..93d6575768 --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_dot.rs @@ -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#""#, + ); + } +} diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs index ad4d68a332..aaa2c7ceec 100644 --- a/crates/ra_analysis/src/completion/complete_path.rs +++ b/crates/ra_analysis/src/completion/complete_path.rs @@ -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(()), }; diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs index 064fbc6f7b..978772fd44 100644 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ b/crates/ra_analysis/src/completion/completion_context.rs @@ -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>, + /// 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::(file.syntax(), off) { + if let Some(if_expr) = + find_node_at_offset::(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 { + 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, diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 911f08468e..c9f9f495da 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -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 } } diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 780a84291a..036e284bfb 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -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; } } } diff --git a/crates/ra_hir/src/adt.rs b/crates/ra_hir/src/adt.rs new file mode 100644 index 0000000000..65c461148a --- /dev/null +++ b/crates/ra_hir/src/adt.rs @@ -0,0 +1,194 @@ +use std::sync::Arc; + +use ra_syntax::{SmolStr, ast::{self, NameOwner, StructFlavor}}; + +use crate::{ + DefId, Cancelable, + db::{HirDatabase}, + type_ref::TypeRef, +}; + +pub struct Struct { + def_id: DefId, +} + +impl Struct { + pub(crate) fn new(def_id: DefId) -> Self { + Struct { def_id } + } + + pub fn def_id(&self) -> DefId { + self.def_id + } + + pub fn variant_data(&self, db: &impl HirDatabase) -> Cancelable> { + Ok(db.struct_data(self.def_id)?.variant_data.clone()) + } + + pub fn struct_data(&self, db: &impl HirDatabase) -> Cancelable> { + Ok(db.struct_data(self.def_id)?) + } + + pub fn name(&self, db: &impl HirDatabase) -> Cancelable> { + Ok(db.struct_data(self.def_id)?.name.clone()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructData { + name: Option, + variant_data: Arc, +} + +impl StructData { + pub(crate) fn new(struct_def: ast::StructDef) -> StructData { + let name = struct_def.name().map(|n| n.text()); + let variant_data = VariantData::new(struct_def.flavor()); + let variant_data = Arc::new(variant_data); + StructData { name, variant_data } + } + + pub fn name(&self) -> Option<&SmolStr> { + self.name.as_ref() + } + + pub fn variant_data(&self) -> &Arc { + &self.variant_data + } +} + +pub struct Enum { + def_id: DefId, +} + +impl Enum { + pub(crate) fn new(def_id: DefId) -> Self { + Enum { def_id } + } + + pub fn def_id(&self) -> DefId { + self.def_id + } + + pub fn name(&self, db: &impl HirDatabase) -> Cancelable> { + Ok(db.enum_data(self.def_id)?.name.clone()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumData { + name: Option, + variants: Vec<(SmolStr, Arc)>, +} + +impl EnumData { + pub(crate) fn new(enum_def: ast::EnumDef) -> Self { + let name = enum_def.name().map(|n| n.text()); + let variants = if let Some(evl) = enum_def.variant_list() { + evl.variants() + .map(|v| { + ( + v.name() + .map(|n| n.text()) + .unwrap_or_else(|| SmolStr::new("[error]")), + Arc::new(VariantData::new(v.flavor())), + ) + }) + .collect() + } else { + Vec::new() + }; + EnumData { name, variants } + } +} + +/// A single field of an enum variant or struct +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructField { + name: SmolStr, + type_ref: TypeRef, +} + +impl StructField { + pub fn name(&self) -> SmolStr { + self.name.clone() + } + pub fn type_ref(&self) -> &TypeRef { + &self.type_ref + } +} + +/// Fields of an enum variant or struct +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariantData { + Struct(Vec), + Tuple(Vec), + Unit, +} + +impl VariantData { + pub fn new(flavor: StructFlavor) -> Self { + match flavor { + StructFlavor::Tuple(fl) => { + let fields = fl + .fields() + .enumerate() + .map(|(i, fd)| StructField { + name: SmolStr::new(i.to_string()), + type_ref: TypeRef::from_ast_opt(fd.type_ref()), + }) + .collect(); + VariantData::Tuple(fields) + } + StructFlavor::Named(fl) => { + let fields = fl + .fields() + .map(|fd| StructField { + name: fd + .name() + .map(|n| n.text()) + .unwrap_or_else(|| SmolStr::new("[error]")), + type_ref: TypeRef::from_ast_opt(fd.type_ref()), + }) + .collect(); + VariantData::Struct(fields) + } + StructFlavor::Unit => VariantData::Unit, + } + } + + pub(crate) fn get_field_type_ref(&self, field_name: &str) -> Option<&TypeRef> { + self.fields() + .iter() + .find(|f| f.name == field_name) + .map(|f| &f.type_ref) + } + + pub fn fields(&self) -> &[StructField] { + match *self { + VariantData::Struct(ref fields) | VariantData::Tuple(ref fields) => fields, + _ => &[], + } + } + pub fn is_struct(&self) -> bool { + if let VariantData::Struct(..) = *self { + true + } else { + false + } + } + pub fn is_tuple(&self) -> bool { + if let VariantData::Tuple(..) = *self { + true + } else { + false + } + } + pub fn is_unit(&self) -> bool { + if let VariantData::Unit = *self { + true + } else { + false + } + } +} diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index d94f75857f..e7f9afa77d 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use ra_syntax::{ + SmolStr, SyntaxNode, ast::FnDefNode, }; @@ -15,6 +16,7 @@ use crate::{ module::{ModuleId, ModuleTree, ModuleSource, nameres::{ItemMap, InputModuleItems}}, ty::{InferenceResult, Ty}, + adt::{StructData, EnumData}, }; salsa::query_group! { @@ -31,6 +33,16 @@ pub trait HirDatabase: SyntaxDatabase use fn query_definitions::fn_syntax; } + fn struct_data(def_id: DefId) -> Cancelable> { + type StructDataQuery; + use fn query_definitions::struct_data; + } + + fn enum_data(def_id: DefId) -> Cancelable> { + type EnumDataQuery; + use fn query_definitions::enum_data; + } + fn infer(fn_id: FnId) -> Cancelable> { type InferQuery; use fn query_definitions::infer; @@ -41,6 +53,11 @@ pub trait HirDatabase: SyntaxDatabase use fn query_definitions::type_for_def; } + fn type_for_field(def_id: DefId, field: SmolStr) -> Cancelable { + type TypeForFieldQuery; + use fn query_definitions::type_for_field; + } + fn file_items(file_id: FileId) -> Arc { type SourceFileItemsQuery; use fn query_definitions::file_items; diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs index d36477b48d..01f0f3a662 100644 --- a/crates/ra_hir/src/function.rs +++ b/crates/ra_hir/src/function.rs @@ -46,8 +46,7 @@ impl Function { } pub fn module(&self, db: &impl HirDatabase) -> Cancelable { - let loc = self.fn_id.0.loc(db); - Module::new(db, loc.source_root_id, loc.module_id) + self.fn_id.0.module(db) } } diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index a0d99a84df..f1cc0ccd08 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -25,6 +25,8 @@ pub mod source_binder; mod krate; mod module; mod function; +mod adt; +mod type_ref; mod ty; use std::ops::Index; @@ -40,8 +42,10 @@ use crate::{ pub use self::{ path::{Path, PathKind}, krate::Crate, - module::{Module, ModuleId, Problem, nameres::ItemMap, ModuleScope, Resolution}, + module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution}, function::{Function, FnScopes}, + adt::{Struct, Enum}, + ty::Ty, }; pub use self::function::FnSignatureInfo; @@ -56,7 +60,11 @@ ra_db::impl_numeric_id!(DefId); pub(crate) enum DefKind { Module, Function, + Struct, + Enum, Item, + + StructCtor, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -68,18 +76,18 @@ pub struct DefLoc { } impl DefKind { - pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> Option { + pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> PerNs { match kind { - SyntaxKind::FN_DEF => Some(DefKind::Function), - SyntaxKind::MODULE => Some(DefKind::Module), + SyntaxKind::FN_DEF => PerNs::values(DefKind::Function), + SyntaxKind::MODULE => PerNs::types(DefKind::Module), + SyntaxKind::STRUCT_DEF => PerNs::both(DefKind::Struct, DefKind::StructCtor), + SyntaxKind::ENUM_DEF => PerNs::types(DefKind::Enum), // These define items, but don't have their own DefKinds yet: - SyntaxKind::STRUCT_DEF => Some(DefKind::Item), - SyntaxKind::ENUM_DEF => Some(DefKind::Item), - SyntaxKind::TRAIT_DEF => Some(DefKind::Item), - SyntaxKind::TYPE_DEF => Some(DefKind::Item), - SyntaxKind::CONST_DEF => Some(DefKind::Item), - SyntaxKind::STATIC_DEF => Some(DefKind::Item), - _ => None, + SyntaxKind::TRAIT_DEF => PerNs::types(DefKind::Item), + SyntaxKind::TYPE_DEF => PerNs::types(DefKind::Item), + SyntaxKind::CONST_DEF => PerNs::values(DefKind::Item), + SyntaxKind::STATIC_DEF => PerNs::values(DefKind::Item), + _ => PerNs::none(), } } } @@ -99,6 +107,8 @@ impl DefLoc { pub enum Def { Module(Module), Function(Function), + Struct(Struct), + Enum(Enum), Item, } @@ -114,10 +124,25 @@ impl DefId { let function = Function::new(self); Def::Function(function) } + DefKind::Struct => { + let struct_def = Struct::new(self); + Def::Struct(struct_def) + } + DefKind::Enum => { + let enum_def = Enum::new(self); + Def::Enum(enum_def) + } + DefKind::StructCtor => Def::Item, DefKind::Item => Def::Item, }; Ok(res) } + + /// For a module, returns that module; for any other def, returns the containing module. + pub fn module(self, db: &impl HirDatabase) -> Cancelable { + let loc = self.loc(db); + Module::new(db, loc.source_root_id, loc.module_id) + } } /// Identifier of item within a specific file. This is stable over reparses, so diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index b5a9971707..f6882cb771 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -193,6 +193,9 @@ salsa::database_storage! { fn submodules() for db::SubmodulesQuery; fn infer() for db::InferQuery; fn type_for_def() for db::TypeForDefQuery; + fn type_for_field() for db::TypeForFieldQuery; + fn struct_data() for db::StructDataQuery; + fn enum_data() for db::EnumDataQuery; } } } diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs index 8911199530..b9d36f01f0 100644 --- a/crates/ra_hir/src/module.rs +++ b/crates/ra_hir/src/module.rs @@ -17,7 +17,7 @@ use crate::{ arena::{Arena, Id}, }; -pub use self::nameres::{ModuleScope, Resolution}; +pub use self::nameres::{ModuleScope, Resolution, Namespace, PerNs}; /// `Module` is API entry point to get all the information /// about a particular module. @@ -115,16 +115,29 @@ impl Module { Ok(res) } - pub fn resolve_path(&self, db: &impl HirDatabase, path: Path) -> Cancelable> { - let mut curr = match path.kind { - PathKind::Crate => self.crate_root(), - PathKind::Self_ | PathKind::Plain => self.clone(), - PathKind::Super => ctry!(self.parent()), - } - .def_id(db); + pub fn resolve_path(&self, db: &impl HirDatabase, path: &Path) -> Cancelable> { + let mut curr_per_ns = PerNs::types( + match path.kind { + PathKind::Crate => self.crate_root(), + PathKind::Self_ | PathKind::Plain => self.clone(), + PathKind::Super => { + if let Some(p) = self.parent() { + p + } else { + return Ok(PerNs::none()); + } + } + } + .def_id(db), + ); - let segments = path.segments; + let segments = &path.segments; for name in segments.iter() { + let curr = if let Some(r) = curr_per_ns.as_ref().take(Namespace::Types) { + r + } else { + return Ok(PerNs::none()); + }; let module = match curr.loc(db) { DefLoc { kind: DefKind::Module, @@ -132,12 +145,17 @@ impl Module { module_id, .. } => Module::new(db, source_root_id, module_id)?, - _ => return Ok(None), + // TODO here would be the place to handle enum variants... + _ => return Ok(PerNs::none()), }; let scope = module.scope(db)?; - curr = ctry!(ctry!(scope.get(&name)).def_id); + curr_per_ns = if let Some(r) = scope.get(&name) { + r.def_id + } else { + return Ok(PerNs::none()); + }; } - Ok(Some(curr)) + Ok(curr_per_ns) } pub fn problems(&self, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> { @@ -145,7 +163,7 @@ impl Module { } } -/// Phisically, rust source is organized as a set of files, but logically it is +/// Physically, rust source is organized as a set of files, but logically it is /// organized as a tree of modules. Usually, a single file corresponds to a /// single module, but it is not nessary the case. /// diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs index 0b152a4062..98cd225dde 100644 --- a/crates/ra_hir/src/module/nameres.rs +++ b/crates/ra_hir/src/module/nameres.rs @@ -118,22 +118,96 @@ enum ImportKind { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Resolution { /// None for unresolved - pub def_id: Option, + pub def_id: PerNs, /// ident by whitch this is imported into local scope. pub import: Option, } -// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -// enum Namespace { -// Types, -// Values, -// } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Namespace { + Types, + Values, +} -// #[derive(Debug)] -// struct PerNs { -// types: Option, -// values: Option, -// } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct PerNs { + pub types: Option, + pub values: Option, +} + +impl PerNs { + pub fn none() -> PerNs { + PerNs { + types: None, + values: None, + } + } + + pub fn values(t: T) -> PerNs { + PerNs { + types: None, + values: Some(t), + } + } + + pub fn types(t: T) -> PerNs { + PerNs { + types: Some(t), + values: None, + } + } + + pub fn both(types: T, values: T) -> PerNs { + PerNs { + types: Some(types), + values: Some(values), + } + } + + pub fn is_none(&self) -> bool { + self.types.is_none() && self.values.is_none() + } + + pub fn take(self, namespace: Namespace) -> Option { + match namespace { + Namespace::Types => self.types, + Namespace::Values => self.values, + } + } + + pub fn take_types(self) -> Option { + self.types + } + + pub fn take_values(self) -> Option { + self.values + } + + pub fn get(&self, namespace: Namespace) -> Option<&T> { + self.as_ref().take(namespace) + } + + pub fn as_ref(&self) -> PerNs<&T> { + PerNs { + types: self.types.as_ref(), + values: self.values.as_ref(), + } + } + + pub fn and_then(self, f: impl Fn(T) -> Option) -> PerNs { + PerNs { + types: self.types.and_then(&f), + values: self.values.and_then(&f), + } + } + + pub fn map(self, f: impl Fn(T) -> U) -> PerNs { + PerNs { + types: self.types.map(&f), + values: self.values.map(&f), + } + } +} impl InputModuleItems { pub(crate) fn new<'a>( @@ -254,7 +328,7 @@ where for dep in krate.dependencies(self.db) { if let Some(module) = dep.krate.root_module(self.db)? { let def_id = module.def_id(self.db); - self.add_module_item(&mut module_items, dep.name, def_id); + self.add_module_item(&mut module_items, dep.name, PerNs::types(def_id)); } } }; @@ -265,7 +339,7 @@ where module_items.items.insert( name.clone(), Resolution { - def_id: None, + def_id: PerNs::none(), import: Some(import), }, ); @@ -277,18 +351,23 @@ where if item.kind == MODULE { continue; } - let def_loc = DefLoc { - kind: DefKind::for_syntax_kind(item.kind).unwrap_or(DefKind::Item), - source_root_id: self.source_root, - module_id, - source_item_id: SourceItemId { - file_id, - item_id: Some(item.id), - }, - }; - let def_id = def_loc.id(self.db); + // depending on the item kind, the location can define something in + // the values namespace, the types namespace, or both + let kind = DefKind::for_syntax_kind(item.kind); + let def_id = kind.map(|k| { + let def_loc = DefLoc { + kind: k, + source_root_id: self.source_root, + module_id, + source_item_id: SourceItemId { + file_id, + item_id: Some(item.id), + }, + }; + def_loc.id(self.db) + }); let resolution = Resolution { - def_id: Some(def_id), + def_id, import: None, }; module_items.items.insert(item.name.clone(), resolution); @@ -303,16 +382,16 @@ where source_item_id: module_id.source(&self.module_tree).0, }; let def_id = def_loc.id(self.db); - self.add_module_item(&mut module_items, name, def_id); + self.add_module_item(&mut module_items, name, PerNs::types(def_id)); } self.result.per_module.insert(module_id, module_items); Ok(()) } - fn add_module_item(&self, module_items: &mut ModuleScope, name: SmolStr, def_id: DefId) { + fn add_module_item(&self, module_items: &mut ModuleScope, name: SmolStr, def_id: PerNs) { let resolution = Resolution { - def_id: Some(def_id), + def_id, import: None, }; module_items.items.insert(name, resolution); @@ -347,15 +426,17 @@ where let is_last = i == import.path.segments.len() - 1; let def_id = match self.result.per_module[&curr].items.get(name) { - None => return Ok(()), - Some(res) => match res.def_id { - Some(it) => it, - None => return Ok(()), - }, + Some(res) if !res.def_id.is_none() => res.def_id, + _ => return Ok(()), }; if !is_last { - curr = match def_id.loc(self.db) { + let type_def_id = if let Some(d) = def_id.take(Namespace::Types) { + d + } else { + return Ok(()); + }; + curr = match type_def_id.loc(self.db) { DefLoc { kind: DefKind::Module, module_id: target_module_id, @@ -370,10 +451,11 @@ where segments: import.path.segments[i + 1..].iter().cloned().collect(), kind: PathKind::Crate, }; - if let Some(def_id) = module.resolve_path(self.db, path)? { + let def_id = module.resolve_path(self.db, &path)?; + if !def_id.is_none() { self.update(module_id, |items| { let res = Resolution { - def_id: Some(def_id), + def_id: def_id, import: Some(ptr), }; items.items.insert(name.clone(), res); @@ -387,7 +469,7 @@ where } else { self.update(module_id, |items| { let res = Resolution { - def_id: Some(def_id), + def_id: def_id, import: Some(ptr), }; items.items.insert(name.clone(), res); diff --git a/crates/ra_hir/src/module/nameres/tests.rs b/crates/ra_hir/src/module/nameres/tests.rs index 3e29c39541..03ea5c1d6c 100644 --- a/crates/ra_hir/src/module/nameres/tests.rs +++ b/crates/ra_hir/src/module/nameres/tests.rs @@ -40,7 +40,7 @@ fn item_map_smoke_test() { ); let name = SmolStr::from("Baz"); let resolution = &item_map.per_module[&module_id].items[&name]; - assert!(resolution.def_id.is_some()); + assert!(resolution.def_id.take_types().is_some()); } #[test] @@ -59,7 +59,7 @@ fn test_self() { ); let name = SmolStr::from("Baz"); let resolution = &item_map.per_module[&module_id].items[&name]; - assert!(resolution.def_id.is_some()); + assert!(resolution.def_id.take_types().is_some()); } #[test] @@ -92,7 +92,7 @@ fn item_map_across_crates() { let name = SmolStr::from("Baz"); let resolution = &item_map.per_module[&module_id].items[&name]; - assert!(resolution.def_id.is_some()); + assert!(resolution.def_id.take_types().is_some()); } #[test] diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs index e04d00900a..0b260072cc 100644 --- a/crates/ra_hir/src/path.rs +++ b/crates/ra_hir/src/path.rs @@ -1,12 +1,12 @@ use ra_syntax::{SmolStr, ast, AstNode, TextRange}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { pub kind: PathKind, pub segments: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PathKind { Plain, Self_, diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs index b654af9204..4a7958a122 100644 --- a/crates/ra_hir/src/query_definitions.rs +++ b/crates/ra_hir/src/query_definitions.rs @@ -19,7 +19,8 @@ use crate::{ imp::Submodule, nameres::{InputModuleItems, ItemMap, Resolver}, }, - ty::{self, InferenceResult, Ty} + ty::{self, InferenceResult, Ty}, + adt::{StructData, EnumData}, }; /// Resolve `FnId` to the corresponding `SyntaxNode` @@ -45,6 +46,32 @@ pub(super) fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable Cancelable { + ty::type_for_field(db, def_id, field) +} + +pub(super) fn struct_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { + let def_loc = def_id.loc(db); + assert!(def_loc.kind == DefKind::Struct); + let syntax = db.file_item(def_loc.source_item_id); + let struct_def = + ast::StructDef::cast(syntax.borrowed()).expect("struct def should point to StructDef node"); + Ok(Arc::new(StructData::new(struct_def.borrowed()))) +} + +pub(super) fn enum_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { + let def_loc = def_id.loc(db); + assert!(def_loc.kind == DefKind::Enum); + let syntax = db.file_item(def_loc.source_item_id); + let enum_def = + ast::EnumDef::cast(syntax.borrowed()).expect("enum def should point to EnumDef node"); + Ok(Arc::new(EnumData::new(enum_def.borrowed()))) +} + pub(super) fn file_items(db: &impl HirDatabase, file_id: FileId) -> Arc { let mut res = SourceFileItems::new(file_id); let source_file = db.source_file(file_id); diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index c759d4c8b1..67b523c2cc 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -11,13 +11,18 @@ use rustc_hash::{FxHashMap}; use ra_db::{LocalSyntaxPtr, Cancelable}; use ra_syntax::{ SmolStr, - ast::{self, AstNode, LoopBodyOwner, ArgListOwner}, + ast::{self, AstNode, LoopBodyOwner, ArgListOwner, PrefixOp}, SyntaxNodeRef }; -use crate::{Def, DefId, FnScopes, Module, Function, Path, db::HirDatabase}; +use crate::{ + Def, DefId, FnScopes, Module, Function, Struct, Enum, Path, + db::HirDatabase, + adt::VariantData, + type_ref::{TypeRef, Mutability}, +}; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum Ty { /// The primitive boolean type. Written as `bool`. Bool, @@ -35,8 +40,15 @@ pub enum Ty { /// A primitive floating-point type. For example, `f64`. Float(primitive::FloatTy), - // Structures, enumerations and unions. - // Adt(AdtDef, Substs), + /// Structures, enumerations and unions. + Adt { + /// The DefId of the struct/enum. + def_id: DefId, + /// The name, for displaying. + name: SmolStr, + // later we'll need generic substitutions here + }, + /// The pointee of a string slice. Written as `str`. Str, @@ -45,12 +57,13 @@ pub enum Ty { /// The pointee of an array slice. Written as `[T]`. Slice(TyRef), - // A raw pointer. Written as `*mut T` or `*const T` - // RawPtr(TypeAndMut<'tcx>), + /// A raw pointer. Written as `*mut T` or `*const T` + RawPtr(TyRef, Mutability), + + /// A reference; a pointer with an associated lifetime. Written as + /// `&'a mut T` or `&'a T`. + Ref(TyRef, Mutability), - // A reference; a pointer with an associated lifetime. Written as - // `&'a mut T` or `&'a T`. - // Ref(Ty<'tcx>, hir::Mutability), /// A pointer to a function. Written as `fn() -> i32`. /// /// For example the type of `bar` here: @@ -107,58 +120,104 @@ pub enum Ty { type TyRef = Arc; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct FnSig { input: Vec, output: Ty, } impl Ty { - pub fn new(_db: &impl HirDatabase, node: ast::TypeRef) -> Cancelable { - use ra_syntax::ast::TypeRef::*; - Ok(match node { - ParenType(_inner) => Ty::Unknown, // TODO - TupleType(_inner) => Ty::Unknown, // TODO - NeverType(..) => Ty::Never, - PathType(inner) => { - let path = if let Some(p) = inner.path() { - p - } else { - return Ok(Ty::Unknown); - }; - if path.qualifier().is_none() { - let name = path - .segment() - .and_then(|s| s.name_ref()) - .map(|n| n.text()) - .unwrap_or(SmolStr::new("")); - if let Some(int_ty) = primitive::IntTy::from_string(&name) { - Ty::Int(int_ty) - } else if let Some(uint_ty) = primitive::UintTy::from_string(&name) { - Ty::Uint(uint_ty) - } else if let Some(float_ty) = primitive::FloatTy::from_string(&name) { - Ty::Float(float_ty) - } else { - // TODO - Ty::Unknown - } - } else { - // TODO - Ty::Unknown - } + pub(crate) fn from_hir( + db: &impl HirDatabase, + module: &Module, + type_ref: &TypeRef, + ) -> Cancelable { + Ok(match type_ref { + TypeRef::Never => Ty::Never, + TypeRef::Tuple(inner) => { + let inner_tys = inner + .iter() + .map(|tr| Ty::from_hir(db, module, tr)) + .collect::>()?; + Ty::Tuple(inner_tys) } - PointerType(_inner) => Ty::Unknown, // TODO - ArrayType(_inner) => Ty::Unknown, // TODO - SliceType(_inner) => Ty::Unknown, // TODO - ReferenceType(_inner) => Ty::Unknown, // TODO - PlaceholderType(_inner) => Ty::Unknown, // TODO - FnPointerType(_inner) => Ty::Unknown, // TODO - ForType(_inner) => Ty::Unknown, // TODO - ImplTraitType(_inner) => Ty::Unknown, // TODO - DynTraitType(_inner) => Ty::Unknown, // TODO + TypeRef::Path(path) => Ty::from_hir_path(db, module, path)?, + TypeRef::RawPtr(inner, mutability) => { + let inner_ty = Ty::from_hir(db, module, inner)?; + Ty::RawPtr(Arc::new(inner_ty), *mutability) + } + TypeRef::Array(_inner) => Ty::Unknown, // TODO + TypeRef::Slice(inner) => { + let inner_ty = Ty::from_hir(db, module, inner)?; + Ty::Slice(Arc::new(inner_ty)) + } + TypeRef::Reference(inner, mutability) => { + let inner_ty = Ty::from_hir(db, module, inner)?; + Ty::Ref(Arc::new(inner_ty), *mutability) + } + TypeRef::Placeholder => Ty::Unknown, // TODO + TypeRef::Fn(params) => { + let mut inner_tys = params + .iter() + .map(|tr| Ty::from_hir(db, module, tr)) + .collect::>>()?; + let return_ty = inner_tys + .pop() + .expect("TypeRef::Fn should always have at least return type"); + let sig = FnSig { + input: inner_tys, + output: return_ty, + }; + Ty::FnPtr(Arc::new(sig)) + } + TypeRef::Error => Ty::Unknown, }) } + pub(crate) fn from_hir_path( + db: &impl HirDatabase, + module: &Module, + path: &Path, + ) -> Cancelable { + if path.is_ident() { + let name = &path.segments[0]; + if let Some(int_ty) = primitive::IntTy::from_string(&name) { + return Ok(Ty::Int(int_ty)); + } else if let Some(uint_ty) = primitive::UintTy::from_string(&name) { + return Ok(Ty::Uint(uint_ty)); + } else if let Some(float_ty) = primitive::FloatTy::from_string(&name) { + return Ok(Ty::Float(float_ty)); + } + } + + // Resolve in module (in type namespace) + let resolved = if let Some(r) = module.resolve_path(db, path)?.take_types() { + r + } else { + return Ok(Ty::Unknown); + }; + let ty = db.type_for_def(resolved)?; + Ok(ty) + } + + // TODO: These should not be necessary long-term, since everything will work on HIR + pub(crate) fn from_ast_opt( + db: &impl HirDatabase, + module: &Module, + node: Option, + ) -> Cancelable { + node.map(|n| Ty::from_ast(db, module, n)) + .unwrap_or(Ok(Ty::Unknown)) + } + + pub(crate) fn from_ast( + db: &impl HirDatabase, + module: &Module, + node: ast::TypeRef, + ) -> Cancelable { + Ty::from_hir(db, module, &TypeRef::from_ast(node)) + } + pub fn unit() -> Self { Ty::Tuple(Vec::new()) } @@ -174,6 +233,8 @@ impl fmt::Display for Ty { Ty::Float(t) => write!(f, "{}", t.ty_to_string()), Ty::Str => write!(f, "str"), Ty::Slice(t) => write!(f, "[{}]", t), + Ty::RawPtr(t, m) => write!(f, "*{}{}", m.as_keyword_for_ptr(), t), + Ty::Ref(t, m) => write!(f, "&{}{}", m.as_keyword_for_ref(), t), Ty::Never => write!(f, "!"), Ty::Tuple(ts) => { write!(f, "(")?; @@ -189,6 +250,7 @@ impl fmt::Display for Ty { } write!(f, ") -> {}", sig.output) } + Ty::Adt { name, .. } => write!(f, "{}", name), Ty::Unknown => write!(f, "[unknown]"), } } @@ -196,34 +258,40 @@ impl fmt::Display for Ty { pub fn type_for_fn(db: &impl HirDatabase, f: Function) -> Cancelable { let syntax = f.syntax(db); + let module = f.module(db)?; let node = syntax.borrowed(); // TODO we ignore type parameters for now let input = node .param_list() .map(|pl| { pl.params() - .map(|p| { - p.type_ref() - .map(|t| Ty::new(db, t)) - .unwrap_or(Ok(Ty::Unknown)) - }) + .map(|p| Ty::from_ast_opt(db, &module, p.type_ref())) .collect() }) .unwrap_or_else(|| Ok(Vec::new()))?; - let output = node - .ret_type() - .and_then(|rt| rt.type_ref()) - .map(|t| Ty::new(db, t)) - .unwrap_or(Ok(Ty::Unknown))?; + let output = Ty::from_ast_opt(db, &module, node.ret_type().and_then(|rt| rt.type_ref()))?; let sig = FnSig { input, output }; Ok(Ty::FnPtr(Arc::new(sig))) } -// TODO this should probably be per namespace (i.e. types vs. values), since for -// a tuple struct `struct Foo(Bar)`, Foo has function type as a value, but -// defines the struct type Foo when used in the type namespace. rustc has a -// separate DefId for the constructor, but with the current DefId approach, that -// seems complicated. +pub fn type_for_struct(db: &impl HirDatabase, s: Struct) -> Cancelable { + Ok(Ty::Adt { + def_id: s.def_id(), + name: s + .name(db)? + .unwrap_or_else(|| SmolStr::new("[unnamed struct]")), + }) +} + +pub fn type_for_enum(db: &impl HirDatabase, s: Enum) -> Cancelable { + Ok(Ty::Adt { + def_id: s.def_id(), + name: s + .name(db)? + .unwrap_or_else(|| SmolStr::new("[unnamed enum]")), + }) +} + pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable { let def = def_id.resolve(db)?; match def { @@ -232,6 +300,8 @@ pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable { Ok(Ty::Unknown) } Def::Function(f) => type_for_fn(db, f), + Def::Struct(s) => type_for_struct(db, s), + Def::Enum(e) => type_for_enum(db, e), Def::Item => { log::debug!("trying to get type for item of unknown type {:?}", def_id); Ok(Ty::Unknown) @@ -239,6 +309,33 @@ pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable { } } +pub(super) fn type_for_field( + db: &impl HirDatabase, + def_id: DefId, + field: SmolStr, +) -> Cancelable { + let def = def_id.resolve(db)?; + let variant_data = match def { + Def::Struct(s) => { + let variant_data = s.variant_data(db)?; + variant_data + } + // TODO: unions + // TODO: enum variants + _ => panic!( + "trying to get type for field in non-struct/variant {:?}", + def_id + ), + }; + let module = def_id.module(db)?; + let type_ref = if let Some(tr) = variant_data.get_field_type_ref(&field) { + tr + } else { + return Ok(Ty::Unknown); + }; + Ty::from_hir(db, &module, &type_ref) +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct InferenceResult { type_of: FxHashMap, @@ -305,32 +402,54 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { }; // resolve in module - let resolved = ctry!(self.module.resolve_path(self.db, path)?); + let resolved = ctry!(self.module.resolve_path(self.db, &path)?.take_values()); let ty = self.db.type_for_def(resolved)?; // TODO we will need to add type variables for type parameters etc. here Ok(Some(ty)) } + fn resolve_variant( + &self, + path: Option, + ) -> Cancelable<(Ty, Option>)> { + let path = if let Some(path) = path.and_then(Path::from_ast) { + path + } else { + return Ok((Ty::Unknown, None)); + }; + let def_id = if let Some(def_id) = self.module.resolve_path(self.db, &path)?.take_types() { + def_id + } else { + return Ok((Ty::Unknown, None)); + }; + Ok(match def_id.resolve(self.db)? { + Def::Struct(s) => { + let struct_data = self.db.struct_data(def_id)?; + let ty = type_for_struct(self.db, s)?; + (ty, Some(struct_data.variant_data().clone())) + } + _ => (Ty::Unknown, None), + }) + } + + fn infer_expr_opt(&mut self, expr: Option) -> Cancelable { + if let Some(e) = expr { + self.infer_expr(e) + } else { + Ok(Ty::Unknown) + } + } + fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable { let ty = match expr { ast::Expr::IfExpr(e) => { if let Some(condition) = e.condition() { - if let Some(e) = condition.expr() { - // TODO if no pat, this should be bool - self.infer_expr(e)?; - } + // TODO if no pat, this should be bool + self.infer_expr_opt(condition.expr())?; // TODO write type for pat }; - let if_ty = if let Some(block) = e.then_branch() { - self.infer_block(block)? - } else { - Ty::Unknown - }; - let else_ty = if let Some(block) = e.else_branch() { - self.infer_block(block)? - } else { - Ty::Unknown - }; + let if_ty = self.infer_block_opt(e.then_branch())?; + let else_ty = self.infer_block_opt(e.else_branch())?; if let Some(ty) = self.unify(&if_ty, &else_ty) { ty } else { @@ -338,62 +457,37 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Ty::Unknown } } - ast::Expr::BlockExpr(e) => { - if let Some(block) = e.block() { - self.infer_block(block)? - } else { - Ty::Unknown - } - } + ast::Expr::BlockExpr(e) => self.infer_block_opt(e.block())?, ast::Expr::LoopExpr(e) => { - if let Some(block) = e.loop_body() { - self.infer_block(block)?; - }; + self.infer_block_opt(e.loop_body())?; // TODO never, or the type of the break param Ty::Unknown } ast::Expr::WhileExpr(e) => { if let Some(condition) = e.condition() { - if let Some(e) = condition.expr() { - // TODO if no pat, this should be bool - self.infer_expr(e)?; - } + // TODO if no pat, this should be bool + self.infer_expr_opt(condition.expr())?; // TODO write type for pat }; - if let Some(block) = e.loop_body() { - // TODO - self.infer_block(block)?; - }; + self.infer_block_opt(e.loop_body())?; // TODO always unit? Ty::Unknown } ast::Expr::ForExpr(e) => { - if let Some(expr) = e.iterable() { - self.infer_expr(expr)?; - } + let _iterable_ty = self.infer_expr_opt(e.iterable()); if let Some(_pat) = e.pat() { // TODO write type for pat } - if let Some(block) = e.loop_body() { - self.infer_block(block)?; - } + self.infer_block_opt(e.loop_body())?; // TODO always unit? Ty::Unknown } ast::Expr::LambdaExpr(e) => { - let _body_ty = if let Some(body) = e.body() { - self.infer_expr(body)? - } else { - Ty::Unknown - }; + let _body_ty = self.infer_expr_opt(e.body())?; Ty::Unknown } ast::Expr::CallExpr(e) => { - let callee_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; + let callee_ty = self.infer_expr_opt(e.expr())?; if let Some(arg_list) = e.arg_list() { for arg in arg_list.args() { // TODO unify / expect argument type @@ -410,11 +504,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } } ast::Expr::MethodCallExpr(e) => { - let _receiver_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; + let _receiver_ty = self.infer_expr_opt(e.expr())?; if let Some(arg_list) = e.arg_list() { for arg in arg_list.args() { // TODO unify / expect argument type @@ -424,20 +514,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Ty::Unknown } ast::Expr::MatchExpr(e) => { - let _ty = if let Some(match_expr) = e.expr() { - self.infer_expr(match_expr)? - } else { - Ty::Unknown - }; + let _ty = self.infer_expr_opt(e.expr())?; if let Some(match_arm_list) = e.match_arm_list() { for arm in match_arm_list.arms() { // TODO type the bindings in pat // TODO type the guard - let _ty = if let Some(e) = arm.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; + let _ty = self.infer_expr_opt(arm.expr())?; } // TODO unify all the match arm types Ty::Unknown @@ -450,68 +532,78 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { ast::Expr::PathExpr(e) => self.infer_path_expr(e)?.unwrap_or(Ty::Unknown), ast::Expr::ContinueExpr(_e) => Ty::Never, ast::Expr::BreakExpr(_e) => Ty::Never, - ast::Expr::ParenExpr(e) => { - if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - } - } + ast::Expr::ParenExpr(e) => self.infer_expr_opt(e.expr())?, ast::Expr::Label(_e) => Ty::Unknown, ast::Expr::ReturnExpr(e) => { - if let Some(e) = e.expr() { - // TODO unify with return type - self.infer_expr(e)?; - }; + self.infer_expr_opt(e.expr())?; Ty::Never } ast::Expr::MatchArmList(_) | ast::Expr::MatchArm(_) | ast::Expr::MatchGuard(_) => { // Can this even occur outside of a match expression? Ty::Unknown } - ast::Expr::StructLit(_e) => Ty::Unknown, + ast::Expr::StructLit(e) => { + let (ty, _variant_data) = self.resolve_variant(e.path())?; + if let Some(nfl) = e.named_field_list() { + for field in nfl.fields() { + // TODO unify with / expect field type + self.infer_expr_opt(field.expr())?; + } + } + ty + } ast::Expr::NamedFieldList(_) | ast::Expr::NamedField(_) => { // Can this even occur outside of a struct literal? Ty::Unknown } ast::Expr::IndexExpr(_e) => Ty::Unknown, - ast::Expr::FieldExpr(_e) => Ty::Unknown, - ast::Expr::TryExpr(e) => { - let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? + ast::Expr::FieldExpr(e) => { + let receiver_ty = self.infer_expr_opt(e.expr())?; + if let Some(nr) = e.name_ref() { + let text = nr.text(); + match receiver_ty { + Ty::Tuple(fields) => { + let i = text.parse::().ok(); + i.and_then(|i| fields.get(i).cloned()) + .unwrap_or(Ty::Unknown) + } + Ty::Adt { def_id, .. } => self.db.type_for_field(def_id, text)?, + _ => Ty::Unknown, + } } else { Ty::Unknown - }; + } + } + ast::Expr::TryExpr(e) => { + let _inner_ty = self.infer_expr_opt(e.expr())?; Ty::Unknown } ast::Expr::CastExpr(e) => { - let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; - let cast_ty = e - .type_ref() - .map(|t| Ty::new(self.db, t)) - .unwrap_or(Ok(Ty::Unknown))?; + let _inner_ty = self.infer_expr_opt(e.expr())?; + let cast_ty = Ty::from_ast_opt(self.db, &self.module, e.type_ref())?; // TODO do the coercion... cast_ty } ast::Expr::RefExpr(e) => { - let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; - Ty::Unknown + let inner_ty = self.infer_expr_opt(e.expr())?; + let m = Mutability::from_mutable(e.is_mut()); + // TODO reference coercions etc. + Ty::Ref(Arc::new(inner_ty), m) } ast::Expr::PrefixExpr(e) => { - let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e)? - } else { - Ty::Unknown - }; - Ty::Unknown + let inner_ty = self.infer_expr_opt(e.expr())?; + match e.op() { + Some(PrefixOp::Deref) => { + match inner_ty { + // builtin deref: + Ty::Ref(ref_inner, _) => (*ref_inner).clone(), + Ty::RawPtr(ptr_inner, _) => (*ptr_inner).clone(), + // TODO Deref::deref + _ => Ty::Unknown, + } + } + _ => Ty::Unknown, + } } ast::Expr::RangeExpr(_e) => Ty::Unknown, ast::Expr::BinExpr(_e) => Ty::Unknown, @@ -521,15 +613,19 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Ok(ty) } + fn infer_block_opt(&mut self, node: Option) -> Cancelable { + if let Some(b) = node { + self.infer_block(b) + } else { + Ok(Ty::Unknown) + } + } + fn infer_block(&mut self, node: ast::Block) -> Cancelable { for stmt in node.statements() { match stmt { ast::Stmt::LetStmt(stmt) => { - let decl_ty = if let Some(type_ref) = stmt.type_ref() { - Ty::new(self.db, type_ref)? - } else { - Ty::Unknown - }; + let decl_ty = Ty::from_ast_opt(self.db, &self.module, stmt.type_ref())?; let ty = if let Some(expr) = stmt.initializer() { // TODO pass expectation let expr_ty = self.infer_expr(expr)?; @@ -544,9 +640,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { }; } ast::Stmt::ExprStmt(expr_stmt) => { - if let Some(expr) = expr_stmt.expr() { - self.infer_expr(expr)?; - } + self.infer_expr_opt(expr_stmt.expr())?; } } } @@ -576,7 +670,7 @@ pub fn infer(db: &impl HirDatabase, function: Function) -> Cancelable String { let (db, _, file_id) = MockDatabase::with_single_file(content); let source_file = db.source_file(file_id); diff --git a/crates/ra_hir/src/ty/tests/data/0001_basics.txt b/crates/ra_hir/src/ty/tests/data/0001_basics.txt index 0c46f243a8..212e92e000 100644 --- a/crates/ra_hir/src/ty/tests/data/0001_basics.txt +++ b/crates/ra_hir/src/ty/tests/data/0001_basics.txt @@ -1,4 +1,4 @@ -[33; 34) 'd': [unknown] +[33; 34) 'd': &[unknown] [88; 94) '1isize': [unknown] [48; 49) 'a': u32 [55; 56) 'b': isize @@ -10,4 +10,4 @@ [17; 18) 'b': isize [100; 106) '"test"': [unknown] [42; 121) '{ ...f32; }': () -[69; 70) 'd': [unknown] +[69; 70) 'd': &[unknown] diff --git a/crates/ra_hir/src/ty/tests/data/0004_struct.txt b/crates/ra_hir/src/ty/tests/data/0004_struct.txt new file mode 100644 index 0000000000..cc8f3665bf --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/0004_struct.txt @@ -0,0 +1,16 @@ +[86; 90) 'C(1)': [unknown] +[121; 122) 'B': [unknown] +[86; 87) 'C': [unknown] +[129; 130) '1': [unknown] +[107; 108) 'a': A +[127; 128) 'C': [unknown] +[139; 142) 'a.b': B +[114; 133) 'A { b:...C(1) }': A +[148; 151) 'a.c': C +[148; 149) 'a': A +[139; 140) 'a': A +[72; 154) '{ ...a.c; }': () +[96; 97) 'B': [unknown] +[88; 89) '1': [unknown] +[82; 83) 'c': [unknown] +[127; 131) 'C(1)': [unknown] diff --git a/crates/ra_hir/src/ty/tests/data/0005_refs.txt b/crates/ra_hir/src/ty/tests/data/0005_refs.txt new file mode 100644 index 0000000000..296e955c19 --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/0005_refs.txt @@ -0,0 +1,23 @@ +[115; 117) '&b': &&mut u32 +[88; 94) '&mut a': &mut &u32 +[146; 147) 'd': *mut u32 +[145; 147) '*d': u32 +[65; 66) 'a': &u32 +[46; 47) 'd': *mut u32 +[59; 150) '{ ... *d; }': () +[116; 117) 'b': &mut u32 +[131; 132) 'c': *const u32 +[130; 132) '*c': u32 +[72; 74) '*a': u32 +[107; 109) '*b': u32 +[108; 109) 'b': &mut u32 +[9; 10) 'a': &u32 +[18; 19) 'b': &mut u32 +[93; 94) 'a': &u32 +[100; 101) 'b': &mut u32 +[81; 82) 'a': &u32 +[80; 82) '&a': &&u32 +[73; 74) 'a': &u32 +[123; 124) 'c': *const u32 +[31; 32) 'c': *const u32 +[138; 139) 'd': *mut u32 diff --git a/crates/ra_hir/src/type_ref.rs b/crates/ra_hir/src/type_ref.rs new file mode 100644 index 0000000000..b36bb35d89 --- /dev/null +++ b/crates/ra_hir/src/type_ref.rs @@ -0,0 +1,110 @@ +//! HIR for references to types. Paths in these are not yet resolved. They can +//! be directly created from an ast::TypeRef, without further queries. + +use ra_syntax::ast; + +use crate::Path; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Mutability { + Shared, + Mut, +} + +impl Mutability { + pub fn from_mutable(mutable: bool) -> Mutability { + if mutable { + Mutability::Mut + } else { + Mutability::Shared + } + } + + pub fn as_keyword_for_ref(self) -> &'static str { + match self { + Mutability::Shared => "", + Mutability::Mut => "mut ", + } + } + + pub fn as_keyword_for_ptr(self) -> &'static str { + match self { + Mutability::Shared => "const ", + Mutability::Mut => "mut ", + } + } +} + +/// Compare ty::Ty +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum TypeRef { + Never, + Placeholder, + Tuple(Vec), + Path(Path), + RawPtr(Box, Mutability), + Reference(Box, Mutability), + Array(Box /*, Expr*/), + Slice(Box), + /// A fn pointer. Last element of the vector is the return type. + Fn(Vec), + // For + // ImplTrait, + // DynTrait, + Error, +} + +impl TypeRef { + /// Converts an `ast::TypeRef` to a `hir::TypeRef`. + pub(crate) fn from_ast(node: ast::TypeRef) -> Self { + use ra_syntax::ast::TypeRef::*; + match node { + ParenType(inner) => TypeRef::from_ast_opt(inner.type_ref()), + TupleType(inner) => TypeRef::Tuple(inner.fields().map(TypeRef::from_ast).collect()), + NeverType(..) => TypeRef::Never, + PathType(inner) => inner + .path() + .and_then(Path::from_ast) + .map(TypeRef::Path) + .unwrap_or(TypeRef::Error), + PointerType(inner) => { + let inner_ty = TypeRef::from_ast_opt(inner.type_ref()); + let mutability = Mutability::from_mutable(inner.is_mut()); + TypeRef::RawPtr(Box::new(inner_ty), mutability) + } + ArrayType(inner) => TypeRef::Array(Box::new(TypeRef::from_ast_opt(inner.type_ref()))), + SliceType(inner) => TypeRef::Slice(Box::new(TypeRef::from_ast_opt(inner.type_ref()))), + ReferenceType(inner) => { + let inner_ty = TypeRef::from_ast_opt(inner.type_ref()); + let mutability = Mutability::from_mutable(inner.is_mut()); + TypeRef::Reference(Box::new(inner_ty), mutability) + } + PlaceholderType(_inner) => TypeRef::Placeholder, + FnPointerType(inner) => { + let ret_ty = TypeRef::from_ast_opt(inner.ret_type().and_then(|rt| rt.type_ref())); + let mut params = if let Some(pl) = inner.param_list() { + pl.params() + .map(|p| p.type_ref()) + .map(TypeRef::from_ast_opt) + .collect() + } else { + Vec::new() + }; + params.push(ret_ty); + TypeRef::Fn(params) + } + // for types are close enough for our purposes to the inner type for now... + ForType(inner) => TypeRef::from_ast_opt(inner.type_ref()), + ImplTraitType(_inner) => TypeRef::Error, + DynTraitType(_inner) => TypeRef::Error, + } + } + + pub(crate) fn from_ast_opt(node: Option) -> Self { + if let Some(node) = node { + TypeRef::from_ast(node) + } else { + TypeRef::Error + } + } +} diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index 560f649895..5f7038f631 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -19,7 +19,7 @@ pub fn server_capabilities() -> ServerCapabilities { hover_provider: Some(true), completion_provider: Some(CompletionOptions { resolve_provider: None, - trigger_characters: Some(vec![":".to_string()]), + trigger_characters: Some(vec![":".to_string(), ".".to_string()]), }), signature_help_provider: Some(SignatureHelpOptions { trigger_characters: Some(vec!["(".to_string(), ",".to_string(), ")".to_string()]), diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 051f1f995b..c0e4e3a36a 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs @@ -55,7 +55,10 @@ impl Conv for CompletionItemKind { CompletionItemKind::Snippet => Snippet, CompletionItemKind::Module => Module, CompletionItemKind::Function => Function, + CompletionItemKind::Struct => Struct, + CompletionItemKind::Enum => Enum, CompletionItemKind::Binding => Variable, + CompletionItemKind::Field => Field, } } } diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index f12479fb46..8fb6b64083 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -363,3 +363,73 @@ impl<'a, N: AstNode<'a>> Iterator for AstChildren<'a, N> { } } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructFlavor<'a> { + Tuple(PosFieldList<'a>), + Named(NamedFieldDefList<'a>), + Unit, +} + +impl<'a> StructFlavor<'a> { + fn from_node>(node: N) -> StructFlavor<'a> { + if let Some(nfdl) = child_opt::<_, NamedFieldDefList>(node) { + StructFlavor::Named(nfdl) + } else if let Some(pfl) = child_opt::<_, PosFieldList>(node) { + StructFlavor::Tuple(pfl) + } else { + StructFlavor::Unit + } + } +} + +impl<'a> StructDef<'a> { + pub fn flavor(self) -> StructFlavor<'a> { + StructFlavor::from_node(self) + } +} + +impl<'a> EnumVariant<'a> { + pub fn flavor(self) -> StructFlavor<'a> { + StructFlavor::from_node(self) + } +} + +impl<'a> PointerType<'a> { + pub fn is_mut(&self) -> bool { + self.syntax().children().any(|n| n.kind() == MUT_KW) + } +} + +impl<'a> ReferenceType<'a> { + pub fn is_mut(&self) -> bool { + self.syntax().children().any(|n| n.kind() == MUT_KW) + } +} + +impl<'a> RefExpr<'a> { + pub fn is_mut(&self) -> bool { + self.syntax().children().any(|n| n.kind() == MUT_KW) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum PrefixOp { + /// The `*` operator for dereferencing + Deref, + /// The `!` operator for logical inversion + Not, + /// The `-` operator for negation + Neg, +} + +impl<'a> PrefixExpr<'a> { + pub fn op(&self) -> Option { + match self.syntax().first_child()?.kind() { + STAR => Some(PrefixOp::Deref), + EXCL => Some(PrefixOp::Not), + MINUS => Some(PrefixOp::Neg), + _ => None, + } + } +} diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index c735338619..c22e026cf9 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -131,7 +131,15 @@ impl> ArrayTypeNode { } -impl<'a> ArrayType<'a> {} +impl<'a> ArrayType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } + + pub fn expr(self) -> Option> { + super::child_opt(self) + } +} // Attr #[derive(Debug, Clone, Copy,)] @@ -806,7 +814,94 @@ impl<'a> ast::NameOwner<'a> for EnumDef<'a> {} impl<'a> ast::TypeParamsOwner<'a> for EnumDef<'a> {} impl<'a> ast::AttrsOwner<'a> for EnumDef<'a> {} impl<'a> ast::DocCommentsOwner<'a> for EnumDef<'a> {} -impl<'a> EnumDef<'a> {} +impl<'a> EnumDef<'a> { + pub fn variant_list(self) -> Option> { + super::child_opt(self) + } +} + +// EnumVariant +#[derive(Debug, Clone, Copy,)] +pub struct EnumVariantNode = OwnedRoot> { + pub(crate) syntax: SyntaxNode, +} +pub type EnumVariant<'a> = EnumVariantNode>; + +impl, R2: TreeRoot> PartialEq> for EnumVariantNode { + fn eq(&self, other: &EnumVariantNode) -> bool { self.syntax == other.syntax } +} +impl> Eq for EnumVariantNode {} +impl> Hash for EnumVariantNode { + fn hash(&self, state: &mut H) { self.syntax.hash(state) } +} + +impl<'a> AstNode<'a> for EnumVariant<'a> { + fn cast(syntax: SyntaxNodeRef<'a>) -> Option { + match syntax.kind() { + ENUM_VARIANT => Some(EnumVariant { syntax }), + _ => None, + } + } + fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } +} + +impl> EnumVariantNode { + pub fn borrowed(&self) -> EnumVariant { + EnumVariantNode { syntax: self.syntax.borrowed() } + } + pub fn owned(&self) -> EnumVariantNode { + EnumVariantNode { syntax: self.syntax.owned() } + } +} + + +impl<'a> ast::NameOwner<'a> for EnumVariant<'a> {} +impl<'a> EnumVariant<'a> { + pub fn expr(self) -> Option> { + super::child_opt(self) + } +} + +// EnumVariantList +#[derive(Debug, Clone, Copy,)] +pub struct EnumVariantListNode = OwnedRoot> { + pub(crate) syntax: SyntaxNode, +} +pub type EnumVariantList<'a> = EnumVariantListNode>; + +impl, R2: TreeRoot> PartialEq> for EnumVariantListNode { + fn eq(&self, other: &EnumVariantListNode) -> bool { self.syntax == other.syntax } +} +impl> Eq for EnumVariantListNode {} +impl> Hash for EnumVariantListNode { + fn hash(&self, state: &mut H) { self.syntax.hash(state) } +} + +impl<'a> AstNode<'a> for EnumVariantList<'a> { + fn cast(syntax: SyntaxNodeRef<'a>) -> Option { + match syntax.kind() { + ENUM_VARIANT_LIST => Some(EnumVariantList { syntax }), + _ => None, + } + } + fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } +} + +impl> EnumVariantListNode { + pub fn borrowed(&self) -> EnumVariantList { + EnumVariantListNode { syntax: self.syntax.borrowed() } + } + pub fn owned(&self) -> EnumVariantListNode { + EnumVariantListNode { syntax: self.syntax.owned() } + } +} + + +impl<'a> EnumVariantList<'a> { + pub fn variants(self) -> impl Iterator> + 'a { + super::children(self) + } +} // Expr #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1036,7 +1131,15 @@ impl> FieldExprNode { } -impl<'a> FieldExpr<'a> {} +impl<'a> FieldExpr<'a> { + pub fn expr(self) -> Option> { + super::child_opt(self) + } + + pub fn name_ref(self) -> Option> { + super::child_opt(self) + } +} // FieldPatList #[derive(Debug, Clone, Copy,)] @@ -1163,7 +1266,15 @@ impl> FnPointerTypeNode { } -impl<'a> FnPointerType<'a> {} +impl<'a> FnPointerType<'a> { + pub fn param_list(self) -> Option> { + super::child_opt(self) + } + + pub fn ret_type(self) -> Option> { + super::child_opt(self) + } +} // ForExpr #[derive(Debug, Clone, Copy,)] @@ -1246,7 +1357,11 @@ impl> ForTypeNode { } -impl<'a> ForType<'a> {} +impl<'a> ForType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} // IfExpr #[derive(Debug, Clone, Copy,)] @@ -1935,6 +2050,10 @@ impl<'a> MethodCallExpr<'a> { pub fn expr(self) -> Option> { super::child_opt(self) } + + pub fn name_ref(self) -> Option> { + super::child_opt(self) + } } // Module @@ -2142,7 +2261,15 @@ impl> NamedFieldNode { } -impl<'a> NamedField<'a> {} +impl<'a> NamedField<'a> { + pub fn name_ref(self) -> Option> { + super::child_opt(self) + } + + pub fn expr(self) -> Option> { + super::child_opt(self) + } +} // NamedFieldDef #[derive(Debug, Clone, Copy,)] @@ -2181,7 +2308,52 @@ impl> NamedFieldDefNode { impl<'a> ast::NameOwner<'a> for NamedFieldDef<'a> {} impl<'a> ast::AttrsOwner<'a> for NamedFieldDef<'a> {} -impl<'a> NamedFieldDef<'a> {} +impl<'a> NamedFieldDef<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} + +// NamedFieldDefList +#[derive(Debug, Clone, Copy,)] +pub struct NamedFieldDefListNode = OwnedRoot> { + pub(crate) syntax: SyntaxNode, +} +pub type NamedFieldDefList<'a> = NamedFieldDefListNode>; + +impl, R2: TreeRoot> PartialEq> for NamedFieldDefListNode { + fn eq(&self, other: &NamedFieldDefListNode) -> bool { self.syntax == other.syntax } +} +impl> Eq for NamedFieldDefListNode {} +impl> Hash for NamedFieldDefListNode { + fn hash(&self, state: &mut H) { self.syntax.hash(state) } +} + +impl<'a> AstNode<'a> for NamedFieldDefList<'a> { + fn cast(syntax: SyntaxNodeRef<'a>) -> Option { + match syntax.kind() { + NAMED_FIELD_DEF_LIST => Some(NamedFieldDefList { syntax }), + _ => None, + } + } + fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } +} + +impl> NamedFieldDefListNode { + pub fn borrowed(&self) -> NamedFieldDefList { + NamedFieldDefListNode { syntax: self.syntax.borrowed() } + } + pub fn owned(&self) -> NamedFieldDefListNode { + NamedFieldDefListNode { syntax: self.syntax.owned() } + } +} + + +impl<'a> NamedFieldDefList<'a> { + pub fn fields(self) -> impl Iterator> + 'a { + super::children(self) + } +} // NamedFieldList #[derive(Debug, Clone, Copy,)] @@ -2218,7 +2390,11 @@ impl> NamedFieldListNode { } -impl<'a> NamedFieldList<'a> {} +impl<'a> NamedFieldList<'a> { + pub fn fields(self) -> impl Iterator> + 'a { + super::children(self) + } +} // NeverType #[derive(Debug, Clone, Copy,)] @@ -2451,7 +2627,11 @@ impl> ParenTypeNode { } -impl<'a> ParenType<'a> {} +impl<'a> ParenType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} // Pat #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -2816,7 +2996,94 @@ impl> PointerTypeNode { } -impl<'a> PointerType<'a> {} +impl<'a> PointerType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} + +// PosField +#[derive(Debug, Clone, Copy,)] +pub struct PosFieldNode = OwnedRoot> { + pub(crate) syntax: SyntaxNode, +} +pub type PosField<'a> = PosFieldNode>; + +impl, R2: TreeRoot> PartialEq> for PosFieldNode { + fn eq(&self, other: &PosFieldNode) -> bool { self.syntax == other.syntax } +} +impl> Eq for PosFieldNode {} +impl> Hash for PosFieldNode { + fn hash(&self, state: &mut H) { self.syntax.hash(state) } +} + +impl<'a> AstNode<'a> for PosField<'a> { + fn cast(syntax: SyntaxNodeRef<'a>) -> Option { + match syntax.kind() { + POS_FIELD => Some(PosField { syntax }), + _ => None, + } + } + fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } +} + +impl> PosFieldNode { + pub fn borrowed(&self) -> PosField { + PosFieldNode { syntax: self.syntax.borrowed() } + } + pub fn owned(&self) -> PosFieldNode { + PosFieldNode { syntax: self.syntax.owned() } + } +} + + +impl<'a> ast::AttrsOwner<'a> for PosField<'a> {} +impl<'a> PosField<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} + +// PosFieldList +#[derive(Debug, Clone, Copy,)] +pub struct PosFieldListNode = OwnedRoot> { + pub(crate) syntax: SyntaxNode, +} +pub type PosFieldList<'a> = PosFieldListNode>; + +impl, R2: TreeRoot> PartialEq> for PosFieldListNode { + fn eq(&self, other: &PosFieldListNode) -> bool { self.syntax == other.syntax } +} +impl> Eq for PosFieldListNode {} +impl> Hash for PosFieldListNode { + fn hash(&self, state: &mut H) { self.syntax.hash(state) } +} + +impl<'a> AstNode<'a> for PosFieldList<'a> { + fn cast(syntax: SyntaxNodeRef<'a>) -> Option { + match syntax.kind() { + POS_FIELD_LIST => Some(PosFieldList { syntax }), + _ => None, + } + } + fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } +} + +impl> PosFieldListNode { + pub fn borrowed(&self) -> PosFieldList { + PosFieldListNode { syntax: self.syntax.borrowed() } + } + pub fn owned(&self) -> PosFieldListNode { + PosFieldListNode { syntax: self.syntax.owned() } + } +} + + +impl<'a> PosFieldList<'a> { + pub fn fields(self) -> impl Iterator> + 'a { + super::children(self) + } +} // PrefixExpr #[derive(Debug, Clone, Copy,)] @@ -3046,7 +3313,11 @@ impl> ReferenceTypeNode { } -impl<'a> ReferenceType<'a> {} +impl<'a> ReferenceType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} // RetType #[derive(Debug, Clone, Copy,)] @@ -3239,7 +3510,11 @@ impl> SliceTypeNode { } -impl<'a> SliceType<'a> {} +impl<'a> SliceType<'a> { + pub fn type_ref(self) -> Option> { + super::child_opt(self) + } +} // SourceFile #[derive(Debug, Clone, Copy,)] @@ -3426,11 +3701,7 @@ impl<'a> ast::NameOwner<'a> for StructDef<'a> {} impl<'a> ast::TypeParamsOwner<'a> for StructDef<'a> {} impl<'a> ast::AttrsOwner<'a> for StructDef<'a> {} impl<'a> ast::DocCommentsOwner<'a> for StructDef<'a> {} -impl<'a> StructDef<'a> { - pub fn fields(self) -> impl Iterator> + 'a { - super::children(self) - } -} +impl<'a> StructDef<'a> {} // StructLit #[derive(Debug, Clone, Copy,)] @@ -3467,7 +3738,15 @@ impl> StructLitNode { } -impl<'a> StructLit<'a> {} +impl<'a> StructLit<'a> { + pub fn path(self) -> Option> { + super::child_opt(self) + } + + pub fn named_field_list(self) -> Option> { + super::child_opt(self) + } +} // StructPat #[derive(Debug, Clone, Copy,)] @@ -3770,7 +4049,11 @@ impl> TupleTypeNode { } -impl<'a> TupleType<'a> {} +impl<'a> TupleType<'a> { + pub fn fields(self) -> impl Iterator> + 'a { + super::children(self) + } +} // TypeDef #[derive(Debug, Clone, Copy,)] diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index e3b9032a0c..4bcff4e144 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -261,18 +261,20 @@ Grammar( "TypeParamsOwner", "AttrsOwner", "DocCommentsOwner" - ], - collections: [ - ["fields", "NamedFieldDef"] ] ), - "NamedFieldDef": ( traits: ["NameOwner", "AttrsOwner"] ), + "NamedFieldDefList": (collections: [["fields", "NamedFieldDef"]]), + "NamedFieldDef": ( traits: ["NameOwner", "AttrsOwner"], options: ["TypeRef"] ), + "PosFieldList": (collections: [["fields", "PosField"]]), + "PosField": ( traits: ["AttrsOwner"], options: ["TypeRef"]), "EnumDef": ( traits: [ "NameOwner", "TypeParamsOwner", "AttrsOwner", "DocCommentsOwner" - ] ), + ], options: [["variant_list", "EnumVariantList"]] ), + "EnumVariantList": ( collections: [["variants", "EnumVariant"]] ), + "EnumVariant": ( traits: ["NameOwner"], options: ["Expr"] ), "TraitDef": ( traits: ["NameOwner", "AttrsOwner", "DocCommentsOwner"] ), "Module": ( traits: ["NameOwner", "AttrsOwner", "DocCommentsOwner" ], @@ -301,17 +303,17 @@ Grammar( ] ), "ImplItem": (), - "ParenType": (), - "TupleType": (), + "ParenType": (options: ["TypeRef"]), + "TupleType": ( collections: [["fields", "TypeRef"]] ), "NeverType": (), "PathType": (options: ["Path"]), - "PointerType": (), - "ArrayType": (), - "SliceType": (), - "ReferenceType": (), + "PointerType": (options: ["TypeRef"]), + "ArrayType": ( options: ["TypeRef", "Expr"] ), + "SliceType": ( options: ["TypeRef"] ), + "ReferenceType": (options: ["TypeRef"]), "PlaceholderType": (), - "FnPointerType": (), - "ForType": (), + "FnPointerType": (options: ["ParamList", "RetType"]), + "ForType": (options: ["TypeRef"]), "ImplTraitType": (), "DynTraitType": (), @@ -392,19 +394,19 @@ Grammar( collections: [ [ "pats", "Pat" ] ] ), "MatchGuard": (), - "StructLit": (), - "NamedFieldList": (), - "NamedField": (), + "StructLit": (options: ["Path", "NamedFieldList"]), + "NamedFieldList": (collections: [ ["fields", "NamedField"] ]), + "NamedField": (options: ["NameRef", "Expr"]), "CallExpr": ( traits: ["ArgListOwner"], options: [ "Expr" ], ), "MethodCallExpr": ( traits: ["ArgListOwner"], - options: [ "Expr" ], + options: [ "Expr", "NameRef" ], ), "IndexExpr": (), - "FieldExpr": (), + "FieldExpr": (options: ["Expr", "NameRef"]), "TryExpr": (options: ["Expr"]), "CastExpr": (options: ["Expr", "TypeRef"]), "RefExpr": (options: ["Expr"]), diff --git a/crates/ra_syntax/src/grammar/expressions.rs b/crates/ra_syntax/src/grammar/expressions.rs index da78d85a2f..2d1f174912 100644 --- a/crates/ra_syntax/src/grammar/expressions.rs +++ b/crates/ra_syntax/src/grammar/expressions.rs @@ -283,14 +283,10 @@ fn postfix_expr( // } L_PAREN if allow_calls => call_expr(p, lhs), L_BRACK if allow_calls => index_expr(p, lhs), - DOT if p.nth(1) == IDENT => { - if p.nth(2) == L_PAREN || p.nth(2) == COLONCOLON { - method_call_expr(p, lhs) - } else { - field_expr(p, lhs) - } + DOT if p.nth(1) == IDENT && (p.nth(2) == L_PAREN || p.nth(2) == COLONCOLON) => { + method_call_expr(p, lhs) } - DOT if p.nth(1) == INT_NUMBER => field_expr(p, lhs), + DOT => field_expr(p, lhs), // test postfix_range // fn foo() { let x = 1..; } DOTDOT | DOTDOTEQ if !EXPR_FIRST.contains(p.nth(1)) => { @@ -355,13 +351,15 @@ fn method_call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { // x.0.bar; // } fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { - assert!(p.at(DOT) && (p.nth(1) == IDENT || p.nth(1) == INT_NUMBER)); + assert!(p.at(DOT)); let m = lhs.precede(p); p.bump(); if p.at(IDENT) { name_ref(p) - } else { + } else if p.at(INT_NUMBER) { p.bump() + } else { + p.error("expected field name or number") } m.complete(p, FIELD_EXPR) } diff --git a/crates/ra_syntax/tests/data/parser/err/0029_field_completion.rs b/crates/ra_syntax/tests/data/parser/err/0029_field_completion.rs new file mode 100644 index 0000000000..a7cdc17bb1 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/err/0029_field_completion.rs @@ -0,0 +1,3 @@ +fn foo(a: A) { + a. +} diff --git a/crates/ra_syntax/tests/data/parser/err/0029_field_completion.txt b/crates/ra_syntax/tests/data/parser/err/0029_field_completion.txt new file mode 100644 index 0000000000..fd2a3f37b7 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/err/0029_field_completion.txt @@ -0,0 +1,35 @@ +SOURCE_FILE@[0; 24) + FN_DEF@[0; 23) + FN_KW@[0; 2) + WHITESPACE@[2; 3) + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 12) + L_PAREN@[6; 7) + PARAM@[7; 11) + BIND_PAT@[7; 8) + NAME@[7; 8) + IDENT@[7; 8) "a" + COLON@[8; 9) + WHITESPACE@[9; 10) + PATH_TYPE@[10; 11) + PATH@[10; 11) + PATH_SEGMENT@[10; 11) + NAME_REF@[10; 11) + IDENT@[10; 11) "A" + R_PAREN@[11; 12) + WHITESPACE@[12; 13) + BLOCK@[13; 23) + L_CURLY@[13; 14) + WHITESPACE@[14; 19) + FIELD_EXPR@[19; 21) + PATH_EXPR@[19; 20) + PATH@[19; 20) + PATH_SEGMENT@[19; 20) + NAME_REF@[19; 20) + IDENT@[19; 20) "a" + DOT@[20; 21) + err: `expected field name or number` + WHITESPACE@[21; 22) + R_CURLY@[22; 23) + WHITESPACE@[23; 24)