Skip to content

Commit 9826391

Browse files
LS Go to Def for Current File Items (#460)
1 parent 3a410b5 commit 9826391

File tree

4 files changed

+568
-88
lines changed

4 files changed

+568
-88
lines changed

compiler/qsc_hir/src/ty.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,28 @@ impl Udt {
479479
Some(field)
480480
}
481481

482+
/// The field with the given name. Returns [None] if this user-defined type does not
483+
/// have a field with the given name.
484+
#[must_use]
485+
pub fn find_field_by_name(&self, name: &str) -> Option<&UdtField> {
486+
Self::find_field_by_name_rec(&self.definition, name)
487+
}
488+
489+
fn find_field_by_name_rec<'a>(def: &'a UdtDef, name: &str) -> Option<&'a UdtField> {
490+
match &def.kind {
491+
UdtDefKind::Field(field) => field.name.as_ref().and_then(|field_name| {
492+
if field_name.as_ref() == name {
493+
Some(field)
494+
} else {
495+
None
496+
}
497+
}),
498+
UdtDefKind::Tuple(defs) => defs
499+
.iter()
500+
.find_map(|def| Self::find_field_by_name_rec(def, name)),
501+
}
502+
}
503+
482504
/// The type of the field at the given path. Returns [None] if the path is not valid for this
483505
/// user-defined type.
484506
#[must_use]
@@ -490,8 +512,7 @@ impl Udt {
490512
/// have a field with the given name.
491513
#[must_use]
492514
pub fn field_ty_by_name(&self, name: &str) -> Option<&Ty> {
493-
let path = self.field_path(name)?;
494-
self.field_ty(&path)
515+
self.find_field_by_name(name).map(|field| &field.ty)
495516
}
496517
}
497518

language_service/src/definition.rs

Lines changed: 182 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
#[cfg(test)]
55
mod tests;
66

7-
use crate::qsc_utils::{map_offset, span_contains, Compilation};
8-
use qsc::hir::{visit::walk_expr, visit::Visitor, Expr, ExprKind, ItemKind, Package, Res};
7+
use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation};
8+
use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor};
99
use qsc::SourceMap;
10+
use qsc::{ast, hir, resolve};
1011

