Repository: rust-analyzer/rust-analyzer
Pull request #441 (2019-01-05 22:00:22)

hir::Expr
Still a bit to do, but I already adapted `FnScopes` and thought I'd get feedback already.


Patch:
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
index 4dead36896..21d77aa978 100644
--- a/crates/ra_analysis/src/completion/complete_scope.rs
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -15,7 +15,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) ->
         None => return Ok(()),
     };
     if let Some(function) = &ctx.function {
-        let scopes = function.scopes(ctx.db);
+        let scopes = function.scopes(ctx.db)?;
         complete_fn(acc, &scopes, ctx.offset);
     }
 
@@ -40,20 +40,17 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) ->
     Ok(())
 }
 
-fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) {
+fn complete_fn(acc: &mut Completions, scopes: &hir::ScopesWithSyntaxMapping, offset: TextUnit) {
     let mut shadowed = FxHashSet::default();
     scopes
         .scope_chain_for_offset(offset)
-        .flat_map(|scope| scopes.entries(scope).iter())
+        .flat_map(|scope| scopes.scopes.entries(scope).iter())
         .filter(|entry| shadowed.insert(entry.name()))
         .for_each(|entry| {
             CompletionItem::new(CompletionKind::Reference, entry.name().to_string())
                 .kind(CompletionItemKind::Binding)
                 .add_to(acc)
         });
-    if scopes.self_param.is_some() {
-        CompletionItem::new(CompletionKind::Reference, "self").add_to(acc);
-    }
 }
 
 #[cfg(test)]
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs
index 5422a400b6..1709be5cf4 100644
--- a/crates/ra_analysis/src/db.rs
+++ b/crates/ra_analysis/src/db.rs
@@ -106,6 +106,9 @@ salsa::database_storage! {
             fn struct_data() for hir::db::StructDataQuery;
             fn enum_data() for hir::db::EnumDataQuery;
             fn impls_in_module() for hir::db::ImplsInModuleQuery;
+            fn body_hir() for hir::db::BodyHirQuery;
+            fn body_syntax_mapping() for hir::db::BodySyntaxMappingQuery;
+            fn fn_signature() for hir::db::FnSignatureQuery;
         }
     }
 }
