microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cd67b36992d2ea20ba330acb56e2e9ac04c11938

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

language_service/src/definition.rs

244lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::protocol::Definition;
8use crate::qsc_utils::{
9 find_item, map_offset, span_contains, Compilation, QSHARP_LIBRARY_URI_SCHEME,
10};
11use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor};
12use qsc::hir::PackageId;
13use qsc::{ast, hir, resolve};
14
15pub(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
40struct DefinitionFinder<'a> {
41 compilation: &'a Compilation,
42 offset: u32,
43 definition: Option<(String, u32)>,
44 curr_callable: Option<&'a ast::CallableDecl>,
45}
46
47impl 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
75impl<'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
222struct AstPatFinder<'a> {
223 node_id: &'a ast::NodeId,
224 result: Option<u32>,
225}
226
227impl<'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