1112
#[derive(Debug, PartialEq)]
1213
pub struct Definition {
@@ -21,15 +22,16 @@ pub(crate) fn get_definition(
2122
) -> Option<Definition> {
2223
// Map the file offset into a SourceMap offset
2324
let offset = map_offset(&compilation.unit.sources, source_name, offset);
24-
let package = &compilation.unit.package;
25+
let ast_package = &compilation.unit.ast;
2526

2627
let mut definition_finder = DefinitionFinder {
27-
package,
28+
compilation,
2829
source_map: &compilation.unit.sources,
2930
offset,
3031
definition: None,
32+
curr_callable: None,
3133
};
32-
definition_finder.visit_package(package);
34+
definition_finder.visit_package(&ast_package.package);
3335

3436
definition_finder
3537
.definition
@@ -40,43 +42,192 @@ pub(crate) fn get_definition(
4042
}
4143

4244
struct DefinitionFinder<'a> {
43-
package: &'a Package,
45+
compilation: &'a Compilation,
4446
source_map: &'a SourceMap,
4547
offset: u32,
4648
definition: Option<(String, u32)>,
49+
curr_callable: Option<&'a ast::CallableDecl>,
4750
}
4851

49-
impl<'a> Visitor<'_> for DefinitionFinder<'a> {
50-
fn visit_expr(&mut self, expr: &Expr) {
52+
impl DefinitionFinder<'_> {
53+
fn set_definition_from_position(&mut self, lo: u32) {
54+
self.definition = Some((
55+
self.source_map
56+
.find_by_offset(lo)
57+
.expect("source should exist for offset")
58+
.name
59+
.to_string(),
60+
lo,
61+
));
62+
}
63+
}
64+
65+
impl<'a> Visitor<'a> for DefinitionFinder<'a> {
66+
// Handles callable and UDT definitions
67+
fn visit_item(&mut self, item: &'a ast::Item) {
68+
if span_contains(item.span, self.offset) {
69+
match &*item.kind {
70+
ast::ItemKind::Callable(decl) => {
71+
if span_contains(decl.name.span, self.offset) {
72+
self.set_definition_from_position(decl.name.span.lo);
73+
} else if span_contains(decl.span, self.offset) {
74+
self.curr_callable = Some(decl);
75+
walk_callable_decl(self, decl);
76+
self.curr_callable = None;
77+
}
78+
// Note: the `item.span` can cover things like doc
79+
// comment, attributes, and visibility keywords, which aren't
80+
// things we want to have hover logic for, while the `decl.span` is
81+
// specific to the contents of the callable decl, which we do want
82+
// hover logic for. If the `if` or `else if` above is not met, then
83+
// the user is hovering over one of these non-decl parts of the item,
84+
// and we want to do nothing.
85+
}
86+
ast::ItemKind::Ty(ident, def) => {
87+
if span_contains(ident.span, self.offset) {
88+
self.set_definition_from_position(ident.span.lo);
89+
} else {
90+
self.visit_ty_def(def);
91+
}
92+
}
93+
_ => {}
94+
}
95+
}
96+
}
97+
98+
// Handles UDT field definitions
99+
fn visit_ty_def(&mut self, def: &'a ast::TyDef) {
100+
if span_contains(def.span, self.offset) {
101+
if let ast::TyDefKind::Field(ident, ty) = &*def.kind {
102+
if let Some(ident) = ident {
103+
if span_contains(ident.span, self.offset) {
104+
self.set_definition_from_position(ident.span.lo);
105+
} else {
106+
self.visit_ty(ty);
107+
}
108+
} else {
109+
self.visit_ty(ty);
110+
}
111+
} else {
112+
walk_ty_def(self, def);
113+
}
114+
}
115+
}
116+
117+
// Handles local variable definitions
118+
fn visit_pat(&mut self, pat: &'a ast::Pat) {
119+
if span_contains(pat.span, self.offset) {
120+
match &*pat.kind {
121+
ast::PatKind::Bind(ident, anno) => {
122+
if span_contains(ident.span, self.offset) {
123+
self.set_definition_from_position(ident.span.lo);
124+
} else if let Some(ty) = anno {
125+
self.visit_ty(ty);
126+
}
127+
}
128+
_ => walk_pat(self, pat),
129+
}
130+
}
131+
}
132+
133+
// Handles UDT field references
134+
fn visit_expr(&mut self, expr: &'a ast::Expr) {
51135
if span_contains(expr.span, self.offset) {
52-
if let ExprKind::Var(res, _) = expr.kind {
53-
let item = match res {
54-
Res::Err => None,
55-
// Just one package plus std for now, so let's live with this hack
56-
Res::Item(item) => {
57-
if item.package.is_none() {
58-
self.package.items.get(item.item)
59-
} else {
60-
// Handling std library is tricky for now
61-
None
136+
match &*expr.kind {
137+
ast::ExprKind::Field(udt, field) if span_contains(field.span, self.offset) => {
138+
if let Some(hir::ty::Ty::Udt(res)) =
139+
self.compilation.unit.ast.tys.terms.get(udt.id)
140+
{
141+
match res {
142+
hir::Res::Item(item_id) => {
143+
if let (Some(item), _) = find_item(self.compilation, item_id) {
144+
match &item.kind {
145+
hir::ItemKind::Ty(_, udt) => {
146+
if let Some(field) = udt.find_field_by_name(&field.name)
147+
{
148+
let span = field.name_span.expect(
149+
"field found via name should have a name",
150+
);
151+
self.set_definition_from_position(span.lo);
152+
}
153+
}
154+
_ => panic!("UDT has invalid resolution."),
155+
}
156+
}
157+
}
158+
_ => panic!("UDT has invalid resolution."),
62159
}
63160
}
64-
Res::Local(_node) => None,
65-
};
66-
if let Some(def) = item {
67-
if let ItemKind::Callable(decl) = &def.kind {
68-
self.definition = Some((
69-
self.source_map
70-
.find_by_offset(decl.name.span.lo)
71-
.expect("source should exist for offset")
72-
.name
73-
.to_string(),
74-
decl.name.span.lo,
75-
));
161+
}
162+
_ => walk_expr(self, expr),
163+
}
164+
}
165+
}
166+
167+
// Handles local variable, UDT, and callable references
168+
fn visit_path(&mut self, path: &'_ ast::Path) {
169+
if span_contains(path.span, self.offset) {
170+
let res = self.compilation.unit.ast.names.get(path.id);
171+
if let Some(res) = res {
172+
match &res {
173+
resolve::Res::Item(item_id) => {
174+
if item_id.package.is_none() {
175+
if let (Some(item), _) = find_item(self.compilation, item_id) {
176+
let lo = match &item.kind {
177+
hir::ItemKind::Callable(decl) => decl.name.span.lo,
178+
hir::ItemKind::Namespace(_, _) => {
179+
panic!(
180+
"Reference node should not refer to a namespace: {}",
181+
path.id
182+
)
183+
}
184+
hir::ItemKind::Ty(ident, _) => ident.span.lo,
185+
};
186+
self.set_definition_from_position(lo);
187+
};
188+
}
76189
}
190+
resolve::Res::Local(node_id) => {
191+
if let Some(curr) = self.curr_callable {
192+
{
193+
let mut finder = AstPatFinder {
194+
node_id,
195+
result: None,
196+
};
197+
finder.visit_callable_decl(curr);
198+
if let Some(lo) = finder.result {
199+
self.set_definition_from_position(lo);
200+
}
201+
}
202+
}
203+
}
204+
_ => {}
77205
}
78206
}
79207
}
80-
walk_expr(self, expr);
208+
}
209+
}
210+
211+
struct AstPatFinder<'a> {
212+
node_id: &'a ast::NodeId,
213+
result: Option<u32>,
214+
}
215+
216+
impl<'a> Visitor<'a> for AstPatFinder<'_> {
217+
fn visit_pat(&mut self, pat: &'a ast::Pat) {
218+
match &*pat.kind {
219+
ast::PatKind::Bind(ident, _) => {
220+
if ident.id == *self.node_id {
221+
self.result = Some(ident.span.lo);
222+
}
223+
}
224+
_ => walk_pat(self, pat),
225+
}
226+
}
227+
228+
fn visit_expr(&mut self, expr: &'a ast::Expr) {
229+
if self.result.is_none() {
230+
walk_expr(self, expr);
231+
}
81232
}
82233
}

0 commit comments

Comments
 (0)