microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

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