blob: ded1b75a4270b7b03a87657d2d4d83f5f4de24cb [file] [log] [blame]
//! The proc macro at the core of munge.
#![deny(
missing_docs,
unsafe_op_in_unsafe_fn,
clippy::missing_safety_doc,
clippy::undocumented_unsafe_blocks,
rustdoc::broken_intra_doc_links,
rustdoc::missing_crate_level_docs
)]
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
parse, parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
token::{Eq, FatArrow, Let, Semi},
Error, Expr, FieldPat, Index, Pat, PatIdent, PatRest, PatSlice, PatStruct,
PatTuple, PatTupleStruct, Path,
};
/// Destructures a value by projecting pointers.
#[proc_macro]
pub fn munge_with_path(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as Input);
destructure(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
struct Input {
crate_path: Path,
_arrow: FatArrow,
destructures: Punctuated<Destructure, Semi>,
}
impl parse::Parse for Input {
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
Ok(Input {
crate_path: input.parse::<Path>()?,
_arrow: input.parse::<FatArrow>()?,
destructures: input.parse_terminated(Destructure::parse, Semi)?,
})
}
}
struct Destructure {
_let_token: Let,
pat: Pat,
_eq_token: Eq,
expr: Expr,
}
impl parse::Parse for Destructure {
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
Ok(Destructure {
_let_token: input.parse::<Let>()?,
pat: Pat::parse_single(input)?,
_eq_token: input.parse::<Eq>()?,
expr: input.parse::<Expr>()?,
})
}
}
fn rest_check(crate_path: &Path, rest: &PatRest) -> (TokenStream, TokenStream) {
let span = rest.dot2_token.span();
let destructurer = quote! { destructurer };
let expr = quote_spanned! { span => {
let phantom = #crate_path::__macro::get_destructure(&#destructurer);
#crate_path::__macro::only_borrow_destructuring_may_use_rest_patterns(
phantom
)
} };
(quote_spanned! { span => _ }, expr)
}
fn parse_pat(
crate_path: &Path,
pat: &Pat,
) -> Result<(TokenStream, TokenStream), Error> {
Ok(match pat {
Pat::Ident(pat_ident) => {
let mutability = &pat_ident.mutability;
let ident = &pat_ident.ident;
if let Some(r#ref) = &pat_ident.by_ref {
return Err(Error::new_spanned(
r#ref,
"`ref` is not allowed in munge destructures",
));
}
if let Some((at, _)) = &pat_ident.subpat {
return Err(Error::new_spanned(
at,
"subpatterns are not allowed in munge destructures",
));
}
(
quote! { #mutability #ident },
quote! {
// SAFETY: `ptr` is a properly-aligned pointer to a subfield
// of the pointer underlying `destructurer`.
unsafe {
#crate_path::__macro::restructure_destructurer(
&destructurer,
ptr,
)
}
},
)
}
Pat::Tuple(PatTuple { elems, .. })
| Pat::TupleStruct(PatTupleStruct { elems, .. }) => {
let parsed = elems
.iter()
.map(|e| parse_pat(crate_path, e))
.collect::<Result<Vec<_>, Error>>()?;
let (bindings, (exprs, indices)) = parsed
.iter()
.enumerate()
.map(|(i, x)| (&x.0, (&x.1, Index::from(i))))
.unzip::<_, _, Vec<_>, (Vec<_>, Vec<_>)>();
(
quote! { (#(#bindings,)*) },
quote! { (
#({
// SAFETY: `ptr` is guaranteed to always be non-null,
// properly-aligned, and valid for reads.
let ptr = unsafe {
::core::ptr::addr_of_mut!((*ptr).#indices)
};
#exprs
},)*
) },
)
}
Pat::Slice(pat_slice) => {
let parsed = pat_slice
.elems
.iter()
.map(|e| parse_pat(crate_path, e))
.collect::<Result<Vec<_>, Error>>()?;
let (bindings, (exprs, indices)) = parsed
.iter()
.enumerate()
.map(|(i, x)| (&x.0, (&x.1, Index::from(i))))
.unzip::<_, _, Vec<_>, (Vec<_>, Vec<_>)>();
(
quote! { (#(#bindings,)*) },
quote! { (
#({
// SAFETY: `ptr` is guaranteed to always be non-null,
// properly-aligned, and valid for reads.
let ptr = unsafe {
::core::ptr::addr_of_mut!((*ptr)[#indices])
};
#exprs
},)*
) },
)
}
Pat::Struct(pat_struct) => {
let parsed = pat_struct
.fields
.iter()
.map(|fp| {
parse_pat(crate_path, &fp.pat).map(|ie| (&fp.member, ie))
})
.collect::<Result<Vec<_>, Error>>()?;
let (members, (bindings, exprs)) =
parsed.into_iter().unzip::<_, _, Vec<_>, (Vec<_>, Vec<_>)>();
let (rest_binding, rest_expr) = if let Some(rest) = &pat_struct.rest
{
let (binding, expr) = rest_check(crate_path, rest);
(Some(binding), Some(expr))
} else {
(None, None)
};
(
quote! { (
#(#bindings,)*
#rest_binding
) },
quote! { (
#({
// SAFETY: `ptr` is guaranteed to always be non-null,
// properly-aligned, and valid for reads.
let ptr = unsafe {
::core::ptr::addr_of_mut!((*ptr).#members)
};
#exprs
},)*
#rest_expr
) },
)
}
Pat::Rest(pat_rest) => rest_check(crate_path, pat_rest),
Pat::Wild(pat_wild) => {
let token = &pat_wild.underscore_token;
(
quote! { #token },
quote! {
// SAFETY: `ptr` is a properly-aligned pointer to a subfield
// of the pointer underlying `destructurer`.
unsafe {
#crate_path::__macro::restructure_destructurer(
&destructurer,
ptr,
)
}
},
)
}
_ => {
return Err(Error::new_spanned(
pat,
"expected a destructuring pattern",
));
}
})
}
fn strip_mut(pat: &Pat) -> Result<Pat, Error> {
Ok(match pat {
Pat::Ident(pat_ident) => Pat::Ident(PatIdent {
attrs: pat_ident.attrs.clone(),
by_ref: None,
mutability: None,
ident: pat_ident.ident.clone(),
subpat: if let Some((at, pat)) = pat_ident.subpat.as_ref() {
Some((*at, Box::new(strip_mut(pat)?)))
} else {
None
},
}),
Pat::Tuple(pat_tuple) => {
let mut elems = Punctuated::new();
for elem in pat_tuple.elems.iter() {
elems.push(strip_mut(elem)?);
}
Pat::Tuple(PatTuple {
attrs: pat_tuple.attrs.clone(),
paren_token: pat_tuple.paren_token,
elems,
})
}
Pat::TupleStruct(pat_tuple_struct) => {
let mut elems = Punctuated::new();
for elem in pat_tuple_struct.elems.iter() {
elems.push(strip_mut(elem)?);
}
Pat::TupleStruct(PatTupleStruct {
attrs: pat_tuple_struct.attrs.clone(),
qself: pat_tuple_struct.qself.clone(),
path: pat_tuple_struct.path.clone(),
paren_token: pat_tuple_struct.paren_token,
elems,
})
}
Pat::Slice(pat_slice) => {
let mut elems = Punctuated::new();
for elem in pat_slice.elems.iter() {
elems.push(strip_mut(elem)?);
}
Pat::Slice(PatSlice {
attrs: pat_slice.attrs.clone(),
bracket_token: pat_slice.bracket_token,
elems,
})
}
Pat::Struct(pat_struct) => {
let mut fields = Punctuated::new();
for field in pat_struct.fields.iter() {
fields.push(FieldPat {
attrs: field.attrs.clone(),
member: field.member.clone(),
colon_token: field.colon_token,
pat: Box::new(strip_mut(&field.pat)?),
});
}
Pat::Struct(PatStruct {
attrs: pat_struct.attrs.clone(),
qself: pat_struct.qself.clone(),
path: pat_struct.path.clone(),
brace_token: pat_struct.brace_token,
fields,
rest: pat_struct.rest.clone(),
})
}
Pat::Rest(pat_rest) => Pat::Rest(pat_rest.clone()),
Pat::Wild(pat_wild) => Pat::Wild(pat_wild.clone()),
_ => todo!(),
})
}
fn destructure(input: Input) -> Result<TokenStream, Error> {
let crate_path = &input.crate_path;
let mut result = TokenStream::new();
for destructure in input.destructures.iter() {
let pat = &destructure.pat;
let expr = &destructure.expr;
let test_pat = strip_mut(pat)?;
let (bindings, exprs) = parse_pat(crate_path, pat)?;
result.extend(quote! {
let mut destructurer = #crate_path::__macro::make_destructurer(
#expr
);
let #bindings = {
#[allow(
unused_mut,
unused_unsafe,
clippy::undocumented_unsafe_blocks,
)]
{
let ptr = #crate_path::__macro::destructurer_ptr(
&mut destructurer
);
#[allow(unreachable_code, unused_variables)]
if false {
// SAFETY: This can never be called.
unsafe {
::core::hint::unreachable_unchecked();
let #test_pat =
#crate_path::__macro::test_destructurer(
&mut destructurer,
);
}
}
#exprs
}
};
});
}
Ok(result)
}