diff --git a/Cargo.lock b/Cargo.lock index 8d1c8b8996f..1e6ad74a3e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,6 +603,7 @@ dependencies = [ "env_logger", "indoc", "itertools", + "num-bigint", "salsa", "serde_json", "smol_str", diff --git a/crates/cairo-lang-plugins/Cargo.toml b/crates/cairo-lang-plugins/Cargo.toml index 3561265781d..ed5a66f5a29 100644 --- a/crates/cairo-lang-plugins/Cargo.toml +++ b/crates/cairo-lang-plugins/Cargo.toml @@ -16,6 +16,7 @@ cairo-lang-syntax = { path = "../cairo-lang-syntax", version = "2.0.0-rc0" } cairo-lang-utils = { path = "../cairo-lang-utils", version = "2.0.0-rc0" } indoc.workspace = true itertools.workspace = true +num-bigint.workspace = true salsa.workspace = true smol_str.workspace = true diff --git a/crates/cairo-lang-plugins/src/lib.rs b/crates/cairo-lang-plugins/src/lib.rs index 36022d484d2..8f433858f73 100644 --- a/crates/cairo-lang-plugins/src/lib.rs +++ b/crates/cairo-lang-plugins/src/lib.rs @@ -3,7 +3,9 @@ use std::sync::Arc; use cairo_lang_semantic::plugin::SemanticPlugin; -use crate::plugins::{ConfigPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin}; +use crate::plugins::{ + ConfigPlugin, ConstevalIntMacroPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin, +}; pub mod plugins; @@ -13,6 +15,7 @@ mod test; /// Gets the list of default plugins to load into the Cairo compiler. pub fn get_default_plugins() -> Vec> { vec![ + Arc::new(ConstevalIntMacroPlugin::default()), Arc::new(DerivePlugin::default()), Arc::new(GenerateTraitPlugin::default()), Arc::new(PanicablePlugin::default()), diff --git a/crates/cairo-lang-plugins/src/plugins/consteval_int.rs b/crates/cairo-lang-plugins/src/plugins/consteval_int.rs new file mode 100644 index 00000000000..742695df51e --- /dev/null +++ b/crates/cairo-lang-plugins/src/plugins/consteval_int.rs @@ -0,0 +1,170 @@ +use std::sync::Arc; + +use cairo_lang_defs::plugin::{ + DynGeneratedFileAuxData, MacroPlugin, PluginDiagnostic, PluginGeneratedFile, PluginResult, +}; +use cairo_lang_semantic::plugin::{AsDynMacroPlugin, SemanticPlugin, TrivialPluginAuxData}; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; +use num_bigint::BigInt; + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct ConstevalIntMacroPlugin; + +impl AsDynMacroPlugin for ConstevalIntMacroPlugin { + fn as_dyn_macro_plugin<'a>(self: Arc) -> Arc + where + Self: 'a, + { + self + } +} +impl SemanticPlugin for ConstevalIntMacroPlugin {} + +impl MacroPlugin for ConstevalIntMacroPlugin { + fn generate_code(&self, db: &dyn SyntaxGroup, item_ast: ast::Item) -> PluginResult { + match item_ast { + ast::Item::Constant(constant_ast) => handle_constant(db, &constant_ast), + _ => PluginResult::default(), + } + } +} + +/// Rewrite a constant declaration that contains a consteval_int macro +/// into a constant declaration with the computed value, +/// e.g. `const a: felt252 = consteval_int!(2 * 2 * 2);` into `const a: felt252 = 8;`. +fn handle_constant(db: &dyn SyntaxGroup, constant_ast: &ast::ItemConstant) -> PluginResult { + let constant_value = constant_ast.value(db); + if let ast::Expr::InlineMacro(inline_macro) = constant_value { + if inline_macro.path(db).as_syntax_node().get_text(db) != "consteval_int" { + return PluginResult::default(); + } + let mut diagnostics = vec![]; + let constant_expression = + extract_consteval_macro_expression(db, &inline_macro, &mut diagnostics); + if constant_expression.is_none() { + return PluginResult { diagnostics, ..Default::default() }; + } + let new_value = compute_constant_expr(db, &constant_expression.unwrap(), &mut diagnostics); + if new_value.is_none() { + return PluginResult { diagnostics, ..Default::default() }; + } + return PluginResult { + code: Some(PluginGeneratedFile { + name: "computed_constants".into(), + content: format!( + "const {}{}= {};", + constant_ast.name(db).text(db), + constant_ast.type_clause(db).as_syntax_node().get_text(db), + new_value.unwrap() + ), + aux_data: DynGeneratedFileAuxData(Arc::new(TrivialPluginAuxData {})), + }), + diagnostics, + remove_original_item: true, + }; + } + PluginResult::default() +} + +/// Extract the actual expression from the consteval_int macro, or fail with diagnostics. +fn extract_consteval_macro_expression( + db: &dyn SyntaxGroup, + macro_ast: &ast::ExprInlineMacro, + diagnostics: &mut Vec, +) -> Option { + let args = macro_ast.arguments(db).args(db).elements(db); + if args.len() != 1 { + diagnostics.push(PluginDiagnostic { + stable_ptr: macro_ast.stable_ptr().untyped(), + message: "consteval_int macro must have a single unnamed argument.".to_string(), + }); + return None; + } + match args[0].arg_clause(db) { + ast::ArgClause::Unnamed(arg) => Some(arg.value(db)), + _ => { + diagnostics.push(PluginDiagnostic { + stable_ptr: macro_ast.stable_ptr().untyped(), + message: "consteval_int macro must have a single unnamed argument.".to_string(), + }); + None + } + } +} + +/// Compute the actual value of an integer expression, or fail with diagnostics. +/// This computation handles arbitrary integers, unlike regular Cairo math. +fn compute_constant_expr( + db: &dyn SyntaxGroup, + value: &ast::Expr, + diagnostics: &mut Vec, +) -> Option { + match value { + ast::Expr::Literal(lit) => lit.numeric_value(db), + ast::Expr::Binary(bin_expr) => match bin_expr.op(db) { + ast::BinaryOperator::Plus(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + + compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Mul(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + * compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Minus(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + - compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Div(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + / compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Mod(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + % compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::And(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + & compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Or(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + | compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + ast::BinaryOperator::Xor(_) => Some( + compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)? + ^ compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?, + ), + _ => { + diagnostics.push(PluginDiagnostic { + stable_ptr: bin_expr.stable_ptr().untyped(), + message: "Unsupported binary operator in consteval_int macro".to_string(), + }); + None + } + }, + ast::Expr::Unary(un_expr) => match un_expr.op(db) { + ast::UnaryOperator::Minus(_) => { + Some(-compute_constant_expr(db, &un_expr.expr(db), diagnostics)?) + } + _ => { + diagnostics.push(PluginDiagnostic { + stable_ptr: un_expr.stable_ptr().untyped(), + message: "Unsupported unary operator in consteval_int macro".to_string(), + }); + None + } + }, + ast::Expr::Parenthesized(paren_expr) => { + compute_constant_expr(db, &paren_expr.expr(db), diagnostics) + } + _ => { + diagnostics.push(PluginDiagnostic { + stable_ptr: value.stable_ptr().untyped(), + message: "Unsupported expression in consteval_int macro".to_string(), + }); + None + } + } +} diff --git a/crates/cairo-lang-plugins/src/plugins/mod.rs b/crates/cairo-lang-plugins/src/plugins/mod.rs index 77ef34b274f..baec02d3bc0 100644 --- a/crates/cairo-lang-plugins/src/plugins/mod.rs +++ b/crates/cairo-lang-plugins/src/plugins/mod.rs @@ -1,9 +1,11 @@ pub use config::*; +pub use consteval_int::*; pub use derive::*; pub use generate_trait::*; pub use panicable::*; mod config; +mod consteval_int; mod derive; mod generate_trait; mod panicable; diff --git a/crates/cairo-lang-plugins/src/test.rs b/crates/cairo-lang-plugins/src/test.rs index cd1469bb76e..dcdea1121a6 100644 --- a/crates/cairo-lang-plugins/src/test.rs +++ b/crates/cairo-lang-plugins/src/test.rs @@ -15,6 +15,7 @@ cairo_lang_test_utils::test_file_test!( expand_plugin, "src/test_data", { + consteval_int: "consteval_int", config: "config", derive: "derive", generate_trait: "generate_trait", diff --git a/crates/cairo-lang-plugins/src/test_data/consteval_int b/crates/cairo-lang-plugins/src/test_data/consteval_int new file mode 100644 index 00000000000..4a21b73f49d --- /dev/null +++ b/crates/cairo-lang-plugins/src/test_data/consteval_int @@ -0,0 +1,107 @@ +//! > Test consteval_int! macro + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +const a: felt252 = 0; + +const b: felt252 = consteval_int!(4 + 5); + +const c: felt252 = 4 + 5; + +const d: felt252 = consteval_int!(23 + 4 * 5 + (4 + 5) / 2); + +const e: u8 = consteval_int!(255 + 1 - 1); + +//! > generated_cairo_code +const a: felt252 = 0; + +const b: felt252 = 9; + +const c: felt252 = 4 + 5; + +const d: felt252 = 47; +const e: u8 = 255; + +//! > expected_diagnostics + +//! > ========================================================================== + +//! > Test bad consteval_int! macros + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +const a: felt252 = consteval_int!(func_call(24)); + +const b: felt252 = consteval_int!('some string'); + +const c: felt252 = consteval_int!(*24); + +const d: felt252 = consteval_int!(~24); + +const e: felt252 = consteval_int!(234 < 5); + +//! > generated_cairo_code +const a: felt252 = consteval_int!(func_call(24)); + + +const b: felt252 = consteval_int!('some string'); + + +const c: felt252 = consteval_int!(*24); + + +const d: felt252 = consteval_int!(~24); + + +const e: felt252 = consteval_int!(234 < 5); + +//! > expected_diagnostics +error: Unsupported expression in consteval_int macro + --> dummy_file.cairo:1:35 +const a: felt252 = consteval_int!(func_call(24)); + ^***********^ + +error: Unsupported expression in consteval_int macro + --> dummy_file.cairo:3:35 +const b: felt252 = consteval_int!('some string'); + ^***********^ + +error: Unsupported unary operator in consteval_int macro + --> dummy_file.cairo:5:35 +const c: felt252 = consteval_int!(*24); + ^*^ + +error: Unsupported unary operator in consteval_int macro + --> dummy_file.cairo:7:35 +const d: felt252 = consteval_int!(~24); + ^*^ + +error: Unsupported binary operator in consteval_int macro + --> dummy_file.cairo:9:35 +const e: felt252 = consteval_int!(234 < 5); + ^*****^ + +//! > ========================================================================== + +//! > Test consteval_int! inside functions (currently does nothing) + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +fn some_func() +{ + return consteval_int!(4 + 5); +} + +//! > generated_cairo_code +fn some_func() +{ + return consteval_int!(4 + 5); +} + +//! > expected_diagnostics diff --git a/tests/bug_samples/issue3130.cairo b/tests/bug_samples/issue3130.cairo new file mode 100644 index 00000000000..17f8ecec707 --- /dev/null +++ b/tests/bug_samples/issue3130.cairo @@ -0,0 +1,7 @@ +const a: felt252 = consteval_int!((4 + 2 * 3) * 256); +const b: felt252 = consteval_int!(0xff & (24 + 5 * 2)); +const c: felt252 = consteval_int!(-0xff & (24 + 5 * 2)); +const d: felt252 = consteval_int!(0xff | (24 + 5 * 2)); + +#[test] +fn main() {} diff --git a/tests/bug_samples/lib.cairo b/tests/bug_samples/lib.cairo index 5e7ea79eb43..a65999025af 100644 --- a/tests/bug_samples/lib.cairo +++ b/tests/bug_samples/lib.cairo @@ -16,6 +16,7 @@ mod issue2939; mod issue2961; mod issue2964; mod issue2995; +mod issue3130; mod issue3153; mod issue3192; mod issue3211;