Skip to content

Commit

Permalink
Added compiler_error! inline macro. (#5357)
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi authored Apr 4, 2024
1 parent 3f114c3 commit dd066ac
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 29 deletions.
106 changes: 78 additions & 28 deletions crates/cairo-lang-defs/src/plugin_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,71 @@ use cairo_lang_syntax::node::helpers::WrappedArgListHelper;
use cairo_lang_syntax::node::{ast, SyntaxNode, TypedSyntaxNode};
use itertools::Itertools;

use crate::plugin::{InlinePluginResult, PluginDiagnostic};
use crate::plugin::{InlinePluginResult, PluginDiagnostic, PluginResult};

/// Trait providing a consistent interface for inline macro calls.
pub trait InlineMacroCall {
type PathNode: TypedSyntaxNode;
type Result: PluginResultTrait;
fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList;
fn path(&self, db: &dyn SyntaxGroup) -> Self::PathNode;
}

impl InlineMacroCall for ast::ExprInlineMacro {
type PathNode = ast::ExprPath;
type Result = InlinePluginResult;

fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
self.arguments(db)
}

fn path(&self, db: &dyn SyntaxGroup) -> ast::ExprPath {
self.path(db)
}
}

impl InlineMacroCall for ast::ItemInlineMacro {
type PathNode = ast::TerminalIdentifier;
type Result = PluginResult;

fn arguments(&self, db: &dyn SyntaxGroup) -> ast::WrappedArgList {
self.arguments(db)
}

fn path(&self, db: &dyn SyntaxGroup) -> ast::TerminalIdentifier {
self.name(db)
}
}

/// Trait providing a consistent interface for the result of a macro plugins.
pub trait PluginResultTrait {
fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self;
}

impl PluginResultTrait for InlinePluginResult {
fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self {
InlinePluginResult { code: None, diagnostics: vec![diagnostic] }
}
}

impl PluginResultTrait for PluginResult {
fn diagnostic_only(diagnostic: PluginDiagnostic) -> Self {
PluginResult { code: None, diagnostics: vec![diagnostic], remove_original_item: true }
}
}