diff --git a/crates/ra_analysis/src/goto_defenition.rs b/crates/ra_analysis/src/goto_defenition.rs
index e37421f8dc..68b6ac3ba4 100644
--- a/crates/ra_analysis/src/goto_defenition.rs
+++ b/crates/ra_analysis/src/goto_defenition.rs
@@ -28,7 +28,7 @@ pub(crate) fn reference_defenition(
     if let Some(fn_descr) =
         hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())?
     {
-        let scope = fn_descr.scopes(db);
+        let scope = fn_descr.scopes(db)?;
         // First try to resolve the symbol locally
         if let Some(entry) = scope.resolve_local_name(name_ref) {
             let nav = NavigationTarget {
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 6ab3c5476b..5988fb7794 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -157,7 +157,7 @@ impl db::RootDatabase {
             .collect::<Vec<_>>();
         ret.extend(
             descr
-                .scopes(self)
+                .scopes(self)?
                 .find_all_refs(binding)
                 .into_iter()
                 .map(|ref_desc| (position.file_id, ref_desc.range)),
@@ -185,7 +185,7 @@ impl db::RootDatabase {
                 position.file_id,
                 name_ref.syntax(),
             )?);
-            let scope = descr.scopes(db);
+            let scope = descr.scopes(db)?;
             let resolved = ctry!(scope.resolve_local_name(name_ref));
             let resolved = resolved.ptr().resolve(source_file);
             let binding = ctry!(find_node_at_offset::<ast::BindPat>(
diff --git a/crates/ra_db/src/syntax_ptr.rs b/crates/ra_db/src/syntax_ptr.rs
index 744cb23523..5bfcedf2bb 100644
--- a/crates/ra_db/src/syntax_ptr.rs
+++ b/crates/ra_db/src/syntax_ptr.rs
@@ -1,6 +1,6 @@
 use ra_syntax::{SourceFileNode, SyntaxKind, SyntaxNode, SyntaxNodeRef, TextRange};
 
-/// A pionter to a syntax node inside a file.
+/// A pointer to a syntax node inside a file.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct LocalSyntaxPtr {
     range: TextRange,
diff --git a/crates/ra_editor/src/assists/split_import.rs b/crates/ra_editor/src/assists/split_import.rs
index e4015f07da..75f9e8dfbb 100644
--- a/crates/ra_editor/src/assists/split_import.rs
+++ b/crates/ra_editor/src/assists/split_import.rs
@@ -19,10 +19,7 @@ pub fn split_import(ctx: AssistCtx) -> Option<Assist> {
     }
 
     let l_curly = colon_colon.range().end();
-    let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) {
-        Some(tree) => tree.syntax().range().end(),
-        None => top_path.syntax().range().end(),
-    };
+    let r_curly = top_path.syntax().range().end();
 
     ctx.build("split import", |edit| {
         edit.insert(l_curly, "{");
@@ -44,13 +41,4 @@ mod tests {
             "use crate::{<|>db::RootDatabase};",
         )
     }
-
-    #[test]
-    fn split_import_works_with_trees() {
-        check_assist(
-            split_import,
-            "use algo:<|>:visitor::{Visitor, visit}",
-            "use algo::{<|>visitor::{Visitor, visit}}",
-        )
-    }
 }
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs
index 58296fc6f3..96a3c60b9e 100644
--- a/crates/ra_hir/src/db.rs
+++ b/crates/ra_hir/src/db.rs
@@ -7,7 +7,7 @@ use crate::{
     DefLoc, DefId, MacroCallLoc, MacroCallId, Name, HirFileId,
     SourceFileItems, SourceItemId,
     query_definitions,
-    FnScopes,
+    FnSignature, FnScopes,
     macros::MacroExpansion,
     module::{ModuleId, ModuleTree, ModuleSource,
     nameres::{ItemMap, InputModuleItems}},
@@ -31,7 +31,7 @@ pub trait HirDatabase: SyntaxDatabase
         use fn crate::macros::expand_macro_invocation;
     }
 
-    fn fn_scopes(def_id: DefId) -> Arc<FnScopes> {
+    fn fn_scopes(def_id: DefId) -> Cancelable<Arc<FnScopes>> {
         type FnScopesQuery;
         use fn query_definitions::fn_scopes;
     }
@@ -93,6 +93,21 @@ pub trait HirDatabase: SyntaxDatabase
         type ImplsInModuleQuery;
         use fn crate::impl_block::impls_in_module;
     }
+
+    fn body_hir(def_id: DefId) -> Cancelable<Arc<crate::expr::Body>> {
+        type BodyHirQuery;
+        use fn crate::expr::body_hir;
+    }
+
+    fn body_syntax_mapping(def_id: DefId) -> Cancelable<Arc<crate::expr::BodySyntaxMapping>> {
+        type BodySyntaxMappingQuery;
+        use fn crate::expr::body_syntax_mapping;
+    }
+
+    fn fn_signature(def_id: DefId) -> Arc<FnSignature> {
+        type FnSignatureQuery;
+        use fn crate::function::fn_signature;
+    }
 }
 
 }
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
new file mode 100644
index 0000000000..6866fc2acf
--- /dev/null
+++ b/crates/ra_hir/src/expr.rs
@@ -0,0 +1,745 @@
+use std::ops::Index;
+use std::sync::Arc;
+
+use rustc_hash::FxHashMap;
+
+use ra_arena::{Arena, RawId, impl_arena_id};
+use ra_db::{LocalSyntaxPtr, Cancelable};
+use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner, NameOwner};
+
+use crate::{Path, type_ref::{Mutability, TypeRef}, Name, HirDatabase, DefId, Def, name::AsName};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ExprId(RawId);
+impl_arena_id!(ExprId);
+
+/// The body of an item (function, const etc.).
+#[derive(Debug, Eq, PartialEq)]
+pub struct Body {
+    exprs: Arena<ExprId, Expr>,
+    pats: Arena<PatId, Pat>,
+    /// The patterns for the function's arguments. While the argument types are
+    /// part of the function signature, the patterns are not (they don't change
+    /// the external type of the function).
+    ///
+    /// If this `Body` is for the body of a constant, this will just be
+    /// empty.
+    args: Vec<PatId>,
+    /// The `ExprId` of the actual body expression.
+    body_expr: ExprId,
+}
+
+/// An item body together with the mapping from syntax nodes to HIR expression
+/// IDs. This is needed to go from e.g. a position in a file to the HIR
+/// expression containing it; but for type inference etc., we want to operate on
+/// a structure that is agnostic to the actual positions of expressions in the
+/// file, so that we don't recompute the type inference whenever some whitespace
+/// is typed.
+#[derive(Debug, Eq, PartialEq)]
+pub struct BodySyntaxMapping {
+    body: Arc<Body>,
+    expr_syntax_mapping: FxHashMap<LocalSyntaxPtr, ExprId>,
+    expr_syntax_mapping_back: FxHashMap<ExprId, LocalSyntaxPtr>,
+    pat_syntax_mapping: FxHashMap<LocalSyntaxPtr, PatId>,
+    pat_syntax_mapping_back: FxHashMap<PatId, LocalSyntaxPtr>,
+}
+
+impl Body {
+    pub fn args(&self) -> &[PatId] {
+        &self.args
+    }
+
+    pub fn body_expr(&self) -> ExprId {
+        self.body_expr
+    }
+}
+
+impl Index<ExprId> for Body {
+    type Output = Expr;
+
+    fn index(&self, expr: ExprId) -> &Expr {
+        &self.exprs[expr]
+    }
+}
+
+impl Index<PatId> for Body {
+    type Output = Pat;
+
+    fn index(&self, pat: PatId) -> &Pat {
+        &self.pats[pat]
+    }
+}
+
+impl BodySyntaxMapping {
+    pub fn expr_syntax(&self, expr: ExprId) -> Option<LocalSyntaxPtr> {
+        self.expr_syntax_mapping_back.get(&expr).cloned()
+    }
+    pub fn syntax_expr(&self, ptr: LocalSyntaxPtr) -> Option<ExprId> {
+        self.expr_syntax_mapping.get(&ptr).cloned()
+    }
+    pub fn pat_syntax(&self, pat: PatId) -> Option<LocalSyntaxPtr> {
+        self.pat_syntax_mapping_back.get(&pat).cloned()
+    }
+    pub fn syntax_pat(&self, ptr: LocalSyntaxPtr) -> Option<PatId> {
+        self.pat_syntax_mapping.get(&ptr).cloned()
+    }
+
+    pub fn body(&self) -> &Arc<Body> {
+        &self.body
+    }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Expr {
+    /// This is produced if syntax tree does not have a required expression piece.
+    Missing,
+    Path(Path),
+    If {
+        condition: ExprId,
+        then_branch: ExprId,
+        else_branch: Option<ExprId>,
+    },
+    Block {
+        statements: Vec<Statement>,
+        tail: Option<ExprId>,
+    },
+    Loop {
+        body: ExprId,
+    },
+    While {
+        condition: ExprId,
+        body: ExprId,
+    },
+    For {
+        iterable: ExprId,
+        pat: PatId,
+        body: ExprId,
+    },
+    Call {
+        callee: ExprId,
+        args: Vec<ExprId>,
+    },
+    MethodCall {
+        receiver: ExprId,
+        method_name: Name,
+        args: Vec<ExprId>,
+    },
+    Match {
+        expr: ExprId,
+        arms: Vec<MatchArm>,
+    },
+    Continue,
+    Break {
+        expr: Option<ExprId>,
+    },
+    Return {
+        expr: Option<ExprId>,
+    },
+    StructLit {
+        path: Option<Path>,
+        fields: Vec<StructLitField>,
+        spread: Option<ExprId>,
+    },
+    Field {
+        expr: ExprId,
+        name: Name,
+    },
+    Try {
+        expr: ExprId,
+    },
+    Cast {
+        expr: ExprId,
+        type_ref: TypeRef,
+    },
+    Ref {
+        expr: ExprId,
+        mutability: Mutability,
+    },
+    UnaryOp {
+        expr: ExprId,
+        op: Option<UnaryOp>,
+    },
+    Lambda {
+        args: Vec<PatId>,
+        arg_types: Vec<Option<TypeRef>>,
+        body: ExprId,
+    },
+}
+
+pub type UnaryOp = ast::PrefixOp;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MatchArm {
+    pub pats: Vec<PatId>,
+    // guard: Option<ExprId>, // TODO
+    pub expr: ExprId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct StructLitField {
+    pub name: Name,
+    pub expr: ExprId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Statement {
+    Let {
+        pat: PatId,
+        type_ref: Option<TypeRef>,
+        initializer: Option<ExprId>,
+    },
+    Expr(ExprId),
+}
+
+impl Expr {
+    pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
+        match self {
+            Expr::Missing => {}
+            Expr::Path(_) => {}
+            Expr::If {
+                condition,
+                then_branch,
+                else_branch,
+            } => {
+                f(*condition);
+                f(*then_branch);
+                if let Some(else_branch) = else_branch {
+                    f(*else_branch);
+                }
+            }
+            Expr::Block { statements, tail } => {
+                for stmt in statements {
+                    match stmt {
+                        Statement::Let { initializer, .. } => {
+                            if let Some(expr) = initializer {
+                                f(*expr);
+                            }
+                        }
+                        Statement::Expr(e) => f(*e),
+                    }
+                }
+                if let Some(expr) = tail {
+                    f(*expr);
+                }
+            }
+            Expr::Loop { body } => f(*body),
+            Expr::While { condition, body } => {
+                f(*condition);
+                f(*body);
+            }
+            Expr::For { iterable, body, .. } => {
+                f(*iterable);
+                f(*body);
+            }
+            Expr::Call { callee, args } => {
+                f(*callee);
+                for arg in args {
+                    f(*arg);
+                }
+            }
+            Expr::MethodCall { receiver, args, .. } => {
+                f(*receiver);
+                for arg in args {
+                    f(*arg);
+                }
+            }
+            Expr::Match { expr, arms } => {
+                f(*expr);
+                for arm in arms {
+                    f(arm.expr);
+                }
+            }
+            Expr::Continue => {}
+            Expr::Break { expr } | Expr::Return { expr } => {
+                if let Some(expr) = expr {
+                    f(*expr);
+                }
+            }
+            Expr::StructLit { fields, spread, .. } => {
+                for field in fields {
+                    f(field.expr);
+                }
+                if let Some(expr) = spread {
+                    f(*expr);
+                }
+            }
+            Expr::Lambda { body, .. } => {
+                f(*body);
+            }
+            Expr::Field { expr, .. }
+            | Expr::Try { expr }
+            | Expr::Cast { expr, .. }
+            | Expr::Ref { expr, .. }
+            | Expr::UnaryOp { expr, .. } => {
+                f(*expr);
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PatId(RawId);
+impl_arena_id!(PatId);
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Pat {
+    Missing,
+    Bind {
+        name: Name,
+    },
+    TupleStruct {
+        path: Option<Path>,
+        args: Vec<PatId>,
+    },
+}
+
+impl Pat {
+    pub fn walk_child_pats(&self, f: impl FnMut(PatId)) {
+        match self {
+            Pat::Missing | Pat::Bind { .. } => {}
+            Pat::TupleStruct { args, .. } => {
+                args.iter().map(|pat| *pat).for_each(f);
+            }
+        }
+    }
+}
+
+// Queries
+
+pub(crate) fn body_hir(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<Body>> {
+    Ok(Arc::clone(&body_syntax_mapping(db, def_id)?.body))
+}
+
+struct ExprCollector {
+    exprs: Arena<ExprId, Expr>,
+    pats: Arena<PatId, Pat>,
+    expr_syntax_mapping: FxHashMap<LocalSyntaxPtr, ExprId>,
+    expr_syntax_mapping_back: FxHashMap<ExprId, LocalSyntaxPtr>,
+    pat_syntax_mapping: FxHashMap<LocalSyntaxPtr, PatId>,
+    pat_syntax_mapping_back: FxHashMap<PatId, LocalSyntaxPtr>,
+}
+
+impl ExprCollector {
+    fn new() -> Self {
+        ExprCollector {
+            exprs: Arena::default(),
+            pats: Arena::default(),
+            expr_syntax_mapping: FxHashMap::default(),
+            expr_syntax_mapping_back: FxHashMap::default(),
+            pat_syntax_mapping: FxHashMap::default(),
+            pat_syntax_mapping_back: FxHashMap::default(),
+        }
+    }
+
+    fn alloc_expr(&mut self, expr: Expr, syntax_ptr: LocalSyntaxPtr) -> ExprId {
+        let id = self.exprs.alloc(expr);
+        self.expr_syntax_mapping.insert(syntax_ptr, id);
+        self.expr_syntax_mapping_back.insert(id, syntax_ptr);
+        id
+    }
+
+    fn alloc_pat(&mut self, pat: Pat, syntax_ptr: LocalSyntaxPtr) -> PatId {
+        let id = self.pats.alloc(pat);
+        self.pat_syntax_mapping.insert(syntax_ptr, id);
+        self.pat_syntax_mapping_back.insert(id, syntax_ptr);
+        id
+    }
+
+    fn empty_block(&mut self) -> ExprId {
+        let block = Expr::Block {
+            statements: Vec::new(),
+            tail: None,
+        };
+        self.exprs.alloc(block)
+    }
+
+    fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
+        let syntax_ptr = LocalSyntaxPtr::new(expr.syntax());
+        match expr {
+            ast::Expr::IfExpr(e) => {
+                if let Some(pat) = e.condition().and_then(|c| c.pat()) {
+                    // if let -- desugar to match
+                    let pat = self.collect_pat(pat);
+                    let match_expr =
+                        self.collect_expr_opt(e.condition().expect("checked above").expr());
+                    let then_branch = self.collect_block_opt(e.then_branch());
+                    let else_branch = e
+                        .else_branch()
+                        .map(|e| self.collect_block(e))
+                        .unwrap_or_else(|| self.empty_block());
+                    let placeholder_pat = self.pats.alloc(Pat::Missing);
+                    let arms = vec![
+                        MatchArm {
+                            pats: vec![pat],
+                            expr: then_branch,
+                        },
+                        MatchArm {
+                            pats: vec![placeholder_pat],
+                            expr: else_branch,
+                        },
+                    ];
+                    self.alloc_expr(
+                        Expr::Match {
+                            expr: match_expr,
+                            arms,
+                        },
+                        syntax_ptr,
+                    )
+                } else {
+                    let condition = self.collect_expr_opt(e.condition().and_then(|c| c.expr()));
+                    let then_branch = self.collect_block_opt(e.then_branch());
+                    let else_branch = e.else_branch().map(|e| self.collect_block(e));
+                    self.alloc_expr(
+                        Expr::If {
+                            condition,
+                            then_branch,
+                            else_branch,
+                        },
+                        syntax_ptr,
+                    )
+                }
+            }
+            ast::Expr::BlockExpr(e) => self.collect_block_opt(e.block()),
+            ast::Expr::LoopExpr(e) => {
+                let body = self.collect_block_opt(e.loop_body());
+                self.alloc_expr(Expr::Loop { body }, syntax_ptr)
+            }
+            ast::Expr::WhileExpr(e) => {
+                let condition = if let Some(condition) = e.condition() {
+                    if condition.pat().is_none() {
+                        self.collect_expr_opt(condition.expr())
+                    } else {
+                        // TODO handle while let
+                        return self.alloc_expr(Expr::Missing, syntax_ptr);
+                    }
+                } else {
+                    self.exprs.alloc(Expr::Missing)
+                };
+                let body = self.collect_block_opt(e.loop_body());
+                self.alloc_expr(Expr::While { condition, body }, syntax_ptr)
+            }
+            ast::Expr::ForExpr(e) => {
+                let iterable = self.collect_expr_opt(e.iterable());
+                let pat = self.collect_pat_opt(e.pat());
+                let body = self.collect_block_opt(e.loop_body());
+                self.alloc_expr(
+                    Expr::For {
+                        iterable,
+                        pat,
+                        body,
+                    },
+                    syntax_ptr,
+                )
+            }
+            ast::Expr::CallExpr(e) => {
+                let callee = self.collect_expr_opt(e.expr());
+                let args = if let Some(arg_list) = e.arg_list() {
+                    arg_list.args().map(|e| self.collect_expr(e)).collect()
+                } else {
+                    Vec::new()
+                };
+                self.alloc_expr(Expr::Call { callee, args }, syntax_ptr)
+            }
+            ast::Expr::MethodCallExpr(e) => {
+                let receiver = self.collect_expr_opt(e.expr());
+                let args = if let Some(arg_list) = e.arg_list() {
+                    arg_list.args().map(|e| self.collect_expr(e)).collect()
+                } else {
+                    Vec::new()
+                };
+                let method_name = e
+                    .name_ref()
+                    .map(|nr| nr.as_name())
+                    .unwrap_or_else(Name::missing);
+                self.alloc_expr(
+                    Expr::MethodCall {
+                        receiver,
+                        method_name,
+                        args,
+                    },
+                    syntax_ptr,
+                )
+            }
+            ast::Expr::MatchExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                let arms = if let Some(match_arm_list) = e.match_arm_list() {
+                    match_arm_list
+                        .arms()
+                        .map(|arm| MatchArm {
+                            pats: arm.pats().map(|p| self.collect_pat(p)).collect(),
+                            expr: self.collect_expr_opt(arm.expr()),
+                        })
+                        .collect()
+                } else {
+                    Vec::new()
+                };
+                self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
+            }
+            ast::Expr::PathExpr(e) => {
+                let path = e
+                    .path()
+                    .and_then(Path::from_ast)
+                    .map(Expr::Path)
+                    .unwrap_or(Expr::Missing);
+                self.alloc_expr(path, syntax_ptr)
+            }
+            ast::Expr::ContinueExpr(_e) => {
+                // TODO: labels
+                self.alloc_expr(Expr::Continue, syntax_ptr)
+            }
+            ast::Expr::BreakExpr(e) => {
+                let expr = e.expr().map(|e| self.collect_expr(e));
+                self.alloc_expr(Expr::Break { expr }, syntax_ptr)
+            }
+            ast::Expr::ParenExpr(e) => {
+                let inner = self.collect_expr_opt(e.expr());
+                // make the paren expr point to the inner expression as well
+                self.expr_syntax_mapping.insert(syntax_ptr, inner);
+                inner
+            }
+            ast::Expr::ReturnExpr(e) => {
+                let expr = e.expr().map(|e| self.collect_expr(e));
+                self.alloc_expr(Expr::Return { expr }, syntax_ptr)
+            }
+            ast::Expr::StructLit(e) => {
+                let path = e.path().and_then(Path::from_ast);
+                let fields = if let Some(nfl) = e.named_field_list() {
+                    nfl.fields()
+                        .map(|field| StructLitField {
+                            name: field
+                                .name_ref()
+                                .map(|nr| nr.as_name())
+                                .unwrap_or_else(Name::missing),
+                            expr: if let Some(e) = field.expr() {
+                                self.collect_expr(e)
+                            } else if let Some(nr) = field.name_ref() {
+                                // field shorthand
+                                let id = self.exprs.alloc(Expr::Path(Path::from_name_ref(nr)));
+                                self.expr_syntax_mapping
+                                    .insert(LocalSyntaxPtr::new(nr.syntax()), id);
+                                self.expr_syntax_mapping_back
+                                    .insert(id, LocalSyntaxPtr::new(nr.syntax()));
+                                id
+                            } else {
+                                self.exprs.alloc(Expr::Missing)
+                            },
+                        })
+                        .collect()
+                } else {
+                    Vec::new()
+                };
+                let spread = e.spread().map(|s| self.collect_expr(s));
+                self.alloc_expr(
+                    Expr::StructLit {
+                        path,
+                        fields,
+                        spread,
+                    },
+                    syntax_ptr,
+                )
+            }
+            ast::Expr::FieldExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                let name = e
+                    .name_ref()
+                    .map(|nr| nr.as_name())
+                    .unwrap_or_else(Name::missing);
+                self.alloc_expr(Expr::Field { expr, name }, syntax_ptr)
+            }
+            ast::Expr::TryExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                self.alloc_expr(Expr::Try { expr }, syntax_ptr)
+            }
+            ast::Expr::CastExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                let type_ref = TypeRef::from_ast_opt(e.type_ref());
+                self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr)
+            }
+            ast::Expr::RefExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                let mutability = Mutability::from_mutable(e.is_mut());
+                self.alloc_expr(Expr::Ref { expr, mutability }, syntax_ptr)
+            }
+            ast::Expr::PrefixExpr(e) => {
+                let expr = self.collect_expr_opt(e.expr());
+                let op = e.op();
+                self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr)
+            }
+            ast::Expr::LambdaExpr(e) => {
+                let mut args = Vec::new();
+                let mut arg_types = Vec::new();
+                if let Some(pl) = e.param_list() {
+                    for param in pl.params() {
+                        let pat = self.collect_pat_opt(param.pat());
+                        let type_ref = param.type_ref().map(TypeRef::from_ast);
+                        args.push(pat);
+                        arg_types.push(type_ref);
+                    }
+                }
+                let body = self.collect_expr_opt(e.body());
+                self.alloc_expr(
+                    Expr::Lambda {
+                        args,
+                        arg_types,
+                        body,
+                    },
+                    syntax_ptr,
+                )
+            }
+
+            // TODO implement HIR for these:
+            ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::IndexExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::TupleExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::ArrayExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::RangeExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::BinExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+            ast::Expr::Literal(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
+        }
+    }
+
+    fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
+        if let Some(expr) = expr {
+            self.collect_expr(expr)
+        } else {
+            self.exprs.alloc(Expr::Missing)
+        }
+    }
+
+    fn collect_block(&mut self, block: ast::Block) -> ExprId {
+        let statements = block
+            .statements()
+            .map(|s| match s {
+                ast::Stmt::LetStmt(stmt) => {
+                    let pat = self.collect_pat_opt(stmt.pat());
+                    let type_ref = stmt.type_ref().map(TypeRef::from_ast);
+                    let initializer = stmt.initializer().map(|e| self.collect_expr(e));
+                    Statement::Let {
+                        pat,
+                        type_ref,
+                        initializer,
+                    }
+                }
+                ast::Stmt::ExprStmt(stmt) => Statement::Expr(self.collect_expr_opt(stmt.expr())),
+            })
+            .collect();
+        let tail = block.expr().map(|e| self.collect_expr(e));
+        self.alloc_expr(
+            Expr::Block { statements, tail },
+            LocalSyntaxPtr::new(block.syntax()),
+        )
+    }
+
+    fn collect_block_opt(&mut self, block: Option<ast::Block>) -> ExprId {
+        if let Some(block) = block {
+            self.collect_block(block)
+        } else {
+            self.exprs.alloc(Expr::Missing)
+        }
+    }
+
+    fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
+        let syntax_ptr = LocalSyntaxPtr::new(pat.syntax());
+        match pat {
+            ast::Pat::BindPat(bp) => {
+                let name = bp
+                    .name()
+                    .map(|nr| nr.as_name())
+                    .unwrap_or_else(Name::missing);
+                self.alloc_pat(Pat::Bind { name }, syntax_ptr)
+            }
+            ast::Pat::TupleStructPat(p) => {
+                let path = p.path().and_then(Path::from_ast);
+                let args = p.args().map(|p| self.collect_pat(p)).collect();
+                self.alloc_pat(Pat::TupleStruct { path, args }, syntax_ptr)
+            }
+            _ => {
+                // TODO
+                self.alloc_pat(Pat::Missing, syntax_ptr)
+            }
+        }
+    }
+
+    fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
+        if let Some(pat) = pat {
+            self.collect_pat(pat)
+        } else {
+            self.pats.alloc(Pat::Missing)
+        }
+    }
+
+    fn into_body_syntax_mapping(self, args: Vec<PatId>, body_expr: ExprId) -> BodySyntaxMapping {
+        let body = Body {
+            exprs: self.exprs,
+            pats: self.pats,
+            args,
+            body_expr,
+        };
+        BodySyntaxMapping {
+            body: Arc::new(body),
+            expr_syntax_mapping: self.expr_syntax_mapping,
+            expr_syntax_mapping_back: self.expr_syntax_mapping_back,
+            pat_syntax_mapping: self.pat_syntax_mapping,
+            pat_syntax_mapping_back: self.pat_syntax_mapping_back,
+        }
+    }
+}
+
+pub(crate) fn collect_fn_body_syntax(node: ast::FnDef) -> BodySyntaxMapping {
+    let mut collector = ExprCollector::new();
+
+    let args = if let Some(param_list) = node.param_list() {
+        let mut args = Vec::new();
+
+        if let Some(self_param) = param_list.self_param() {
+            let self_param = LocalSyntaxPtr::new(
+                self_param
+                    .self_kw()
+                    .expect("self param without self keyword")
+                    .syntax(),
+            );
+            let arg = collector.alloc_pat(
+                Pat::Bind {
+                    name: Name::self_param(),
+                },
+                self_param,
+            );
+            args.push(arg);
+        }
+
+        for param in param_list.params() {
+            let pat = if let Some(pat) = param.pat() {
+                pat
+            } else {
+                continue;
+            };
+            args.push(collector.collect_pat(pat));
+        }
+        args
+    } else {
+        Vec::new()
+    };
+
+    let body = collector.collect_block_opt(node.body());
+    collector.into_body_syntax_mapping(args, body)
+}
+
+pub(crate) fn body_syntax_mapping(
+    db: &impl HirDatabase,
+    def_id: DefId,
+) -> Cancelable<Arc<BodySyntaxMapping>> {
+    let def = def_id.resolve(db)?;
+
+    let body_syntax_mapping = match def {
+        Def::Function(f) => {
+            let node = f.syntax(db);
+            let node = node.borrowed();
+
+            collect_fn_body_syntax(node)
+        }
+        // TODO: consts, etc.
+        _ => panic!("Trying to get body for item type without body"),
+    };
+
+    Ok(Arc::new(body_syntax_mapping))
+}
diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs
index 75ef308ae4..4627be071f 100644
--- a/crates/ra_hir/src/function.rs
+++ b/crates/ra_hir/src/function.rs
@@ -11,9 +11,9 @@ use ra_syntax::{
     ast::{self, AstNode, DocCommentsOwner, NameOwner},
 };
 
