microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/pythontelem

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/compile/preprocess.rs

195lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use core::str::FromStr;
5use qsc_ast::{
6 ast::{Attr, ExprKind, Idents, ItemKind, Namespace, PathKind, Stmt, StmtKind, UnOp},
7 mut_visit::MutVisitor,
8};
9use qsc_hir::hir;
10use std::rc::Rc;
11
12use super::TargetCapabilityFlags;
13
14#[cfg(test)]
15mod tests;
16
17#[derive(PartialEq, Hash, Clone, Debug)]
18pub struct TrackedName {
19 pub name: Rc<str>,
20 pub namespace: Rc<str>,
21}
22
23pub(crate) struct Conditional {
24 capabilities: TargetCapabilityFlags,
25 dropped_names: Vec<TrackedName>,
26 included_names: Vec<TrackedName>,
27}
28
29impl 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
46impl 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
124fn 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