microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/language_service/src/code_action/wrap_in_array.rs
138lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | //! Code action: "Convert to single-element array" |
| 5 | //! Detects when a value is passed where an array of that type is expected, |
| 6 | //! and offers to wrap it in `[...]`. |
| 7 | |
| 8 | #[cfg(test)] |
| 9 | mod tests; |
| 10 | |
| 11 | use qsc::{ |
| 12 | Span, |
| 13 | ast::{self, Expr}, |
| 14 | compile::{ErrorKind, TyInfoKind}, |
| 15 | display::Lookup, |
| 16 | hir::ty::Ty, |
| 17 | line_column::Encoding, |
| 18 | }; |
| 19 | |
| 20 | use super::is_error_relevant; |
| 21 | use crate::{ |
| 22 | compilation::Compilation, |
| 23 | protocol::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit}, |
| 24 | qsc_utils::into_range, |
| 25 | }; |
| 26 | |
| 27 | pub(crate) fn wrap_in_array_fixes( |
| 28 | compilation: &Compilation, |
| 29 | source_name: &str, |
| 30 | span: Span, |
| 31 | encoding: Encoding, |
| 32 | ) -> Vec<CodeAction> { |
| 33 | let mut code_actions = Vec::new(); |
| 34 | |
| 35 | let unit = compilation.user_unit(); |
| 36 | let package = &unit.ast.package; |
| 37 | let source_map = &unit.sources; |
| 38 | |
| 39 | let ty_mismatches = compilation |
| 40 | .compile_errors |
| 41 | .iter() |
| 42 | .filter(|error| is_error_relevant(error, span)) |
| 43 | .filter_map(|error| match error.error() { |
| 44 | ErrorKind::Frontend(frontend_error) => frontend_error.ty_mismatch(), |
| 45 | _ => None, |
| 46 | }); |
| 47 | |
| 48 | for (expected, actual, error_span) in ty_mismatches { |
| 49 | // Check if expected is Array(T) and actual is a matching primitive T. |
| 50 | // Scoped to primitives to include Qubit, exclude tuples, and provide an intelligible stopping point. |
| 51 | if let TyInfoKind::Array(item_ty) = &expected.kind |
| 52 | && item_ty.as_ref() == &actual.kind |
| 53 | && matches!(actual.kind, TyInfoKind::Prim(_)) |
| 54 | { |
| 55 | // Verify via the type table that the expression is truly a primitive type. |
| 56 | // The error's `actual` field can be simplified (e.g. an array mismatch |
| 57 | // decomposes to element-level), so we check the real expression type. |
| 58 | let Some(expr_id) = find_expr_at(package, error_span) else { |
| 59 | continue; |
| 60 | }; |
| 61 | let Some(ty) = compilation.get_ty(expr_id) else { |
| 62 | continue; |
| 63 | }; |
| 64 | if !matches!(ty, Ty::Prim(_)) { |
| 65 | continue; |
| 66 | } |
| 67 | |
| 68 | // Generate the fix: wrap the expression in [...] |
| 69 | // Note that this depends on the error span excluding surrounding parens |
| 70 | // so we don't end up with something like `F[(q)]`. |
| 71 | let open_range = into_range( |
| 72 | encoding, |
| 73 | Span { |
| 74 | lo: error_span.lo, |
| 75 | hi: error_span.lo, |
| 76 | }, |
| 77 | source_map, |
| 78 | ); |
| 79 | |
| 80 | let close_range = into_range( |
| 81 | encoding, |
| 82 | Span { |
| 83 | lo: error_span.hi, |
| 84 | hi: error_span.hi, |
| 85 | }, |
| 86 | source_map, |
| 87 | ); |
| 88 | |
| 89 | code_actions.push(CodeAction { |
| 90 | title: "Convert to single-element array".to_string(), |
| 91 | edit: Some(WorkspaceEdit { |
| 92 | changes: vec![( |
| 93 | source_name.to_string(), |
| 94 | vec![ |
| 95 | TextEdit { |
| 96 | new_text: "[".to_string(), |
| 97 | range: open_range, |
| 98 | }, |
| 99 | TextEdit { |
| 100 | new_text: "]".to_string(), |
| 101 | range: close_range, |
| 102 | }, |
| 103 | ], |
| 104 | )], |
| 105 | }), |
| 106 | kind: Some(CodeActionKind::QuickFix), |
| 107 | is_preferred: None, |
| 108 | }); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | code_actions |
| 113 | } |
| 114 | |
| 115 | /// Finds the AST expression whose span exactly matches `target` and returns its `NodeId`. |
| 116 | fn find_expr_at(package: &ast::Package, target: Span) -> Option<ast::NodeId> { |
| 117 | let mut finder = ExprSpanFinder { |
| 118 | target, |
| 119 | found: None, |
| 120 | }; |
| 121 | ast::visit::Visitor::visit_package(&mut finder, package); |
| 122 | finder.found |
| 123 | } |
| 124 | |
| 125 | struct ExprSpanFinder { |
| 126 | target: Span, |
| 127 | found: Option<ast::NodeId>, |
| 128 | } |
| 129 | |
| 130 | impl<'a> ast::visit::Visitor<'a> for ExprSpanFinder { |
| 131 | fn visit_expr(&mut self, expr: &'a Expr) { |
| 132 | if expr.span == self.target { |
| 133 | self.found = Some(expr.id); |
| 134 | } else if self.target.intersection(&expr.span).is_some() { |
| 135 | ast::visit::walk_expr(self, expr); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |