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::{SyntaxNode, TypedSyntaxNode, ast};
4use cairo_lang_utils::require;
5use itertools::Itertools;
6
7use crate::plugin::{InlinePluginResult, PluginDiagnostic, PluginResult};
8
9/// Trait providing a consistent interface for inline macro calls.
10pub trait InlineMacroCall {
11    type PathNode: TypedSyntaxNode;
12    type Result: PluginResultTrait;
13    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList;
14    fn path(&self, db: &dyn SyntaxGroup) -> Self::PathNode;
15}
16
17impl InlineMacroCall for ast::ExprInlineMacro {
18    type PathNode = ast::ExprPath;
19    type Result = InlinePluginResult;
20
21    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
22        self.arguments(db)
23    }
24
25    fn path(&self, db: &dyn SyntaxGroup) -> ast::ExprPath {
26        self.path(db)
27    }
28}
29
30impl InlineMacroCall for ast::ItemInlineMacro {
31    type PathNode = ast::TerminalIdentifier;
32    type Result = PluginResult;
33
34    fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
35        self.arguments(db)
36    }
37
38    fn path(&self, db: &dyn SyntaxGroup) -> ast::TerminalIdentifier {
39        self.name(db)
40    }
41}
42
43/// Trait providing a consistent interface for the result of a macro plugins.
44pub trait PluginResultTrait {
45    fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self;
46}
47
48impl PluginResultTrait for InlinePluginResult {
49    fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self {
50        InlinePluginResult { code: None, diagnostics: vec![diagnostic] }
51    }
52}
53
54impl PluginResultTrait for PluginResult {
55    fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self {
56        PluginResult { code: None, diagnostics: vec![diagnostic], remove_original_item: true }
57    }
58}
59
60/// Returns diagnostics for an unsupported bracket type.
61pub fn unsupported_bracket_diagnostic<CallAst: InlineMacroCall>(
62    db: &dyn SyntaxGroup,
63    macro_ast: &CallAst,
64) -> CallAst::Result {
65    CallAst::Result::diagnostic_only(PluginDiagnostic::error(
66        macro_ast.arguments(db).left_bracket_stable_ptr(db),
67        format!(
68            "Macro `{}` does not support this bracket type.",
69            macro_ast.path(db).as_syntax_node().get_text_without_trivia(db)
70        ),
71    ))
72}
73
74/// Extracts a single unnamed argument.
75pub fn extract_single_unnamed_arg(
76    db: &dyn SyntaxGroup,
77    macro_arguments: ast::ArgList,
78) -> Option<ast::Expr> {
79    if let Ok([arg]) = <[_; 1]>::try_from(macro_arguments.elements(db)) {
80        try_extract_unnamed_arg(db, &arg)
81    } else {
82        None
83    }
84}
85
86/// Extracts `n` unnamed arguments.
87pub fn extract_unnamed_args(
88    db: &dyn SyntaxGroup,
89    macro_arguments: &ast::ArgList,
90    n: usize,
91) -> Option<Vec<ast::Expr>> {
92    let elements = macro_arguments.elements(db);
93    require(elements.len() == n)?;
94    elements.iter().map(|x| try_extract_unnamed_arg(db, x)).collect()
95}
96
97/// Gets the syntax of an argument, and extracts the value if it is unnamed.
98pub fn try_extract_unnamed_arg(db: &dyn SyntaxGroup, arg_ast: &ast::Arg) -> Option<ast::Expr> {
99    if let ast::ArgClause::Unnamed(arg_clause) = arg_ast.arg_clause(db) {
100        Some(arg_clause.value(db))
101    } else {
102        None
103    }
104}
105
106/// Escapes a node for use in a format string.
107pub fn escape_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> String {
108    node.get_text_without_trivia(db).replace('{', "{{").replace('}', "}}").escape_unicode().join("")
109}
110
111/// Macro to extract unnamed arguments of an inline macro.
112///
113/// Gets the expected number of unnamed arguments, and the pattern for the allowed bracket types,
114/// and returns a fixed size array with the argument expressions.
115///
116/// Example usage (2 arguments, allowing `()` or `{}` brackets):
117/// let [arg1, arg2] = extract_macro_unnamed_args!(
118///     db,
119///     syntax,
120///     2,
121///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_)
122/// );
123#[macro_export]
124macro_rules! extract_macro_unnamed_args {
125    ($db:expr, $syntax:expr, $n:expr, $pattern:pat) => {{
126        let arguments = $crate::plugin_utils::InlineMacroCall::arguments($syntax, $db);
127        if !matches!(arguments, $pattern) {
128            return $crate::plugin_utils::unsupported_bracket_diagnostic($db, $syntax);
129        }
130        // `unwrap` is ok because the above `matches` condition ensures it's not None (unless
131        // the pattern contains the `Missing` variant).
132        let macro_arg_list =
133            cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(&arguments, $db)
134                .unwrap();
135
136        let args = $crate::plugin_utils::extract_unnamed_args($db, &macro_arg_list, $n);
137        let Some(args) = args else {
138            return $crate::plugin_utils::PluginResultTrait::diagnostic_only(
139                PluginDiagnostic::error(
140                    $syntax,
141                    format!(
142                        "Macro `{}` must have exactly {} unnamed arguments.",
143                        $crate::plugin_utils::InlineMacroCall::path($syntax, $db)
144                            .as_syntax_node()
145                            .get_text_without_trivia($db),
146                        $n
147                    ),
148                ),
149            );
150        };
151        let args: [ast::Expr; $n] = args.try_into().unwrap();
152        args
153    }};
154}
155
156/// Macro to extract a single unnamed argument of an inline macro.
157/// Gets the pattern for the allowed bracket types, and returns the argument expression.
158///
159/// Example usage (allowing `()` or `{}` brackets):
160/// let arg = extract_macro_single_unnamed_arg!(
161///     db,
162///     syntax,
163///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_)
164/// );
165#[macro_export]
166macro_rules! extract_macro_single_unnamed_arg {
167    ($db:expr, $syntax:expr, $pattern:pat) => {{
168        let [x] = $crate::extract_macro_unnamed_args!($db, $syntax, 1, $pattern);
169        x
170    }};
171}