#![cfg_attr(feature = "strict", deny(warnings))]
#![recursion_limit="128"]
extern crate proc_macro;
extern crate proc_macro2;
extern crate syn;
#[macro_use] extern crate quote;
extern crate itertools;
#[macro_use] extern crate bitflags;
use proc_macro2::{TokenStream, Span};
use quote::ToTokens;
use syn::spanned::Spanned;
mod ast;
mod visibility;
#[proc_macro_derive(Shrinkwrap, attributes(shrinkwrap))]
pub fn shrinkwrap(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
use ast::{ShrinkwrapFlags, validate_derive_input};
use visibility::field_visibility;
use visibility::FieldVisibility::*;
let input: syn::DeriveInput = syn::parse(tokens)
.unwrap();
let validate_result = validate_derive_input(input);
let mut tokens = TokenStream::new();
match validate_result {
Err(error) => error.to_tokens(&mut tokens),
Ok((details, input)) => {
impl_immut_borrows(&details, &input)
.to_tokens(&mut tokens);
if details.flags.contains(ShrinkwrapFlags::SW_TRANSFORMERS) {
impl_transformers(&details, &input)
.to_tokens(&mut tokens);
}
if details.flags.contains(ShrinkwrapFlags::SW_MUT) {
if !details.flags.contains(ast::ShrinkwrapFlags::SW_IGNORE_VIS) {
match field_visibility(&details.visibility, &input.inner_field.vis) {
Restricted => {
let outer_vis = show_visibility(&details.visibility);
let inner_vis = show_visibility(&input.inner_field.vis);
let msg = format!(
"Encountered an error while mutably Shrinkwrapping this field.
This field is less visible than its containing struct; the containing
struct
`{}'
has visibility
`{}'
while this field has visibility
`{}'
Implementing mutable shrinkwraps could allow outside code to modify the
inner value, even when visibility modifiers say that they can't.
Some ways to solve this problem:
1) Change the visibility of the inner field to be the same as its
containing struct: `{}'
2) Turn off mutable shrinkwraps if you don't need them
3) Override this check and implement mutable shrinkwraps anyways by using
#[shrinkwrap(unsafe_ignore_visibility)] on your struct",
&details.ident,
outer_vis,
inner_vis,
outer_vis
);
quote_spanned!(input.inner_field.span()=> compile_error!(#msg);)
.to_tokens(&mut tokens);
},
CantDetermine => {
let outer_vis = show_visibility(&details.visibility);
let inner_vis = show_visibility(&input.inner_field.vis);
let msg = format!(
"Encountered an error while mutably Shrinkwrapping this field.
I can't determine whether the inner field is at least as visible as its
containing struct; the containing struct
`{}'
has visibility
`{}'
while this field has visibility
`{}'
If the inner field is less visible than its containing struct, implementing
mutable shrinkwraps could allow outside code to modify the inner value,
even when visibility modifiers say that they can't.
Some ways to solve this problem:
1) Change the visibility of the inner field to be the same as its
containing struct: `{}'
2) Turn off mutable shrinkwraps if you don't need them
3) Override this check and implement mutable shrinkwraps anyways by using
#[shrinkwrap(unsafe_ignore_visibility)] on your struct",
&details.ident,
outer_vis,
inner_vis,
outer_vis
);
quote_spanned!(input.inner_field.span()=> compile_error!(#msg);)
.to_tokens(&mut tokens);
},
_ => ()
}
}
impl_mut_borrows(&details, &input)
.to_tokens(&mut tokens);
}
}
}
tokens.into()
}
fn impl_immut_borrows(details: &ast::StructDetails, input: &ast::Struct) -> TokenStream {
let &ast::StructDetails { ref ident, ref generics, .. } = details;
let &ast::Struct { ref inner_field, ref inner_field_name, .. } = input;
let inner_type = &inner_field.ty;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let rust = syn::Ident::new(RUST, Span::call_site());
quote! {
impl #impl_generics ::#rust::ops::Deref for #ident #ty_generics #where_clause {
type Target = #inner_type;
fn deref(&self) -> &Self::Target {
&self.#inner_field_name
}
}
impl #impl_generics ::#rust::borrow::Borrow<#inner_type> for #ident #ty_generics #where_clause {
fn borrow(&self) -> &#inner_type {
&self.#inner_field_name
}
}
impl #impl_generics ::#rust::convert::AsRef<#inner_type> for #ident #ty_generics #where_clause {
fn as_ref(&self) -> &#inner_type {
&self.#inner_field_name
}
}
}
}
fn impl_mut_borrows(details: &ast::StructDetails, input: &ast::Struct) -> TokenStream {
let &ast::StructDetails { ref ident, ref generics, .. } = details;
let &ast::Struct { ref inner_field, ref inner_field_name, .. } = input;
let inner_type = &inner_field.ty;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let rust = syn::Ident::new(RUST, Span::call_site());
quote! {
impl #impl_generics ::#rust::ops::DerefMut for #ident #ty_generics #where_clause {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.#inner_field_name
}
}
impl #impl_generics ::#rust::borrow::BorrowMut<#inner_type> for #ident #ty_generics #where_clause {
fn borrow_mut(&mut self) -> &mut #inner_type {
&mut self.#inner_field_name
}
}
impl #impl_generics ::#rust::convert::AsMut<#inner_type> for #ident #ty_generics #where_clause {
fn as_mut(&mut self) -> &mut #inner_type {
&mut self.#inner_field_name
}
}
}
}
fn impl_transformers(details: &ast::StructDetails, input: &ast::Struct) -> TokenStream {
let &ast::StructDetails { ref ident, ref generics, .. } = details;
let &ast::Struct { ref inner_field, ref inner_field_name, .. } = input;
let inner_type = &inner_field.ty;
let inner_visibility = &inner_field.vis;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let f = quote!( __SHRINKWRAP_F );
let t = quote!( __SHRINKWRAP_T );
quote! {
#[allow(dead_code, non_camel_case_types)]
impl #impl_generics #ident #ty_generics #where_clause {
pub fn siphon<#t, #f: FnMut(#inner_type) -> #t>(self, mut f: #f) -> #t {
f(self.#inner_field_name)
}
#inner_visibility fn transform<#f>(&mut self, mut f: #f) -> &mut Self
where #f: FnMut(&mut #inner_type)
{
f(&mut self.#inner_field_name);
self
}
}
}
}
fn show_visibility(vis: &syn::Visibility) -> String {
match vis {
syn::Visibility::Inherited => "<private>".into(),
_ => {
let mut tokens = TokenStream::new();
vis.to_tokens(&mut tokens);
format!("{}", tokens)
}
}
}
#[cfg(feature = "std")]
const RUST: &str = "std";
#[cfg(not(feature = "std"))]
const RUST: &str = "core";