diff --git a/Cargo.lock b/Cargo.lock index bf3e45cde..2b1e66ec0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -731,6 +731,7 @@ dependencies = [ "anyhow", "cairo-lang-macro-attributes", "cairo-lang-macro-stable", + "libc", "serde", "serde_json", ] diff --git a/plugins/cairo-lang-macro-stable/src/lib.rs b/plugins/cairo-lang-macro-stable/src/lib.rs index 5be4e843d..a7ed41db9 100644 --- a/plugins/cairo-lang-macro-stable/src/lib.rs +++ b/plugins/cairo-lang-macro-stable/src/lib.rs @@ -15,6 +15,26 @@ pub enum StableAuxData { Some(*mut c_char), } +/// Diagnostic returned by the procedural macro. +/// +/// This struct implements FFI-safe stable ABI. +#[repr(C)] +#[derive(Debug)] +pub struct StableDiagnostic { + pub message: *mut c_char, + pub severity: StableSeverity, +} + +/// The severity of a diagnostic. +/// +/// This struct implements FFI-safe stable ABI. +#[repr(C)] +#[derive(Debug)] +pub enum StableSeverity { + Error, + Warning, +} + /// Procedural macro result. /// /// This struct implements FFI-safe stable ABI. @@ -22,14 +42,22 @@ pub enum StableAuxData { #[derive(Debug)] pub enum StableProcMacroResult { /// Plugin has not taken any action. - Leave, + Leave { + diagnostics: *mut StableDiagnostic, + diagnostics_n: usize, + }, /// Plugin generated [`StableTokenStream`] replacement. Replace { token_stream: StableTokenStream, aux_data: StableAuxData, + diagnostics: *mut StableDiagnostic, + diagnostics_n: usize, }, /// Plugin ordered item removal. - Remove, + Remove { + diagnostics: *mut StableDiagnostic, + diagnostics_n: usize, + }, } impl StableTokenStream { diff --git a/plugins/cairo-lang-macro/Cargo.toml b/plugins/cairo-lang-macro/Cargo.toml index 7ca51d792..0b466f8e4 100644 --- a/plugins/cairo-lang-macro/Cargo.toml +++ b/plugins/cairo-lang-macro/Cargo.toml @@ -14,6 +14,7 @@ repository.workspace = true [dependencies] anyhow.workspace = true +libc.workspace = true cairo-lang-macro-attributes = { path = "../cairo-lang-macro-attributes" } cairo-lang-macro-stable = { path = "../cairo-lang-macro-stable" } serde.workspace = true diff --git a/plugins/cairo-lang-macro/src/lib.rs b/plugins/cairo-lang-macro/src/lib.rs index 2c3546ffa..cd9b1bc57 100644 --- a/plugins/cairo-lang-macro/src/lib.rs +++ b/plugins/cairo-lang-macro/src/lib.rs @@ -1,21 +1,25 @@ +use libc::{free, malloc}; use serde_json::Value; -use std::ffi::{c_char, CString}; +use std::ffi::{c_char, c_void, CString}; use std::fmt::Display; pub use cairo_lang_macro_attributes::*; -use cairo_lang_macro_stable::{StableAuxData, StableProcMacroResult, StableTokenStream}; +use cairo_lang_macro_stable::{ + StableAuxData, StableDiagnostic, StableProcMacroResult, StableSeverity, StableTokenStream, +}; #[derive(Debug)] pub enum ProcMacroResult { /// Plugin has not taken any action. - Leave, + Leave { diagnostics: Vec }, /// Plugin generated [`TokenStream`] replacement. Replace { token_stream: TokenStream, aux_data: Option, + diagnostics: Vec, }, /// Plugin ordered item removal. - Remove, + Remove { diagnostics: Vec }, } #[derive(Debug, Default, Clone)] @@ -48,6 +52,20 @@ impl AuxData { } } +/// Diagnostic returned by the procedural macro. +#[derive(Debug)] +pub struct Diagnostic { + pub message: String, + pub severity: Severity, +} + +/// The severity of a diagnostic. +#[derive(Debug)] +pub enum Severity { + Error, + Warning, +} + impl ProcMacroResult { /// Convert to FFI-safe representation. /// @@ -55,15 +73,33 @@ impl ProcMacroResult { #[doc(hidden)] pub fn into_stable(self) -> StableProcMacroResult { match self { - ProcMacroResult::Leave => StableProcMacroResult::Leave, - ProcMacroResult::Remove => StableProcMacroResult::Remove, + ProcMacroResult::Leave { diagnostics } => { + let (ptr, n) = unsafe { Diagnostic::allocate(diagnostics) }; + StableProcMacroResult::Leave { + diagnostics: ptr, + diagnostics_n: n, + } + } + ProcMacroResult::Remove { diagnostics } => { + let (ptr, n) = unsafe { Diagnostic::allocate(diagnostics) }; + StableProcMacroResult::Remove { + diagnostics: ptr, + diagnostics_n: n, + } + } ProcMacroResult::Replace { token_stream, aux_data, - } => StableProcMacroResult::Replace { - token_stream: token_stream.into_stable(), - aux_data: AuxData::maybe_into_stable(aux_data), - }, + diagnostics, + } => { + let (ptr, n) = unsafe { Diagnostic::allocate(diagnostics) }; + StableProcMacroResult::Replace { + token_stream: token_stream.into_stable(), + aux_data: AuxData::maybe_into_stable(aux_data), + diagnostics: ptr, + diagnostics_n: n, + } + } } } @@ -73,15 +109,33 @@ impl ProcMacroResult { #[doc(hidden)] pub unsafe fn from_stable(result: StableProcMacroResult) -> Self { match result { - StableProcMacroResult::Leave => ProcMacroResult::Leave, - StableProcMacroResult::Remove => ProcMacroResult::Remove, + StableProcMacroResult::Leave { + diagnostics, + diagnostics_n, + } => { + let diagnostics = Diagnostic::deallocate(diagnostics, diagnostics_n); + ProcMacroResult::Leave { diagnostics } + } + StableProcMacroResult::Remove { + diagnostics, + diagnostics_n, + } => { + let diagnostics = Diagnostic::deallocate(diagnostics, diagnostics_n); + ProcMacroResult::Remove { diagnostics } + } StableProcMacroResult::Replace { token_stream, aux_data, - } => ProcMacroResult::Replace { - token_stream: TokenStream::from_stable(token_stream), - aux_data: AuxData::from_stable(aux_data).unwrap(), - }, + diagnostics, + diagnostics_n, + } => { + let diagnostics = Diagnostic::deallocate(diagnostics, diagnostics_n); + ProcMacroResult::Replace { + token_stream: TokenStream::from_stable(token_stream), + aux_data: AuxData::from_stable(aux_data).unwrap(), + diagnostics, + } + } } } } @@ -138,6 +192,87 @@ impl AuxData { } } +impl Diagnostic { + /// Convert to FFI-safe representation. + /// + /// # Safety + #[doc(hidden)] + pub fn into_stable(self) -> StableDiagnostic { + let cstr = CString::new(self.message).unwrap(); + StableDiagnostic { + message: cstr.into_raw(), + severity: self.severity.into_stable(), + } + } + + /// Convert to native Rust representation. + /// + /// # Safety + #[doc(hidden)] + pub unsafe fn from_stable(diagnostic: StableDiagnostic) -> Self { + Self { + message: raw_to_string(diagnostic.message), + severity: Severity::from_stable(diagnostic.severity), + } + } + + /// Allocate dynamic array with FFI-safe diagnostics. + /// + /// # Safety + #[doc(hidden)] + pub unsafe fn allocate(diagnostics: Vec) -> (*mut StableDiagnostic, usize) { + let stable_diagnostics = diagnostics + .into_iter() + .map(|diagnostic| diagnostic.into_stable()) + .collect::>(); + let n = stable_diagnostics.len(); + let ptr = malloc(std::mem::size_of::() * n) as *mut StableDiagnostic; + if ptr.is_null() { + panic!("memory allocation with malloc failed"); + } + (ptr, n) + } + + /// Deallocate dynamic array of diagnostics, returning a vector. + /// + /// # Safety + pub unsafe fn deallocate(ptr: *mut StableDiagnostic, n: usize) -> Vec { + let mut diagnostics: Vec = Vec::with_capacity(n); + for i in 0..n { + let ptr = ptr.add(i); + let diag = std::ptr::read(ptr); + let diag = Diagnostic::from_stable(diag); + diagnostics.push(diag); + } + free(ptr as *mut c_void); + diagnostics + } +} + +impl Severity { + /// Convert to FFI-safe representation. + /// + /// # Safety + #[doc(hidden)] + pub fn into_stable(self) -> StableSeverity { + match self { + Severity::Error => StableSeverity::Error, + Severity::Warning => StableSeverity::Warning, + } + } + + /// Convert to native Rust representation. + /// + /// # Safety + #[doc(hidden)] + pub unsafe fn from_stable(severity: StableSeverity) -> Self { + match severity { + StableSeverity::Error => Self::Error, + StableSeverity::Warning => Self::Warning, + } + } +} + unsafe fn raw_to_string(raw: *mut c_char) -> String { if raw.is_null() { String::default() diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 6e36db66a..e1bf95075 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -1,15 +1,17 @@ use crate::compiler::plugin::proc_macro::{FromItemAst, ProcMacroInstance}; use crate::core::{Config, Package, PackageId}; use anyhow::Result; +use cairo_lang_defs::plugin::PluginDiagnostic; use cairo_lang_defs::plugin::{ DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, MacroPluginMetadata, PluginGeneratedFile, PluginResult, }; -use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream}; +use cairo_lang_macro::{AuxData, Diagnostic, ProcMacroResult, Severity, TokenStream}; use cairo_lang_semantic::plugin::PluginSuite; use cairo_lang_syntax::attribute::structured::AttributeListStructurize; -use cairo_lang_syntax::node::ast; use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::ids::SyntaxStablePtrId; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use itertools::Itertools; use smol_str::SmolStr; use std::any::Any; @@ -135,10 +137,12 @@ impl MacroPlugin for ProcMacroHostPlugin { .into_iter() .chain(self.handle_attribute(db, item_ast.clone())) .chain(self.handle_derive(db, item_ast.clone())); + let stable_ptr = item_ast.clone().stable_ptr().untyped(); let mut token_stream = TokenStream::from_item_ast(db, item_ast); let mut aux_data: Option = None; let mut modified = false; + let mut all_diagnostics: Vec = Vec::new(); for input in expansions { let instance = self .macros @@ -149,19 +153,24 @@ impl MacroPlugin for ProcMacroHostPlugin { ProcMacroResult::Replace { token_stream: new_token_stream, aux_data: new_aux_data, + diagnostics, } => { token_stream = new_token_stream; aux_data = new_aux_data; modified = true; + all_diagnostics.extend(diagnostics); } - ProcMacroResult::Remove => { + ProcMacroResult::Remove { diagnostics } => { + all_diagnostics.extend(diagnostics); return PluginResult { + diagnostics: into_cairo_diagnostics(all_diagnostics, stable_ptr), code: None, - diagnostics: Vec::new(), remove_original_item: true, - } + }; + } + ProcMacroResult::Leave { diagnostics } => { + all_diagnostics.extend(diagnostics); } - ProcMacroResult::Leave => {} }; } if modified { @@ -173,11 +182,15 @@ impl MacroPlugin for ProcMacroHostPlugin { aux_data: aux_data .map(|ad| DynGeneratedFileAuxData::new(ProcMacroAuxData(ad.to_value()))), }), - diagnostics: Vec::new(), + diagnostics: into_cairo_diagnostics(all_diagnostics, stable_ptr), remove_original_item: true, } } else { - PluginResult::default() + PluginResult { + code: None, + diagnostics: into_cairo_diagnostics(all_diagnostics, stable_ptr), + remove_original_item: false, + } } } @@ -189,6 +202,23 @@ impl MacroPlugin for ProcMacroHostPlugin { } } +fn into_cairo_diagnostics( + diagnostics: Vec, + stable_ptr: SyntaxStablePtrId, +) -> Vec { + diagnostics + .into_iter() + .map(|diag| PluginDiagnostic { + stable_ptr, + message: diag.message, + severity: match diag.severity { + Severity::Error => cairo_lang_diagnostics::Severity::Error, + Severity::Warning => cairo_lang_diagnostics::Severity::Warning, + }, + }) + .collect_vec() +} + /// A Scarb wrapper around the `ProcMacroHost` compiler plugin. /// /// This struct represent the compiler plugin in terms of Scarb data model.