Skip to main content

cairo_lang_defs/
plugin_utils.rs

1use cairo_lang_syntax::node::db::SyntaxGroup;
2use cairo_lang_syntax::node::helpers::WrappedArgListHelper;
3use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
4use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode, ast};
5use cairo_lang_utils::require;
6use itertools::Itertools;
7
8use crate::plugin::{InlinePluginResult, PluginDiagnostic, PluginResult};
9
10/// Trait providing a consistent interface for inline macro calls.
11pub trait InlineMacroCall {
12    type PathNode: TypedSyntaxNode;
13    type Result: PluginResultTrait;
14    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList;
15    fn path(&self, db: &dyn SyntaxGroup) -> Self::PathNode;
16}
17
18impl InlineMacroCall for ast::LegacyExprInlineMacro {
19    type PathNode = ast::ExprPath;
20    type Result = InlinePluginResult;
21
22    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
23        self.arguments(db)
24    }
25
26    fn path(&self, db: &dyn SyntaxGroup) -> ast::ExprPath {
27        self.path(db)
28    }
29}
30
31impl InlineMacroCall for ast::LegacyItemInlineMacro {
32    type PathNode = ast::ExprPath;
33    type Result = PluginResult;
34
35    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
36        self.arguments(db)
37    }
38
39    fn path(&self, db: &dyn SyntaxGroup) -> ast::ExprPath {
40        self.path(db)
41    }
42}
43
44/// Trait providing a consistent interface for the result of a macro plugins.
45pub trait PluginResultTrait {
46    fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self;
47}
48
49impl PluginResultTrait for InlinePluginResult {
50    fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self {
51        InlinePluginResult { code: None, diagnostics: vec![diagnostic] }
52    }
53}
54
55impl PluginResultTrait for PluginResult {
56    fn diagnostic_only(diagnostic: PluginDiagnostic) -> 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<CallAst: InlineMacroCall>(
63    db: &dyn SyntaxGroup,
64    legacy_macro_ast: &CallAst,
65    macro_ast: impl Into<SyntaxStablePtrId>,
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)
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(
89    db: &dyn SyntaxGroup,
90    macro_arguments: ast::ArgList,
91) -> Option<ast::Expr> {
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(
98    db: &dyn SyntaxGroup,
99    macro_arguments: &ast::ArgList,
100    n: usize,
101) -> Option<Vec<ast::Expr>> {
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: &dyn SyntaxGroup, arg_ast: &ast::Arg) -> Option<ast::Expr> {
109    if let ast::ArgClause::Unnamed(arg_clause) = arg_ast.arg_clause(db) {
110        Some(arg_clause.value(db))
111    } else {
112        None
113    }
114}
115
116/// Escapes a node for use in a format string.
117pub fn escape_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> String {
118    node.get_text_without_trivia(db).replace('{', "{{").replace('}', "}}").escape_unicode().join("")
119}
120
121/// Macro to extract unnamed arguments of an inline macro.
122///
123/// Gets the expected number of unnamed arguments, and the pattern for the allowed bracket types,
124/// and returns a fixed size array with the argument expressions.
125///
126/// Example usage (2 arguments, allowing `()` or `{}` brackets):
127/// let [arg1, arg2] = extract_macro_unnamed_args!(
128///     db,
129///     syntax,
130///     2,
131///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_),
132///     token_tree_syntax.stable_ptr(db)
133/// );
134#[macro_export]
135macro_rules! extract_macro_unnamed_args {
136    ($db:expr, $syntax:expr, $n:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
137        let arguments = $crate::plugin_utils::InlineMacroCall::arguments($syntax, $db);
138        if !matches!(arguments, $pattern) {
139            return $crate::plugin_utils::unsupported_bracket_diagnostic(
140                $db,
141                $syntax,
142                $diagnostics_ptr,
143            );
144        }
145        // `unwrap` is ok because the above `matches` condition ensures it's not None (unless
146        // the pattern contains the `Missing` variant).
147        let macro_arg_list =
148            cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(&arguments, $db)
149                .unwrap();
150
151        let args = $crate::plugin_utils::extract_unnamed_args($db, &macro_arg_list, $n);
152        let Some(args) = args else {
153            return $crate::plugin_utils::PluginResultTrait::diagnostic_only(
154                PluginDiagnostic::error(
155                    $diagnostics_ptr,
156                    format!(
157                        "Macro `{}` must have exactly {} unnamed arguments.",
158                        $crate::plugin_utils::InlineMacroCall::path($syntax, $db)
159                            .as_syntax_node()
160                            .get_text_without_trivia($db),
161                        $n
162                    ),
163                ),
164            );
165        };
166        let args: [ast::Expr; $n] = args.try_into().unwrap();
167        args
168    }};
169}
170
171/// Macro to extract a single unnamed argument of an inline macro.
172///
173/// Gets the pattern for the allowed bracket types, and returns the argument expression.
174/// The arguments are extracted from a `WrappedArgList` node syntax, as was in legacy inline macros.
175/// However, as macros are now parsed as general token trees, the diagnostics pointer is passed to
176/// the macro to allow pointing to the original location.
177///
178/// Example usage (allowing `()` or `{}` brackets):
179/// let arg = extract_macro_single_unnamed_arg!(
180///     db,
181///     arg_list_syntax,
182///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_),
183///     token_tree_syntax.stable_ptr(db)
184/// );
185#[macro_export]
186macro_rules! extract_macro_single_unnamed_arg {
187    ($db:expr, $syntax:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
188        let [x] = $crate::extract_macro_unnamed_args!($db, $syntax, 1, $pattern, $diagnostics_ptr);
189        x
190    }};
191}