microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
brlackey/magnets-fermion-classes

Branches

Tags

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

Clone

HTTPS

Download ZIP

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)]
9mod tests;
10
11use qsc::{
12 Span,
13 ast::{self, Expr},
14 compile::{ErrorKind, TyInfoKind},
15 display::Lookup,
16 hir::ty::Ty,
17 line_column::Encoding,
18};
19
20use super::is_error_relevant;
21use crate::{
22 compilation::Compilation,
23 protocol::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit},
24 qsc_utils::into_range,
25};
26
27pub(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`.
116fn 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
125struct ExprSpanFinder {
126 target: Span,
127 found: Option<ast::NodeId>,
128}
129
130impl<'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