Repository: rust-analyzer/rust-analyzer
Pull request #610 (2019-01-23 14:37:40)

move SyntaxPtr to ra_syntax


Patch:
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index dbeb9ec716..32d7e09b99 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -1,6 +1,5 @@
 //! ra_db defines basic database traits. The concrete DB is defined by ra_ide_api.
 mod cancellation;
-mod syntax_ptr;
 mod input;
 mod loc2id;
 pub mod mock;
@@ -12,7 +11,6 @@ use ra_syntax::{TextUnit, TextRange, SourceFile, TreeArc};
 pub use ::salsa as salsa;
 pub use crate::{
     cancellation::Canceled,
-    syntax_ptr::LocalSyntaxPtr,
     input::{
         FilesDatabase, FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency,
         FileTextQuery, FileSourceRootQuery, SourceRootQuery, LocalRootsQuery, LibraryRootsQuery, CrateGraphQuery,
diff --git a/crates/ra_db/src/syntax_ptr.rs b/crates/ra_db/src/syntax_ptr.rs
deleted file mode 100644
index 5270826da3..0000000000
--- a/crates/ra_db/src/syntax_ptr.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use ra_syntax::{AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TreeArc};
-
-/// A pointer to a syntax node inside a file.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct LocalSyntaxPtr {
-    range: TextRange,
-    kind: SyntaxKind,
-}
-
-impl LocalSyntaxPtr {
-    pub fn new(node: &SyntaxNode) -> LocalSyntaxPtr {
-        LocalSyntaxPtr {
-            range: node.range(),
-            kind: node.kind(),
-        }
-    }
-
-    pub fn resolve(self, file: &SourceFile) -> TreeArc<SyntaxNode> {
-        let mut curr = file.syntax();
-        loop {
-            if curr.range() == self.range && curr.kind() == self.kind {
-                return curr.to_owned();
-            }
-            curr = curr
-                .children()
-                .find(|it| self.range.is_subrange(&it.range()))
-                .unwrap_or_else(|| panic!("can't resolve local ptr to SyntaxNode: {:?}", self))
-        }
-    }
-
-    pub fn range(self) -> TextRange {
-        self.range
-    }
-
-    pub fn kind(self) -> SyntaxKind {
-        self.kind
-    }
-}
-
-#[test]
-fn test_local_syntax_ptr() {
-    use ra_syntax::{ast, AstNode};
-    let file = SourceFile::parse("struct Foo { f: u32, }");
-    let field = file
-        .syntax()
-        .descendants()
-        .find_map(ast::NamedFieldDef::cast)
-        .unwrap();
-    let ptr = LocalSyntaxPtr::new(field.syntax());
-    let field_syntax = ptr.resolve(&file);
-    assert_eq!(field.syntax(), &*field_syntax);
-}
diff --git a/crates/ra_hir/src/code_model_impl/function/scope.rs b/crates/ra_hir/src/code_model_impl/function/scope.rs
index 3a7d53a93d..c5d1de5eb5 100644
--- a/crates/ra_hir/src/code_model_impl/function/scope.rs
+++ b/crates/ra_hir/src/code_model_impl/function/scope.rs
@@ -3,12 +3,11 @@ use std::sync::Arc;
 use rustc_hash::{FxHashMap, FxHashSet};
 
 use ra_syntax::{
-    AstNode, SyntaxNode, TextUnit, TextRange,
+    AstNode, SyntaxNode, TextUnit, TextRange, SyntaxNodePtr,
     algo::generate,
     ast,
 };
 use ra_arena::{Arena, RawId, impl_arena_id};
-use ra_db::LocalSyntaxPtr;
 
 use crate::{Name, AsName, expr::{PatId, ExprId, Pat, Expr, Body, Statement, BodySyntaxMapping}};
 
@@ -126,7 +125,7 @@ pub struct ScopesWithSyntaxMapping {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ScopeEntryWithSyntax {
     name: Name,
-    ptr: LocalSyntaxPtr,
+    ptr: SyntaxNodePtr,
 }
 
 impl ScopeEntryWithSyntax {
@@ -134,7 +133,7 @@ impl ScopeEntryWithSyntax {
         &self.name
     }
 
-    pub fn ptr(&self) -> LocalSyntaxPtr {
+    pub fn ptr(&self) -> SyntaxNodePtr {
         self.ptr
     }
 }
@@ -169,7 +168,7 @@ impl ScopesWithSyntaxMapping {
 
     // 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 {
+    fn adjust(&self, ptr: SyntaxNodePtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId {
         let r = ptr.range();
         let child_scopes = self
             .scopes
@@ -212,7 +211,7 @@ impl ScopesWithSyntaxMapping {
 
     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 name_ptr = SyntaxNodePtr::new(pat.syntax());
         fn_def
             .syntax()
             .descendants()
@@ -230,7 +229,7 @@ impl ScopesWithSyntaxMapping {
 
     fn scope_for(&self, node: &SyntaxNode) -> Option<ScopeId> {
         node.ancestors()
-            .map(LocalSyntaxPtr::new)
+            .map(SyntaxNodePtr::new)
             .filter_map(|ptr| self.syntax_mapping.syntax_expr(ptr))
             .find_map(|it| self.scopes.scope_for(it))
     }
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index 51dae8e25b..1a38216922 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -4,8 +4,10 @@ use std::sync::Arc;
 use rustc_hash::FxHashMap;
 
 use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
-use ra_db::LocalSyntaxPtr;
-use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor};
+use ra_syntax::{
+    SyntaxNodePtr, AstNode,
+    ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor}
+};
 
 use crate::{Path, type_ref::{Mutability, TypeRef}, Name, HirDatabase, DefId, Def, name::AsName};
 use crate::ty::primitive::{UintTy, UncertainIntTy, UncertainFloatTy};
@@ -38,10 +40,10 @@ pub struct Body {
 #[derive(Debug, Eq, PartialEq)]
 pub struct BodySyntaxMapping {
     body: Arc<Body>,
-    expr_syntax_mapping: FxHashMap<LocalSyntaxPtr, ExprId>,
-    expr_syntax_mapping_back: ArenaMap<ExprId, LocalSyntaxPtr>,
-    pat_syntax_mapping: FxHashMap<LocalSyntaxPtr, PatId>,
-    pat_syntax_mapping_back: ArenaMap<PatId, LocalSyntaxPtr>,
+    expr_syntax_mapping: FxHashMap<SyntaxNodePtr, ExprId>,
+    expr_syntax_mapping_back: ArenaMap<ExprId, SyntaxNodePtr>,
+    pat_syntax_mapping: FxHashMap<SyntaxNodePtr, PatId>,
+    pat_syntax_mapping_back: ArenaMap<PatId, SyntaxNodePtr>,
 }
 
 impl Body {
@@ -71,31 +73,31 @@ impl Index<PatId> for Body {
 }
 
 impl BodySyntaxMapping {
-    pub fn expr_syntax(&self, expr: ExprId) -> Option<LocalSyntaxPtr> {
+    pub fn expr_syntax(&self, expr: ExprId) -> Option<SyntaxNodePtr> {
         self.expr_syntax_mapping_back.get(expr).cloned()
     }
 
-    pub fn syntax_expr(&self, ptr: LocalSyntaxPtr) -> Option<ExprId> {
+    pub fn syntax_expr(&self, ptr: SyntaxNodePtr) -> Option<ExprId> {
         self.expr_syntax_mapping.get(&ptr).cloned()
     }
 
     pub fn node_expr(&self, node: &ast::Expr) -> Option<ExprId> {
         self.expr_syntax_mapping
-            .get(&LocalSyntaxPtr::new(node.syntax()))
+            .get(&SyntaxNodePtr::new(node.syntax()))
             .cloned()
     }
 
-    pub fn pat_syntax(&self, pat: PatId) -> Option<LocalSyntaxPtr> {
+    pub fn pat_syntax(&self, pat: PatId) -> Option<SyntaxNodePtr> {
         self.pat_syntax_mapping_back.get(pat).cloned()
     }
 
-    pub fn syntax_pat(&self, ptr: LocalSyntaxPtr) -> Option<PatId> {
+    pub fn syntax_pat(&self, ptr: SyntaxNodePtr) -> Option<PatId> {
         self.pat_syntax_mapping.get(&ptr).cloned()
     }
 
     pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
         self.pat_syntax_mapping
-            .get(&LocalSyntaxPtr::new(node.syntax()))
+            .get(&SyntaxNodePtr::new(node.syntax()))
             .cloned()
     }
 
@@ -440,10 +442,10 @@ pub(crate) fn body_hir(db: &impl HirDatabase, def_id: DefId) -> Arc<Body> {
 struct ExprCollector {
     exprs: Arena<ExprId, Expr>,
     pats: Arena<PatId, Pat>,
-    expr_syntax_mapping: FxHashMap<LocalSyntaxPtr, ExprId>,
-    expr_syntax_mapping_back: ArenaMap<ExprId, LocalSyntaxPtr>,
-    pat_syntax_mapping: FxHashMap<LocalSyntaxPtr, PatId>,
-    pat_syntax_mapping_back: ArenaMap<PatId, LocalSyntaxPtr>,
+    expr_syntax_mapping: FxHashMap<SyntaxNodePtr, ExprId>,
+    expr_syntax_mapping_back: ArenaMap<ExprId, SyntaxNodePtr>,
+    pat_syntax_mapping: FxHashMap<SyntaxNodePtr, PatId>,
+    pat_syntax_mapping_back: ArenaMap<PatId, SyntaxNodePtr>,
 }
 
 impl ExprCollector {
@@ -458,14 +460,14 @@ impl ExprCollector {
         }
     }
 
-    fn alloc_expr(&mut self, expr: Expr, syntax_ptr: LocalSyntaxPtr) -> ExprId {
+    fn alloc_expr(&mut self, expr: Expr, syntax_ptr: SyntaxNodePtr) -> 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 {
+    fn alloc_pat(&mut self, pat: Pat, syntax_ptr: SyntaxNodePtr) -> PatId {
         let id = self.pats.alloc(pat);
         self.pat_syntax_mapping.insert(syntax_ptr, id);
         self.pat_syntax_mapping_back.insert(id, syntax_ptr);
@@ -481,7 +483,7 @@ impl ExprCollector {
     }
 
     fn collect_expr(&mut self, expr: &ast::Expr) -> ExprId {
-        let syntax_ptr = LocalSyntaxPtr::new(expr.syntax());
+        let syntax_ptr = SyntaxNodePtr::new(expr.syntax());
         match expr.kind() {
             ast::ExprKind::IfExpr(e) => {
                 if let Some(pat) = e.condition().and_then(|c| c.pat()) {
@@ -643,9 +645,9 @@ impl ExprCollector {
                                 // field shorthand
                                 let id = self.exprs.alloc(Expr::Path(Path::from_name_ref(nr)));
                                 self.expr_syntax_mapping
-                                    .insert(LocalSyntaxPtr::new(nr.syntax()), id);
+                                    .insert(SyntaxNodePtr::new(nr.syntax()), id);
                                 self.expr_syntax_mapping_back
-                                    .insert(id, LocalSyntaxPtr::new(nr.syntax()));
+                                    .insert(id, SyntaxNodePtr::new(nr.syntax()));
                                 id
                             } else {
                                 self.exprs.alloc(Expr::Missing)
@@ -806,7 +808,7 @@ impl ExprCollector {
         let tail = block.expr().map(|e| self.collect_expr(e));
         self.alloc_expr(
             Expr::Block { statements, tail },
-            LocalSyntaxPtr::new(block.syntax()),
+            SyntaxNodePtr::new(block.syntax()),
         )
     }
 
@@ -883,7 +885,7 @@ impl ExprCollector {
             // TODO: implement
             ast::PatKind::SlicePat(_) | ast::PatKind::RangePat(_) => Pat::Missing,
         };
-        let syntax_ptr = LocalSyntaxPtr::new(pat.syntax());
+        let syntax_ptr = SyntaxNodePtr::new(pat.syntax());
         self.alloc_pat(pattern, syntax_ptr)
     }
 
@@ -919,7 +921,7 @@ pub(crate) fn collect_fn_body_syntax(node: &ast::FnDef) -> BodySyntaxMapping {
         let mut params = Vec::new();
 
         if let Some(self_param) = param_list.self_param() {
-            let self_param = LocalSyntaxPtr::new(
+            let self_param = SyntaxNodePtr::new(
                 self_param
                     .self_kw()
                     .expect("self param without self keyword")
diff --git a/crates/ra_hir/src/macros.rs b/crates/ra_hir/src/macros.rs
index 220bee94e0..7ca34d4345 100644
--- a/crates/ra_hir/src/macros.rs
+++ b/crates/ra_hir/src/macros.rs
@@ -9,9 +9,8 @@
 /// those yet, so all macros are string based at the moment!
 use std::sync::Arc;
 
-use ra_db::LocalSyntaxPtr;
 use ra_syntax::{
-    TextRange, TextUnit, SourceFile, AstNode, SyntaxNode, TreeArc,
+    TextRange, TextUnit, SourceFile, AstNode, SyntaxNode, TreeArc, SyntaxNodePtr,
     ast::{self, NameOwner},
 };
 
@@ -80,7 +79,7 @@ impl MacroDef {
         let file = SourceFile::parse(&text);
         let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?;
         let match_arg = match_expr.expr()?;
-        let ptr = LocalSyntaxPtr::new(match_arg.syntax());
+        let ptr = SyntaxNodePtr::new(match_arg.syntax());
         let src_range = TextRange::offset_len(0.into(), TextUnit::of_str(&input.text));
         let ranges_map = vec![(src_range, match_arg.syntax().range())];
         let res = MacroExpansion {
@@ -94,7 +93,7 @@ impl MacroDef {
         let text = format!(r"fn dummy() {{ {}; }}", input.text);
         let file = SourceFile::parse(&text);
         let array_expr = file.syntax().descendants().find_map(ast::ArrayExpr::cast)?;
-        let ptr = LocalSyntaxPtr::new(array_expr.syntax());
+        let ptr = SyntaxNodePtr::new(array_expr.syntax());
         let src_range = TextRange::offset_len(0.into(), TextUnit::of_str(&input.text));
         let ranges_map = vec![(src_range, array_expr.syntax().range())];
         let res = MacroExpansion {
@@ -119,7 +118,7 @@ impl MacroDef {
         let file = SourceFile::parse(&text);
         let trait_def = file.syntax().descendants().find_map(ast::TraitDef::cast)?;
         let name = trait_def.name()?;
-        let ptr = LocalSyntaxPtr::new(trait_def.syntax());
+        let ptr = SyntaxNodePtr::new(trait_def.syntax());
         let ranges_map = vec![(src_range, name.syntax().range())];
         let res = MacroExpansion {
             text,
@@ -146,7 +145,7 @@ pub struct MacroExpansion {
     /// Implementation detail: internally, a macro is expanded to the whole file,
     /// even if it is an expression. This `ptr` selects the actual expansion from
     /// the expanded file.
-    ptr: LocalSyntaxPtr,
+    ptr: SyntaxNodePtr,
 }
 
 impl MacroExpansion {
@@ -157,7 +156,7 @@ impl MacroExpansion {
     }
 
     pub fn syntax(&self) -> TreeArc<SyntaxNode> {
-        self.ptr.resolve(&self.file())
+        self.ptr.to_node(&self.file()).to_owned()
     }
     /// Maps range in the source code to the range in the expanded code.
     pub fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> {
diff --git a/crates/ra_hir/src/nameres/lower.rs b/crates/ra_hir/src/nameres/lower.rs
index 52448644cd..ab6f3a9bce 100644
--- a/crates/ra_hir/src/nameres/lower.rs
+++ b/crates/ra_hir/src/nameres/lower.rs
@@ -1,10 +1,10 @@
 use std::sync::Arc;
 
 use ra_syntax::{
-    SyntaxKind, AstNode, SourceFile, TreeArc,
+    SyntaxKind, AstNode, SourceFile, TreeArc, SyntaxNodePtr,
     ast::{self, ModuleItemOwner},
 };
-use ra_db::{SourceRootId, LocalSyntaxPtr};
+use ra_db::{SourceRootId};
 use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
 
 use crate::{
@@ -72,13 +72,13 @@ pub struct LoweredModule {
 
 #[derive(Debug, Default, PartialEq, Eq)]
 pub struct ImportSourceMap {
-    map: ArenaMap<ImportId, LocalSyntaxPtr>,
+    map: ArenaMap<ImportId, SyntaxNodePtr>,
 }
 
 impl ImportSourceMap {
     fn insert(&mut self, import: ImportId, segment: &ast::PathSegment) {
         self.map
-            .insert(import, LocalSyntaxPtr::new(segment.syntax()))
+            .insert(import, SyntaxNodePtr::new(segment.syntax()))
     }
 
     pub fn get(&self, source: &ModuleSource, import: ImportId) -> TreeArc<ast::PathSegment> {
@@ -87,7 +87,7 @@ impl ImportSourceMap {
             ModuleSource::Module(m) => m.syntax().ancestors().find_map(SourceFile::cast).unwrap(),
         };
 
-        ast::PathSegment::cast(&self.map[import].resolve(file))
+        ast::PathSegment::cast(self.map[import].to_node(file))
             .unwrap()
             .to_owned()
     }
diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs
index 798fb7c135..0449c1902a 100644
--- a/crates/ra_ide_api/src/call_info.rs
+++ b/crates/ra_ide_api/src/call_info.rs
@@ -23,8 +23,8 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
         .into_iter()
         .find(|it| it.ptr.kind() == FN_DEF)?;
     let fn_file = db.source_file(symbol.file_id);
-    let fn_def = symbol.ptr.resolve(&fn_file);
-    let fn_def = ast::FnDef::cast(&fn_def).unwrap();
+    let fn_def = symbol.ptr.to_node(&fn_file);
+    let fn_def = ast::FnDef::cast(fn_def).unwrap();
     let mut call_info = CallInfo::new(fn_def)?;
     // If we have a calling expression let's find which argument we are on
     let num_params = call_info.parameters.len();
diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs
index fa79908a1b..ddd9354ecb 100644
--- a/crates/ra_ide_api/src/imp.rs
+++ b/crates/ra_ide_api/src/imp.rs
@@ -153,7 +153,7 @@ impl db::RootDatabase {
                 source_binder::function_from_child_node(db, position.file_id, name_ref.syntax())?;
             let scope = descr.scopes(db);
             let resolved = scope.resolve_local_name(name_ref)?;
-            let resolved = resolved.ptr().resolve(source_file);
+            let resolved = resolved.ptr().to_node(source_file);
             let binding = find_node_at_offset::<ast::BindPat>(syntax, resolved.range().end())?;
             Some((binding, descr))
         }
diff --git a/crates/ra_ide_api/src/symbol_index.rs b/crates/ra_ide_api/src/symbol_index.rs
index b563dfe88c..1b5d1eb1d4 100644
--- a/crates/ra_ide_api/src/symbol_index.rs
+++ b/crates/ra_ide_api/src/symbol_index.rs
@@ -27,13 +27,13 @@ use std::{
 
 use fst::{self, Streamer};
 use ra_syntax::{
-    SyntaxNode, SourceFile, SmolStr, TreeArc, AstNode,
+    SyntaxNode, SyntaxNodePtr, SourceFile, SmolStr, TreeArc, AstNode,
     algo::{visit::{visitor, Visitor}, find_covering_node},
     SyntaxKind::{self, *},
     ast::{self, NameOwner},
 };
 use ra_db::{
-    SourceRootId, FilesDatabase, LocalSyntaxPtr,
+    SourceRootId, FilesDatabase,
     salsa::{self, ParallelDatabase},
 };
 use rayon::prelude::*;
@@ -62,7 +62,7 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
 
     for (name, text_range) in hir::source_binder::macro_symbols(db, file_id) {
         let node = find_covering_node(source_file.syntax(), text_range);
-        let ptr = LocalSyntaxPtr::new(node);
+        let ptr = SyntaxNodePtr::new(node);
         symbols.push(FileSymbol { file_id, name, ptr })
     }
 
@@ -196,13 +196,13 @@ fn is_type(kind: SyntaxKind) -> bool {
 pub(crate) struct FileSymbol {
     pub(crate) file_id: FileId,
     pub(crate) name: SmolStr,
-    pub(crate) ptr: LocalSyntaxPtr,
+    pub(crate) ptr: SyntaxNodePtr,
 }
 
-fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, LocalSyntaxPtr)> {
-    fn decl<N: NameOwner>(node: &N) -> Option<(SmolStr, LocalSyntaxPtr)> {
+fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr)> {
+    fn decl<N: NameOwner>(node: &N) -> Option<(SmolStr, SyntaxNodePtr)> {
         let name = node.name()?.text().clone();
-        let ptr = LocalSyntaxPtr::new(node.syntax());
+        let ptr = SyntaxNodePtr::new(node.syntax());
         Some((name, ptr))
     }
     visitor()
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index bc311cbbcb..97b196118c 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -35,6 +35,7 @@ mod syntax_kinds;
 pub mod utils;
 mod validation;
 mod yellow;
+mod ptr;
 
 pub use rowan::{SmolStr, TextRange, TextUnit};
 pub use crate::{
@@ -42,6 +43,7 @@ pub use crate::{
     lexer::{tokenize, Token},
     syntax_kinds::SyntaxKind,
     yellow::{Direction, SyntaxError, SyntaxNode, WalkEvent, Location, TreeArc},
+    ptr::SyntaxNodePtr,
 };
 
 use ra_text_edit::AtomTextEdit;
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs
new file mode 100644
index 0000000000..e8c40e5d3e
--- /dev/null
+++ b/crates/ra_syntax/src/ptr.rs
@@ -0,0 +1,53 @@
+use crate::{
+    AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange,
+    algo::generate,
+};
+
+/// A pointer to a syntax node inside a file. It can be used to remember a
+/// specific node across reparses of the same file.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct SyntaxNodePtr {
+    range: TextRange,
+    kind: SyntaxKind,
+}
+
+impl SyntaxNodePtr {
+    pub fn new(node: &SyntaxNode) -> SyntaxNodePtr {
+        SyntaxNodePtr {
+            range: node.range(),
+            kind: node.kind(),
+        }
+    }
+
+    pub fn to_node(self, source_file: &SourceFile) -> &SyntaxNode {
+        generate(Some(source_file.syntax()), |&node| {
+            node.children()
+                .find(|it| self.range.is_subrange(&it.range()))
+        })
+        .find(|it| it.range() == self.range && it.kind() == self.kind)
+        .unwrap_or_else(|| panic!("can't resolve local ptr to SyntaxNode: {:?}", self))
+    }
+
+    pub fn range(self) -> TextRange {
+        self.range
+    }
+
+    pub fn kind(self) -> SyntaxKind {
+        self.kind
+    }
+}
+
+#[test]
+fn test_local_syntax_ptr() {
+    use crate::{ast, AstNode};
+
+    let file = SourceFile::parse("struct Foo { f: u32, }");
+    let field = file
+        .syntax()
+        .descendants()
+        .find_map(ast::NamedFieldDef::cast)
+        .unwrap();
+    let ptr = SyntaxNodePtr::new(field.syntax());
+    let field_syntax = ptr.to_node(&file);
+    assert_eq!(field.syntax(), &*field_syntax);
+}


Test patch:
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 9bf1fd3b23..92c74cf008 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -562,7 +562,7 @@ fn infer(content: &str) -> String {
         // sort ranges for consistency
         types.sort_by_key(|(ptr, _)| (ptr.range().start(), ptr.range().end()));
         for (syntax_ptr, ty) in &types {
-            let node = syntax_ptr.resolve(&source_file);
+            let node = syntax_ptr.to_node(&source_file);
             write!(
                 acc,
                 "{} '{}': {}\n",