-use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock};
+use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock, expr::{Body, BodySyntaxMapping}, type_ref::{TypeRef, Mutability}, Name};
 
-pub use self::scope::FnScopes;
+pub use self::scope::{FnScopes, ScopesWithSyntaxMapping};
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Function {
@@ -36,8 +36,25 @@ impl Function {
         ast::FnDef::cast(syntax.borrowed()).unwrap().owned()
     }
 
-    pub fn scopes(&self, db: &impl HirDatabase) -> Arc<FnScopes> {
-        db.fn_scopes(self.def_id)
+    pub fn body(&self, db: &impl HirDatabase) -> Cancelable<Arc<Body>> {
+        db.body_hir(self.def_id)
+    }
+
+    pub fn body_syntax_mapping(&self, db: &impl HirDatabase) -> Cancelable<Arc<BodySyntaxMapping>> {
+        db.body_syntax_mapping(self.def_id)
+    }
+
+    pub fn scopes(&self, db: &impl HirDatabase) -> Cancelable<ScopesWithSyntaxMapping> {
+        let scopes = db.fn_scopes(self.def_id)?;
+        let syntax_mapping = db.body_syntax_mapping(self.def_id)?;
+        Ok(ScopesWithSyntaxMapping {
+            scopes,
+            syntax_mapping,
+        })
+    }
+
+    pub fn signature(&self, db: &impl HirDatabase) -> Arc<FnSignature> {
+        db.fn_signature(self.def_id)
     }
 
     pub fn signature_info(&self, db: &impl HirDatabase) -> Option<FnSignatureInfo> {
@@ -63,6 +80,60 @@ impl Function {
     }
 }
 
+/// The declared signature of a function.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FnSignature {
+    args: Vec<TypeRef>,
+    ret_type: TypeRef,
+}
+
+impl FnSignature {
+    pub fn args(&self) -> &[TypeRef] {
+        &self.args
+    }
+
+    pub fn ret_type(&self) -> &TypeRef {
+        &self.ret_type
+    }
+}
+
+pub(crate) fn fn_signature(db: &impl HirDatabase, def_id: DefId) -> Arc<FnSignature> {
+    let func = Function::new(def_id);
+    let syntax = func.syntax(db);
+    let node = syntax.borrowed();
+    let mut args = Vec::new();
+    if let Some(param_list) = node.param_list() {
+        if let Some(self_param) = param_list.self_param() {
+            let self_type = if let Some(type_ref) = self_param.type_ref() {
+                TypeRef::from_ast(type_ref)
+            } else {
+                let self_type = TypeRef::Path(Name::self_type().into());
+                match self_param.flavor() {
+                    ast::SelfParamFlavor::Owned => self_type,
+                    ast::SelfParamFlavor::Ref => {
+                        TypeRef::Reference(Box::new(self_type), Mutability::Shared)
+                    }
+                    ast::SelfParamFlavor::MutRef => {
+                        TypeRef::Reference(Box::new(self_type), Mutability::Mut)
+                    }
+                }
+            };
+            args.push(self_type);
+        }
+        for param in param_list.params() {
+            let type_ref = TypeRef::from_ast_opt(param.type_ref());
+            args.push(type_ref);
+        }
+    }
+    let ret_type = if let Some(type_ref) = node.ret_type().and_then(|rt| rt.type_ref()) {
+        TypeRef::from_ast(type_ref)
+    } else {
+        TypeRef::unit()
+    };
+    let sig = FnSignature { args, ret_type };
+    Arc::new(sig)
+}
+
 #[derive(Debug, Clone)]
 pub struct FnSignatureInfo {
     pub name: String,
diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs
index 42bfe4f325..0a12f0b35b 100644
--- a/crates/ra_hir/src/function/scope.rs
+++ b/crates/ra_hir/src/function/scope.rs
@@ -1,14 +1,16 @@
+use std::sync::Arc;
+
 use rustc_hash::{FxHashMap, FxHashSet};
 
 use ra_syntax::{
     AstNode, SyntaxNodeRef, TextUnit, TextRange,
     algo::generate,
-    ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
+    ast,
 };
 use ra_arena::{Arena, RawId, impl_arena_id};
 use ra_db::LocalSyntaxPtr;
 
-use crate::{Name, AsName};
+use crate::{Name, AsName, expr::{PatId, ExprId, Pat, Expr, Body, Statement, BodySyntaxMapping}};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct ScopeId(RawId);
@@ -16,15 +18,15 @@ impl_arena_id!(ScopeId);
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct FnScopes {
-    pub self_param: Option<LocalSyntaxPtr>,
+    body: Arc<Body>,
     scopes: Arena<ScopeId, ScopeData>,
-    scope_for: FxHashMap<LocalSyntaxPtr, ScopeId>,
+    scope_for: FxHashMap<ExprId, ScopeId>,
 }
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct ScopeEntry {
     name: Name,
-    ptr: LocalSyntaxPtr,
+    pat: PatId,
 }
 
 #[derive(Debug, PartialEq, Eq)]
@@ -34,28 +36,100 @@ pub struct ScopeData {
 }
 
 impl FnScopes {
-    pub(crate) fn new(fn_def: ast::FnDef) -> FnScopes {
+    pub(crate) fn new(body: Arc<Body>) -> FnScopes {
         let mut scopes = FnScopes {
-            self_param: fn_def
-                .param_list()
-                .and_then(|it| it.self_param())
-                .map(|it| LocalSyntaxPtr::new(it.syntax())),
+            body: body.clone(),
             scopes: Arena::default(),
             scope_for: FxHashMap::default(),
         };
         let root = scopes.root_scope();
-        scopes.add_params_bindings(root, fn_def.param_list());
-        if let Some(body) = fn_def.body() {
-            compute_block_scopes(body, &mut scopes, root)
-        }
+        scopes.add_params_bindings(root, body.args());
+        compute_expr_scopes(body.body_expr(), &body, &mut scopes, root);
         scopes
     }
     pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
         &self.scopes[scope].entries
     }
+    pub fn scope_chain_for<'a>(&'a self, expr: ExprId) -> impl Iterator<Item = ScopeId> + 'a {
+        generate(self.scope_for(expr), move |&scope| {
+            self.scopes[scope].parent
+        })
+    }
+
+    pub fn resolve_local_name<'a>(
+        &'a self,
+        context_expr: ExprId,
+        name: Name,
+    ) -> Option<&'a ScopeEntry> {
+        let mut shadowed = FxHashSet::default();
+        let ret = self
+            .scope_chain_for(context_expr)
+            .flat_map(|scope| self.entries(scope).iter())
+            .filter(|entry| shadowed.insert(entry.name()))
+            .find(|entry| entry.name() == &name);
+        ret
+    }
+
+    fn root_scope(&mut self) -> ScopeId {
+        self.scopes.alloc(ScopeData {
+            parent: None,
+            entries: vec![],
+        })
+    }
+    fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
+        self.scopes.alloc(ScopeData {
+            parent: Some(parent),
+            entries: vec![],
+        })
+    }
+    fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
+        match &body[pat] {
+            Pat::Bind { name } => self.scopes[scope].entries.push(ScopeEntry {
+                name: name.clone(),
+                pat,
+            }),
+            p => p.walk_child_pats(|pat| self.add_bindings(body, scope, pat)),
+        }
+    }
+    fn add_params_bindings(&mut self, scope: ScopeId, params: &[PatId]) {
+        let body = Arc::clone(&self.body);
+        params
+            .into_iter()
+            .for_each(|pat| self.add_bindings(&body, scope, *pat));
+    }
+    fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
+        self.scope_for.insert(node, scope);
+    }
+    fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
+        self.scope_for.get(&expr).map(|&scope| scope)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ScopesWithSyntaxMapping {
+    pub syntax_mapping: Arc<BodySyntaxMapping>,
+    pub scopes: Arc<FnScopes>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ScopeEntryWithSyntax {
+    name: Name,
+    ptr: LocalSyntaxPtr,
+}
+
+impl ScopeEntryWithSyntax {
+    pub fn name(&self) -> &Name {
+        &self.name
+    }
+    pub fn ptr(&self) -> LocalSyntaxPtr {
+        self.ptr
+    }
+}
+
+impl ScopesWithSyntaxMapping {
     pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item = ScopeId> + 'a {
         generate(self.scope_for(node), move |&scope| {
-            self.scopes[scope].parent
+            self.scopes.scopes[scope].parent
         })
     }
     pub fn scope_chain_for_offset<'a>(
@@ -63,26 +137,30 @@ impl FnScopes {
         offset: TextUnit,
     ) -> impl Iterator<Item = ScopeId> + 'a {
         let scope = self
+            .scopes
             .scope_for
             .iter()
-            // find containin scope
+            .filter_map(|(id, scope)| Some((self.syntax_mapping.expr_syntax(*id)?, scope)))
+            // find containing scope
             .min_by_key(|(ptr, _scope)| {
                 (
                     !(ptr.range().start() <= offset && offset <= ptr.range().end()),
                     ptr.range().len(),
                 )
             })
-            .map(|(ptr, scope)| self.adjust(*ptr, *scope, offset));
+            .map(|(ptr, scope)| self.adjust(ptr, *scope, offset));
 
-        generate(scope, move |&scope| self.scopes[scope].parent)
+        generate(scope, move |&scope| self.scopes.scopes[scope].parent)
     }
     // XXX: during completion, cursor might be outside of any particular
     // expression. Try to figure out the correct scope...
     fn adjust(&self, ptr: LocalSyntaxPtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId {
         let r = ptr.range();
         let child_scopes = self
+            .scopes
             .scope_for
             .iter()
+            .filter_map(|(id, scope)| Some((self.syntax_mapping.expr_syntax(*id)?, scope)))
             .map(|(ptr, scope)| (ptr.range(), scope))
             .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r);
 
@@ -100,22 +178,27 @@ impl FnScopes {
             .unwrap_or(original_scope)
     }
 
-    pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> {
+    pub fn resolve_local_name(&self, name_ref: ast::NameRef) -> Option<ScopeEntryWithSyntax> {
         let mut shadowed = FxHashSet::default();
         let name = name_ref.as_name();
         let ret = self
             .scope_chain(name_ref.syntax())
-            .flat_map(|scope| self.entries(scope).iter())
+            .flat_map(|scope| self.scopes.entries(scope).iter())
             .filter(|entry| shadowed.insert(entry.name()))
             .filter(|entry| entry.name() == &name)
             .nth(0);
-        ret
+        ret.and_then(|entry| {
+            Some(ScopeEntryWithSyntax {
+                name: entry.name().clone(),
+                ptr: self.syntax_mapping.pat_syntax(entry.pat())?,
+            })
+        })
     }
 
     pub fn find_all_refs(&self, pat: ast::BindPat) -> Vec<ReferenceDescriptor> {
         let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap();
         let name_ptr = LocalSyntaxPtr::new(pat.syntax());
-        let refs: Vec<_> = fn_def
+        fn_def
             .syntax()
             .descendants()
             .filter_map(ast::NameRef::cast)
@@ -127,203 +210,94 @@ impl FnScopes {
                 name: name_ref.syntax().text().to_string(),
                 range: name_ref.syntax().range(),
             })
-            .collect();
-
-        refs
+            .collect()
     }
 
-    fn root_scope(&mut self) -> ScopeId {
-        self.scopes.alloc(ScopeData {
-            parent: None,
-            entries: vec![],
-        })
-    }
-    fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
-        self.scopes.alloc(ScopeData {
-            parent: Some(parent),
-            entries: vec![],
-        })
-    }
-    fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
-        let entries = pat
-            .syntax()
-            .descendants()
-            .filter_map(ast::BindPat::cast)
-            .filter_map(ScopeEntry::new);
-        self.scopes[scope].entries.extend(entries);
-    }
-    fn add_params_bindings(&mut self, scope: ScopeId, params: Option<ast::ParamList>) {
-        params
-            .into_iter()
-            .flat_map(|it| it.params())
-            .filter_map(|it| it.pat())
-            .for_each(|it| self.add_bindings(scope, it));
-    }
-    fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
-        self.scope_for.insert(LocalSyntaxPtr::new(node), scope);
-    }
     fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
         node.ancestors()
             .map(LocalSyntaxPtr::new)