/// Returns diagnostics for an unsupported bracket type.
pub fn unsupported_bracket_diagnostic(
pub fn unsupported_bracket_diagnostic<CallAst: InlineMacroCall>(
db: &dyn SyntaxGroup,
macro_ast: &ast::ExprInlineMacro,
) -> InlinePluginResult {
InlinePluginResult {
code: None,
diagnostics: vec![PluginDiagnostic::error(
macro_ast.arguments(db).left_bracket_stable_ptr(db),
format!(
"Macro `{}` does not support this bracket type.",
macro_ast.path(db).as_syntax_node().get_text_without_trivia(db)
),
)],
}
macro_ast: &CallAst,
) -> CallAst::Result {
CallAst::Result::diagnostic_only(PluginDiagnostic::error(
macro_ast.arguments(db).left_bracket_stable_ptr(db),
format!(
"Macro `{}` does not support this bracket type.",
macro_ast.path(db).as_syntax_node().get_text_without_trivia(db)
),
))
}

/// Extracts a single unnamed argument.
Expand Down Expand Up @@ -75,28 +123,30 @@ pub fn escape_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> String {
#[macro_export]
macro_rules! extract_macro_unnamed_args {
($db:expr, $syntax:expr, $n:expr, $pattern:pat) => {{
if !matches!($syntax.arguments($db), $pattern) {
let arguments = $crate::plugin_utils::InlineMacroCall::arguments($syntax, $db);
if !matches!(arguments, $pattern) {
return $crate::plugin_utils::unsupported_bracket_diagnostic($db, $syntax);
}
// `unwrap` is ok because the above `matches` condition ensures it's not None (unless
// the pattern contains the `Missing` variant).
let macro_arg_list = cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(
&$syntax.arguments($db),
$db,
)
.unwrap();
let macro_arg_list =
cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(&arguments, $db)
.unwrap();

let args = $crate::plugin_utils::extract_unnamed_args($db, &macro_arg_list, $n);
let Some(args) = args else {
let diagnostics = vec![PluginDiagnostic::error(
$syntax.stable_ptr().untyped(),
format!(
"Macro `{}` must have exactly {} unnamed arguments.",
$syntax.path($db).as_syntax_node().get_text_without_trivia($db),
$n
return $crate::plugin_utils::PluginResultTrait::diagnostic_only(
PluginDiagnostic::error(
$syntax.stable_ptr().untyped(),
format!(
"Macro `{}` must have exactly {} unnamed arguments.",
$crate::plugin_utils::InlineMacroCall::path($syntax, $db)
.as_syntax_node()
.get_text_without_trivia($db),
$n
),
),
)];
return InlinePluginResult { code: None, diagnostics };
);
};
let args: [ast::Expr; $n] = args.try_into().unwrap();
args
Expand Down
5 changes: 4 additions & 1 deletion crates/cairo-lang-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::sync::Arc;

use cairo_lang_defs::plugin::MacroPlugin;

use crate::plugins::{ConfigPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin};
use crate::plugins::{
CompileErrorPlugin, ConfigPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin,
};

pub mod plugins;
#[cfg(any(feature = "testing", test))]
Expand All @@ -21,5 +23,6 @@ pub fn get_base_plugins() -> Vec<Arc<dyn MacroPlugin>> {
Arc::new(DerivePlugin::default()),
Arc::new(GenerateTraitPlugin::default()),
Arc::new(PanicablePlugin::default()),
Arc::new(CompileErrorPlugin::default()),
]
}
46 changes: 46 additions & 0 deletions crates/cairo-lang-plugins/src/plugins/compile_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use cairo_lang_defs::extract_macro_single_unnamed_arg;
use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginResult};
use cairo_lang_defs::plugin_utils::PluginResultTrait;
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::{ast, Terminal, TypedStablePtr, TypedSyntaxNode};

/// Plugin that allows writing item level `compile_error!` causing a diagnostic.
/// Useful for testing that `cfg` attributes are valid.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct CompileErrorPlugin;

impl MacroPlugin for CompileErrorPlugin {
fn generate_code(
&self,
db: &dyn SyntaxGroup,
item_ast: ast::ModuleItem,
_metadata: &MacroPluginMetadata<'_>,
) -> PluginResult {
if let ast::ModuleItem::InlineMacro(inline_macro_ast) = item_ast {
if inline_macro_ast.name(db).text(db) == "compile_error" {
let compilation_error_arg = extract_macro_single_unnamed_arg!(
db,
&inline_macro_ast,
ast::WrappedArgList::ParenthesizedArgList(_)
);
let ast::Expr::String(err_message) = compilation_error_arg else {
return PluginResult::diagnostic_only(PluginDiagnostic::error(
compilation_error_arg.stable_ptr().untyped(),
"`compiler_error!` argument must be an unnamed string argument."
.to_string(),
));
};
return PluginResult::diagnostic_only(PluginDiagnostic::error(
inline_macro_ast.stable_ptr().untyped(),
err_message.text(db).to_string(),
));
}
}
PluginResult { code: None, diagnostics: vec![], remove_original_item: false }
}

fn declared_attributes(&self) -> Vec<String> {
vec![]
}
}
2 changes: 2 additions & 0 deletions crates/cairo-lang-plugins/src/plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub use compile_error::*;
pub use config::*;
pub use derive::*;
pub use generate_trait::*;
pub use panicable::*;

mod compile_error;
mod config;
mod derive;
mod generate_trait;
Expand Down
1 change: 1 addition & 0 deletions crates/cairo-lang-plugins/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cairo_lang_test_utils::test_file_test!(
expand_plugin,
"src/test_data",
{
compile_error: "compile_error",
config: "config",
derive: "derive",
generate_trait: "generate_trait",
Expand Down
61 changes: 61 additions & 0 deletions crates/cairo-lang-plugins/src/test_data/compile_error
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! > Test diagnostics of compile error.

//! > test_runner_name
test_expand_plugin(expect_diagnostics: true)

//! > cairo_code
compile_error!();

compile_error!(1);

compile_error!("message", "extra");

compile_error!("error message");

//! > expanded_cairo_code

//! > expected_diagnostics
error: Macro `compile_error` must have exactly 1 unnamed arguments.
--> test_src/lib.cairo:1:1
compile_error!();
^***************^

error: `compiler_error!` argument must be an unnamed string argument.
--> test_src/lib.cairo:3:16
compile_error!(1);
^

error: Macro `compile_error` must have exactly 1 unnamed arguments.
--> test_src/lib.cairo:5:1
compile_error!("message", "extra");
^*********************************^

error: "error message"
--> test_src/lib.cairo:7:1
compile_error!("error message");
^******************************^

//! > ==========================================================================

//! > Test usage of `compile_error!` with `cfg`.

//! > test_runner_name
test_expand_plugin(expect_diagnostics: true)

//! > cfg
["noignore"]

//! > cairo_code
#[cfg(noignore)]
compile_error!("show");

#[cfg(ignore)]
compile_error!("ignore");

//! > expanded_cairo_code

//! > expected_diagnostics
error: "show"
--> test_src/lib.cairo:1:1
#[cfg(noignore)]
^**************^

0 comments on commit dd066ac

Please sign in to comment.