shrinkwraprs-0.3.0/.gitignore010066400017500001750000000001061323561217100144600ustar0000000000000000# Temp files *~ \#*\# # Build outputs /target/ **/*.rs.bk Cargo.lock shrinkwraprs-0.3.0/.gitlab-ci.yml010066400017500001750000000006451356442335700151470ustar0000000000000000image: rust:1.31 stages: - build - test build-lib: stage: build script: - cargo build --lib --features strict build-examples: stage: build script: - cargo build --examples test: stage: test script: # with default feature std - cargo test # without default feature std # don’t try to build examples (rust-lang/cargo#5387) - cargo test --no-default-features --lib --tests shrinkwraprs-0.3.0/CHANGELOG.md010066400017500001750000000041661357431735700143310ustar0000000000000000# Changelog ## [Unreleased] ## [v0.3.0] -- 2019-12-03 * Removed the `map_ref` and `map_mut` functions because they were essentially useless. * Renamed `map` to `siphon` to better reflect how it consumes the value. * Added a new function `transform` in place of `map_mut`, which is actually useful because it allows chained updates. * Added a new attribute `#[shrinkwrap(transformers)]`; `siphon` and `transform` are not derived by default, and must be enabled using this flag. ## [v0.2.3] -- 2019-11-18 * Improved error messages on visibility errors * Attached error messages to correct source code spans instead of the derive attribute * Fixed visibility checks around `pub(self)` and `pub(in self)` to be equivalent to private visibility ## [v0.2.2] -- 2019-11-17 * Updated dependencies to latest major versions ## [v0.2.1] -- 2019-01-24 * Added the ability to generate `#[nostd]`-compatible code through feature flags -- thanks @dazabani! * The default is still to emit code that uses `std`. * Use the feature flag `std` to emit `std`-compatible code, or omit it to emit code that uses `core`. ## [v0.2.0] -- 2018-02-10 * Added visibility checking on mutable derives to help prevent deriving mutable traits when inner field is less mutable than surrounding struct * Added `#[shrinkwrap(unsafe_ignore_visibility)]` flag to override this behavior when desired. * Removed `#[derive(ShrinkwrapMut)]` proc macro; replaced with `#[shrinkwrap(mutable)]` attribute. ## [v0.1.1] -- 2018-02-07 * Added a changelog * Implemented mapping methods `map()`, `map_ref()`, `map_mut()` for mapping functions over wrapped values (useful for function call chaining) * Added support for structs with lifetimes and generic parameters ## [v0.1.0] -- 2018-02-06 * Split out derivation of mutable traits (`DerefMut`, `BorrowMut`, `AsMut`) into separate derive trait `ShrinkwrapMut` ## [v0.0.2] -- 2018-02-04 * Fixed typoes in documentation -- no functionality changes ## [v0.0.1] -- 2018-02-04 * Initial release -- implemented `#[derive(Shrinkwrap)]` to auto-derive `Deref`, `DerefMut`, `Borrow`, `BorrowMut`, `AsRef`, and `AsMut`. shrinkwraprs-0.3.0/Cargo.toml.orig010066400017500001750000000015761357431735700154110ustar0000000000000000[package] name = "shrinkwraprs" version = "0.3.0" authors = [ "William Yao " ] description = "Auto-derive for Rust conversion traits -- make working with newtypes a breeze" documentation = "https://docs.rs/shrinkwraprs" repository = "https://gitlab.com/williamyaoh/shrinkwraprs" readme = "README.md" license = "BSD-3-Clause" keywords = [ "conversion", "convert", "newtype" ] categories = [ "rust-patterns", "development-tools", "no-std" ] [dependencies] syn = { version = "1.0.8", features = [ "default", "extra-traits" ] } quote = "1.0.2" itertools = "0.8.1" bitflags = "1.0.1" proc-macro2 = "1.0.6" [lib] proc-macro = true [features] default = ["std"] # Forces crate to compile cleanly. Mostly used for CI; build must pass with this # feature to be merged in. strict = [] # Makes the crate emit code that refers to paths in `std` rather than `core`. std = [] shrinkwraprs-0.3.0/Cargo.toml0000644000000024321357431742600117020ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "shrinkwraprs" version = "0.3.0" authors = ["William Yao "] description = "Auto-derive for Rust conversion traits -- make working with newtypes a breeze" documentation = "https://docs.rs/shrinkwraprs" readme = "README.md" keywords = ["conversion", "convert", "newtype"] categories = ["rust-patterns", "development-tools", "no-std"] license = "BSD-3-Clause" repository = "https://gitlab.com/williamyaoh/shrinkwraprs" [lib] proc-macro = true [dependencies.bitflags] version = "1.0.1" [dependencies.itertools] version = "0.8.1" [dependencies.proc-macro2] version = "1.0.6" [dependencies.quote] version = "1.0.2" [dependencies.syn] version = "1.0.8" features = ["default", "extra-traits"] [features] default = ["std"] std = [] strict = [] shrinkwraprs-0.3.0/LICENSE010066400017500001750000000027071356467341600135240ustar0000000000000000Copyright (c) 2018-2019, William Yao All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. shrinkwraprs-0.3.0/README.md010066400017500001750000000064421357431735700137760ustar0000000000000000# shrinkwraprs [![pipeline status](https://gitlab.com/williamyaoh/shrinkwraprs/badges/master/pipeline.svg)](https://gitlab.com/williamyaoh/shrinkwraprs/commits/master) [![latest version](https://img.shields.io/crates/v/shrinkwraprs.svg)](https://crates.io/crates/shrinkwraprs) [![api documentation](https://docs.rs/shrinkwraprs/badge.svg)](https://docs.rs/shrinkwraprs) [![license](https://img.shields.io/badge/license-BSD--3-ff69b4.svg)](https://gitlab.com/williamyaoh/shrinkwraprs/blob/master/LICENSE) Making wrapper types allows us to give more compile-time guarantees about our code being correct: ```rust // Now we can't mix up widths and heights; the compiler will yell at us! struct Width(u64); struct Height(u64); ``` But... they're kind of a pain to work with. If you ever need to get at that wrapped `u64`, you need to constantly pattern-match back and forth to wrap and unwrap the values. `shrinkwraprs` aims to alleviate this pain by allowing you to derive implementations of various conversion traits by deriving `Shrinkwrap`. ## Functionality implemented Currently, using `#[derive(Shrinkwrap)]` will derive the following traits for all structs: * `AsRef` * `Borrow` * `Deref` Additionally, using `#[shrinkwrap(mutable)]` will also derive the following traits: * `AsMut` * `BorrowMut` * `DerefMut` Finally, one more option is `#[shrinkwrap(transformers)]`, which will derive some useful inherent functions for transforming the wrapped data: * `fn transform(&mut self, mut f: F) -> &mut Self where F: FnMut(&mut InnerType)` * `fn siphon(self, mut f: F) -> T where F: FnMut(InnerType) -> T` ...where `transform` makes it easy to chain updates on the inner value, and `siphon` allows you to easily move out the inner value to produce a value of a different type. `transform` will have the same visibility as the inner field, which ensures that `transform` doesn't leak the possibility of changing the inner value (potentially in invariant-violating ways). `siphon` has the same visibility as the struct itself, since it *doesn't* provide a direct way for callers to break your data. ## Cool, how do I use it? First, add `shrinkwraprs` as a dependency in your `Cargo.toml`: ```toml [dependencies] shrinkwraprs = "0.3.0" ``` Then, just slap a `#[derive(Shrinkwrap)]` on any structs you want convenient-ified: ```rust #[macro_use] extern crate shrinkwraprs; #[derive(Shrinkwrap)] struct Email(String); fn main() { let email = Email("chiya+snacks@natsumeya.jp".into()); let is_discriminated_email = email.contains("+"); // Woohoo, we can use the email like a string! /* ... */ } ``` If you have multiple fields, but there's only one field you want to be able to deref/borrow as, mark it with `#[shrinkwrap(main_field)]`: ```rust #[derive(Shrinkwrap)] struct Email { spamminess: f64, #[shrinkwrap(main_field)] addr: String } #[derive(Shrinkwrap)] struct CodeSpan(u32, u32, #[shrinkwrap(main_field)] Token); ``` If you also want to be able to modify the wrapped value directly, add the attribute `#[shrinkwrap(mutable)]` as well: ```rust #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct InputBuffer { buffer: String } ... let mut input_buffer = /* ... */; input_buffer.push_str("some values"); ... ``` shrinkwraprs-0.3.0/examples/generics.rs010066400017500001750000000005561356443767600165100ustar0000000000000000#![cfg(feature = "std")] #[macro_use] extern crate shrinkwraprs; #[derive(Shrinkwrap)] pub struct Email<'a>(&'a str); #[derive(Shrinkwrap)] pub struct GenericType(T); fn main() { let email = Email("satsuki.kiryuuin@honnouji.edu"); println!("email len: {}", email.len()); let generic = GenericType(20_i32); println!("inside value: {}", *generic); } shrinkwraprs-0.3.0/examples/structs.rs010066400017500001750000000015131356443641200163750ustar0000000000000000//! Showing how to use shrinkwrap for different types of structs. #![cfg(feature = "std")] #![allow(dead_code)] #[macro_use] extern crate shrinkwraprs; #[derive(Shrinkwrap)] struct Foo(i32); #[derive(Shrinkwrap)] struct Bar(i32, #[shrinkwrap(main_field)] String); #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct Baz { field1: String } #[derive(Shrinkwrap)] struct Quux { field1: u32, #[shrinkwrap(main_field)] field2: String } #[derive(Shrinkwrap)] #[shrinkwrap(mutable, unsafe_ignore_visibility)] pub struct MyStruct { field1: u32 } fn is_commercial(b: &Baz) -> bool { (**b).contains(".co") } fn main() { let mut email = Baz { field1: "chiya+snacks@natsumeya.jp".into() }; println!("is_commercial: {}", is_commercial(&email)); (*email).push_str(".com"); println!("is_commercial: {}", is_commercial(&email)); } shrinkwraprs-0.3.0/examples/transformers.rs010066400017500001750000000010711357431726500174170ustar0000000000000000//! Example usages of the mapping functions. #![cfg(feature = "std")] #![allow(unused_variables)] #[macro_use] extern crate shrinkwraprs; #[derive(Shrinkwrap)] #[shrinkwrap(transformers)] pub struct Email(String); #[derive(Debug)] pub struct Sanitized(String); fn main() { let mut email = Email("aoi.miyamori@musashino".into()); email .transform(|s| s.push_str(".co")) .transform(|s| s.push_str(".jp")); println!("{}", *email); // email has been moved out of after siphoning let output = email.siphon(Sanitized); println!("{:?}", output); } shrinkwraprs-0.3.0/src/ast.rs010066400017500001750000000166371357431726500144500ustar0000000000000000//! We want to make sure that the struct that our caller passes us is in the //! right form. However, we don't want to clutter up our code generation //! logic with lots of error handling. So instead, we take in our `DeriveInput` //! and do all the error handling in one place, transforming it into an AST //! specific to our crate if it's valid. use syn; use syn::spanned::Spanned; use proc_macro2::{Span, TokenStream}; use itertools::Itertools; type Fields = Vec; bitflags! { /// Controls which code and implementations we generate. pub struct ShrinkwrapFlags: u32 { const SW_MUT = 0b00000001; const SW_IGNORE_VIS = 0b00000010; const SW_TRANSFORMERS = 0b00000100; } } pub struct StructDetails { pub flags: ShrinkwrapFlags, pub ident: syn::Ident, pub generics: syn::Generics, pub visibility: syn::Visibility } /// Represents either a tuple or bracketed struct with at least one field. pub struct Struct { pub inner_field: syn::Field, pub inner_field_name: proc_macro2::TokenStream } /// Check if the input stream matches our required data structures. /// The TokenStream on error contains a compile error pointing to the right place. pub fn validate_derive_input(input: syn::DeriveInput) -> Result<(StructDetails, Struct), TokenStream> { // We *don't* want to use `panic` and `unwrap` here, even though they're // safe, because we want our compile errors to be attached to the right // lines of code. use syn::{DeriveInput, DataStruct, FieldsUnnamed, FieldsNamed}; use syn::Data::{Struct, Enum, Union}; use syn::Fields::{Named, Unnamed}; let whole_span = input.span(); let DeriveInput { attrs, vis, ident, generics, data, .. } = input; let flags = shrinkwrap_flags(&attrs); let details = StructDetails { flags, ident, visibility: vis, generics }; let strct = match data { Struct(DataStruct { fields: Unnamed(FieldsUnnamed { unnamed: fields, .. }), .. }) => { let fields = fields.into_iter().collect_vec(); validate_tuple(whole_span, fields) }, Struct(DataStruct { fields: Named(FieldsNamed { named: fields, .. }), .. }) => { let fields = fields.into_iter().collect_vec(); validate_nontuple(whole_span, fields) }, Struct(..) => Err(compile_error_at(whole_span, "Shrinkwrap needs a struct with at least one field!")), Enum(..) => Err(compile_error_at(whole_span, "Shrinkwrap does not support enums!")), Union(..) => Err(compile_error_at(whole_span, "Shrinkwrap does not support C-style unions!")) }?; Ok((details, strct)) } /// Specifically for working with attributes like #[shrinkwrap(..)], where /// a name is combined with a list of attributes. Get the list of attributes /// matching the tag. fn tagged_attrs(tag: &str, attrs: &[syn::Attribute]) -> Vec { use syn::{Meta, MetaList}; let mut result = vec![]; for attr in attrs { let meta = attr.parse_meta(); if let Ok(Meta::List(MetaList { path, nested, .. })) = meta { if path.is_ident(tag) { result.extend(nested); } } } result } fn shrinkwrap_flags(attrs: &[syn::Attribute]) -> ShrinkwrapFlags { use syn::{Meta, NestedMeta}; let meta = tagged_attrs("shrinkwrap", attrs); let mut flags = ShrinkwrapFlags::empty(); for attr in meta { if let NestedMeta::Meta(Meta::Path(path)) = attr { if path.is_ident("mutable") { flags |= ShrinkwrapFlags::SW_MUT; } else if path.is_ident("unsafe_ignore_visibility") { flags |= ShrinkwrapFlags::SW_IGNORE_VIS; } else if path.is_ident("transformers") { flags |= ShrinkwrapFlags::SW_TRANSFORMERS; } } } flags } fn is_marked(field: &syn::Field) -> bool { use syn::{Meta, NestedMeta}; let meta = tagged_attrs("shrinkwrap", &field.attrs); meta.into_iter().any(|meta| { if let NestedMeta::Meta(Meta::Path(path)) = meta { path.is_ident("main_field") } else { false } }) } /// Only a single field, out of all a struct's fields, can be marked as /// the main field that we deref to. So let's find that field. /// We also return the 0-based number of the marked field. fn find_marked_field(whole_span: Span, fields: Fields) -> Result<((usize, syn::Field), Fields), TokenStream> { let (marked, unmarked) = fields.into_iter() .enumerate() .partition::, _>(|&(_, ref field)| is_marked(field)); let marked_len = marked.len(); let single: Option<(_,)> = marked.into_iter() .collect_tuple(); match (single, unmarked.len()) { (Some((field,)), _) => { let unmarked = unmarked.into_iter() .map(|(_, field)| field) .collect_vec(); Ok((field, unmarked)) } (None, 1) => { let single: (_,) = unmarked.into_iter() .collect_tuple() .unwrap(); Ok((single.0, vec![])) }, _ => if marked_len == 0 { Err(compile_error_at( whole_span, "Shrinkwrap doesn't know which field you want this struct to convert to. Did you forget to mark a field with #[shrinkwrap(main_field)]?" )) } else { Err(compile_error_at( whole_span, "Shrinkwrap sees too many marked fields in this struct. Did you accidentally mark more than one field with #[shrinkwrap(main_field)]?" )) } } } fn validate_tuple(whole_span: Span, fields: Fields) -> Result { if fields.len() == 0 { return Err(compile_error_at( whole_span, "Shrinkwrap requires tuple structs to have at least one field!" )); } let ((marked_index, marked_field), _) = find_marked_field(whole_span, fields)?; let index: syn::Index = marked_index.into(); Ok(Struct { inner_field: marked_field, inner_field_name: quote!( #index ) }) } fn validate_nontuple(whole_span: Span, fields: Fields) -> Result { if fields.len() == 0 { return Err(compile_error_at( whole_span, "Shrinkwrap requires structs to have at least one field!" )); } let ((_, marked_field), _) = find_marked_field(whole_span, fields)?; let ident = marked_field.ident .clone() .unwrap(); Ok(Struct { inner_field: marked_field, inner_field_name: quote!( #ident ) }) } fn compile_error_at(at: Span, msg: &str) -> TokenStream { quote_spanned!(at=> compile_error!(#msg);) } #[cfg(test)] mod tests { use syn; use itertools::Itertools; use super::*; #[test] fn test_field_attribute_found() { let input = r" struct Foo { field1: u32, #[shrinkwrap(main_field)] field2: u32 } "; let strct: syn::DeriveInput = syn::parse_str(input) .unwrap(); match strct.data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { let marked = fields.into_iter() .filter(|field| is_marked(field)); let field: (syn::Field,) = marked .collect_tuple() .unwrap(); let ident = field.0.ident .unwrap(); assert_eq!(&ident, "field2"); }, _ => panic!() } } #[test] fn test_field_attribute_not_found() { let input = r" struct Foo { field1: u32, field2: u32 } "; let strct: syn::DeriveInput = syn::parse_str(input) .unwrap(); match strct.data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { let marked = fields.into_iter() .filter(|field| is_marked(field)) .collect_vec(); assert_eq!(marked.len(), 0); }, _ => panic!() } } } shrinkwraprs-0.3.0/src/lib.rs010066400017500001750000000257351357431726500144260ustar0000000000000000//! # shrinkwraprs //! //! Making wrapper types allows us to give more compile-time //! guarantees about our code being correct: //! //! ```ignore //! // Now we can't mix up widths and heights; the compiler will yell at us! //! struct Width(u64); //! struct Height(u64); //! ``` //! //! But... they're kind of a pain to work with. If you ever need to get at //! that wrapped `u64`, you need to constantly pattern-match back and forth //! to wrap and unwrap the values. //! //! `shrinkwraprs` aims to alleviate this pain by allowing you to derive //! implementations of various conversion traits by deriving //! `Shrinkwrap`. //! //! ## Functionality implemented //! //! Currently, using `#[derive(Shrinkwrap)]` will derive the following traits //! for all structs: //! //! * `AsRef` //! * `Borrow` //! * `Deref` //! //! Additionally, using `#[shrinkwrap(mutable)]` will also //! derive the following traits: //! //! * `AsMut` //! * `BorrowMut` //! * `DerefMut` //! //! Finally, one more option is `#[shrinkwrap(transformers)]`, which will derive //! some useful inherent functions for transforming the wrapped data: //! //! * `fn transform(&mut self, mut f: F) -> &mut Self where F: FnMut(&mut InnerType)` //! * `fn siphon(self, mut f: F) -> T where F: FnMut(InnerType) -> T` //! //! ...where `transform` makes it easy to chain updates on the inner value, and //! `siphon` allows you to easily move out the inner value to produce a value //! of a different type. //! //! `transform` will have the same visibility as the inner field, which ensures that //! `transform` doesn't leak the possibility of changing the inner value //! (potentially in invariant-violating ways). `siphon` has the same visibility as //! the struct itself, since it *doesn't* provide a direct way for callers to break //! your data. //! //! ## Cool, how do I use it? //! //! ```ignore //! #[macro_use] extern crate shrinkwraprs; //! //! #[derive(Shrinkwrap)] //! struct Email(String); //! //! fn main() { //! let email = Email("chiya+snacks@natsumeya.jp".into()); //! //! let is_discriminated_email = //! email.contains("+"); // Woohoo, we can use the email like a string! //! //! /* ... */ //! } //! ``` //! //! If you have multiple fields, but there's only one field you want to be able //! to deref/borrow as, mark it with `#[shrinkwrap(main_field)]`: //! //! ```ignore //! #[derive(Shrinkwrap)] //! struct Email { //! spamminess: f64, //! #[shrinkwrap(main_field)] addr: String //! } //! //! #[derive(Shrinkwrap)] //! struct CodeSpan(u32, u32, #[shrinkwrap(main_field)] Token); //! ``` //! //! If you also want to be able to modify the wrapped value directly, //! add the attribute `#[shrinkwrap(mutable)]` as well: //! //! ```ignore //! #[derive(Shrinkwrap)] //! #[shrinkwrap(mutable)] //! struct InputBuffer { //! buffer: String //! } //! //! ... //! let mut input_buffer = /* ... */; //! input_buffer.push_str("some values"); //! ... //! ``` // Additionally, perhaps subsume some functionality from // [`from_variants`](https://crates.io/crates/from_variants)? #![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) { // Make sure that the inner field isn't less visible than the outer struct. 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() } // When generating our code, we need to be careful not to leak things into the // surrounding code. For example, we don't use imports unless they're inside a // scope, because otherwise we'd be inserting invisible imports whenever a user // used #[derive(Shrinkwrap)]. 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(); // This is a *massive* hack to avoid variable capture, but I can't figure out // how to get `quote` to enforce hygiene or generate a gensym. 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 { /// Create a new value by calling a function over the wrapped value, /// consuming the outer value in the process. pub fn siphon<#t, #f: FnMut(#inner_type) -> #t>(self, mut f: #f) -> #t { f(self.#inner_field_name) } /// Update the outer value by calling a function on the wrapped value, which /// might update the wrapped value in place. Returns a mutable ref to the /// original outer value for easy chaining. #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 } } } } /// Turn the visibility AST into what you might see in source code. fn show_visibility(vis: &syn::Visibility) -> String { match vis { syn::Visibility::Inherited => "".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"; shrinkwraprs-0.3.0/src/visibility.rs010066400017500001750000000161651356467253700160510ustar0000000000000000//! We want to make sure that providing mutable traits doesn't accidentally //! leak the internal implementation details of a shrinkwrapped type. //! //! To do that, we need to make sure that the inner field has the same //! visibility as the shrinkwrapped struct itself. If it doesn't, we can //! give the user an error and refuse to generate implementations. use syn; use itertools::Itertools; // When checking for visibility containment, we can make use of the guarantee // that the langauge provides us that any visibility path must be a parent // module of the current one. This means, for instance, that we don't have // to worry about the possibility of the visibility paths "diverging". #[derive(PartialEq)] #[cfg_attr(test, derive(Debug))] pub enum PathComponent { /// Effectively, this means private. Inherited, Pub, Crate, InSelf, InSuper, Mod(String) } #[cfg_attr(test, derive(PartialEq, Debug))] pub enum FieldVisibility { /// The inner field is *at least* as visible as its containing struct. Visible, /// The inner field is less visible than its containing struct. Restricted, /// We can't figure out how the visibilities relate, probably due to the /// paths starting at different points (e.g. one is self and the other /// is ::a::b::c) CantDetermine } /// Check what the relation between the given struct's visibility and the /// field's visibility is. pub fn field_visibility(struct_vis: &syn::Visibility, field_vis: &syn::Visibility) -> FieldVisibility { let struct_vis = to_path(struct_vis); let field_vis = to_path(field_vis); fn check_head(struct_vis: &[PathComponent], field_vis: &[PathComponent]) -> FieldVisibility { match (struct_vis.split_first(), field_vis.split_first()) { (_, None) | (Some((&PathComponent::Inherited, _)), _) => FieldVisibility::Visible, (None, _) | (_, Some((&PathComponent::Inherited, _))) => FieldVisibility::Restricted, (Some((sh, sr)), Some((fh, fr))) => if sh == fh { check_head(sr, fr) } else { FieldVisibility::CantDetermine } } } // If the field is marked `pub`, then we know it's definitely visible... if &field_vis == &vec![ PathComponent::Pub ] { return FieldVisibility::Visible; } // ...and if that's not the case, but the struct is marked `pub`, we know // the field is definitely restricted. if &struct_vis == &vec![ PathComponent::Pub ] { return FieldVisibility::Restricted; } check_head(&struct_vis, &field_vis) } fn to_path(path: &syn::Visibility) -> Vec { use syn::Visibility::*; // Visibilities of pub(self) and pub(in self) are equivalent to // no visibility modifier at all, so we need to make sure using // that doesn't report false positives. // // Technically this still doesn't catch *really* weird cases, like // if someone wrote pub(in self::self), but it should be good // enough. if is_pub_self(path) { return vec![ PathComponent::Inherited ]; } match path { &Public(..) => vec![ PathComponent::Pub ], &Crate(..) => vec![ PathComponent::Pub, PathComponent::Crate ], &Inherited => vec![ PathComponent::Inherited ], &Restricted(ref vis) => to_path_restricted(&vis.path) } } fn to_path_restricted(path: &syn::Path) -> Vec { let segments = path.segments.iter() .map(|path_segment| &path_segment.ident) .collect_vec(); match segments.split_first() { None => vec![], Some((ident, rest)) => { let mut result; if *ident == "self" { result = vec![ PathComponent::InSelf ]; } else if *ident == "super" { result = vec![ PathComponent::InSuper ]; } else if *ident == "crate" { result = vec![ PathComponent::Pub, PathComponent::Crate ]; } else { // We add these components in non-self/super paths to allow us to // match them up with visibilities like `pub` and `pub(crate)`. result = vec![ PathComponent::Pub, PathComponent::Crate, PathComponent::Mod(ident.to_string()) ]; } let rest = rest.iter() .map(|ident| PathComponent::Mod(ident.to_string())); result.extend(rest); result } } } fn is_pub_self(vis: &syn::Visibility) -> bool { if let syn::Visibility::Restricted(syn::VisRestricted { ref path, .. }) = vis { path.is_ident("self") } else { false } } #[cfg(test)] mod path_convert_tests { use std::convert::From; use syn::{self, Visibility}; use super::{PathComponent, to_path}; use super::PathComponent::*; impl<'a> From<&'a str> for PathComponent { fn from(input: &'a str) -> Self { Mod(input.to_string()) } } macro_rules! vis_test { ($test_name:ident => $input:expr; $($component:expr),+) => { #[test] fn $test_name() { let vis: Visibility = syn::parse_str($input) .expect("path input is structured incorrectly!"); let vis = to_path(&vis); let expected = vec![ $($component.into()),+ ]; assert_eq!(&vis, &expected); } } } vis_test!(vis_test1 => "pub"; Pub); vis_test!(vis_test2 => "pub(crate)"; Pub, Crate); vis_test!(vis_test3 => ""; Inherited); vis_test!(vis_test4 => "pub(self)"; Inherited); vis_test!(vis_test5 => "pub(super)"; InSuper); vis_test!(vis_test6 => "pub(in ::a::b::c)"; Pub, Crate, "a", "b", "c"); vis_test!(vis_test7 => "pub(in ::super::b)"; InSuper, "b"); } #[cfg(test)] mod field_visibility_tests { use syn::{self, Visibility}; use super::field_visibility; use super::FieldVisibility::*; macro_rules! field_vis_test { ($test_name:ident => $struct_vis: expr; $field_vis: expr; $vis: expr) => { #[test] fn $test_name() { let struct_vis: Visibility = syn::parse_str($struct_vis) .expect("failed to parse struct visibility"); let field_vis: Visibility = syn::parse_str($field_vis) .expect("failed to parse field visibility"); let vis = field_visibility(&struct_vis, &field_vis); assert_eq!(vis, $vis); } } } field_vis_test!(test_field_vis1 => "pub"; "pub"; Visible); field_vis_test!(test_field_vis2 => ""; ""; Visible); field_vis_test!(test_field_vis3 => "pub(in a::b::c)"; "pub(in a::b)"; Visible); field_vis_test!(test_field_vis4 => "pub(in a::b)"; "pub(in a::b::c)"; Restricted); field_vis_test!(test_field_vis5 => "pub"; "pub(crate)"; Restricted); field_vis_test!(test_field_vis6 => "pub(crate)"; "pub(in a::b::c)"; Restricted); field_vis_test!(test_field_vis7 => "pub"; ""; Restricted); field_vis_test!(test_field_vis8 => ""; "pub"; Visible); field_vis_test!(test_field_vis9 => "pub(in a::b::c)"; "pub(self)"; Restricted); field_vis_test!(test_field_vis10 => "pub(in a::b::c)"; "pub(super)"; CantDetermine); field_vis_test!(test_field_vis11 => "pub"; "pub(self)"; Restricted); field_vis_test!(test_field_vis12 => "pub(in a::b::c)"; "pub"; Visible); field_vis_test!(test_field_vis13 => "pub(self)"; "pub(self)"; Visible); field_vis_test!(test_field_vis14 => "pub(super)"; "pub(super)"; Visible); field_vis_test!(test_field_vis15 => "pub(crate)"; "pub(crate)"; Visible); field_vis_test!(test_field_vis16 => "pub(in a::b::c)"; "pub(in a::b::c)"; Visible); } shrinkwraprs-0.3.0/tests/impl_immut.rs010066400017500001750000000020031356115577600163710ustar0000000000000000#![allow(unused_variables, dead_code)] #[macro_use] extern crate shrinkwraprs; extern crate core; #[derive(Shrinkwrap)] struct Email(String); #[derive(Shrinkwrap)] struct CodeSpan(u64, u64, #[shrinkwrap(main_field)] String); #[derive(Shrinkwrap)] struct PhoneNumber { number: String } #[derive(Shrinkwrap)] struct FileContents { #[shrinkwrap(main_field)] contents: String, linked_inodes: u64 } #[test] fn test_tuple_can_deref() { let email = Email("chiya+snacks@natsumeya.jp".into()); assert!(email.contains("+")); } #[test] fn test_nary_tuple_can_deref() { let span = CodeSpan(0, 24, " impl ".into()); assert_eq!(span.trim(), "impl"); } #[test] fn test_single_can_deref() { let number = PhoneNumber { number: "+1 (800) 273-8255".into() }; let is_collect_call = number.contains("(800)"); assert!(is_collect_call); } #[test] fn test_multi_can_deref() { let contents = FileContents { contents: "fjkfdlsjfkdlsjflks".into(), linked_inodes: 3 }; assert!(contents.len() > 0); } shrinkwraprs-0.3.0/tests/impl_mut.rs010066400017500001750000000021101356115577600160420ustar0000000000000000#![allow(unused_variables, dead_code)] #[macro_use] extern crate shrinkwraprs; extern crate core; #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct Email(String); #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct CodeSpan(u64, u64, #[shrinkwrap(main_field)] String); #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct PhoneNumber { number: String } #[derive(Shrinkwrap)] #[shrinkwrap(mutable)] struct FileContents { #[shrinkwrap(main_field)] contents: String, linked_inodes: u64 } #[test] fn test_tuple_can_deref_mut() { let mut email = Email("chiya+snacks@natsumeya.jp".into()); email.push_str(".co"); } #[test] fn test_nary_tuple_can_deref_mut() { let mut span = CodeSpan(0, 24, " impl ".into()); span.push_str("!Sync for MyCell"); } #[test] fn test_single_can_deref_mut() { let mut number = PhoneNumber { number: "+1 (800) 273-8255".into() }; number.push_str(" (20)"); } #[test] fn test_multi_can_deref_mut() { let mut contents = FileContents { contents: "fjkfdlsjfkdlsjflks".into(), linked_inodes: 3 }; contents.push_str("fdjskl"); } shrinkwraprs-0.3.0/tests/transformers.rs010066400017500001750000000010131357431726500167370ustar0000000000000000#![allow(unused_variables)] #[macro_use] extern crate shrinkwraprs; extern crate core; #[derive(Shrinkwrap)] #[shrinkwrap(transformers)] pub struct Email(String); #[test] fn test_map_mut() { let mut email = Email("aoi.miyamori@musashino".into()); let orig_len = email.len(); email .transform(|s| s.push_str(".co")) .transform(|s| s.push_str(".jp")); let new_len = email.len(); let siphoned_len = email.siphon(|s| s.len()); assert_eq!(new_len, orig_len + 6); assert_eq!(new_len, siphoned_len); } shrinkwraprs-0.3.0/.cargo_vcs_info.json0000644000000001121357431742600136750ustar00{ "git": { "sha1": "9aff985bcab09df9ac3701d88e043d1e35d5cdc2" } } shrinkwraprs-0.3.0/Cargo.lock0000644000000055571357431742600116720ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "itertools" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro2" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "shrinkwraprs" version = "0.3.0" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "87fa75c9dea7b07be3138c49abbb83fd4bea199b5cdc76f9804458edc5da0d6e" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"