use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error;
use quote::{format_ident, quote, quote_spanned, TokenStreamExt};
use syn::{parse2, spanned::Spanned, Ident, ItemFn, PathArguments};
use crate::{
call::Call,
helpers::{add_span_to_signature, combine_cfg},
precondition::{CfgPrecondition, Precondition, ReadWrite},
};
pub(crate) fn render_as_ident(precondition: &CfgPrecondition) -> Ident {
fn escape_non_ident_chars(string: String) -> String {
string
.chars()
.map(|c| match c {
'0'..='9' | 'a'..='z' | 'A'..='Z' => c.to_string(),
'_' => "__".to_string(), other => format!("_{:x}", other as u32),
})
.collect()
}
let mut ident = match precondition.precondition() {
Precondition::ValidPtr {
ident, read_write, ..
} => format_ident!(
"_valid_ptr_{}_{}",
ident,
match read_write {
ReadWrite::Read { .. } => "r",
ReadWrite::Write { .. } => "w",
ReadWrite::Both { .. } => "rw",
}
),
Precondition::ProperAlign { ident, .. } => format_ident!("_proper_align_{}", ident),
Precondition::Boolean(expr) => format_ident!(
"_boolean_{}",
escape_non_ident_chars(quote! { #expr }.to_string())
),
Precondition::Custom(string) => {
format_ident!("_custom_{}", escape_non_ident_chars(string.value()))
}
};
ident.set_span(precondition.span());
ident
}
pub(crate) fn render_pre(
preconditions: Vec<CfgPrecondition>,
function: &mut ItemFn,
span: Span,
) -> TokenStream {
let combined_cfg = combine_cfg(&preconditions, span);
if function.sig.receiver().is_some() {
emit_error!(
span,
"preconditions are not supported for methods on the stable compiler"
);
return quote! { #function };
}
let vis = &function.vis;
let mut preconditions_rendered = TokenStream::new();
preconditions_rendered.append_all(
preconditions
.iter()
.map(render_as_ident)
.map(|ident| quote_spanned! { span=> #vis #ident: (), }),
);
let function_name = function.sig.ident.clone();
let struct_def = quote_spanned! { span=>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[cfg(all(not(doc), #combined_cfg))]
#vis struct #function_name {
#preconditions_rendered
}
};
add_span_to_signature(span, &mut function.sig);
function.sig.inputs.push(
parse2(quote_spanned! { span=>
#[cfg(all(not(doc), #combined_cfg))]
_: #function_name
})
.expect("parses as valid function argument"),
);
quote! {
#struct_def
#function
}
}
pub(crate) fn render_assure(
preconditions: Vec<CfgPrecondition>,
mut call: Call,
span: Span,
) -> Call {
let combined_cfg = combine_cfg(&preconditions, span);
if !call.is_function() {
emit_error!(
call,
"method calls are not supported by `pre` on the stable compiler"
);
return call;
}
let mut path;
if let Some(p) = call.path() {
path = p;
} else {
match &call {
Call::Function(call) => emit_error!(
call.func,
"unable to determine at compile time which function is being called";
help = "use a direct path to the function instead"
),
_ => unreachable!("we already checked that it's a function"),
}
return call;
}
if let Some(last_path_segment) = path.path.segments.last_mut() {
last_path_segment.arguments = PathArguments::None;
last_path_segment.ident.set_span(span);
}
let mut preconditions_rendered = TokenStream::new();
preconditions_rendered.append_all(
preconditions
.iter()
.map(render_as_ident)
.map(|ident| quote_spanned! { span=> #ident: (), }),
);
call.args_mut().push(
parse2(quote_spanned! { span=>
#[cfg(all(not(doc), #combined_cfg))]
#path {
#preconditions_rendered
}
})
.expect("parses as an expression"),
);
call
}