|
| 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(¯o_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 | +} |
0 commit comments