Repository: rust-analyzer/rust-analyzer
Pull request #332 (2018-12-25 14:47:02)

Struct types
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.


Patch:
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<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,
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<Arc<VariantData>> {
+        Ok(db.struct_data(self.def_id)?.variant_data.clone())
+    }
+
+    pub fn struct_data(&self, db: &impl HirDatabase) -> Cancelable<Arc<StructData>> {
+        Ok(db.struct_data(self.def_id)?)
+    }
+
+    pub fn name(&self, db: &impl HirDatabase) -> Cancelable<Option<SmolStr>> {
+        Ok(db.struct_data(self.def_id)?.name.clone())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StructData {
+    name: Option<SmolStr>,
+    variant_data: Arc<VariantData>,
+}
+
+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<VariantData> {
+        &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<Option<SmolStr>> {
+        Ok(db.enum_data(self.def_id)?.name.clone())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct EnumData {
+    name: Option<SmolStr>,
+    variants: Vec<(SmolStr, Arc<VariantData>)>,
+}
+
+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<StructField>),
+    Tuple(Vec<StructField>),
+    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<Arc<StructData>> {
+        type StructDataQuery;
+        use fn query_definitions::struct_data;
+    }
+
+    fn enum_data(def_id: DefId) -> Cancelable<Arc<EnumData>> {
+        type EnumDataQuery;
+        use fn query_definitions::enum_data;
+    }
+
     fn infer(fn_id: FnId) -> Cancelable<Arc<InferenceResult>> {
         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<Ty> {
+        type TypeForFieldQuery;
+        use fn query_definitions::type_for_field;
+    }
+
     fn file_items(file_id: FileId) -> Arc<SourceFileItems> {
         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<Module> {
-        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<DefKind> {
+    pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> PerNs<DefKind> {
         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<Module> {
+        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<Option<DefId>> {
-        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<PerNs<DefId>> {
+        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<DefId>,
+    pub def_id: PerNs<DefId>,
     /// ident by whitch this is imported into local scope.
     pub import: Option<NamedImport>,
 }
 
-// #[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, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct PerNs<T> {
+    pub types: Option<T>,
+    pub values: Option<T>,
+}
+
+impl<T> PerNs<T> {
+    pub fn none() -> PerNs<T> {
+        PerNs {
+            types: None,
+            values: None,
+        }
+    }
+
+    pub fn values(t: T) -> PerNs<T> {
+        PerNs {
+            types: None,
+            values: Some(t),
+        }
+    }
+
+    pub fn types(t: T) -> PerNs<T> {
+        PerNs {
+            types: Some(t),
+            values: None,
+        }
+    }
+
+    pub fn both(types: T, values: T) -> PerNs<T> {
+        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<T> {
+        match namespace {
+            Namespace::Types => self.types,
+            Namespace::Values => self.values,
+        }
+    }
+
+    pub fn take_types(self) -> Option<T> {
+        self.types
+    }
+
+    pub fn take_values(self) -> Option<T> {
+        self.values
+    }
 
-// #[derive(Debug)]
-// struct PerNs<T> {
-//     types: Option<T>,
-//     values: Option<T>,
-// }
+    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<U>(self, f: impl Fn(T) -> Option<U>) -> PerNs<U> {
+        PerNs {
+            types: self.types.and_then(&f),
+            values: self.values.and_then(&f),
+        }
+    }
+
+    pub fn map<U>(self, f: impl Fn(T) -> U) -> PerNs<U> {
+        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<DefId>) {
         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/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<SmolStr>,
 }
 
-#[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<T
     ty::type_for_def(db, def_id)
 }
 
+pub(super) fn type_for_field(
+    db: &impl HirDatabase,
+    def_id: DefId,
+    field: SmolStr,
+) -> Cancelable<Ty> {
+    ty::type_for_field(db, def_id, field)
+}
+
+pub(super) fn struct_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<StructData>> {
+    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<Arc<EnumData>> {
+    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<SourceFileItems> {
     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<Ty>;
 
-#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
 pub struct FnSig {
     input: Vec<Ty>,
     output: Ty,
 }
 
 impl Ty {
-    pub fn new(_db: &impl HirDatabase, node: ast::TypeRef) -> Cancelable<Self> {
-        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);
+    pub(crate) fn from_hir(
+        db: &impl HirDatabase,
+        module: &Module,
+        type_ref: &TypeRef,
+    ) -> Cancelable<Self> {
+        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::<Cancelable<_>>()?;
+                Ty::Tuple(inner_tys)
+            }
+            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::<Cancelable<Vec<_>>>()?;
+                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,
                 };
-                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
-                }
+                Ty::FnPtr(Arc::new(sig))
             }
-            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::Error => Ty::Unknown,
         })
     }
 
+    pub(crate) fn from_hir_path(
+        db: &impl HirDatabase,
+        module: &Module,
+        path: &Path,
+    ) -> Cancelable<Self> {
+        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<ast::TypeRef>,
+    ) -> Cancelable<Self> {
+        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<Self> {
+        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<Ty> {
     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<Ty> {
+    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<Ty> {
+    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<Ty> {
     let def = def_id.resolve(db)?;
     match def {
@@ -232,6 +300,8 @@ pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Ty> {
             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<Ty> {
     }
 }
 
+pub(super) fn type_for_field(
+    db: &impl HirDatabase,
+    def_id: DefId,
+    field: SmolStr,
+) -> Cancelable<Ty> {
+    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<LocalSyntaxPtr, Ty>,
@@ -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<ast::Path>,
+    ) -> Cancelable<(Ty, Option<Arc<VariantData>>)> {
+        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<ast::Expr>) -> Cancelable<Ty> {
+        if let Some(e) = expr {
+            self.infer_expr(e)
+        } else {
+            Ok(Ty::Unknown)
+        }
+    }
+
     fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable<Ty> {
         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::<usize>().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<ast::Block>) -> Cancelable<Ty> {
+        if let Some(b) = node {
+            self.infer_block(b)
+        } else {
+            Ok(Ty::Unknown)
+        }
+    }
+
     fn infer_block(&mut self, node: ast::Block) -> Cancelable<Ty> {
         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<InferenceR
                 continue;
             };
             if let Some(type_ref) = param.type_ref() {
-                let ty = Ty::new(db, type_ref)?;
+                let ty = Ty::from_ast(db, &ctx.module, type_ref)?;
                 ctx.type_of.insert(LocalSyntaxPtr::new(pat.syntax()), ty);
             } else {
                 // TODO self param
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<TypeRef>),
+    Path(Path),
+    RawPtr(Box<TypeRef>, Mutability),
+    Reference(Box<TypeRef>, Mutability),
+    Array(Box<TypeRef> /*, Expr*/),
+    Slice(Box<TypeRef>),
+    /// A fn pointer. Last element of the vector is the return type.
+    Fn(Vec<TypeRef>),
+    // 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<ast::TypeRef>) -> 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<N: AstNode<'a>>(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<PrefixOp> {
+        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<R: TreeRoot<RaTypes>> ArrayTypeNode<R> {
 }
 
 
-impl<'a> ArrayType<'a> {}
+impl<'a> ArrayType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+
+    pub fn expr(self) -> Option<Expr<'a>> {
+        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<EnumVariantList<'a>> {
+        super::child_opt(self)
+    }
+}
+
+// EnumVariant
+#[derive(Debug, Clone, Copy,)]
+pub struct EnumVariantNode<R: TreeRoot<RaTypes> = OwnedRoot> {
+    pub(crate) syntax: SyntaxNode<R>,
+}
+pub type EnumVariant<'a> = EnumVariantNode<RefRoot<'a>>;
+
+impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<EnumVariantNode<R1>> for EnumVariantNode<R2> {
+    fn eq(&self, other: &EnumVariantNode<R1>) -> bool { self.syntax == other.syntax }
+}
+impl<R: TreeRoot<RaTypes>> Eq for EnumVariantNode<R> {}
+impl<R: TreeRoot<RaTypes>> Hash for EnumVariantNode<R> {
+    fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
+}
+
+impl<'a> AstNode<'a> for EnumVariant<'a> {
+    fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
+        match syntax.kind() {
+            ENUM_VARIANT => Some(EnumVariant { syntax }),
+            _ => None,
+        }
+    }
+    fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
+}
+
+impl<R: TreeRoot<RaTypes>> EnumVariantNode<R> {
+    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<Expr<'a>> {
+        super::child_opt(self)
+    }
+}
+
+// EnumVariantList
+#[derive(Debug, Clone, Copy,)]
+pub struct EnumVariantListNode<R: TreeRoot<RaTypes> = OwnedRoot> {
+    pub(crate) syntax: SyntaxNode<R>,
+}
+pub type EnumVariantList<'a> = EnumVariantListNode<RefRoot<'a>>;
+
+impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<EnumVariantListNode<R1>> for EnumVariantListNode<R2> {
+    fn eq(&self, other: &EnumVariantListNode<R1>) -> bool { self.syntax == other.syntax }
+}
+impl<R: TreeRoot<RaTypes>> Eq for EnumVariantListNode<R> {}
+impl<R: TreeRoot<RaTypes>> Hash for EnumVariantListNode<R> {
+    fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
+}
+
+impl<'a> AstNode<'a> for EnumVariantList<'a> {
+    fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
+        match syntax.kind() {
+            ENUM_VARIANT_LIST => Some(EnumVariantList { syntax }),
+            _ => None,
+        }
+    }
+    fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
+}
+
+impl<R: TreeRoot<RaTypes>> EnumVariantListNode<R> {
+    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<Item = EnumVariant<'a>> + 'a {
+        super::children(self)
+    }
+}
 
 // Expr
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -1036,7 +1131,15 @@ impl<R: TreeRoot<RaTypes>> FieldExprNode<R> {
 }
 
 
-impl<'a> FieldExpr<'a> {}
+impl<'a> FieldExpr<'a> {
+    pub fn expr(self) -> Option<Expr<'a>> {
+        super::child_opt(self)
+    }
+
+    pub fn name_ref(self) -> Option<NameRef<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // FieldPatList
 #[derive(Debug, Clone, Copy,)]
@@ -1163,7 +1266,15 @@ impl<R: TreeRoot<RaTypes>> FnPointerTypeNode<R> {
 }
 
 
-impl<'a> FnPointerType<'a> {}
+impl<'a> FnPointerType<'a> {
+    pub fn param_list(self) -> Option<ParamList<'a>> {
+        super::child_opt(self)
+    }
+
+    pub fn ret_type(self) -> Option<RetType<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // ForExpr
 #[derive(Debug, Clone, Copy,)]
@@ -1246,7 +1357,11 @@ impl<R: TreeRoot<RaTypes>> ForTypeNode<R> {
 }
 
 
-impl<'a> ForType<'a> {}
+impl<'a> ForType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // IfExpr
 #[derive(Debug, Clone, Copy,)]
@@ -1935,6 +2050,10 @@ impl<'a> MethodCallExpr<'a> {
     pub fn expr(self) -> Option<Expr<'a>> {
         super::child_opt(self)
     }
+
+    pub fn name_ref(self) -> Option<NameRef<'a>> {
+        super::child_opt(self)
+    }
 }
 
 // Module
@@ -2142,7 +2261,15 @@ impl<R: TreeRoot<RaTypes>> NamedFieldNode<R> {
 }
 
 
-impl<'a> NamedField<'a> {}
+impl<'a> NamedField<'a> {
+    pub fn name_ref(self) -> Option<NameRef<'a>> {
+        super::child_opt(self)
+    }
+
+    pub fn expr(self) -> Option<Expr<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // NamedFieldDef
 #[derive(Debug, Clone, Copy,)]
@@ -2181,7 +2308,52 @@ impl<R: TreeRoot<RaTypes>> NamedFieldDefNode<R> {
 
 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<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
+
+// NamedFieldDefList
+#[derive(Debug, Clone, Copy,)]
+pub struct NamedFieldDefListNode<R: TreeRoot<RaTypes> = OwnedRoot> {
+    pub(crate) syntax: SyntaxNode<R>,
+}
+pub type NamedFieldDefList<'a> = NamedFieldDefListNode<RefRoot<'a>>;
+
+impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<NamedFieldDefListNode<R1>> for NamedFieldDefListNode<R2> {
+    fn eq(&self, other: &NamedFieldDefListNode<R1>) -> bool { self.syntax == other.syntax }
+}
+impl<R: TreeRoot<RaTypes>> Eq for NamedFieldDefListNode<R> {}
+impl<R: TreeRoot<RaTypes>> Hash for NamedFieldDefListNode<R> {
+    fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
+}
+
+impl<'a> AstNode<'a> for NamedFieldDefList<'a> {
+    fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
+        match syntax.kind() {
+            NAMED_FIELD_DEF_LIST => Some(NamedFieldDefList { syntax }),
+            _ => None,
+        }
+    }
+    fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
+}
+
+impl<R: TreeRoot<RaTypes>> NamedFieldDefListNode<R> {
+    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<Item = NamedFieldDef<'a>> + 'a {
+        super::children(self)
+    }
+}
 
 // NamedFieldList
 #[derive(Debug, Clone, Copy,)]
@@ -2218,7 +2390,11 @@ impl<R: TreeRoot<RaTypes>> NamedFieldListNode<R> {
 }
 
 
-impl<'a> NamedFieldList<'a> {}
+impl<'a> NamedFieldList<'a> {
+    pub fn fields(self) -> impl Iterator<Item = NamedField<'a>> + 'a {
+        super::children(self)
+    }
+}
 
 // NeverType
 #[derive(Debug, Clone, Copy,)]
@@ -2451,7 +2627,11 @@ impl<R: TreeRoot<RaTypes>> ParenTypeNode<R> {
 }
 
 
-impl<'a> ParenType<'a> {}
+impl<'a> ParenType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // Pat
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -2816,7 +2996,94 @@ impl<R: TreeRoot<RaTypes>> PointerTypeNode<R> {
 }
 
 
-impl<'a> PointerType<'a> {}
+impl<'a> PointerType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
+
+// PosField
+#[derive(Debug, Clone, Copy,)]
+pub struct PosFieldNode<R: TreeRoot<RaTypes> = OwnedRoot> {
+    pub(crate) syntax: SyntaxNode<R>,
+}
+pub type PosField<'a> = PosFieldNode<RefRoot<'a>>;
+
+impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<PosFieldNode<R1>> for PosFieldNode<R2> {
+    fn eq(&self, other: &PosFieldNode<R1>) -> bool { self.syntax == other.syntax }
+}
+impl<R: TreeRoot<RaTypes>> Eq for PosFieldNode<R> {}
+impl<R: TreeRoot<RaTypes>> Hash for PosFieldNode<R> {
+    fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
+}
+
+impl<'a> AstNode<'a> for PosField<'a> {
+    fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
+        match syntax.kind() {
+            POS_FIELD => Some(PosField { syntax }),
+            _ => None,
+        }
+    }
+    fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
+}
+
+impl<R: TreeRoot<RaTypes>> PosFieldNode<R> {
+    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<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
+
+// PosFieldList
+#[derive(Debug, Clone, Copy,)]
+pub struct PosFieldListNode<R: TreeRoot<RaTypes> = OwnedRoot> {
+    pub(crate) syntax: SyntaxNode<R>,
+}
+pub type PosFieldList<'a> = PosFieldListNode<RefRoot<'a>>;
+
+impl<R1: TreeRoot<RaTypes>, R2: TreeRoot<RaTypes>> PartialEq<PosFieldListNode<R1>> for PosFieldListNode<R2> {
+    fn eq(&self, other: &PosFieldListNode<R1>) -> bool { self.syntax == other.syntax }
+}
+impl<R: TreeRoot<RaTypes>> Eq for PosFieldListNode<R> {}
+impl<R: TreeRoot<RaTypes>> Hash for PosFieldListNode<R> {
+    fn hash<H: Hasher>(&self, state: &mut H) { self.syntax.hash(state) }
+}
+
+impl<'a> AstNode<'a> for PosFieldList<'a> {
+    fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
+        match syntax.kind() {
+            POS_FIELD_LIST => Some(PosFieldList { syntax }),
+            _ => None,
+        }
+    }
+    fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
+}
+
+impl<R: TreeRoot<RaTypes>> PosFieldListNode<R> {
+    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<Item = PosField<'a>> + 'a {
+        super::children(self)
+    }
+}
 
 // PrefixExpr
 #[derive(Debug, Clone, Copy,)]
@@ -3046,7 +3313,11 @@ impl<R: TreeRoot<RaTypes>> ReferenceTypeNode<R> {
 }
 
 
-impl<'a> ReferenceType<'a> {}
+impl<'a> ReferenceType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // RetType
 #[derive(Debug, Clone, Copy,)]
@@ -3239,7 +3510,11 @@ impl<R: TreeRoot<RaTypes>> SliceTypeNode<R> {
 }
 
 
-impl<'a> SliceType<'a> {}
+impl<'a> SliceType<'a> {
+    pub fn type_ref(self) -> Option<TypeRef<'a>> {
+        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<Item = NamedFieldDef<'a>> + 'a {
-        super::children(self)
-    }
-}
+impl<'a> StructDef<'a> {}
 
 // StructLit
 #[derive(Debug, Clone, Copy,)]
@@ -3467,7 +3738,15 @@ impl<R: TreeRoot<RaTypes>> StructLitNode<R> {
 }
 
 
-impl<'a> StructLit<'a> {}
+impl<'a> StructLit<'a> {
+    pub fn path(self) -> Option<Path<'a>> {
+        super::child_opt(self)
+    }
+
+    pub fn named_field_list(self) -> Option<NamedFieldList<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // StructPat
 #[derive(Debug, Clone, Copy,)]
@@ -3770,7 +4049,11 @@ impl<R: TreeRoot<RaTypes>> TupleTypeNode<R> {
 }
 
 
-impl<'a> TupleType<'a> {}
+impl<'a> TupleType<'a> {
+    pub fn fields(self) -> impl Iterator<Item = TypeRef<'a>> + '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)
 }


Test patch:
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/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index b6c02cd80c..a76925b58e 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -68,6 +68,51 @@ fn test() {
     );
 }
 
+#[test]
+fn infer_struct() {
+    check_inference(
+        r#"
+struct A {
+    b: B,
+    c: C,
+}
+struct B;
+struct C(usize);
+
+fn test() {
+    let c = C(1);
+    B;
+    let a: A = A { b: B, c: C(1) };
+    a.b;
+    a.c;
+}
+"#,
+        "0004_struct.txt",
+    );
+}
+
+#[test]
+fn infer_refs_and_ptrs() {
+    check_inference(
+        r#"
+fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) {
+    a;
+    *a;
+    &a;
+    &mut a;
+    b;
+    *b;
+    &b;
+    c;
+    *c;
+    d;
+    *d;
+}
+"#,
+        "0005_refs.txt",
+    );
+}
+
 fn infer(content: &str) -> 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_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)