Skip to content

Commit

Permalink
Implement procedural macro diagnostics
Browse files Browse the repository at this point in the history
commit-id:f06e9653
  • Loading branch information
maciektr committed Feb 20, 2024
1 parent 27d39ea commit b066df1
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 26 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 30 additions & 2 deletions plugins/cairo-lang-macro-stable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,49 @@ 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.
#[repr(C)]
#[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 {
Expand Down
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
167 changes: 151 additions & 16 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Diagnostic> },
/// Plugin generated [`TokenStream`] replacement.
Replace {
token_stream: TokenStream,
aux_data: Option<AuxData>,
diagnostics: Vec<Diagnostic>,
},
/// Plugin ordered item removal.
Remove,
Remove { diagnostics: Vec<Diagnostic> },
}

#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -48,22 +52,54 @@ 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.
///
/// # Safety
#[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,
}
}
}
}

Expand All @@ -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,
}
}
}
}
}
Expand Down Expand Up @@ -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<Self>) -> (*mut StableDiagnostic, usize) {
let stable_diagnostics = diagnostics
.into_iter()
.map(|diagnostic| diagnostic.into_stable())
.collect::<Vec<_>>();
let n = stable_diagnostics.len();
let ptr = malloc(std::mem::size_of::<StableDiagnostic>() * 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<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = 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()
Expand Down
46 changes: 38 additions & 8 deletions scarb/src/compiler/plugin/proc_macro/host.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<AuxData> = None;
let mut modified = false;
let mut all_diagnostics: Vec<Diagnostic> = Vec::new();
for input in expansions {
let instance = self
.macros
Expand All @@ -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 {
Expand All @@ -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,
}
}
}

Expand All @@ -189,6 +202,23 @@ impl MacroPlugin for ProcMacroHostPlugin {
}
}

fn into_cairo_diagnostics(
diagnostics: Vec<Diagnostic>,
stable_ptr: SyntaxStablePtrId,
) -> Vec<PluginDiagnostic> {
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.
Expand Down

0 comments on commit b066df1

Please sign in to comment.