microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/qiskit2-explore

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_frontend/src/compile/preprocess.rs

287lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use core::str::FromStr;
5use qsc_ast::{
6 ast::{
7 Attr, ExprKind, Idents, ItemKind, Namespace, Package, PathKind, Stmt, StmtKind,
8 TopLevelNode, UnOp,
9 },
10 mut_visit::{MutVisitor, walk_stmt},
11 visit::Visitor,
12};
13use qsc_data_structures::{span::Span, target::Profile};
14use qsc_hir::hir;
15use std::rc::Rc;
16
17use super::{SourceMap, TargetCapabilityFlags};
18
19#[cfg(test)]
20mod tests;
21
22/// Transformation to detect `@EntryPoint` attribute in the AST.
23#[derive(Default)]
24pub struct DetectEntryPointProfile {
25 pub profile: Option<(Profile, Span)>,
26}
27
28impl DetectEntryPointProfile {
29 #[must_use]
30 pub fn new() -> Self {
31 Self { profile: None }
32 }
33}
34
35impl Visitor<'_> for DetectEntryPointProfile {
36 fn visit_attr(&mut self, attr: &Attr) {
37 if hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::EntryPoint) {
38 // Try to parse the argument as a profile name
39 if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() {
40 if let ExprKind::Path(PathKind::Ok(path)) = inner.kind.as_ref() {
41 if let Ok(profile) = Profile::from_str(path.name.name.as_ref()) {
42 self.profile = Some((profile, path.span));
43 }
44 }
45 }
46 }
47 }
48}
49
50#[derive(PartialEq, Hash, Clone, Debug)]
51pub struct TrackedName {
52 pub name: Rc<str>,
53 pub namespace: Rc<str>,
54}
55
56pub(crate) struct Conditional {
57 capabilities: TargetCapabilityFlags,
58 dropped_names: Vec<TrackedName>,
59 included_names: Vec<TrackedName>,
60}
61
62impl Conditional {
63 pub(crate) fn new(capabilities: TargetCapabilityFlags) -> Self {
64 Self {
65 capabilities,
66 dropped_names: Vec::new(),
67 included_names: Vec::new(),
68 }
69 }
70
71 pub(crate) fn into_names(self) -> Vec<TrackedName> {
72 self.dropped_names
73 .into_iter()
74 .filter(|n| !self.included_names.contains(n))
75 .collect()
76 }
77}
78
79impl MutVisitor for Conditional {
80 fn visit_namespace(&mut self, namespace: &mut Namespace) {
81 namespace.items = namespace
82 .items
83 .iter()
84 .filter_map(|item| {
85 if matches_config(&item.attrs, self.capabilities) {
86 match item.kind.as_ref() {
87 ItemKind::Callable(callable) => {
88 self.included_names.push(TrackedName {
89 name: callable.name.name.clone(),
90 namespace: namespace.name.full_name(),
91 });
92 }
93 ItemKind::Ty(ident, _) => self.included_names.push(TrackedName {
94 name: ident.name.clone(),
95 namespace: namespace.name.full_name(),
96 }),
97 _ => {}
98 }
99 Some(item.clone())
100 } else {
101 match item.kind.as_ref() {
102 ItemKind::Callable(callable) => {
103 self.dropped_names.push(TrackedName {
104 name: callable.name.name.clone(),
105 namespace: namespace.name.full_name(),
106 });
107 }
108 ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName {
109 name: ident.name.clone(),
110 namespace: namespace.name.full_name(),
111 }),
112 _ => {}
113 }
114 None
115 }
116 })
117 .collect::<Vec<_>>()
118 .into_boxed_slice();
119 }
120
121 fn visit_stmt(&mut self, stmt: &mut Stmt) {
122 if let StmtKind::Item(item) = stmt.kind.as_mut() {
123 if matches_config(&item.attrs, self.capabilities) {
124 match item.kind.as_ref() {
125 ItemKind::Callable(callable) => {
126 self.included_names.push(TrackedName {
127 name: callable.name.name.clone(),
128 namespace: Rc::from(""),
129 });
130 }
131 ItemKind::Ty(ident, _) => self.included_names.push(TrackedName {
132 name: ident.name.clone(),
133 namespace: Rc::from(""),
134 }),
135 _ => {}
136 }
137 } else {
138 match item.kind.as_ref() {
139 ItemKind::Callable(callable) => {
140 self.dropped_names.push(TrackedName {
141 name: callable.name.name.clone(),
142 namespace: Rc::from(""),
143 });
144 }
145 ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName {
146 name: ident.name.clone(),
147 namespace: Rc::from(""),
148 }),
149 _ => {}
150 }
151 stmt.kind = Box::new(StmtKind::Empty);
152 }
153 }
154 }
155}
156
157fn matches_config(attrs: &[Box<Attr>], capabilities: TargetCapabilityFlags) -> bool {
158 let attrs: Vec<_> = attrs
159 .iter()
160 .filter(|attr| hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::Config))
161 .collect();
162
163 if attrs.is_empty() {
164 return true;
165 }
166 let mut found_capabilities = TargetCapabilityFlags::empty();
167 let mut disallowed_capabilities = TargetCapabilityFlags::empty();
168 let mut base = false;
169 let mut not_base = false;
170
171 // When checking attributes, anything we don't recognize (invalid form or invalid capability) gets
172 // left in the compilation by returning true. This ensures that later compilation steps, specifically lowering
173 // from AST to HIR, can check the attributes and return errors as appropriate.
174 for attr in attrs {
175 if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() {
176 match inner.kind.as_ref() {
177 ExprKind::Path(PathKind::Ok(path)) => {
178 if let Ok(capability) = TargetCapabilityFlags::from_str(path.name.name.as_ref())
179 {
180 if capability.is_empty() {
181 base = true;
182 }
183 found_capabilities |= capability;
184 } else {
185 return true; // Unknown capability, so we assume it matches
186 }
187 }
188 ExprKind::UnOp(UnOp::NotL, inner) => {
189 if let ExprKind::Path(PathKind::Ok(path)) = inner.kind.as_ref() {
190 if let Ok(capability) =
191 TargetCapabilityFlags::from_str(path.name.name.as_ref())
192 {
193 if capability.is_empty() {
194 not_base = true;
195 }
196 disallowed_capabilities |= capability;
197 } else {
198 return true; // Unknown capability, so we assume it matches
199 }
200 } else {
201 return true; // Unknown config attribute, so we assume it matches
202 }
203 }
204 _ => return true, // Unknown config attribute, so we assume it matches
205 }
206 } else {
207 // Something other than a parenthesized expression, so we assume it matches
208 return true;
209 }
210 }
211 if found_capabilities.is_empty() && disallowed_capabilities.is_empty() {
212 if not_base && !base {
213 // There was at least one config attribute, but it was "not Base" so
214 // ensure that the capabilities are not empty.
215 return capabilities != TargetCapabilityFlags::empty();
216 } else if base && !not_base {
217 // There was at least one config attribute, but it was Base
218 // Therefore, we only match if there are no capabilities
219 return capabilities == TargetCapabilityFlags::empty();
220 }
221
222 // The config specified both "Base" and "not Base" which is a contradiction, but we
223 // drop the item in this case.
224 return false;
225 }
226 capabilities.contains(found_capabilities)
227 && (disallowed_capabilities.is_empty() || !capabilities.contains(disallowed_capabilities))
228}
229
230// Visitor to remove spans from circuit callables defined in QSC files.
231// This will remove the spans for the contents of these circuit callables,
232// but it is important for the language server that the span for the callable itself
233// is preserved, so that the user can navigate to the definition of the callable.
234pub(crate) struct RemoveCircuitSpans {
235 qsc_spans: Vec<Span>,
236}
237
238impl RemoveCircuitSpans {
239 pub(crate) fn new(sources: &SourceMap) -> Self {
240 let qsc_spans = sources
241 .iter()
242 .filter(|source| {
243 std::path::Path::new(source.name.as_ref())
244 .extension()
245 .is_some_and(|ext| ext.eq_ignore_ascii_case("qsc"))
246 })
247 .map(|source| {
248 let start = source.offset;
249 let end = start
250 + u32::try_from(source.contents.len()).expect("source length exceeds u32::MAX");
251 Span { lo: start, hi: end }
252 })
253 .collect();
254
255 Self { qsc_spans }
256 }
257}
258
259impl MutVisitor for RemoveCircuitSpans {
260 fn visit_package(&mut self, package: &mut Package) {
261 // We only want to visit namespaces
262 package.nodes.iter_mut().for_each(|n| match n {
263 TopLevelNode::Namespace(ns) => self.visit_namespace(ns),
264 TopLevelNode::Stmt(_) => {}
265 });
266 }
267
268 fn visit_namespace(&mut self, namespace: &mut Namespace) {
269 // We only want to visit circuit callables
270 namespace.items.iter_mut().for_each(|item| {
271 if let ItemKind::Callable(callable) = item.kind.as_mut() {
272 // Check if the callable's span is inside a QSC file's span
273 self.qsc_spans
274 .iter()
275 .any(|s| s.lo <= callable.span.lo && s.hi >= callable.span.hi)
276 .then(|| {
277 self.visit_callable_decl(callable);
278 });
279 }
280 });
281 }
282
283 fn visit_stmt(&mut self, stmt: &mut Stmt) {
284 stmt.span = Span::default(); // Clear the span for the statement
285 walk_stmt(self, stmt);
286 }
287}
288