cairo_lang_defs/
plugin_utils.rs1use cairo_lang_syntax::node::helpers::WrappedArgListHelper;
2use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
3use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode, ast};
4use cairo_lang_utils::require;
5use itertools::Itertools;
6use salsa::Database;
7
8use crate::plugin::{InlinePluginResult, PluginDiagnostic, PluginResult};
9
10pub trait InlineMacroCall<'db> {
12 type PathNode: TypedSyntaxNode<'db>;
13 type Result: PluginResultTrait<'db>;
14 fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db>;
15 fn path(&self, db: &'db dyn Database) -> Self::PathNode;
16}
17
18impl<'db> InlineMacroCall<'db> for ast::LegacyExprInlineMacro<'db> {
19 type PathNode = ast::ExprPath<'db>;
20 type Result = InlinePluginResult<'db>;
21
22 fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db> {
23 self.arguments(db)
24 }
25
26 fn path(&self, db: &'db dyn Database) -> ast::ExprPath<'db> {
27 self.path(db)
28 }
29}
30
31impl<'db> InlineMacroCall<'db> for ast::LegacyItemInlineMacro<'db> {
32 type PathNode = ast::ExprPath<'db>;
33 type Result = PluginResult<'db>;
34
35 fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db> {
36 self.arguments(db)
37 }
38
39 fn path(&self, db: &'db dyn Database) -> ast::ExprPath<'db> {
40 self.path(db)
41 }
42}
43
44pub trait PluginResultTrait<'db> {
46 fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self;
47}
48
49impl<'db> PluginResultTrait<'db> for InlinePluginResult<'db> {
50 fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self {
51 InlinePluginResult { code: None, diagnostics: vec![diagnostic] }
52 }
53}
54
55impl<'db> PluginResultTrait<'db> for PluginResult<'db> {
56 fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self {
57 PluginResult { code: None, diagnostics: vec![diagnostic], remove_original_item: true }
58 }
59}
60
61pub fn unsupported_bracket_diagnostic<'db, CallAst: InlineMacroCall<'db>>(
63 db: &'db dyn Database,
64 legacy_macro_ast: &CallAst,
65 macro_ast: impl Into<SyntaxStablePtrId<'db>>,
66) -> CallAst::Result {
67 CallAst::Result::diagnostic_only(PluginDiagnostic::error_with_inner_span(
68 db,
69 macro_ast,
70 legacy_macro_ast.arguments(db).left_bracket_syntax_node(db),
71 format!(
72 "Macro `{}` does not support this bracket type.",
73 legacy_macro_ast.path(db).as_syntax_node().get_text_without_trivia(db).long(db)
74 ),
75 ))
76}
77
78pub fn not_legacy_macro_diagnostic(stable_ptr: SyntaxStablePtrId<'_>) -> PluginDiagnostic<'_> {
79 PluginDiagnostic::error(
80 stable_ptr,
81 "Macro can not be parsed as legacy macro. Expected an argument list wrapped in either \
82 parentheses, brackets, or braces."
83 .to_string(),
84 )
85}
86
87pub fn extract_single_unnamed_arg<'db>(
89 db: &'db dyn Database,
90 macro_arguments: ast::ArgList<'db>,
91) -> Option<ast::Expr<'db>> {
92 let mut elements = macro_arguments.elements(db);
93 if elements.len() == 1 { try_extract_unnamed_arg(db, &elements.next()?) } else { None }
94}
95
96pub fn extract_unnamed_args<'db>(
98 db: &'db dyn Database,
99 macro_arguments: &ast::ArgList<'db>,
100 n: usize,
101) -> Option<Vec<ast::Expr<'db>>> {
102 let elements = macro_arguments.elements(db);
103 require(elements.len() == n)?;
104 elements.map(|x| try_extract_unnamed_arg(db, &x)).collect()
105}
106
107pub fn try_extract_unnamed_arg<'db>(
109 db: &'db dyn Database,
110 arg_ast: &ast::Arg<'db>,
111) -> Option<ast::Expr<'db>> {
112 if let ast::ArgClause::Unnamed(arg_clause) = arg_ast.arg_clause(db) {
113 Some(arg_clause.value(db))
114 } else {
115 None
116 }
117}
118
119pub fn escape_node(db: &dyn Database, node: SyntaxNode<'_>) -> String {
121 node.get_text_without_trivia(db)
122 .long(db)
123 .replace('{', "{{")
124 .replace('}', "}}")
125 .escape_unicode()
126 .join("")
127}
128
129#[macro_export]
143macro_rules! extract_macro_unnamed_args {
144 ($db:expr, $syntax:expr, $n:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
145 let arguments = $crate::plugin_utils::InlineMacroCall::arguments($syntax, $db);
146 if !matches!(arguments, $pattern) {
147 return $crate::plugin_utils::unsupported_bracket_diagnostic(
148 $db,
149 $syntax,
150 $diagnostics_ptr,
151 );
152 }
153 let macro_arg_list =
156 cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(&arguments, $db)
157 .unwrap();
158
159 let args = $crate::plugin_utils::extract_unnamed_args($db, ¯o_arg_list, $n);
160 let Some(args) = args else {
161 return $crate::plugin_utils::PluginResultTrait::diagnostic_only(
162 PluginDiagnostic::error(
163 $diagnostics_ptr,
164 format!(
165 "Macro `{}` must have exactly {} unnamed arguments.",
166 $crate::plugin_utils::InlineMacroCall::path($syntax, $db)
167 .as_syntax_node()
168 .get_text_without_trivia($db)
169 .long($db),
170 $n
171 ),
172 ),
173 );
174 };
175 let args: [ast::Expr; $n] = args.try_into().unwrap();
176 args
177 }};
178}
179
180#[macro_export]
195macro_rules! extract_macro_single_unnamed_arg {
196 ($db:expr, $syntax:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
197 let [x] = $crate::extract_macro_unnamed_args!($db, $syntax, 1, $pattern, $diagnostics_ptr);
198 x
199 }};
200}