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 Mar 5, 2024
1 parent 182ba69 commit 9f17249
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 52 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion plugins/cairo-lang-macro-attributes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ repository.workspace = true
proc-macro = true

[dependencies]
camino.workspace = true
quote.workspace = true
syn = { workspace = true, features = ["full", "extra-traits"] }
44 changes: 44 additions & 0 deletions plugins/cairo-lang-macro-stable/src/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// This struct encapsulates a stable ABI representation of struct.
///
/// Please be aware that the memory management of values passing the FFI-barrier is tricky.
#[repr(C)]
#[derive(Debug)]
#[doc(hidden)]
pub struct StableSlice<T> {
ptr: *mut T,
len: usize,
}

impl<T> Copy for StableSlice<T> {}

impl<T> Clone for StableSlice<T> {
fn clone(&self) -> Self {
*self
}
}

impl<T> StableSlice<T> {
/// Create a new `StableSlice` from a Vector.
/// Note that the vector will not be deallocated automatically.
/// Please make sure to use `into_owned` afterward, to free the memory.
pub fn new(mut x: Vec<T>) -> Self {
x.shrink_to_fit();
assert_eq!(x.len(), x.capacity());
let ptr = x.as_mut_ptr();
let len = x.len();
std::mem::forget(x);
Self { ptr, len }
}

/// Convert to owned vector.
pub fn into_owned(self) -> Vec<T> {
unsafe { Vec::from_raw_parts(self.ptr, self.len, self.len) }
}

/// Returns raw pointer and length.
/// Can be used to construct a slice.
/// No ownership is transferred.
pub fn into_raw_parts(self) -> (*mut T, usize) {
(self.ptr, self.len)
}
}
38 changes: 36 additions & 2 deletions plugins/cairo-lang-macro-stable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::ffi::StableSlice;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

pub mod ffi;

/// Token stream.
///
/// This struct implements FFI-safe stable ABI.
Expand All @@ -15,21 +18,52 @@ 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, Clone)]
pub enum StableProcMacroResult {
/// Plugin has not taken any action.
Leave,
Leave {
diagnostics: StableSlice<StableDiagnostic>,
// diagnostics: *mut StableDiagnostic,
// diagnostics_n: usize,
},
/// Plugin generated [`StableTokenStream`] replacement.
Replace {
diagnostics: StableSlice<StableDiagnostic>,
token_stream: StableTokenStream,
aux_data: StableAuxData,
// diagnostics: *mut StableDiagnostic,
// diagnostics_n: usize,
},
/// Plugin ordered item removal.
Remove,
Remove {
diagnostics: StableSlice<StableDiagnostic>,
// diagnostics: *mut StableDiagnostic,
// diagnostics_n: usize,
},
}

impl StableTokenStream {
Expand Down
214 changes: 191 additions & 23 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
pub use cairo_lang_macro_attributes::*;
use cairo_lang_macro_stable::ffi::StableSlice;
use cairo_lang_macro_stable::{
StableAuxData, StableDiagnostic, StableProcMacroResult, StableSeverity, StableTokenStream,
};
use std::ffi::{c_char, CStr, CString};
use std::fmt::Display;

pub use cairo_lang_macro_attributes::*;
use cairo_lang_macro_stable::{StableAuxData, StableProcMacroResult, StableTokenStream};
use std::slice;

#[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 @@ -53,22 +57,76 @@ impl Display for 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 Diagnostic {
pub fn error(message: impl ToString) -> Self {
Self {
message: message.to_string(),
severity: Severity::Error,
}
}

pub fn warn(message: impl ToString) -> Self {
Self {
message: message.to_string(),
severity: Severity::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 diagnostics = diagnostics
.into_iter()
.map(|d| d.into_stable())
.collect::<Vec<_>>();
StableProcMacroResult::Leave {
diagnostics: StableSlice::new(diagnostics),
}
}
ProcMacroResult::Remove { diagnostics } => {
let diagnostics = diagnostics
.into_iter()
.map(|d| d.into_stable())
.collect::<Vec<_>>();
StableProcMacroResult::Remove {
diagnostics: StableSlice::new(diagnostics),
}
}
ProcMacroResult::Replace {
token_stream,
aux_data,
} => StableProcMacroResult::Replace {
token_stream: token_stream.into_stable(),
aux_data: AuxData::maybe_into_stable(aux_data),
},
diagnostics,
} => {
let diagnostics = diagnostics
.into_iter()
.map(|d| d.into_stable())
.collect::<Vec<_>>();
StableProcMacroResult::Replace {
token_stream: token_stream.into_stable(),
aux_data: AuxData::maybe_into_stable(aux_data),
diagnostics: StableSlice::new(diagnostics),
}
}
}
}

