microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
source/compiler/qsc_frontend/src/compile/preprocess.rs
286lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use core::str::FromStr; |
| 5 | use 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 | }; |
| 13 | use qsc_data_structures::{span::Span, target::Profile}; |
| 14 | use qsc_hir::hir; |
| 15 | use std::rc::Rc; |
| 16 | |
| 17 | use super::{SourceMap, TargetCapabilityFlags}; |
| 18 | |
| 19 | #[cfg(test)] |
| 20 | mod tests; |
| 21 | |
| 22 | /// Transformation to detect `@EntryPoint` attribute in the AST. |
| 23 | #[derive(Default)] |
| 24 | pub struct DetectEntryPointProfile { |
| 25 | pub profile: Option<(Profile, Span)>, |
| 26 | } |
| 27 | |
| 28 | impl DetectEntryPointProfile { |
| 29 | #[must_use] |
| 30 | pub fn new() -> Self { |
| 31 | Self { profile: None } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | impl 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)] |
| 50 | pub struct TrackedName { |
| 51 | pub name: Rc<str>, |
| 52 | pub namespace: Rc<str>, |
| 53 | } |
| 54 | |
| 55 | pub(crate) struct Conditional { |
| 56 | capabilities: TargetCapabilityFlags, |
| 57 | dropped_names: Vec<TrackedName>, |
| 58 | included_names: Vec<TrackedName>, |
| 59 | } |
| 60 | |
| 61 | impl 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 | |
| 78 | impl 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 | |
| 156 | fn 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. |
| 233 | pub(crate) struct RemoveCircuitSpans { |
| 234 | qsc_spans: Vec<Span>, |
| 235 | } |
| 236 | |
| 237 | impl 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 | |
| 258 | impl 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 | |