microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
source/language_service/src/completion/ast_context.rs
327lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use qsc::{ |
| 5 | ast::{ |
| 6 | self, Attr, Block, CallableDecl, Expr, ExprKind, FieldAssign, FieldDef, FunctorExpr, Ident, |
| 7 | Idents, ImportKind, Item, ItemKind, Namespace, NodeId, Package, Pat, Path, QubitInit, |
| 8 | SpecDecl, Stmt, StructDecl, Ty, TyDef, TyKind, |
| 9 | visit::{self, Visitor}, |
| 10 | }, |
| 11 | parse::completion::PathKind, |
| 12 | }; |
| 13 | use std::rc::Rc; |
| 14 | |
| 15 | /// Provides additional syntactic context for the cursor offset, |
| 16 | /// for various special cases where it's needed. |
| 17 | #[derive(Debug)] |
| 18 | pub(super) struct AstContext<'a> { |
| 19 | path_qualifier: Option<Vec<&'a Ident>>, |
| 20 | context: Option<Context<'a>>, |
| 21 | offset: u32, |
| 22 | } |
| 23 | |
| 24 | #[derive(Debug, Clone)] |
| 25 | enum Context<'a> { |
| 26 | /// The cursor is on a path or incomplete path. |
| 27 | Path(PathKind), |
| 28 | /// The cursor is on a field access or a field assignment expression. |
| 29 | Field { |
| 30 | /// The type of this expression will be the record used for the field access. |
| 31 | record: &'a Expr, |
| 32 | }, |
| 33 | /// The cursor is on an attribute. |
| 34 | AttrArg(Rc<str>), |
| 35 | } |
| 36 | |
| 37 | impl<'a> AstContext<'a> { |
| 38 | pub fn init(offset: u32, package: &'a Package) -> Self { |
| 39 | let mut offset_visitor = OffsetVisitor { |
| 40 | offset, |
| 41 | visitor: AstContext { |
| 42 | offset, |
| 43 | context: None, |
| 44 | path_qualifier: None, |
| 45 | }, |
| 46 | }; |
| 47 | |
| 48 | offset_visitor.visit_package(package); |
| 49 | |
| 50 | offset_visitor.visitor |
| 51 | } |
| 52 | |
| 53 | fn set_context(&mut self, context: Context<'a>) { |
| 54 | // Ignores update of context if the cursor is in an attribute argument |
| 55 | if matches!(self.context, Some(Context::AttrArg(_))) { |
| 56 | return; |
| 57 | } |
| 58 | self.context = Some(context); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | impl<'a> Visitor<'a> for AstContext<'a> { |
| 63 | fn visit_item(&mut self, item: &'a Item) { |
| 64 | match &*item.kind { |
| 65 | ItemKind::Open(..) => self.set_context(Context::Path(PathKind::Namespace)), |
| 66 | ItemKind::ImportOrExport(decl) => { |
| 67 | self.set_context(Context::Path(PathKind::Import)); |
| 68 | for item in &decl.items { |
| 69 | if ImportKind::Wildcard == item.kind && item.span.contains(self.offset) { |
| 70 | // Special case when the cursor falls *between* the |
| 71 | // `Path` and the glob asterisk, |
| 72 | // e.g. `foo.bar.|*` . In that case, the visitor |
| 73 | // will not visit the path since the cursor technically |
| 74 | // is not within the path. |
| 75 | self.visit_path_kind(&item.path); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | _ => {} |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | fn visit_ty(&mut self, ty: &Ty) { |
| 84 | if let TyKind::Path(..) = *ty.kind { |
| 85 | self.set_context(Context::Path(PathKind::Ty)); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | fn visit_expr(&mut self, expr: &'a Expr) { |
| 90 | if let ExprKind::Path(..) = *expr.kind { |
| 91 | self.set_context(Context::Path(PathKind::Expr)); |
| 92 | } else if let ExprKind::Struct(..) = expr.kind.as_ref() { |
| 93 | self.set_context(Context::Field { record: expr }); |
| 94 | } else if let ExprKind::Field(record, _) = expr.kind.as_ref() { |
| 95 | self.set_context(Context::Field { record }); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | fn visit_path_kind(&mut self, path: &'a ast::PathKind) { |
| 100 | if let Some(Context::Field { .. }) = self.context { |
| 101 | self.set_context(Context::Path(PathKind::Struct)); |
| 102 | } |
| 103 | self.path_qualifier = match path { |
| 104 | ast::PathKind::Ok(path) => Some(path.iter().collect()), |
| 105 | ast::PathKind::Err(Some(incomplete_path)) => { |
| 106 | Some(incomplete_path.segments.iter().collect()) |
| 107 | } |
| 108 | ast::PathKind::Err(None) => None, |
| 109 | }; |
| 110 | } |
| 111 | |
| 112 | fn visit_attr(&mut self, attr: &'a Attr) { |
| 113 | if attr.arg.span.contains(self.offset) { |
| 114 | self.set_context(Context::AttrArg(attr.name.name.clone())); |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | impl AstContext<'_> { |
| 120 | /// Returns the path kind and the path qualifier before the cursor offset. |
| 121 | /// |
| 122 | /// Returns `None` if the cursor is not on a path. |
| 123 | pub fn path_segment_context(&self) -> Option<(PathKind, Vec<Rc<str>>)> { |
| 124 | let Some(Context::Path(path_kind)) = self.context else { |
| 125 | return None; |
| 126 | }; |
| 127 | |
| 128 | let qualifier = self.segments_before_offset(); |
| 129 | |
| 130 | if qualifier.is_empty() { |
| 131 | return None; |
| 132 | } |
| 133 | |
| 134 | Some((path_kind, qualifier)) |
| 135 | } |
| 136 | |
| 137 | /// Returns the node ID of the node that the field access is being performed on, if any. |
| 138 | /// When this is a field assignment, returns the node ID of the struct expression. |
| 139 | /// |
| 140 | /// This node ID can then be used to look up the type to get field names. |
| 141 | /// |
| 142 | /// Returns `None` if the cursor is not on a field access expression or path. |
| 143 | pub fn field_access_context(&self) -> Option<NodeId> { |
| 144 | if let Some(Context::Field { record }) = &self.context { |
| 145 | // Unambiguously a field access expression, record node is an `Expr` |
| 146 | Some(record.id) |
| 147 | } else { |
| 148 | // A `Path` that may or may not be a field access expression, |
| 149 | // record node is the last identifier before the cursor |
| 150 | self.idents_before_cursor().last().map(|ident| ident.id) |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | /// Returns the name of the attribute, if the cursor is on an attribute argument. |
| 155 | /// If the cursor is not on an attribute argument, returns `None`. |
| 156 | pub fn get_name_of_attr_for_attr_arg(&self) -> Option<Rc<str>> { |
| 157 | if let Some(Context::AttrArg(name)) = &self.context { |
| 158 | Some(name.clone()) |
| 159 | } else { |
| 160 | None |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | fn idents_before_cursor(&self) -> impl Iterator<Item = &Ident> { |
| 165 | self.path_qualifier |
| 166 | .iter() |
| 167 | .flatten() |
| 168 | .copied() |
| 169 | .take_while(|i| i.span.hi < self.offset) |
| 170 | } |
| 171 | |
| 172 | fn segments_before_offset(&self) -> Vec<Rc<str>> { |
| 173 | self.idents_before_cursor() |
| 174 | .map(|i| i.name.clone()) |
| 175 | .collect::<Vec<_>>() |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /// A [`Visitor`] wrapper that only descends into a node |
| 180 | /// if the given offset falls within that node. |
| 181 | struct OffsetVisitor<T> { |
| 182 | offset: u32, |
| 183 | visitor: T, |
| 184 | } |
| 185 | |
| 186 | impl<'a, T> Visitor<'a> for OffsetVisitor<T> |
| 187 | where |
| 188 | T: Visitor<'a>, |
| 189 | { |
| 190 | fn visit_namespace(&mut self, namespace: &'a Namespace) { |
| 191 | if namespace.span.touches(self.offset) { |
| 192 | self.visitor.visit_namespace(namespace); |
| 193 | visit::walk_namespace(self, namespace); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | fn visit_item(&mut self, item: &'a Item) { |
| 198 | if item.span.touches(self.offset) { |
| 199 | self.visitor.visit_item(item); |
| 200 | visit::walk_item(self, item); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | fn visit_attr(&mut self, attr: &'a Attr) { |
| 205 | if attr.span.touches(self.offset) { |
| 206 | self.visitor.visit_attr(attr); |
| 207 | visit::walk_attr(self, attr); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | fn visit_ty_def(&mut self, def: &'a TyDef) { |
| 212 | if def.span.touches(self.offset) { |
| 213 | self.visitor.visit_ty_def(def); |
| 214 | visit::walk_ty_def(self, def); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | fn visit_callable_decl(&mut self, decl: &'a CallableDecl) { |
| 219 | if decl.span.touches(self.offset) { |
| 220 | self.visitor.visit_callable_decl(decl); |
| 221 | visit::walk_callable_decl(self, decl); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | fn visit_struct_decl(&mut self, decl: &'a StructDecl) { |
| 226 | if decl.span.touches(self.offset) { |
| 227 | self.visitor.visit_struct_decl(decl); |
| 228 | visit::walk_struct_decl(self, decl); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | fn visit_field_def(&mut self, def: &'a FieldDef) { |
| 233 | if def.span.touches(self.offset) { |
| 234 | self.visitor.visit_field_def(def); |
| 235 | visit::walk_field_def(self, def); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | fn visit_spec_decl(&mut self, decl: &'a SpecDecl) { |
| 240 | if decl.span.touches(self.offset) { |
| 241 | self.visitor.visit_spec_decl(decl); |
| 242 | visit::walk_spec_decl(self, decl); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | fn visit_functor_expr(&mut self, expr: &'a FunctorExpr) { |
| 247 | if expr.span.touches(self.offset) { |
| 248 | self.visitor.visit_functor_expr(expr); |
| 249 | visit::walk_functor_expr(self, expr); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | fn visit_ty(&mut self, ty: &'a Ty) { |
| 254 | if ty.span.touches(self.offset) { |
| 255 | self.visitor.visit_ty(ty); |
| 256 | visit::walk_ty(self, ty); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | fn visit_block(&mut self, block: &'a Block) { |
| 261 | if block.span.touches(self.offset) { |
| 262 | self.visitor.visit_block(block); |
| 263 | visit::walk_block(self, block); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | fn visit_stmt(&mut self, stmt: &'a Stmt) { |
| 268 | if stmt.span.touches(self.offset) { |
| 269 | self.visitor.visit_stmt(stmt); |
| 270 | visit::walk_stmt(self, stmt); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | fn visit_expr(&mut self, expr: &'a Expr) { |
| 275 | if expr.span.touches(self.offset) { |
| 276 | self.visitor.visit_expr(expr); |
| 277 | visit::walk_expr(self, expr); |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | fn visit_field_assign(&mut self, assign: &'a FieldAssign) { |
| 282 | if assign.span.touches(self.offset) { |
| 283 | self.visitor.visit_field_assign(assign); |
| 284 | visit::walk_field_assign(self, assign); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | fn visit_pat(&mut self, pat: &'a Pat) { |
| 289 | if pat.span.touches(self.offset) { |
| 290 | self.visitor.visit_pat(pat); |
| 291 | visit::walk_pat(self, pat); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | fn visit_qubit_init(&mut self, init: &'a QubitInit) { |
| 296 | if init.span.touches(self.offset) { |
| 297 | self.visitor.visit_qubit_init(init); |
| 298 | visit::walk_qubit_init(self, init); |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | fn visit_path(&mut self, path: &'a Path) { |
| 303 | if path.span.touches(self.offset) { |
| 304 | self.visitor.visit_path(path); |
| 305 | visit::walk_path(self, path); |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | fn visit_path_kind(&mut self, path: &'a ast::PathKind) { |
| 310 | let span = match path { |
| 311 | ast::PathKind::Ok(path) => &path.span, |
| 312 | ast::PathKind::Err(Some(incomplete_path)) => &incomplete_path.span, |
| 313 | ast::PathKind::Err(None) => return, |
| 314 | }; |
| 315 | |
| 316 | if span.touches(self.offset) { |
| 317 | self.visitor.visit_path_kind(path); |
| 318 | visit::walk_path_kind(self, path); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | fn visit_ident(&mut self, ident: &'a Ident) { |
| 323 | if ident.span.touches(self.offset) { |
| 324 | self.visitor.visit_ident(ident); |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |