microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
language_service/src/definition.rs
244lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | #[cfg(test)] |
| 5 | mod tests; |
| 6 | |
| 7 | use crate::protocol::Definition; |
| 8 | use crate::qsc_utils::{ |
| 9 | find_item, map_offset, span_contains, Compilation, QSHARP_LIBRARY_URI_SCHEME, |
| 10 | }; |
| 11 | use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor}; |
| 12 | use qsc::hir::PackageId; |
| 13 | use qsc::{ast, hir, resolve}; |
| 14 | |
| 15 | pub(crate) fn get_definition( |
| 16 | compilation: &Compilation, |
| 17 | source_name: &str, |
| 18 | offset: u32, |
| 19 | ) -> Option<Definition> { |
| 20 | // Map the file offset into a SourceMap offset |
| 21 | let offset = map_offset(&compilation.unit.sources, source_name, offset); |
| 22 | let ast_package = &compilation.unit.ast; |
| 23 | |
| 24 | let mut definition_finder = DefinitionFinder { |
| 25 | compilation, |
| 26 | offset, |
| 27 | definition: None, |
| 28 | curr_callable: None, |
| 29 | }; |
| 30 | definition_finder.visit_package(&ast_package.package); |
| 31 | |
| 32 | definition_finder |
| 33 | .definition |
| 34 | .map(|(name, offset)| Definition { |
| 35 | source: name, |
| 36 | offset, |
| 37 | }) |
| 38 | } |
| 39 | |
| 40 | struct DefinitionFinder<'a> { |
| 41 | compilation: &'a Compilation, |
| 42 | offset: u32, |
| 43 | definition: Option<(String, u32)>, |
| 44 | curr_callable: Option<&'a ast::CallableDecl>, |
| 45 | } |
| 46 | |
| 47 | impl DefinitionFinder<'_> { |
| 48 | fn set_definition_from_position(&mut self, lo: u32, package_id: Option<PackageId>) { |
| 49 | let source_map = match package_id { |
| 50 | Some(id) => { |
| 51 | &self |
| 52 | .compilation |
| 53 | .package_store |
| 54 | .get(id) |
| 55 | .unwrap_or_else(|| panic!("package should exist for id {id}")) |
| 56 | .sources |
| 57 | } |
| 58 | None => &self.compilation.unit.sources, |
| 59 | }; |
| 60 | let source = source_map |
| 61 | .find_by_offset(lo) |
| 62 | .expect("source should exist for offset"); |
| 63 | // Note: Having a package_id means the position references a foreign package. |
| 64 | // Currently the only supported foreign packages are our library packages, |
| 65 | // URI's to which need to include our custom library scheme. |
| 66 | let source_name = match package_id { |
| 67 | Some(_) => format!("{}:{}", QSHARP_LIBRARY_URI_SCHEME, source.name), |
| 68 | None => source.name.to_string(), |
| 69 | }; |
| 70 | |
| 71 | self.definition = Some((source_name, lo - source.offset)); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | impl<'a> Visitor<'a> for DefinitionFinder<'a> { |
| 76 | // Handles callable and UDT definitions |
| 77 | fn visit_item(&mut self, item: &'a ast::Item) { |
| 78 | if span_contains(item.span, self.offset) { |
| 79 | match &*item.kind { |
| 80 | ast::ItemKind::Callable(decl) => { |
| 81 | if span_contains(decl.name.span, self.offset) { |
| 82 | self.set_definition_from_position(decl.name.span.lo, None); |
| 83 | } else if span_contains(decl.span, self.offset) { |
| 84 | self.curr_callable = Some(decl); |
| 85 | walk_callable_decl(self, decl); |
| 86 | self.curr_callable = None; |
| 87 | } |
| 88 | // Note: the `item.span` can cover things like doc |
| 89 | // comment, attributes, and visibility keywords, which aren't |
| 90 | // things we want to have hover logic for, while the `decl.span` is |
| 91 | // specific to the contents of the callable decl, which we do want |
| 92 | // hover logic for. If the `if` or `else if` above is not met, then |
| 93 | // the user is hovering over one of these non-decl parts of the item, |
| 94 | // and we want to do nothing. |
| 95 | } |
| 96 | ast::ItemKind::Ty(ident, def) => { |
| 97 | if span_contains(ident.span, self.offset) { |
| 98 | self.set_definition_from_position(ident.span.lo, None); |
| 99 | } else { |
| 100 | self.visit_ty_def(def); |
| 101 | } |
| 102 | } |
| 103 | _ => {} |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // Handles UDT field definitions |
| 109 | fn visit_ty_def(&mut self, def: &'a ast::TyDef) { |
| 110 | if span_contains(def.span, self.offset) { |
| 111 | if let ast::TyDefKind::Field(ident, ty) = &*def.kind { |
| 112 | if let Some(ident) = ident { |
| 113 | if span_contains(ident.span, self.offset) { |
| 114 | self.set_definition_from_position(ident.span.lo, None); |
| 115 | } else { |
| 116 | self.visit_ty(ty); |
| 117 | } |
| 118 | } else { |
| 119 | self.visit_ty(ty); |
| 120 | } |
| 121 | } else { |
| 122 | walk_ty_def(self, def); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // Handles local variable definitions |
| 128 | fn visit_pat(&mut self, pat: &'a ast::Pat) { |
| 129 | if span_contains(pat.span, self.offset) { |
| 130 | match &*pat.kind { |
| 131 | ast::PatKind::Bind(ident, anno) => { |
| 132 | if span_contains(ident.span, self.offset) { |
| 133 | self.set_definition_from_position(ident.span.lo, None); |
| 134 | } else if let Some(ty) = anno { |
| 135 | self.visit_ty(ty); |
| 136 | } |
| 137 | } |
| 138 | _ => walk_pat(self, pat), |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Handles UDT field references |
| 144 | fn visit_expr(&mut self, expr: &'a ast::Expr) { |
| 145 | if span_contains(expr.span, self.offset) { |
| 146 | match &*expr.kind { |
| 147 | ast::ExprKind::Field(udt, field) if span_contains(field.span, self.offset) => { |
| 148 | if let Some(hir::ty::Ty::Udt(res)) = |
| 149 | self.compilation.unit.ast.tys.terms.get(udt.id) |
| 150 | { |
| 151 | match res { |
| 152 | hir::Res::Item(item_id) => { |
| 153 | if let (Some(item), _) = find_item(self.compilation, item_id) { |
| 154 | match &item.kind { |
| 155 | hir::ItemKind::Ty(_, udt) => { |
| 156 | if let Some(field) = udt.find_field_by_name(&field.name) |
| 157 | { |
| 158 | let span = field.name_span.expect( |
| 159 | "field found via name should have a name", |
| 160 | ); |
| 161 | self.set_definition_from_position( |
| 162 | span.lo, |
| 163 | item_id.package, |
| 164 | ); |
| 165 | } |
| 166 | } |
| 167 | _ => panic!("UDT has invalid resolution."), |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | _ => panic!("UDT has invalid resolution."), |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | _ => walk_expr(self, expr), |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | // Handles local variable, UDT, and callable references |
| 181 | fn visit_path(&mut self, path: &'_ ast::Path) { |
| 182 | if span_contains(path.span, self.offset) { |
| 183 | let res = self.compilation.unit.ast.names.get(path.id); |
| 184 | if let Some(res) = res { |
| 185 | match &res { |
| 186 | resolve::Res::Item(item_id) => { |
| 187 | if let (Some(item), _) = find_item(self.compilation, item_id) { |
| 188 | let lo = match &item.kind { |
| 189 | hir::ItemKind::Callable(decl) => decl.name.span.lo, |
| 190 | hir::ItemKind::Namespace(_, _) => { |
| 191 | panic!( |
| 192 | "Reference node should not refer to a namespace: {}", |
| 193 | path.id |
| 194 | ) |
| 195 | } |
| 196 | hir::ItemKind::Ty(ident, _) => ident.span.lo, |
| 197 | }; |
| 198 | self.set_definition_from_position(lo, item_id.package); |
| 199 | }; |
| 200 | } |
| 201 | resolve::Res::Local(node_id) => { |
| 202 | if let Some(curr) = self.curr_callable { |
| 203 | { |
| 204 | let mut finder = AstPatFinder { |
| 205 | node_id, |
| 206 | result: None, |
| 207 | }; |
| 208 | finder.visit_callable_decl(curr); |
| 209 | if let Some(lo) = finder.result { |
| 210 | self.set_definition_from_position(lo, None); |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | _ => {} |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | struct AstPatFinder<'a> { |
| 223 | node_id: &'a ast::NodeId, |
| 224 | result: Option<u32>, |
| 225 | } |
| 226 | |
| 227 | impl<'a> Visitor<'a> for AstPatFinder<'_> { |
| 228 | fn visit_pat(&mut self, pat: &'a ast::Pat) { |
| 229 | match &*pat.kind { |
| 230 | ast::PatKind::Bind(ident, _) => { |
| 231 | if ident.id == *self.node_id { |
| 232 | self.result = Some(ident.span.lo); |
| 233 | } |
| 234 | } |
| 235 | _ => walk_pat(self, pat), |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | fn visit_expr(&mut self, expr: &'a ast::Expr) { |
| 240 | if self.result.is_none() { |
| 241 | walk_expr(self, expr); |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | |