1use std::iter::Peekable;
3
4use base_db::Crate;
5use cfg::{CfgAtom, CfgExpr};
6use intern::{Symbol, sym};
7use rustc_hash::FxHashSet;
8use syntax::{
9 AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T,
10 ast::{self, Attr, HasAttrs, Meta, TokenTree, VariantList},
11};
12use tracing::{debug, warn};
13
14use crate::{MacroCallLoc, MacroDefKind, db::ExpandDatabase, proc_macro::ProcMacroKind};
15
16fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: Crate) -> Option<bool> {
17 if !attr.simple_name().as_deref().map(|v| v == "cfg")? {
18 return None;
19 }
20 let cfg = parse_from_attr_token_tree(&attr.meta()?.token_tree()?)?;
21 let enabled = krate.cfg_options(db).check(&cfg) != Some(false);
22 Some(enabled)
23}
24
25fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: Crate) -> Option<bool> {
26 if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? {
27 return None;
28 }
29 check_cfg_attr_value(db, &attr.token_tree()?, krate)
30}
31
32pub fn check_cfg_attr_value(
33 db: &dyn ExpandDatabase,
34 attr: &TokenTree,
35 krate: Crate,
36) -> Option<bool> {
37 let cfg_expr = parse_from_attr_token_tree(attr)?;
38 let enabled = krate.cfg_options(db).check(&cfg_expr) != Some(false);
39 Some(enabled)
40}
41
42fn process_has_attrs_with_possible_comma<I: HasAttrs>(
43 db: &dyn ExpandDatabase,
44 items: impl Iterator<Item = I>,
45 krate: Crate,
46 remove: &mut FxHashSet<SyntaxElement>,
47) -> Option<()> {
48 for item in items {
49 let field_attrs = item.attrs();
50 'attrs: for attr in field_attrs {
51 if let Some(enabled) = check_cfg(db, &attr, krate) {
52 if enabled {
53 debug!("censoring {:?}", attr.syntax());
54 remove.insert(attr.syntax().clone().into());
55 } else {
56 debug!("censoring {:?}", item.syntax());
57 remove.insert(item.syntax().clone().into());
58 remove_possible_comma(&item, remove);
60 break 'attrs;
61 }
62 }
63
64 if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
65 if enabled {
66 debug!("Removing cfg_attr tokens {:?}", attr);
67 let meta = attr.meta()?;
68 let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?;
69 remove.extend(removes_from_cfg_attr);
70 } else {
71 debug!("censoring type cfg_attr {:?}", item.syntax());
72 remove.insert(attr.syntax().clone().into());
73 }
74 }
75 }
76 }
77 Some(())
78}
79
80#[derive(Debug, PartialEq, Eq, Clone, Copy)]
81enum CfgExprStage {
82 StrippigCfgExpr,
84 FoundComma,
86 EverythingElse,
90}
91
92fn remove_tokens_within_cfg_attr(meta: Meta) -> Option<FxHashSet<SyntaxElement>> {
94 let mut remove: FxHashSet<SyntaxElement> = FxHashSet::default();
95 debug!("Enabling attribute {}", meta);
96 let meta_path = meta.path()?;
97 debug!("Removing {:?}", meta_path.syntax());
98 remove.insert(meta_path.syntax().clone().into());
99
100 let meta_tt = meta.token_tree()?;
101 debug!("meta_tt {}", meta_tt);
102 let mut stage = CfgExprStage::StrippigCfgExpr;
103 for tt in meta_tt.token_trees_and_tokens() {
104 debug!("Checking {:?}. Stage: {:?}", tt, stage);
105 match (stage, tt) {
106 (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Node(node)) => {
107 remove.insert(node.syntax().clone().into());
108 }
109 (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Token(token)) => {
110 if token.kind() == T![,] {
111 stage = CfgExprStage::FoundComma;
112 }
113 remove.insert(token.into());
114 }
115 (CfgExprStage::FoundComma, syntax::NodeOrToken::Token(token))
116 if (token.kind() == T![,] || token.kind() == T![')']) =>
117 {
118 stage = CfgExprStage::EverythingElse;
120 remove.insert(token.into());
121 }
122 (CfgExprStage::EverythingElse, syntax::NodeOrToken::Node(node)) => {
123 remove.insert(node.syntax().clone().into());
124 }
125 (CfgExprStage::EverythingElse, syntax::NodeOrToken::Token(token)) => {
126 remove.insert(token.into());
127 }
128 _ => {}
130 }
131 }
132 if stage != CfgExprStage::EverythingElse {
133 warn!("Invalid cfg_attr attribute. {:?}", meta_tt);
134 return None;
135 }
136 Some(remove)
137}
138fn remove_possible_comma(item: &impl AstNode, res: &mut FxHashSet<SyntaxElement>) {
140 if let Some(comma) = item.syntax().next_sibling_or_token().filter(|it| it.kind() == T![,]) {
141 res.insert(comma);
142 }
143}
144fn process_enum(
145 db: &dyn ExpandDatabase,
146 variants: VariantList,
147 krate: Crate,
148 remove: &mut FxHashSet<SyntaxElement>,
149) -> Option<()> {
150 'variant: for variant in variants.variants() {
151 for attr in variant.attrs() {
152 if let Some(enabled) = check_cfg(db, &attr, krate) {
153 if enabled {
154 debug!("censoring {:?}", attr.syntax());
155 remove.insert(attr.syntax().clone().into());
156 } else {
157 debug!("censoring type {:?}", variant.syntax());
159 remove.insert(variant.syntax().clone().into());
160 remove_possible_comma(&variant, remove);
162 continue 'variant;
163 }
164 }
165
166 if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
167 if enabled {
168 debug!("Removing cfg_attr tokens {:?}", attr);
169 let meta = attr.meta()?;
170 let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?;
171 remove.extend(removes_from_cfg_attr);
172 } else {
173 debug!("censoring type cfg_attr {:?}", variant.syntax());
174 remove.insert(attr.syntax().clone().into());
175 }
176 }
177 }
178 if let Some(fields) = variant.field_list() {
179 match fields {
180 ast::FieldList::RecordFieldList(fields) => {
181 process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
182 }
183 ast::FieldList::TupleFieldList(fields) => {
184 process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
185 }
186 }
187 }
188 }
189 Some(())
190}
191
192pub(crate) fn process_cfg_attrs(
193 db: &dyn ExpandDatabase,
194 node: &SyntaxNode,
195 loc: &MacroCallLoc,
196) -> Option<FxHashSet<SyntaxElement>> {
197 let is_derive = match loc.def.kind {
199 MacroDefKind::BuiltInDerive(..)
200 | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) => true,
201 MacroDefKind::BuiltInAttr(_, expander) => expander.is_derive(),
202 _ => false,
203 };
204 let mut remove = FxHashSet::default();
205
206 let item = ast::Item::cast(node.clone())?;
207 for attr in item.attrs() {
208 if let Some(enabled) = check_cfg_attr(db, &attr, loc.krate) {
209 if enabled {
210 debug!("Removing cfg_attr tokens {:?}", attr);
211 let meta = attr.meta()?;
212 let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?;
213 remove.extend(removes_from_cfg_attr);
214 } else {
215 debug!("Removing type cfg_attr {:?}", item.syntax());
216 remove.insert(attr.syntax().clone().into());
217 }
218 }
219 }
220
221 if is_derive {
222 match item {
225 ast::Item::Struct(it) => match it.field_list()? {
226 ast::FieldList::RecordFieldList(fields) => {
227 process_has_attrs_with_possible_comma(
228 db,
229 fields.fields(),
230 loc.krate,
231 &mut remove,
232 )?;
233 }
234 ast::FieldList::TupleFieldList(fields) => {
235 process_has_attrs_with_possible_comma(
236 db,
237 fields.fields(),
238 loc.krate,
239 &mut remove,
240 )?;
241 }
242 },
243 ast::Item::Enum(it) => {
244 process_enum(db, it.variant_list()?, loc.krate, &mut remove)?;
245 }
246 ast::Item::Union(it) => {
247 process_has_attrs_with_possible_comma(
248 db,
249 it.record_field_list()?.fields(),
250 loc.krate,
251 &mut remove,
252 )?;
253 }
254 _ => {}
256 }
257 }
258 Some(remove)
259}
260fn parse_from_attr_token_tree(tt: &TokenTree) -> Option<CfgExpr> {
262 let mut iter = tt
263 .token_trees_and_tokens()
264 .filter(is_not_whitespace)
265 .skip(1)
266 .take_while(is_not_closing_paren)
267 .peekable();
268 next_cfg_expr_from_syntax(&mut iter)
269}
270
271fn is_not_closing_paren(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
272 !matches!(element, NodeOrToken::Token(token) if (token.kind() == syntax::T![')']))
273}
274fn is_not_whitespace(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
275 !matches!(element, NodeOrToken::Token(token) if (token.kind() == SyntaxKind::WHITESPACE))
276}
277
278fn next_cfg_expr_from_syntax<I>(iter: &mut Peekable<I>) -> Option<CfgExpr>
279where
280 I: Iterator<Item = NodeOrToken<ast::TokenTree, syntax::SyntaxToken>>,
281{
282 let name = match iter.next() {
283 None => return None,
284 Some(NodeOrToken::Token(element)) => match element.kind() {
285 syntax::T![ident] => Symbol::intern(element.text()),
286 _ => return Some(CfgExpr::Invalid),
287 },
288 Some(_) => return Some(CfgExpr::Invalid),
289 };
290 let result = match &name {
291 s if [&sym::all, &sym::any, &sym::not].contains(&s) => {
292 let mut preds = Vec::new();
293 let Some(NodeOrToken::Node(tree)) = iter.next() else {
294 return Some(CfgExpr::Invalid);
295 };
296 let mut tree_iter = tree
297 .token_trees_and_tokens()
298 .filter(is_not_whitespace)
299 .skip(1)
300 .take_while(is_not_closing_paren)
301 .peekable();
302 while tree_iter.peek().is_some() {
303 let pred = next_cfg_expr_from_syntax(&mut tree_iter);
304 if let Some(pred) = pred {
305 preds.push(pred);
306 }
307 }
308 let group = match &name {
309 s if *s == sym::all => CfgExpr::All(preds.into_boxed_slice()),
310 s if *s == sym::any => CfgExpr::Any(preds.into_boxed_slice()),
311 s if *s == sym::not => {
312 CfgExpr::Not(Box::new(preds.pop().unwrap_or(CfgExpr::Invalid)))
313 }
314 _ => unreachable!(),
315 };
316 Some(group)
317 }
318 _ => match iter.peek() {
319 Some(NodeOrToken::Token(element)) if (element.kind() == syntax::T![=]) => {
320 iter.next();
321 match iter.next() {
322 Some(NodeOrToken::Token(value_token))
323 if (value_token.kind() == syntax::SyntaxKind::STRING) =>
324 {
325 let value = value_token.text();
326 Some(CfgExpr::Atom(CfgAtom::KeyValue {
327 key: name,
328 value: Symbol::intern(value.trim_matches('"')),
329 }))
330 }
331 _ => None,
332 }
333 }
334 _ => Some(CfgExpr::Atom(CfgAtom::Flag(name))),
335 },
336 };
337 if let Some(NodeOrToken::Token(element)) = iter.peek()
338 && element.kind() == syntax::T![,]
339 {
340 iter.next();
341 }
342 result
343}
344#[cfg(test)]
345mod tests {
346 use cfg::DnfExpr;
347 use expect_test::{Expect, expect};
348 use syntax::{AstNode, SourceFile, ast::Attr};
349
350 use crate::cfg_process::parse_from_attr_token_tree;
351
352 fn check_dnf_from_syntax(input: &str, expect: Expect) {
353 let parse = SourceFile::parse(input, span::Edition::CURRENT);
354 let node = match parse.tree().syntax().descendants().find_map(Attr::cast) {
355 Some(it) => it,
356 None => {
357 let node = std::any::type_name::<Attr>();
358 panic!("Failed to make ast node `{node}` from text {input}")
359 }
360 };
361 let node = node.clone_subtree();
362 assert_eq!(node.syntax().text_range().start(), 0.into());
363
364 let cfg = parse_from_attr_token_tree(&node.meta().unwrap().token_tree().unwrap()).unwrap();
365 let actual = format!("#![cfg({})]", DnfExpr::new(&cfg));
366 expect.assert_eq(&actual);
367 }
368 #[test]
369 fn cfg_from_attr() {
370 check_dnf_from_syntax(r#"#[cfg(test)]"#, expect![[r#"#![cfg(test)]"#]]);
371 check_dnf_from_syntax(r#"#[cfg(not(never))]"#, expect![[r#"#![cfg(not(never))]"#]]);
372 }
373}