microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
compiler/qsc_frontend/src/compile/preprocess.rs
195lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use core::str::FromStr; |
| 5 | use qsc_ast::{ |
| 6 | ast::{Attr, ExprKind, Idents, ItemKind, Namespace, PathKind, Stmt, StmtKind, UnOp}, |
| 7 | mut_visit::MutVisitor, |
| 8 | }; |
| 9 | use qsc_hir::hir; |
| 10 | use std::rc::Rc; |
| 11 | |
| 12 | use super::TargetCapabilityFlags; |
| 13 | |
| 14 | #[cfg(test)] |
| 15 | mod tests; |
| 16 | |
| 17 | #[derive(PartialEq, Hash, Clone, Debug)] |
| 18 | pub struct TrackedName { |
| 19 | pub name: Rc<str>, |
| 20 | pub namespace: Rc<str>, |
| 21 | } |
| 22 | |
| 23 | pub(crate) struct Conditional { |
| 24 | capabilities: TargetCapabilityFlags, |
| 25 | dropped_names: Vec<TrackedName>, |
| 26 | included_names: Vec<TrackedName>, |
| 27 | } |
| 28 | |
| 29 | impl Conditional { |
| 30 | pub(crate) fn new(capabilities: TargetCapabilityFlags) -> Self { |
| 31 | Self { |
| 32 | capabilities, |
| 33 | dropped_names: Vec::new(), |
| 34 | included_names: Vec::new(), |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | pub(crate) fn into_names(self) -> Vec<TrackedName> { |
| 39 | self.dropped_names |
| 40 | .into_iter() |
| 41 | .filter(|n| !self.included_names.contains(n)) |
| 42 | .collect() |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | impl MutVisitor for Conditional { |
| 47 | fn visit_namespace(&mut self, namespace: &mut Namespace) { |
| 48 | namespace.items = namespace |
| 49 | .items |
| 50 | .iter() |
| 51 | .filter_map(|item| { |
| 52 | if matches_config(&item.attrs, self.capabilities) { |
| 53 | match item.kind.as_ref() { |
| 54 | ItemKind::Callable(callable) => { |
| 55 | self.included_names.push(TrackedName { |
| 56 | name: callable.name.name.clone(), |
| 57 | namespace: namespace.name.full_name(), |
| 58 | }); |
| 59 | } |
| 60 | ItemKind::Ty(ident, _) => self.included_names.push(TrackedName { |
| 61 | name: ident.name.clone(), |
| 62 | namespace: namespace.name.full_name(), |
| 63 | }), |
| 64 | _ => {} |
| 65 | } |
| 66 | Some(item.clone()) |
| 67 | } else { |
| 68 | match item.kind.as_ref() { |
| 69 | ItemKind::Callable(callable) => { |
| 70 | self.dropped_names.push(TrackedName { |
| 71 | name: callable.name.name.clone(), |
| 72 | namespace: namespace.name.full_name(), |
| 73 | }); |
| 74 | } |
| 75 | ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName { |
| 76 | name: ident.name.clone(), |
| 77 | namespace: namespace.name.full_name(), |
| 78 | }), |
| 79 | _ => {} |
| 80 | } |
| 81 | None |
| 82 | } |
| 83 | }) |
| 84 | .collect::<Vec<_>>() |
| 85 | .into_boxed_slice(); |
| 86 | } |
| 87 | |
| 88 | fn visit_stmt(&mut self, stmt: &mut Stmt) { |
| 89 | if let StmtKind::Item(item) = stmt.kind.as_mut() { |
| 90 | if matches_config(&item.attrs, self.capabilities) { |
| 91 | match item.kind.as_ref() { |
| 92 | ItemKind::Callable(callable) => { |
| 93 | self.included_names.push(TrackedName { |
| 94 | name: callable.name.name.clone(), |
| 95 | namespace: Rc::from(""), |
| 96 | }); |
| 97 | } |
| 98 | ItemKind::Ty(ident, _) => self.included_names.push(TrackedName { |
| 99 | name: ident.name.clone(), |
| 100 | namespace: Rc::from(""), |
| 101 | }), |
| 102 | _ => {} |
| 103 | } |
| 104 | } else { |
| 105 | match item.kind.as_ref() { |
| 106 | ItemKind::Callable(callable) => { |
| 107 | self.dropped_names.push(TrackedName { |
| 108 | name: callable.name.name.clone(), |
| 109 | namespace: Rc::from(""), |
| 110 | }); |
| 111 | } |
| 112 | ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName { |
| 113 | name: ident.name.clone(), |
| 114 | namespace: Rc::from(""), |
| 115 | }), |
| 116 | _ => {} |
| 117 | } |
| 118 | stmt.kind = Box::new(StmtKind::Empty); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | fn matches_config(attrs: &[Box<Attr>], capabilities: TargetCapabilityFlags) -> bool { |
| 125 | let attrs: Vec<_> = attrs |
| 126 | .iter() |
| 127 | .filter(|attr| hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::Config)) |
| 128 | .collect(); |
| 129 | |
| 130 | if attrs.is_empty() { |
| 131 | return true; |
| 132 | } |
| 133 | let mut found_capabilities = TargetCapabilityFlags::empty(); |
| 134 | let mut disallowed_capabilities = TargetCapabilityFlags::empty(); |
| 135 | let mut base = false; |
| 136 | let mut not_base = false; |
| 137 | |
| 138 | // When checking attributes, anything we don't recognize (invalid form or invalid capability) gets |
| 139 | // left in the compilation by returning true. This ensures that later compilation steps, specifically lowering |
| 140 | // from AST to HIR, can check the attributes and return errors as appropriate. |
| 141 | for attr in attrs { |
| 142 | if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() { |
| 143 | match inner.kind.as_ref() { |
| 144 | ExprKind::Path(PathKind::Ok(path)) => { |
| 145 | if let Ok(capability) = TargetCapabilityFlags::from_str(path.name.name.as_ref()) |
| 146 | { |
| 147 | if capability.is_empty() { |
| 148 | base = true; |
| 149 | } |
| 150 | found_capabilities |= capability; |
| 151 | } else { |
| 152 | return true; // Unknown capability, so we assume it matches |
| 153 | } |
| 154 | } |
| 155 | ExprKind::UnOp(UnOp::NotL, inner) => { |
| 156 | if let ExprKind::Path(PathKind::Ok(path)) = inner.kind.as_ref() { |
| 157 | if let Ok(capability) = |
| 158 | TargetCapabilityFlags::from_str(path.name.name.as_ref()) |
| 159 | { |
| 160 | if capability.is_empty() { |
| 161 | not_base = true; |
| 162 | } |
| 163 | disallowed_capabilities |= capability; |
| 164 | } else { |
| 165 | return true; // Unknown capability, so we assume it matches |
| 166 | } |
| 167 | } else { |
| 168 | return true; // Unknown config attribute, so we assume it matches |
| 169 | } |
| 170 | } |
| 171 | _ => return true, // Unknown config attribute, so we assume it matches |
| 172 | } |
| 173 | } else { |
| 174 | // Something other than a parenthesized expression, so we assume it matches |
| 175 | return true; |
| 176 | } |
| 177 | } |
| 178 | if found_capabilities.is_empty() && disallowed_capabilities.is_empty() { |
| 179 | if not_base && !base { |
| 180 | // There was at least one config attribute, but it was "not Base" so |
| 181 | // ensure that the capabilities are not empty. |
| 182 | return capabilities != TargetCapabilityFlags::empty(); |
| 183 | } else if base && !not_base { |
| 184 | // There was at least one config attribute, but it was Base |
| 185 | // Therefore, we only match if there are no capabilities |
| 186 | return capabilities == TargetCapabilityFlags::empty(); |
| 187 | } |
| 188 | |
| 189 | // The config specified both "Base" and "not Base" which is a contradiction, but we |
| 190 | // drop the item in this case. |
| 191 | return false; |
| 192 | } |
| 193 | capabilities.contains(found_capabilities) |
| 194 | && (disallowed_capabilities.is_empty() || !capabilities.contains(disallowed_capabilities)) |
| 195 | } |
| 196 | |