microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.0.10-rc

Branches

Tags

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

Clone

HTTPS

Download ZIP

language_service/src/definition.rs

216lines · 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_ident, 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 let context = self.curr_callable;
85 self.curr_callable = Some(decl);
86 walk_callable_decl(self, decl);
87 self.curr_callable = context;
88 }
89 // Note: the `item.span` can cover things like doc
90 // comment, attributes, and visibility keywords, which aren't
91 // things we want to have hover logic for, while the `decl.span` is
92 // specific to the contents of the callable decl, which we do want
93 // hover logic for. If the `if` or `else if` above is not met, then
94 // the user is hovering over one of these non-decl parts of the item,
95 // and we want to do nothing.
96 }
97 ast::ItemKind::Ty(ident, def) => {
98 if span_contains(ident.span, self.offset) {
99 self.set_definition_from_position(ident.span.lo, None);
100 } else {
101 self.visit_ty_def(def);
102 }
103 }
104 _ => {}
105 }
106 }
107 }
108
109 // Handles UDT field definitions
110 fn visit_ty_def(&mut self, def: &'a ast::TyDef) {
111 if span_contains(def.span, self.offset) {
112 if let ast::TyDefKind::Field(ident, ty) = &*def.kind {
113 if let Some(ident) = ident {
114 if span_contains(ident.span, self.offset) {
115 self.set_definition_from_position(ident.span.lo, None);
116 } else {
117 self.visit_ty(ty);
118 }
119 } else {
120 self.visit_ty(ty);
121 }
122 } else {
123 walk_ty_def(self, def);
124 }
125 }
126 }
127
128 // Handles local variable definitions
129 fn visit_pat(&mut self, pat: &'a ast::Pat) {
130 if span_contains(pat.span, self.offset) {
131 match &*pat.kind {
132 ast::PatKind::Bind(ident, anno) => {
133 if span_contains(ident.span, self.offset) {
134 self.set_definition_from_position(ident.span.lo, None);
135 } else if let Some(ty) = anno {
136 self.visit_ty(ty);
137 }
138 }
139 _ => walk_pat(self, pat),
140 }
141 }
142 }
143
144 // Handles UDT field references
145 fn visit_expr(&mut self, expr: &'a ast::Expr) {
146 if span_contains(expr.span, self.offset) {
147 match &*expr.kind {
148 ast::ExprKind::Field(udt, field) if span_contains(field.span, self.offset) => {
149 if let Some(hir::ty::Ty::Udt(res)) =
150 self.compilation.unit.ast.tys.terms.get(udt.id)
151 {
152 match res {
153 hir::Res::Item(item_id) => {
154 if let (Some(item), _) = find_item(self.compilation, item_id) {
155 match &item.kind {
156 hir::ItemKind::Ty(_, udt) => {
157 if let Some(field) = udt.find_field_by_name(&field.name)
158 {
159 let span = field.name_span.expect(
160 "field found via name should have a name",
161 );
162 self.set_definition_from_position(
163 span.lo,
164 item_id.package,
165 );
166 }
167 }
168 _ => panic!("UDT has invalid resolution."),
169 }
170 }
171 }
172 _ => panic!("UDT has invalid resolution."),
173 }
174 }
175 }
176 _ => walk_expr(self, expr),
177 }
178 }
179 }
180
181 // Handles local variable, UDT, and callable references
182 fn visit_path(&mut self, path: &'_ ast::Path) {
183 if span_contains(path.span, self.offset) {
184 let res = self.compilation.unit.ast.names.get(path.id);
185 if let Some(res) = res {
186 match &res {
187 resolve::Res::Item(item_id) => {
188 if let (Some(item), _) = find_item(self.compilation, item_id) {
189 let lo = match &item.kind {
190 hir::ItemKind::Callable(decl) => decl.name.span.lo,
191 hir::ItemKind::Namespace(_, _) => {
192 panic!(
193 "Reference node should not refer to a namespace: {}",
194 path.id
195 )
196 }
197 hir::ItemKind::Ty(ident, _) => ident.span.lo,
198 };
199 self.set_definition_from_position(lo, item_id.package);
200 };
201 }
202 resolve::Res::Local(node_id) => {
203 if let Some(curr) = self.curr_callable {
204 {
205 if let Some(ident) = find_ident(node_id, curr) {
206 self.set_definition_from_position(ident.span.lo, None);
207 }
208 }
209 }
210 }
211 _ => {}
212 }
213 }
214 }
215 }
216}
217