Skip to content

Commit 3492bb5

Browse files
committed
Yet another attempt at nice API
1 parent cba3c99 commit 3492bb5

13 files changed

+399
-236
lines changed

crates/ra_hir/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ macro_rules! impl_froms {
2626
}
2727
}
2828

29+
mod semantics;
2930
pub mod db;
3031
pub mod source_analyzer;
3132
pub mod source_binder;
@@ -45,6 +46,7 @@ pub use crate::{
4546
StructField, Trait, Type, TypeAlias, TypeParam, Union, VariantDef,
4647
},
4748
has_source::HasSource,
49+
semantics::Semantics,
4850
source_analyzer::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer},
4951
source_binder::SourceBinder,
5052
};

crates/ra_hir/src/semantics.rs

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use std::{cell::RefCell, iter::successors};
2+
3+
use ra_db::{FileId, FileRange};
4+
use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken, TextRange};
5+
use rustc_hash::FxHashMap;
6+
7+
use crate::{
8+
db::HirDatabase, source_binder::ToDef, Function, HirFileId, InFile, MacroDef, Origin,
9+
PathResolution, SourceAnalyzer, SourceBinder, StructField, Type,
10+
};
11+
12+
pub struct Semantics<'a, DB> {
13+
pub db: &'a DB,
14+
sb: RefCell<SourceBinder<'a, DB>>,
15+
cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
16+
}
17+
18+
impl<DB: HirDatabase> Semantics<'_, DB> {
19+
pub fn new(db: &DB) -> Semantics<DB> {
20+
let sb = RefCell::new(SourceBinder::new(db));
21+
Semantics { db, sb, cache: RefCell::default() }
22+
}
23+
24+
pub fn file_syntax(&self, file_id: FileId) -> ast::SourceFile {
25+
let tree = self.db.parse(file_id).tree();
26+
self.cache(tree.syntax().clone(), file_id.into());
27+
tree
28+
}
29+
30+
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
31+
let parent = token.parent();
32+
let parent = self.find_file(parent);
33+
let sa = self.sb.borrow_mut().analyze(parent.as_ref(), None);
34+
35+
let token = successors(Some(parent.with_value(token)), |token| {
36+
let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
37+
let tt = macro_call.token_tree()?;
38+
if !token.value.text_range().is_subrange(&tt.syntax().text_range()) {
39+
return None;
40+
}
41+
let exp = sa.expand(self.db, token.with_value(&macro_call))?;
42+
let token = exp.map_token_down(self.db, token.as_ref())?;
43+
44+
self.cache(find_root(&token.value.parent()), token.file_id);
45+
46+
Some(token)
47+
})
48+
.last()
49+
.unwrap();
50+
51+
token.value
52+
}
53+
54+
pub fn original_range(&self, node: &SyntaxNode) -> FileRange {
55+
if let Some((range, Origin::Call)) = self.original_range_and_origin(node) {
56+
return range;
57+
}
58+
let node = self.find_file(node.clone());
59+
60+
if let Some(expansion) = node.file_id.expansion_info(self.db) {
61+
if let Some(call_node) = expansion.call_node() {
62+
return FileRange {
63+
file_id: call_node.file_id.original_file(self.db),
64+
range: call_node.value.text_range(),
65+
};
66+
}
67+
}
68+
69+
// FIXME: we should never hit this case?
70+
FileRange { file_id: node.file_id.original_file(self.db), range: node.value.text_range() }
71+
}
72+
73+
fn original_range_and_origin(&self, node: &SyntaxNode) -> Option<(FileRange, Origin)> {
74+
let node = self.find_file(node.clone());
75+
let expansion = node.file_id.expansion_info(self.db)?;
76+
77+
// the input node has only one token ?
78+
let single = node.value.first_token()? == node.value.last_token()?;
79+
80+
// FIXME: We should handle recurside macro expansions
81+
let (range, origin) = node.value.descendants().find_map(|it| {
82+
let first = it.first_token()?;
83+
let last = it.last_token()?;
84+
85+
if !single && first == last {
86+
return None;
87+
}
88+
89+
// Try to map first and last tokens of node, and, if success, return the union range of mapped tokens
90+
let (first, first_origin) = expansion.map_token_up(node.with_value(&first))?;
91+
let (last, last_origin) = expansion.map_token_up(node.with_value(&last))?;
92+
93+
if first.file_id != last.file_id || first_origin != last_origin {
94+
return None;
95+
}
96+
97+
// FIXME: Add union method in TextRange
98+
Some((
99+
first.with_value(union_range(first.value.text_range(), last.value.text_range())),
100+
first_origin,
101+
))
102+
})?;
103+
104+
return Some((
105+
FileRange { file_id: range.file_id.original_file(self.db), range: range.value },
106+
origin,
107+
));
108+
109+
fn union_range(a: TextRange, b: TextRange) -> TextRange {
110+
let start = a.start().min(b.start());
111+
let end = a.end().max(b.end());
112+
TextRange::from_to(start, end)
113+
}
114+
}
115+
116+
pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
117+
let node = self.find_file(node);
118+
node.ancestors_with_macros(self.db).map(|it| it.value)
119+
}
120+
121+
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
122+
self.analyze(expr.syntax().clone()).type_of(self.db, &expr)
123+
}
124+
125+
pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<Type> {
126+
self.analyze(pat.syntax().clone()).type_of_pat(self.db, &pat)
127+
}
128+
129+
pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
130+
self.analyze(call.syntax().clone()).resolve_method_call(call)
131+
}
132+
133+
pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<StructField> {
134+
self.analyze(field.syntax().clone()).resolve_field(field)
135+
}
136+
137+
pub fn resolve_record_field(&self, field: &ast::RecordField) -> Option<StructField> {
138+
self.analyze(field.syntax().clone()).resolve_record_field(field)
139+
}
140+
141+
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
142+
let sa = self.analyze(macro_call.syntax().clone());
143+
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
144+
sa.resolve_macro_call(self.db, macro_call)
145+
}
146+
147+
pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
148+
self.analyze(path.syntax().clone()).resolve_path(self.db, path)
149+
}
150+
151+
// FIXME: use this instead?
152+
// pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
153+
154+
pub fn to_def<T: ToDef>(&self, src: T) -> Option<T::Def> {
155+
let src = self.find_file(src.syntax().clone()).with_value(src);
156+
let mut sb = self.sb.borrow_mut();
157+
T::to_def(&mut sb, src)
158+
}
159+
160+
fn analyze(&self, node: SyntaxNode) -> SourceAnalyzer {
161+
let src = self.find_file(node.clone());
162+
self.sb.borrow_mut().analyze(src.as_ref(), None)
163+
}
164+
165+
fn cache(&self, root_node: SyntaxNode, file_id: HirFileId) {
166+
assert!(root_node.parent().is_none());
167+
let mut cache = self.cache.borrow_mut();
168+
let prev = cache.insert(root_node, file_id);
169+
assert!(prev.is_none());
170+
}
171+
fn lookup(&self, root_node: &SyntaxNode) -> HirFileId {
172+
let cache = self.cache.borrow();
173+
cache[root_node]
174+
}
175+
176+
fn find_file(&self, node: SyntaxNode) -> InFile<SyntaxNode> {
177+
let root_node = find_root(&node);
178+
let file_id = self.lookup(&root_node);
179+
InFile::new(file_id, node)
180+
}
181+
}
182+
183+
fn find_root(node: &SyntaxNode) -> SyntaxNode {
184+
node.ancestors().last().unwrap()
185+
}