Expand All @@ -80,15 +138,38 @@ 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 } => {
let (ptr, n) = diagnostics.into_raw_parts();
let diagnostics = slice::from_raw_parts(ptr, n)
.iter()
.map(|d| Diagnostic::from_stable(d))
.collect::<Vec<_>>();
ProcMacroResult::Leave { diagnostics }
}
StableProcMacroResult::Remove { diagnostics } => {
let (ptr, n) = diagnostics.into_raw_parts();
let diagnostics = slice::from_raw_parts(ptr, n)
.iter()
.map(|d| Diagnostic::from_stable(d))
.collect::<Vec<_>>();
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),
},
diagnostics,
} => {
let (ptr, n) = diagnostics.into_raw_parts();
let diagnostics = slice::from_raw_parts(ptr, n)
.iter()
.map(|d| Diagnostic::from_stable(d))
.collect::<Vec<_>>();
ProcMacroResult::Replace {
token_stream: TokenStream::from_stable(token_stream),
aux_data: AuxData::from_stable(aux_data),
diagnostics,
}
}
}
}

Expand All @@ -101,15 +182,38 @@ impl ProcMacroResult {
#[doc(hidden)]
pub unsafe fn from_owned_stable(result: StableProcMacroResult) -> Self {
match result {
StableProcMacroResult::Leave => ProcMacroResult::Leave,
StableProcMacroResult::Remove => ProcMacroResult::Remove,
StableProcMacroResult::Leave { diagnostics } => {
let diagnostics = diagnostics.into_owned();
let diagnostics = diagnostics
.into_iter()
.map(|d| Diagnostic::from_owned_stable(d))
.collect::<Vec<_>>();
ProcMacroResult::Leave { diagnostics }
}
StableProcMacroResult::Remove { diagnostics } => {
let diagnostics = diagnostics.into_owned();
let diagnostics = diagnostics
.into_iter()
.map(|d| Diagnostic::from_owned_stable(d))
.collect::<Vec<_>>();
ProcMacroResult::Remove { diagnostics }
}
StableProcMacroResult::Replace {
token_stream,
aux_data,
} => ProcMacroResult::Replace {
token_stream: TokenStream::from_owned_stable(token_stream),
aux_data: AuxData::from_owned_stable(aux_data),
},
diagnostics,
} => {
let diagnostics = diagnostics.into_owned();
let diagnostics = diagnostics
.into_iter()
.map(|d| Diagnostic::from_owned_stable(d))
.collect::<Vec<_>>();
ProcMacroResult::Replace {
token_stream: TokenStream::from_owned_stable(token_stream),
aux_data: AuxData::from_owned_stable(aux_data),
diagnostics,
}
}
}
}
}
Expand Down Expand Up @@ -195,6 +299,70 @@ impl AuxData {
}
}

impl Diagnostic {
// Convert to FFI-safe representation.
///
/// # Safety
#[doc(hidden)]
pub fn into_stable(self) -> StableDiagnostic {
StableDiagnostic {
message: CString::new(self.message).unwrap().into_raw(),
severity: self.severity.into_stable(),
}
}

/// Convert to native Rust representation, without taking the ownership of the string.
///
/// Note that you still need to free the memory by calling `from_owned_stable`.
///
/// # Safety
#[doc(hidden)]
pub unsafe fn from_stable(diagnostic: &StableDiagnostic) -> Self {
Self {
message: from_raw_cstr(diagnostic.message),
severity: Severity::from_stable(&diagnostic.severity),
}
}

/// Convert to native Rust representation, with taking the ownership of the string.
///
/// Useful when you need to free the allocated memory.
/// Only use on the same side of FFI-barrier, where the memory has been allocated.
///
/// # Safety
#[doc(hidden)]
pub unsafe fn from_owned_stable(diagnostic: StableDiagnostic) -> Self {
Self {
message: from_raw_cstring(diagnostic.message),
severity: Severity::from_stable(&diagnostic.severity),
}
}
}

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,
}
}
}

// Create a string from a raw pointer to a c_char.
// Note that this will free the underlying memory.
unsafe fn from_raw_cstring(raw: *mut c_char) -> String {
Expand Down
Loading

0 comments on commit 9f17249

Please sign in to comment.