cairo_lang_plugins/plugins/
config.rs1use std::vec;
2
3use cairo_lang_defs::patcher::PatchBuilder;
4use cairo_lang_defs::plugin::{
5 MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginGeneratedFile, PluginResult,
6};
7use cairo_lang_filesystem::cfg::{Cfg, CfgSet};
8use cairo_lang_syntax::attribute::structured::{
9 Attribute, AttributeArg, AttributeArgVariant, AttributeStructurize,
10};
11use cairo_lang_syntax::node::db::SyntaxGroup;
12use cairo_lang_syntax::node::helpers::{BodyItems, QueryAttrs};
13use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode, ast};
14use cairo_lang_utils::try_extract_matches;
15use itertools::Itertools;
16
17#[derive(Debug, Clone)]
21enum PredicateTree {
22 Cfg(Cfg),
23 Not(Box<PredicateTree>),
24 And(Vec<PredicateTree>),
25 Or(Vec<PredicateTree>),
26}
27
28impl PredicateTree {
29 fn evaluate(&self, cfg_set: &CfgSet) -> bool {
33 match self {
34 PredicateTree::Cfg(cfg) => cfg_set.contains(cfg),
35 PredicateTree::Not(inner) => !inner.evaluate(cfg_set),
36 PredicateTree::And(predicates) => predicates.iter().all(|p| p.evaluate(cfg_set)),
37 PredicateTree::Or(predicates) => predicates.iter().any(|p| p.evaluate(cfg_set)),
38 }
39 }
40}
41
42pub enum ConfigPredicatePart {
44 Cfg(Cfg),
46 Call(ast::ExprFunctionCall),
48}
49
50#[derive(Debug, Default)]
55#[non_exhaustive]
56pub struct ConfigPlugin;
57
58const CFG_ATTR: &str = "cfg";
59
60impl MacroPlugin for ConfigPlugin {
61 fn generate_code(
62 &self,
63 db: &dyn SyntaxGroup,
64 item_ast: ast::ModuleItem,
65 metadata: &MacroPluginMetadata<'_>,
66 ) -> PluginResult {
67 let mut diagnostics = vec![];
68
69 if should_drop(db, metadata.cfg_set, &item_ast, &mut diagnostics) {
70 PluginResult { code: None, diagnostics, remove_original_item: true }
71 } else if let Some(builder) =
72 handle_undropped_item(db, metadata.cfg_set, item_ast, &mut diagnostics)
73 {
74 let (content, code_mappings) = builder.build();
75 PluginResult {
76 code: Some(PluginGeneratedFile {
77 name: "config".into(),
78 content,
79 code_mappings,
80 aux_data: None,
81 diagnostics_note: Default::default(),
82 }),
83 diagnostics,
84 remove_original_item: true,
85 }
86 } else {
87 PluginResult { code: None, diagnostics, remove_original_item: false }
88 }
89 }
90
91 fn declared_attributes(&self) -> Vec<String> {
92 vec![CFG_ATTR.to_string()]
93 }
94}
95
96pub struct ItemsInCfg<'a, Item: QueryAttrs> {
99 db: &'a dyn SyntaxGroup,
100 cfg_set: &'a CfgSet,
101 iterator: <Vec<Item> as IntoIterator>::IntoIter,
102}
103
104impl<Item: QueryAttrs> Iterator for ItemsInCfg<'_, Item> {
105 type Item = Item;
106
107 fn next(&mut self) -> Option<Self::Item> {
108 self.iterator.find(|item| !should_drop(self.db, self.cfg_set, item, &mut vec![]))
109 }
110}
111
112pub trait HasItemsInCfgEx<Item: QueryAttrs>: BodyItems<Item = Item> {
114 fn iter_items_in_cfg<'a>(
115 &self,
116 db: &'a dyn SyntaxGroup,
117 cfg_set: &'a CfgSet,
118 ) -> ItemsInCfg<'a, Item>;
119}
120
121impl<Item: QueryAttrs, Body: BodyItems<Item = Item>> HasItemsInCfgEx<Item> for Body {
122 fn iter_items_in_cfg<'a>(
123 &self,
124 db: &'a dyn SyntaxGroup,
125 cfg_set: &'a CfgSet,
126 ) -> ItemsInCfg<'a, Item> {
127 ItemsInCfg { db, cfg_set, iterator: self.items_vec(db).into_iter() }
128 }
129}
130
131fn handle_undropped_item<'a>(
135 db: &'a dyn SyntaxGroup,
136 cfg_set: &CfgSet,
137 item_ast: ast::ModuleItem,
138 diagnostics: &mut Vec<PluginDiagnostic>,
139) -> Option<PatchBuilder<'a>> {
140 match item_ast {
141 ast::ModuleItem::Trait(trait_item) => {
142 let body = try_extract_matches!(trait_item.body(db), ast::MaybeTraitBody::Some)?;
143 let items = get_kept_items_nodes(db, cfg_set, &body.items_vec(db), diagnostics)?;
144 let mut builder = PatchBuilder::new(db, &trait_item);
145 builder.add_node(trait_item.attributes(db).as_syntax_node());
146 builder.add_node(trait_item.trait_kw(db).as_syntax_node());
147 builder.add_node(trait_item.name(db).as_syntax_node());
148 builder.add_node(trait_item.generic_params(db).as_syntax_node());
149 builder.add_node(body.lbrace(db).as_syntax_node());
150 for item in items {
151 builder.add_node(item);
152 }
153 builder.add_node(body.rbrace(db).as_syntax_node());
154 Some(builder)
155 }
156 ast::ModuleItem::Impl(impl_item) => {
157 let body = try_extract_matches!(impl_item.body(db), ast::MaybeImplBody::Some)?;
158 let items = get_kept_items_nodes(db, cfg_set, &body.items_vec(db), diagnostics)?;
159 let mut builder = PatchBuilder::new(db, &impl_item);
160 builder.add_node(impl_item.attributes(db).as_syntax_node());
161 builder.add_node(impl_item.impl_kw(db).as_syntax_node());
162 builder.add_node(impl_item.name(db).as_syntax_node());
163 builder.add_node(impl_item.generic_params(db).as_syntax_node());
164 builder.add_node(impl_item.of_kw(db).as_syntax_node());
165 builder.add_node(impl_item.trait_path(db).as_syntax_node());
166 builder.add_node(body.lbrace(db).as_syntax_node());
167 for item in items {
168 builder.add_node(item);
169 }
170 builder.add_node(body.rbrace(db).as_syntax_node());
171 Some(builder)
172 }
173 _ => None,
174 }
175}
176
177fn get_kept_items_nodes<Item: QueryAttrs + TypedSyntaxNode>(
180 db: &dyn SyntaxGroup,
181 cfg_set: &CfgSet,
182 all_items: &[Item],
183 diagnostics: &mut Vec<PluginDiagnostic>,
184) -> Option<Vec<cairo_lang_syntax::node::SyntaxNode>> {
185 let mut any_dropped = false;
186 let mut kept_items_nodes = vec![];
187 for item in all_items {
188 if should_drop(db, cfg_set, item, diagnostics) {
189 any_dropped = true;
190 } else {
191 kept_items_nodes.push(item.as_syntax_node());
192 }
193 }
194 if any_dropped { Some(kept_items_nodes) } else { None }
195}
196
197fn should_drop<Item: QueryAttrs>(
199 db: &dyn SyntaxGroup,
200 cfg_set: &CfgSet,
201 item: &Item,
202 diagnostics: &mut Vec<PluginDiagnostic>,
203) -> bool {
204 item.query_attr(db, CFG_ATTR).into_iter().any(|attr| {
205 match parse_predicate(db, attr.structurize(db), diagnostics) {
206 Some(predicate_tree) => !predicate_tree.evaluate(cfg_set),
207 None => false,
208 }
209 })
210}
211
212fn parse_predicate(
214 db: &dyn SyntaxGroup,
215 attr: Attribute,
216 diagnostics: &mut Vec<PluginDiagnostic>,
217) -> Option<PredicateTree> {
218 Some(PredicateTree::And(
219 attr.args
220 .into_iter()
221 .filter_map(|arg| parse_predicate_item(db, arg, diagnostics))
222 .collect(),
223 ))
224}
225
226fn parse_predicate_item(
228 db: &dyn SyntaxGroup,
229 item: AttributeArg,
230 diagnostics: &mut Vec<PluginDiagnostic>,
231) -> Option<PredicateTree> {
232 match extract_config_predicate_part(db, &item) {
233 Some(ConfigPredicatePart::Cfg(cfg)) => Some(PredicateTree::Cfg(cfg)),
234 Some(ConfigPredicatePart::Call(call)) => {
235 let operator = call.path(db).as_syntax_node().get_text(db);
236 let args = call
237 .arguments(db)
238 .arguments(db)
239 .elements(db)
240 .iter()
241 .map(|arg| AttributeArg::from_ast(arg.clone(), db))
242 .collect_vec();
243
244 match operator.as_str() {
245 "not" => {
246 if args.len() != 1 {
247 diagnostics.push(PluginDiagnostic::error(
248 call.stable_ptr(),
249 "`not` operator expects exactly one argument.".into(),
250 ));
251 None
252 } else {
253 Some(PredicateTree::Not(Box::new(parse_predicate_item(
254 db,
255 args[0].clone(),
256 diagnostics,
257 )?)))
258 }
259 }
260 "and" => {
261 if args.len() < 2 {
262 diagnostics.push(PluginDiagnostic::error(
263 call.stable_ptr(),
264 "`and` operator expects at least two arguments.".into(),
265 ));
266 None
267 } else {
268 Some(PredicateTree::And(
269 args.into_iter()
270 .filter_map(|arg| parse_predicate_item(db, arg, diagnostics))
271 .collect(),
272 ))
273 }
274 }
275 "or" => {
276 if args.len() < 2 {
277 diagnostics.push(PluginDiagnostic::error(
278 call.stable_ptr(),
279 "`or` operator expects at least two arguments.".into(),
280 ));
281 None
282 } else {
283 Some(PredicateTree::Or(
284 args.into_iter()
285 .filter_map(|arg| parse_predicate_item(db, arg, diagnostics))
286 .collect(),
287 ))
288 }
289 }
290 _ => {
291 diagnostics.push(PluginDiagnostic::error(
292 call.stable_ptr(),
293 format!("Unsupported operator: `{}`.", operator),
294 ));
295 None
296 }
297 }
298 }
299 None => {
300 diagnostics.push(PluginDiagnostic::error(
301 item.arg.stable_ptr().untyped(),
302 "Invalid configuration argument.".into(),
303 ));
304 None
305 }
306 }
307}
308
309fn extract_config_predicate_part(
311 db: &dyn SyntaxGroup,
312 arg: &AttributeArg,
313) -> Option<ConfigPredicatePart> {
314 match &arg.variant {
315 AttributeArgVariant::Unnamed(ast::Expr::Path(path)) => {
316 let segments = path.elements(db);
317 if let [ast::PathSegment::Simple(segment)] = &segments[..] {
318 Some(ConfigPredicatePart::Cfg(Cfg::name(segment.ident(db).text(db).to_string())))
319 } else {
320 None
321 }
322 }
323 AttributeArgVariant::Unnamed(ast::Expr::FunctionCall(call)) => {
324 Some(ConfigPredicatePart::Call(call.clone()))
325 }
326 AttributeArgVariant::Named { name, value } => {
327 let value_text = match value {
328 ast::Expr::String(terminal) => terminal.string_value(db).unwrap_or_default(),
329 ast::Expr::ShortString(terminal) => terminal.string_value(db).unwrap_or_default(),
330 _ => return None,
331 };
332
333 Some(ConfigPredicatePart::Cfg(Cfg::kv(name.text.to_string(), value_text)))
334 }
335 _ => None,
336 }
337}