-            .filter_map(|it| self.scope_for.get(&it).map(|&scope| scope))
-            .next()
+            .filter_map(|ptr| self.syntax_mapping.syntax_expr(ptr))
+            .find_map(|it| self.scopes.scope_for(it))
     }
 }
 
 impl ScopeEntry {
-    fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
-        let name = pat.name()?.as_name();
-        let res = ScopeEntry {
-            name,
-            ptr: LocalSyntaxPtr::new(pat.syntax()),
-        };
-        Some(res)
-    }
     pub fn name(&self) -> &Name {
         &self.name
     }
-    pub fn ptr(&self) -> LocalSyntaxPtr {
-        self.ptr
+    pub fn pat(&self) -> PatId {
+        self.pat
     }
 }
 
-fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
-    // A hack for completion :(
-    scopes.set_scope(block.syntax(), scope);
-    for stmt in block.statements() {
+fn compute_block_scopes(
+    statements: &[Statement],
+    tail: Option<ExprId>,
+    body: &Body,
+    scopes: &mut FnScopes,
+    mut scope: ScopeId,
+) {
+    for stmt in statements {
         match stmt {
-            ast::Stmt::LetStmt(stmt) => {
-                if let Some(expr) = stmt.initializer() {
-                    scopes.set_scope(expr.syntax(), scope);
-                    compute_expr_scopes(expr, scopes, scope);
+            Statement::Let {
+                pat, initializer, ..
+            } => {
+                if let Some(expr) = initializer {
+                    scopes.set_scope(*expr, scope);
+                    compute_expr_scopes(*expr, body, scopes, scope);
                 }
                 scope = scopes.new_scope(scope);
-                if let Some(pat) = stmt.pat() {
-                    scopes.add_bindings(scope, pat);
-                }
+                scopes.add_bindings(body, scope, *pat);
             }
-            ast::Stmt::ExprStmt(expr_stmt) => {
-                if let Some(expr) = expr_stmt.expr() {
-                    scopes.set_scope(expr.syntax(), scope);
-                    compute_expr_scopes(expr, scopes, scope);
-                }
+            Statement::Expr(expr) => {
+                scopes.set_scope(*expr, scope);
+                compute_expr_scopes(*expr, body, scopes, scope);
             }
         }
     }
-    if let Some(expr) = block.expr() {
-        scopes.set_scope(expr.syntax(), scope);
-        compute_expr_scopes(expr, scopes, scope);
+    if let Some(expr) = tail {
+        compute_expr_scopes(expr, body, scopes, scope);
     }
 }
 
-fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
-    match expr {
-        ast::Expr::IfExpr(e) => {
-            let cond_scope = e
-                .condition()
-                .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
-            if let Some(block) = e.then_branch() {
-                compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
-            }
-            if let Some(block) = e.else_branch() {
-                compute_block_scopes(block, scopes, scope);
-            }
-        }
-        ast::Expr::BlockExpr(e) => {
-            if let Some(block) = e.block() {
-                compute_block_scopes(block, scopes, scope);
-            }
-        }
-        ast::Expr::LoopExpr(e) => {
-            if let Some(block) = e.loop_body() {
-                compute_block_scopes(block, scopes, scope);
-            }
-        }
-        ast::Expr::WhileExpr(e) => {
-            let cond_scope = e
-                .condition()
-                .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
-            if let Some(block) = e.loop_body() {
-                compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
-            }
+fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut FnScopes, scope: ScopeId) {
+    scopes.set_scope(expr, scope);
+    match &body[expr] {
+        Expr::Block { statements, tail } => {
+            compute_block_scopes(&statements, *tail, body, scopes, scope);
         }
-        ast::Expr::ForExpr(e) => {
-            if let Some(expr) = e.iterable() {
-                compute_expr_scopes(expr, scopes, scope);
-            }
-            let mut scope = scope;
-            if let Some(pat) = e.pat() {
-                scope = scopes.new_scope(scope);
-                scopes.add_bindings(scope, pat);
-            }
-            if let Some(block) = e.loop_body() {
-                compute_block_scopes(block, scopes, scope);
-            }
-        }
-        ast::Expr::LambdaExpr(e) => {
+        Expr::For {
+            iterable,
+            pat,
+            body: body_expr,
+        } => {
+            compute_expr_scopes(*iterable, body, scopes, scope);
             let scope = scopes.new_scope(scope);
-            scopes.add_params_bindings(scope, e.param_list());
-            if let Some(body) = e.body() {
-                scopes.set_scope(body.syntax(), scope);
-                compute_expr_scopes(body, scopes, scope);
-            }
+            scopes.add_bindings(body, scope, *pat);
+            compute_expr_scopes(*body_expr, body, scopes, scope);
         }
-        ast::Expr::CallExpr(e) => {
-            compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
-        }
-        ast::Expr::MethodCallExpr(e) => {
-            compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
+        Expr::Lambda {
+            args,
+            body: body_expr,
+            ..
+        } => {
+            let scope = scopes.new_scope(scope);
+            scopes.add_params_bindings(scope, &args);
+            compute_expr_scopes(*body_expr, body, scopes, scope);
         }
-        ast::Expr::MatchExpr(e) => {
-            if let Some(expr) = e.expr() {
-                compute_expr_scopes(expr, scopes, scope);
-            }
-            for arm in e.match_arm_list().into_iter().flat_map(|it| it.arms()) {
+        Expr::Match { expr, arms } => {
+            compute_expr_scopes(*expr, body, scopes, scope);
+            for arm in arms {
                 let scope = scopes.new_scope(scope);
-                for pat in arm.pats() {
-                    scopes.add_bindings(scope, pat);
-                }
-                if let Some(expr) = arm.expr() {
-                    compute_expr_scopes(expr, scopes, scope);
+                for pat in &arm.pats {
+                    scopes.add_bindings(body, scope, *pat);
                 }
+                scopes.set_scope(arm.expr, scope);
+                compute_expr_scopes(arm.expr, body, scopes, scope);
             }
         }
-        _ => expr
-            .syntax()
-            .children()
-            .filter_map(ast::Expr::cast)
-            .for_each(|expr| compute_expr_scopes(expr, scopes, scope)),
+        e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
     };
-
-    fn compute_call_scopes(
-        receiver: Option<ast::Expr>,
-        arg_list: Option<ast::ArgList>,
-        scopes: &mut FnScopes,
-        scope: ScopeId,
-    ) {
-        arg_list
-            .into_iter()
-            .flat_map(|it| it.args())
-            .chain(receiver)
-            .for_each(|expr| compute_expr_scopes(expr, scopes, scope));
-    }
-
-    fn compute_cond_scopes(
-        cond: ast::Condition,
-        scopes: &mut FnScopes,
-        scope: ScopeId,
-    ) -> Option<ScopeId> {
-        if let Some(expr) = cond.expr() {
-            compute_expr_scopes(expr, scopes, scope);
-        }
-        if let Some(pat) = cond.pat() {
-            let s = scopes.new_scope(scope);
-            scopes.add_bindings(s, pat);
-            Some(s)
-        } else {
-            None
-        }
-    }
 }
 
 #[derive(Debug)]
@@ -338,6 +312,8 @@ mod tests {
     use ra_syntax::SourceFileNode;
     use test_utils::{extract_offset, assert_eq_text};
 
+    use crate::expr;
+
     use super::*;
 
     fn do_check(code: &str, expected: &[&str]) {
@@ -353,15 +329,20 @@ mod tests {
         let file = SourceFileNode::parse(&code);
         let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
         let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
-        let scopes = FnScopes::new(fn_def);
+        let body_hir = expr::collect_fn_body_syntax(fn_def);
+        let scopes = FnScopes::new(Arc::clone(body_hir.body()));
+        let scopes = ScopesWithSyntaxMapping {
+            scopes: Arc::new(scopes),
+            syntax_mapping: Arc::new(body_hir),
+        };
         let actual = scopes
             .scope_chain(marker.syntax())
-            .flat_map(|scope| scopes.entries(scope))
+            .flat_map(|scope| scopes.scopes.entries(scope))
             .map(|it| it.name().to_string())
             .collect::<Vec<_>>()
             .join("\n");
         let expected = expected.join("\n");
-        assert_eq_text!(&actual, &expected);
+        assert_eq_text!(&expected, &actual);
     }
 
     #[test]
@@ -389,7 +370,7 @@ mod tests {
     }
 
     #[test]
-    fn test_metod_call_scope() {
+    fn test_method_call_scope() {
         do_check(
             r"
             fn quux() {
@@ -445,10 +426,15 @@ mod tests {
         let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
         let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
 
-        let scopes = FnScopes::new(fn_def);
+        let body_hir = expr::collect_fn_body_syntax(fn_def);
+        let scopes = FnScopes::new(Arc::clone(body_hir.body()));
+        let scopes = ScopesWithSyntaxMapping {
+            scopes: Arc::new(scopes),
+            syntax_mapping: Arc::new(body_hir),
+        };
 
         let local_name_entry = scopes.resolve_local_name(name_ref).unwrap();
-        let local_name = local_name_entry.ptr().resolve(&file);
+        let local_name = local_name_entry.ptr();
         let expected_name =
             find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
         assert_eq!(local_name.range(), expected_name.syntax().range());
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 2abcec441b..d600b91df3 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -32,6 +32,7 @@ mod adt;
 mod type_ref;
 mod ty;
 mod impl_block;
+mod expr;
 
 use crate::{
     db::HirDatabase,
@@ -46,7 +47,7 @@ pub use self::{
     ids::{HirFileId, DefId, DefLoc, MacroCallId, MacroCallLoc},
     macros::{MacroDef, MacroInput, MacroExpansion},
     module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
-    function::{Function, FnScopes},
+    function::{Function, FnSignature, FnScopes, ScopesWithSyntaxMapping},
     adt::{Struct, Enum},
     ty::Ty,
     impl_block::{ImplBlock, ImplItem},
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index a9db932ff0..8d176662cd 100644
--- a/crates/ra_hir/src/mock.rs
+++ b/crates/ra_hir/src/mock.rs
@@ -208,6 +208,9 @@ salsa::database_storage! {
             fn struct_data() for db::StructDataQuery;
             fn enum_data() for db::EnumDataQuery;
             fn impls_in_module() for db::ImplsInModuleQuery;
+            fn body_hir() for db::BodyHirQuery;
+            fn body_syntax_mapping() for db::BodySyntaxMappingQuery;
+            fn fn_signature() for db::FnSignatureQuery;
         }
     }
 }
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs
index 017caf442e..90229bc540 100644
--- a/crates/ra_hir/src/name.rs
+++ b/crates/ra_hir/src/name.rs
@@ -31,6 +31,14 @@ impl Name {
         Name::new("[missing name]".into())
     }
 
+    pub(crate) fn self_param() -> Name {
+        Name::new("self".into())
+    }
+
+    pub(crate) fn self_type() -> Name {
+        Name::new("Self".into())
+    }
+
     pub(crate) fn tuple_field_name(idx: usize) -> Name {
         Name::new(idx.to_string().into())
     }
@@ -51,7 +59,8 @@ impl Name {
             "u128" => KnownName::U128,
             "f32" => KnownName::F32,
             "f64" => KnownName::F64,
-            "Self" => KnownName::Self_,
+            "Self" => KnownName::SelfType,
+            "self" => KnownName::SelfParam,
             _ => return None,
         };
         Some(name)
@@ -104,5 +113,6 @@ pub(crate) enum KnownName {
     F32,
     F64,
 
-    Self_,
+    SelfType,
+    SelfParam,
 }
diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs
index 9fdfa0d131..dcf4cf8b65 100644
--- a/crates/ra_hir/src/path.rs
+++ b/crates/ra_hir/src/path.rs
@@ -65,6 +65,11 @@ impl Path {
         }
     }
 
+    /// Converts an `ast::NameRef` into a single-identifier `Path`.
+    pub fn from_name_ref(name_ref: ast::NameRef) -> Path {
+        name_ref.as_name().into()
+    }
+
     /// `true` is this path is a single identifier, like `foo`
     pub fn is_ident(&self) -> bool {
         self.kind == PathKind::Plain && self.segments.len() == 1
@@ -84,6 +89,15 @@ impl Path {
     }
 }
 
+impl From<Name> for Path {
+    fn from(name: Name) -> Path {
+        Path {
+            kind: PathKind::Plain,
+            segments: vec![name],
+        }
+    }
+}
+
 fn expand_use_tree(
     prefix: Option<Path>,
     tree: ast::UseTree,
diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs
index a5d99bedae..d9ee9d37f0 100644
--- a/crates/ra_hir/src/query_definitions.rs
+++ b/crates/ra_hir/src/query_definitions.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
 use ra_db::{SourceRootId, Cancelable,};
 
 use crate::{
-    SourceFileItems, SourceItemId, DefKind, Function, DefId, Name, AsName, HirFileId,
+    SourceFileItems, SourceItemId, DefKind, DefId, Name, AsName, HirFileId,
     MacroCallLoc,
     db::HirDatabase,
     function::FnScopes,
@@ -23,11 +23,10 @@ use crate::{
     adt::{StructData, EnumData},
 };
 
-pub(super) fn fn_scopes(db: &impl HirDatabase, def_id: DefId) -> Arc<FnScopes> {
-    let function = Function::new(def_id);
-    let syntax = function.syntax(db);
-    let res = FnScopes::new(syntax.borrowed());
-    Arc::new(res)
+pub(super) fn fn_scopes(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<FnScopes>> {
+    let body = db.body_hir(def_id)?;
+    let res = FnScopes::new(body);
+    Ok(Arc::new(res))
 }
 
 pub(super) fn struct_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<StructData>> {
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index 85bd844696..29a3960e91 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -87,6 +87,18 @@ fn module_from_source(
     Ok(Some(Module::new(db, source_root_id, module_id)?))
 }
 
+pub fn function_from_position(
+    db: &impl HirDatabase,
+    position: FilePosition,
+) -> Cancelable<Option<Function>> {
+    let file = db.source_file(position.file_id);
+    let fn_def = ctry!(find_node_at_offset::<ast::FnDef>(
+        file.syntax(),
+        position.offset
+    ));
+    function_from_source(db, position.file_id, fn_def)
+}
+
 pub fn function_from_source(
     db: &impl HirDatabase,
     file_id: FileId,
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index e33762e0dd..8c320a7053 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -31,10 +31,11 @@ use ra_syntax::{
 };
 
 use crate::{
-    Def, DefId, FnScopes, Module, Function, Struct, Enum, Path, Name, AsName, ImplBlock,
+    Def, DefId, Module, Function, Struct, Enum, Path, Name, AsName, ImplBlock,
     db::HirDatabase,
     type_ref::{TypeRef, Mutability},
     name::KnownName,
+    ScopesWithSyntaxMapping,
 };
 
 /// The ID of a type variable.
@@ -305,7 +306,7 @@ impl Ty {
                 return Ok(Ty::Uint(uint_ty));
             } else if let Some(float_ty) = primitive::FloatTy::from_name(name) {
                 return Ok(Ty::Float(float_ty));
-            } else if name.as_known_name() == Some(KnownName::Self_) {
+            } else if name.as_known_name() == Some(KnownName::SelfType) {
                 return Ty::from_hir_opt(db, module, None, impl_block.map(|i| i.target_type()));
             }
         }
@@ -515,7 +516,7 @@ impl InferenceResult {
 #[derive(Clone, Debug)]
 struct InferenceContext<'a, D: HirDatabase> {
     db: &'a D,
-    scopes: Arc<FnScopes>,
+    scopes: ScopesWithSyntaxMapping,
     /// The self param for the current method, if it exists.
     self_param: Option<LocalSyntaxPtr>,
     module: Module,
@@ -529,7 +530,7 @@ struct InferenceContext<'a, D: HirDatabase> {
 impl<'a, D: HirDatabase> InferenceContext<'a, D> {
     fn new(
         db: &'a D,
-        scopes: Arc<FnScopes>,
+        scopes: ScopesWithSyntaxMapping,
         module: Module,
         impl_block: Option<ImplBlock>,
     ) -> Self {
@@ -826,10 +827,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 self.infer_expr_opt(e.expr(), &Expectation::none())?;
                 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) => {
                 let (ty, def_id) = self.resolve_variant(e.path())?;
                 if let Some(nfl) = e.named_field_list() {
@@ -845,10 +842,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 }
                 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) => {
                 let receiver_ty = self.infer_expr_opt(e.expr(), &Expectation::none())?;
@@ -1016,7 +1009,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
 
 pub fn infer(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<InferenceResult>> {
     let function = Function::new(def_id); // TODO: consts also need inference
-    let scopes = function.scopes(db);
+    let scopes = function.scopes(db)?;
     let module = function.module(db)?;
     let impl_block = function.impl_block(db)?;
     let mut ctx = InferenceContext::new(db, scopes, module, impl_block);
diff --git a/crates/ra_hir/src/type_ref.rs b/crates/ra_hir/src/type_ref.rs
index b36bb35d89..859f330c24 100644
--- a/crates/ra_hir/src/type_ref.rs
+++ b/crates/ra_hir/src/type_ref.rs
@@ -107,4 +107,8 @@ impl TypeRef {
             TypeRef::Error
         }
     }
+
+    pub fn unit() -> TypeRef {
+        TypeRef::Tuple(Vec::new())
+    }
 }
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 7df6a9c466..24f72393a3 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -378,7 +378,11 @@ impl<R: TreeRoot<RaTypes>> BreakExprNode<R> {
 }
 
 
-impl<'a> BreakExpr<'a> {}
+impl<'a> BreakExpr<'a> {
+    pub fn expr(self) -> Option<Expr<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // Byte
 #[derive(Debug, Clone, Copy,)]
@@ -923,12 +927,7 @@ pub enum Expr<'a> {
     BlockExpr(BlockExpr<'a>),
     ReturnExpr(ReturnExpr<'a>),
     MatchExpr(MatchExpr<'a>),
-    MatchArmList(MatchArmList<'a>),
-    MatchArm(MatchArm<'a>),
-    MatchGuard(MatchGuard<'a>),
     StructLit(StructLit<'a>),
-    NamedFieldList(NamedFieldList<'a>),
-    NamedField(NamedField<'a>),
     CallExpr(CallExpr<'a>),
     IndexExpr(IndexExpr<'a>),
     MethodCallExpr(MethodCallExpr<'a>),
@@ -960,12 +959,7 @@ impl<'a> AstNode<'a> for Expr<'a> {
             BLOCK_EXPR => Some(Expr::BlockExpr(BlockExpr { syntax })),
             RETURN_EXPR => Some(Expr::ReturnExpr(ReturnExpr { syntax })),
             MATCH_EXPR => Some(Expr::MatchExpr(MatchExpr { syntax })),
-            MATCH_ARM_LIST => Some(Expr::MatchArmList(MatchArmList { syntax })),
-            MATCH_ARM => Some(Expr::MatchArm(MatchArm { syntax })),
-            MATCH_GUARD => Some(Expr::MatchGuard(MatchGuard { syntax })),
             STRUCT_LIT => Some(Expr::StructLit(StructLit { syntax })),
-            NAMED_FIELD_LIST => Some(Expr::NamedFieldList(NamedFieldList { syntax })),
-            NAMED_FIELD => Some(Expr::NamedField(NamedField { syntax })),
             CALL_EXPR => Some(Expr::CallExpr(CallExpr { syntax })),
             INDEX_EXPR => Some(Expr::IndexExpr(IndexExpr { syntax })),
             METHOD_CALL_EXPR => Some(Expr::MethodCallExpr(MethodCallExpr { syntax })),
@@ -997,12 +991,7 @@ impl<'a> AstNode<'a> for Expr<'a> {
             Expr::BlockExpr(inner) => inner.syntax(),
             Expr::ReturnExpr(inner) => inner.syntax(),
             Expr::MatchExpr(inner) => inner.syntax(),
-            Expr::MatchArmList(inner) => inner.syntax(),
-            Expr::MatchArm(inner) => inner.syntax(),
-            Expr::MatchGuard(inner) => inner.syntax(),
             Expr::StructLit(inner) => inner.syntax(),
-            Expr::NamedFieldList(inner) => inner.syntax(),
-            Expr::NamedField(inner) => inner.syntax(),
             Expr::CallExpr(inner) => inner.syntax(),
             Expr::IndexExpr(inner) => inner.syntax(),
             Expr::MethodCallExpr(inner) => inner.syntax(),
@@ -3880,6 +3869,10 @@ impl<'a> StructLit<'a> {
     pub fn named_field_list(self) -> Option<NamedFieldList<'a>> {
         super::child_opt(self)
     }
+
+    pub fn spread(self) -> Option<Expr<'a>> {
+        super::child_opt(self)
+    }
 }
 
 // StructPat
@@ -4147,7 +4140,15 @@ impl<R: TreeRoot<RaTypes>> TupleStructPatNode<R> {
 }
 
 
-impl<'a> TupleStructPat<'a> {}
+impl<'a> TupleStructPat<'a> {
+    pub fn args(self) -> impl Iterator<Item = Pat<'a>> + 'a {
+        super::children(self)
+    }
+
+    pub fn path(self) -> Option<Path<'a>> {
+        super::child_opt(self)
+    }
+}
 
 // TupleType
 #[derive(Debug, Clone, Copy,)]
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index c55e9e07a8..3c640ed47e 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -384,7 +384,7 @@ Grammar(
             options: [ "Condition" ]
         ),
         "ContinueExpr": (),
-        "BreakExpr": (),
+        "BreakExpr": (options: ["Expr"]),
         "Label": (),
         "BlockExpr": (
             options: [ "Block" ]
@@ -404,7 +404,7 @@ Grammar(
             collections: [ [ "pats", "Pat" ] ]
         ),
         "MatchGuard": (),
-        "StructLit": (options: ["Path", "NamedFieldList"]),
+        "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]),
         "NamedFieldList": (collections: [ ["fields", "NamedField"] ]),
         "NamedField": (options: ["NameRef", "Expr"]),
         "CallExpr": (
@@ -446,12 +446,7 @@ Grammar(
                 "BlockExpr",
                 "ReturnExpr",
                 "MatchExpr",
-                "MatchArmList",
-                "MatchArm",
-                "MatchGuard",
                 "StructLit",
-                "NamedFieldList",
-                "NamedField",
                 "CallExpr",
                 "IndexExpr",
                 "MethodCallExpr",
@@ -472,7 +467,10 @@ Grammar(
         "PathPat": (),
         "StructPat": (),
         "FieldPatList": (),
-        "TupleStructPat": (),
+        "TupleStructPat": (
+            options: ["Path"],
+            collections: [["args", "Pat"]],
+        ),
         "TuplePat": (),
         "SlicePat": (),
         "RangePat": (),


Test patch:
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index fb53fcf0be..515c66e851 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -1,7 +1,10 @@
+use std::sync::Arc;
 use std::fmt::Write;
 use std::path::{PathBuf, Path};
 use std::fs;
 
+use salsa::Database;
+
 use ra_db::{SyntaxDatabase};
 use ra_syntax::ast::{self, AstNode};
 use test_utils::{project_dir, assert_eq_text, read_text};
@@ -217,3 +220,44 @@ fn ellipsize(mut text: String, max_len: usize) -> String {
 fn test_data_dir() -> PathBuf {
     project_dir().join("crates/ra_hir/src/ty/tests/data")
 }
+
+#[test]
+#[should_panic] // TODO this should work once hir::Expr is used
+fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
+    let (mut db, pos) = MockDatabase::with_position(
+        "
+        //- /lib.rs
+        fn foo() -> i32 {
+            <|>1 + 1
+        }
+    ",
+    );
+    let func = source_binder::function_from_position(&db, pos)
+        .unwrap()
+        .unwrap();
+    {
+        let events = db.log_executed(|| {
+            func.infer(&db).unwrap();
+        });
+        assert!(format!("{:?}", events).contains("infer"))
+    }
+
+    let new_text = "
+        fn foo() -> i32 {
+            1
+            +
+            1
+        }
+    "
+    .to_string();
+
+    db.query_mut(ra_db::FileTextQuery)
+        .set(pos.file_id, Arc::new(new_text));
+
+    {
+        let events = db.log_executed(|| {
+            func.infer(&db).unwrap();
+        });
+        assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events)
+    }
+}