cairo_lang_defs/
plugin_utils.rs

1use 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
10/// Trait providing a consistent interface for inline macro calls.
11pub 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
44/// Trait providing a consistent interface for the result of a macro plugins.
45pub 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
61/// Returns diagnostics for an unsupported bracket type.
62pub 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
87/// Extracts a single unnamed argument.
88pub 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
96/// Extracts `n` unnamed arguments.
97pub 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
107/// Gets the syntax of an argument, and extracts the value if it is unnamed.
108pub 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
119/// Escapes a node for use in a format string.
120pub 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 to extract unnamed arguments of an inline macro.
130///
131/// Gets the expected number of unnamed arguments, and the pattern for the allowed bracket types,
132/// and returns a fixed size array with the argument expressions.
133///
134/// Example usage (2 arguments, allowing `()` or `{}` brackets):
135/// let [arg1, arg2] = extract_macro_unnamed_args!(
136///     db,
137///     syntax,
138///     2,
139///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_),
140///     token_tree_syntax.stable_ptr(db)
141/// );
142#[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        // `unwrap` is ok because the above `matches` condition ensures it's not None (unless
154        // the pattern contains the `Missing` variant).
155        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, &macro_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 to extract a single unnamed argument of an inline macro.
181///
182/// Gets the pattern for the allowed bracket types, and returns the argument expression.
183/// The arguments are extracted from a `WrappedArgList` node syntax, as was in legacy inline macros.
184/// However, as macros are now parsed as general token trees, the diagnostics pointer is passed to
185/// the macro to allow pointing to the original location.
186///
187/// Example usage (allowing `()` or `{}` brackets):
188/// let arg = extract_macro_single_unnamed_arg!(
189///     db,
190///     arg_list_syntax,
191///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_),
192///     token_tree_syntax.stable_ptr(db)
193/// );
194#[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}