crates/ra_ide/src/expand.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken, TextRange};
88

99
use crate::FileRange;
1010

11+
// TODO: remove this
1112
pub(crate) fn original_range(db: &RootDatabase, node: InFile<&SyntaxNode>) -> FileRange {
1213
if let Some((range, Origin::Call)) = original_range_and_origin(db, node) {
1314
return range;
@@ -70,6 +71,7 @@ fn original_range_and_origin(
7071
}
7172
}
7273

74+
// TODO: remove this
7375
pub(crate) fn descend_into_macros(
7476
db: &RootDatabase,
7577
file_id: FileId,

crates/ra_ide/src/goto_definition.rs

+19-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! FIXME: write short doc here
22
3-
use hir::{db::AstDatabase, InFile, SourceBinder};
4-
use ra_ide_db::{symbol_index, RootDatabase};
3+
use hir::Semantics;
4+
use ra_ide_db::{defs::classify_name, symbol_index, RootDatabase};
55
use ra_syntax::{
66
ast::{self},
77
match_ast, AstNode,
@@ -11,27 +11,26 @@ use ra_syntax::{
1111

1212
use crate::{
1313
display::{ToNav, TryToNav},
14-
expand::descend_into_macros,
15-
references::{classify_name, classify_name_ref},
14+
references::classify_name_ref,
1615
FilePosition, NavigationTarget, RangeInfo,
1716
};
1817

1918
pub(crate) fn goto_definition(
2019
db: &RootDatabase,
2120
position: FilePosition,
2221
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
23-
let file = db.parse_or_expand(position.file_id.into())?;
22+
let sema = Semantics::new(db);
23+
let file = sema.file_syntax(position.file_id).syntax().clone();
2424
let original_token = pick_best(file.token_at_offset(position.offset))?;
25-
let token = descend_into_macros(db, position.file_id, original_token.clone());
25+
let token = sema.descend_into_macros(original_token.clone());
2626

27-
let mut sb = SourceBinder::new(db);
2827
let nav_targets = match_ast! {
29-
match (token.value.parent()) {
28+
match (token.parent()) {
3029
ast::NameRef(name_ref) => {
31-
reference_definition(&mut sb, token.with_value(&name_ref)).to_vec()
30+
reference_definition(&sema, &name_ref).to_vec()
3231
},
3332
ast::Name(name) => {
34-
name_definition(&mut sb, token.with_value(&name))?
33+
name_definition(&sema, &name)?
3534
},
3635
_ => return None,
3736
}
@@ -68,33 +67,33 @@ impl ReferenceResult {
6867
}
6968

7069
pub(crate) fn reference_definition(
71-
sb: &mut SourceBinder<RootDatabase>,
72-
name_ref: InFile<&ast::NameRef>,
70+
sema: &Semantics<RootDatabase>,
71+
name_ref: &ast::NameRef,
7372
) -> ReferenceResult {
7473
use self::ReferenceResult::*;
7574

76-
let name_kind = classify_name_ref(sb, name_ref);
75+
let name_kind = classify_name_ref(sema, name_ref);
7776
if let Some(def) = name_kind {
78-
return match def.try_to_nav(sb.db) {
77+
return match def.try_to_nav(sema.db) {
7978
Some(nav) => ReferenceResult::Exact(nav),
8079
None => ReferenceResult::Approximate(Vec::new()),
8180
};
8281
}
8382

8483
// Fallback index based approach:
85-
let navs = symbol_index::index_resolve(sb.db, name_ref.value)
84+
let navs = symbol_index::index_resolve(sema.db, name_ref)
8685
.into_iter()
87-
.map(|s| s.to_nav(sb.db))
86+
.map(|s| s.to_nav(sema.db))
8887
.collect();
8988
Approximate(navs)
9089
}
9190

9291
fn name_definition(
93-
sb: &mut SourceBinder<RootDatabase>,
94-
name: InFile<&ast::Name>,
92+
sema: &Semantics<RootDatabase>,
93+
name: &ast::Name,
9594
) -> Option<Vec<NavigationTarget>> {
96-
let def = classify_name(sb, name)?;
97-
let nav = def.try_to_nav(sb.db)?;
95+
let def = classify_name(sema, name)?;
96+
let nav = def.try_to_nav(sema.db)?;
9897
Some(vec![nav])
9998
}
10099

crates/ra_ide/src/goto_type_definition.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
//! FIXME: write short doc here
22
3-
use hir::db::AstDatabase;
43
use ra_ide_db::RootDatabase;
5-
use ra_syntax::{ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
4+
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
65

7-
use crate::{
8-
display::ToNav, expand::descend_into_macros, FilePosition, NavigationTarget, RangeInfo,
9-
};
6+
use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
107

118
pub(crate) fn goto_type_definition(
129
db: &RootDatabase,
1310
position: FilePosition,
1411
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
15-
let file = db.parse_or_expand(position.file_id.into())?;
16-
let token = pick_best(file.token_at_offset(position.offset))?;
17-
let token = descend_into_macros(db, position.file_id, token);
18-
19-
let node = token
20-
.value
21-
.ancestors()
22-
.find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
23-
24-
let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None);
12+
let sema = hir::Semantics::new(db);
13+
14+
let file: ast::SourceFile = sema.file_syntax(position.file_id);
15+
let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?;
16+
let token: SyntaxToken = sema.descend_into_macros(token);
17+
18+
let (ty, node) = sema.ancestors_with_macros(token.parent()).find_map(|node| {
19+
let ty = match_ast! {
20+
match node {
21+
ast::Expr(expr) => { sema.type_of_expr(&expr)? },
22+
ast::Pat(pat) => { sema.type_of_pat(&pat)? },
23+
_ => { return None },
24+
}
25+
};
2526

26-
let ty: hir::Type = ast::Expr::cast(node.clone())
27-
.and_then(|e| analyzer.type_of(db, &e))
28-
.or_else(|| ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p)))?;
27+
Some((ty, node))
28+
})?;
2929

3030
let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?;
3131

0 commit comments

Comments
 (0)