smart-default-0.7.1/.cargo_vcs_info.json0000644000000001360000000000100136110ustar { "git": { "sha1": "e03d1dee473603df260f474460448b2e1e96f327" }, "path_in_vcs": "" }smart-default-0.7.1/.github/workflows/ci.yml000064400000000000000000000065611046102023000171240ustar 00000000000000name: CI on: pull_request: push: branches: [master] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write checks: write jobs: ci: name: CI needs: [test, clippy, docs] runs-on: ubuntu-latest steps: - name: Done run: exit 0 test: name: Tests strategy: fail-fast: false matrix: os: [ubuntu-latest] rust: [1.69.0, nightly] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - name: Ready cache if: matrix.os == 'ubuntu-latest' run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/ - name: Cache cargo uses: actions/cache@v1 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Test smart-default run: cargo test --all-targets fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: nightly components: rustfmt - name: Run fmt --all -- --check run: cargo fmt --all -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: nightly components: clippy - name: Cache cargo uses: actions/cache@v1 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run clippy --all-targets -- uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-targets -- docs: name: Docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: nightly - name: Cache cargo uses: actions/cache@v1 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run doc tests run: cargo test --doc - name: Check smart-default docs run: cargo doc --no-deps docs-ghpages: name: Update Docs in GitHub Pages runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v3 - name: Build docs env: GITHUB_REPO: ${{ github.repository }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: |- cargo doc --verbose && echo "" > target/doc/index.html - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: target/doc deploy-ghpages: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: docs-ghpages if: github.ref == 'refs/heads/master' steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 smart-default-0.7.1/.gitignore000064400000000000000000000005221046102023000143700ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk /target/ **/*.rs.bk Cargo.lock smart-default-0.7.1/CHANGELOG.md000064400000000000000000000033251046102023000142150ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ## 0.7.1 - 2023-04-24 ### Fixed - Fixed bug where the macro fails on valid default expression that is also a valid attribute meta because it was expecting the `_code` hack. ## 0.7.0 - 2023-04-23 ### Changed - Update `syn` to version 2 ## 0.6.0 - 2019-12-13 ### Changed - Update `syn`, `quote` and `proc-macro2` versions to `1.*.*`. ## 0.5.2 - 2019-04-13 ### Fixed - Omit linting of generated code by adding `#[automatically_derived]` attribute. ## 0.5.1 - 2019-03-01 ### Fixed - Don't use a multi-pattern `if let`, as this unnecessarily requires version 1.33.0 of Rust. ## 0.5.0 - 2019-03-01 ### Changed - When the default is a string literal, strap an `.into()` after it to automatically convert it to `String` when needed. ## 0.4.0 - 2019-02-19 ### Added - `#[default(_code = "...")]` syntax for defaults that cannot be parsed as attributes no matter what. ## 0.3.0 - 2018-11-02 ### Changed - Require Rust 1.30+. - Use direct attribute value instead of having to wrap them in strings. - Moved the docs from the module level to the custom derive. ### Added - `#[default(...)]` syntax in addition to `#[default = ...]`. This is required to deal with some parsing limitations. ## 0.2.0 - 2017-08-21 ### Added - Support generic types. - Generate doc for the trait impl that describes the default values. ## 0.1.0 - 2017-08-18 ### Added - Custom derive `SmartDefault` for implementing the `Default` trait. - `#[default = ...]` attribute on fields for smart-default. smart-default-0.7.1/Cargo.lock0000644000000021340000000000100115640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "proc-macro2" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "smart-default" version = "0.7.1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" smart-default-0.7.1/Cargo.toml0000644000000017340000000000100116140ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "smart-default" version = "0.7.1" authors = ["IdanArye "] description = "Rust custom-derive macro for Default with more control on the fields" documentation = "https://idanarye.github.io/rust-smart-default/" readme = "README.md" keywords = ["default"] license = "MIT" repository = "https://github.com/idanarye/rust-smart-default" [lib] proc-macro = true [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" smart-default-0.7.1/Cargo.toml.orig000064400000000000000000000007211046102023000152700ustar 00000000000000[package] name = "smart-default" description = "Rust custom-derive macro for Default with more control on the fields" version = "0.7.1" edition = "2021" authors = ["IdanArye "] license = "MIT" repository = "https://github.com/idanarye/rust-smart-default" documentation = "https://idanarye.github.io/rust-smart-default/" readme = "README.md" keywords = ["default"] [lib] proc-macro = true [dependencies] syn = "2" quote = "1" proc-macro2 = "1" smart-default-0.7.1/LICENSE000064400000000000000000000020521046102023000134050ustar 00000000000000MIT License Copyright (c) 2017 Idan Arye Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. smart-default-0.7.1/README.md000064400000000000000000000020441046102023000136600ustar 00000000000000[![Build Status](https://github.com/idanarye/rust-smart-default/workflows/CI/badge.svg)](https://github.com/idanarye/rust-smart-default/actions) [![Latest Version](https://img.shields.io/crates/v/smart-default.svg)](https://crates.io/crates/smart-default) [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://idanarye.github.io/rust-smart-default/) # Rust SmartDefault Custom derive for automatically implementing the `Default` trait with customized default values: ```rust use smart_default::SmartDefault; #[derive(SmartDefault)] enum Foo { Bar, #[default] Baz { #[default = 12] a: i32, b: i32, #[default(Some(Default::default()))] c: Option, #[default(_code = "vec![1, 2, 3]")] d: Vec, #[default = "four"] e: String, }, Qux(i32), } assert!(Foo::default() == Foo::Baz { a: 12, b: 0, c: Some(0), d: vec![1, 2, 3], e: "four".to_owned(), }); ``` Requires Rust 1.30+ (for non-string values in attributes) smart-default-0.7.1/examples/example.rs000064400000000000000000000012031046102023000162140ustar 00000000000000use smart_default::SmartDefault; #[derive(PartialEq, SmartDefault, Debug)] #[allow(dead_code)] enum Foo { Bar, #[default] Baz { #[default(12)] a: i32, b: i32, #[default(Some(Default::default()))] c: Option, #[default(_code = "vec![1, 2, 3]")] d: Vec, #[default = "four"] e: String, }, Qux(i32), } fn main() { assert!( Foo::default() == Foo::Baz { a: 12, b: 0, c: Some(0), d: vec![1, 2, 3], e: "four".to_owned(), } ); } smart-default-0.7.1/src/body_impl.rs000064400000000000000000000131111046102023000155110ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use syn::parse::Error; use syn::spanned::Spanned; use syn::DeriveInput; use crate::default_attr::{ConversionStrategy, DefaultAttr}; use crate::util::find_only; pub fn impl_my_derive(input: &DeriveInput) -> Result { let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (default_expr, doc) = match input.data { syn::Data::Struct(ref body) => { let (body_assignment, doc) = default_body_tt(&body.fields)?; ( quote! { #name #body_assignment }, format!("Return `{}{}`", name, doc), ) } syn::Data::Enum(ref body) => { let default_variant = find_only(body.variants.iter(), |variant| { if let Some(meta) = DefaultAttr::find_in_attributes(&variant.attrs)? { if meta.code.is_none() { Ok(true) } else { Err(Error::new( meta.code.span(), "Attribute #[default] on variants should have no value", )) } } else { Ok(false) } })? .ok_or_else(|| Error::new(input.span(), "No default variant"))?; let default_variant_name = &default_variant.ident; let (body_assignment, doc) = default_body_tt(&default_variant.fields)?; ( quote! { #name :: #default_variant_name #body_assignment }, format!("Return `{}::{}{}`", name, default_variant_name, doc), ) } syn::Data::Union(_) => { panic!() } }; Ok(quote! { #[automatically_derived] impl #impl_generics Default for #name #ty_generics #where_clause { #[doc = #doc] fn default() -> Self { #default_expr } } }) } /// Return a token-tree for the default "body" - the part after the name that contains the values. /// That is, the `{ ... }` part for structs, the `(...)` part for tuples, and nothing for units. fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> { let mut doc = String::new(); use std::fmt::Write; let body_tt = match body { syn::Fields::Named(ref fields) => { doc.push_str(" {"); let result = { let field_assignments = fields .named .iter() .map(|field| { let field_name = field.ident.as_ref(); let (default_value, default_doc) = field_default_expr_and_doc(field)?; write!( &mut doc, "\n {}: {},", field_name.expect("field value in struct is empty"), default_doc ) .unwrap(); // let default_value = default_value.into_token_stream(); Ok(quote! { #field_name : #default_value }) }) .collect::, Error>>()?; quote! { { #( #field_assignments ),* } } }; if doc.ends_with(',') { doc.pop(); doc.push('\n'); }; doc.push('}'); result } syn::Fields::Unnamed(ref fields) => { doc.push('('); let result = { let field_assignments = fields .unnamed .iter() .map(|field| { let (default_value, default_doc) = field_default_expr_and_doc(field)?; write!(&mut doc, "{}, ", default_doc).unwrap(); Ok(default_value) }) .collect::, Error>>()?; quote! { ( #( #field_assignments ),* ) } }; if doc.ends_with(", ") { doc.pop(); doc.pop(); }; doc.push(')'); result } &syn::Fields::Unit => quote! {}, }; Ok((body_tt, doc)) } /// Return a default expression for a field based on it's `#[default = "..."]` attribute. Panic /// if there is more than one, of if there is a `#[default]` attribute without value. fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String), Error> { if let Some(default_attr) = DefaultAttr::find_in_attributes(&field.attrs)? { let conversion_strategy = default_attr.conversion_strategy(); let field_value = default_attr.code.ok_or_else(|| { Error::new(field.span(), "Expected #[default = ...] or #[default(...)]") })?; let field_value = match conversion_strategy { ConversionStrategy::NoConversion => field_value, ConversionStrategy::Into => quote!((#field_value).into()), }; let field_doc = format!("{}", field_value); Ok((field_value, field_doc)) } else { Ok(( quote! { Default::default() }, "Default::default()".to_owned(), )) } } smart-default-0.7.1/src/default_attr.rs000064400000000000000000000061631046102023000162220ustar 00000000000000use proc_macro2::TokenStream; use quote::ToTokens; use syn::{parse::Error, MetaNameValue}; use crate::util::find_only; #[derive(Debug, Clone, Copy)] pub enum ConversionStrategy { NoConversion, Into, } pub struct DefaultAttr { pub code: Option, conversion_strategy: Option, } impl DefaultAttr { pub fn find_in_attributes(attrs: &[syn::Attribute]) -> Result, Error> { if let Some(default_attr) = find_only(attrs.iter(), |attr| Ok(attr.path().is_ident("default")))? { match &default_attr.meta { syn::Meta::Path(_) => Ok(Some(Self { code: None, conversion_strategy: None, })), syn::Meta::List(meta) => { // If the meta contains exactly (_code = "...") take the string literal as the // expression if let Ok(ParseCodeHack(code_hack)) = syn::parse(meta.tokens.clone().into()) { Ok(Some(Self { code: Some(code_hack), conversion_strategy: Some(ConversionStrategy::NoConversion), })) } else { Ok(Some(Self { code: Some(meta.tokens.clone()), conversion_strategy: None, })) } } syn::Meta::NameValue(MetaNameValue { value, .. }) => Ok(Some(Self { code: Some(value.into_token_stream()), conversion_strategy: None, })), } } else { Ok(None) } } pub fn conversion_strategy(&self) -> ConversionStrategy { if let Some(conversion_strategy) = self.conversion_strategy { // Conversion strategy already set return conversion_strategy; } let code = if let Some(code) = &self.code { code } else { // #[default] - so no conversion (`Default::default()` already has the correct type) return ConversionStrategy::NoConversion; }; match syn::parse::(code.clone().into()) { Ok(syn::Lit::Str(_)) | Ok(syn::Lit::ByteStr(_)) => { // A string literal - so we need a conversion in case we need to make it a `String` return ConversionStrategy::Into; } _ => {} } // Not handled by one of the rules, so we don't convert it to avoid causing trouble ConversionStrategy::NoConversion } } struct ParseCodeHack(TokenStream); impl syn::parse::Parse for ParseCodeHack { fn parse(input: syn::parse::ParseStream) -> syn::Result { let ident: syn::Ident = input.parse()?; if ident != "_code" { return Err(Error::new(ident.span(), "Expected `_code`")); } input.parse::()?; let code: syn::LitStr = input.parse()?; let code: TokenStream = code.parse()?; Ok(ParseCodeHack(code)) } } smart-default-0.7.1/src/lib.rs000064400000000000000000000053121046102023000143050ustar 00000000000000use syn::{parse_macro_input, DeriveInput}; mod body_impl; mod default_attr; mod util; /// # Smart Default /// /// This crate provides a custom derive for `SmartDefault`. `SmartDefault` is not a real trait - /// deriving it will actually `impl Default`. The difference from regular `#[derive(Default)]` is /// that `#[derive(SmartDefault)]` allows you to use `#[default = "..."]` attributes to customize /// the `::default()` method and to support `struct`s that don't have `Default` for all their /// fields - and even `enum`s! /// /// # Examples /// /// ``` /// use smart_default::SmartDefault; /// /// # fn main() { /// #[derive(SmartDefault)] /// # #[derive(PartialEq)] /// # #[allow(dead_code)] /// enum Foo { /// Bar, /// #[default] /// Baz { /// #[default = 12] /// a: i32, /// b: i32, /// #[default(Some(Default::default()))] /// c: Option, /// #[default(_code = "vec![1, 2, 3]")] /// d: Vec, /// #[default = "four"] /// e: String, /// }, /// Qux(i32), /// } /// /// assert!(Foo::default() == Foo::Baz { /// a: 12, /// b: 0, /// c: Some(0), /// d: vec![1, 2, 3], /// e: "four".to_owned(), /// }); /// # } /// ``` /// /// * `Baz` has the `#[default]` attribute. This means that the default `Foo` is a `Foo::Baz`. Only /// one variant may have a `#[default]` attribute, and that attribute must have no value. /// * `a` has a `#[default = 12]` attribute. This means that it's default value is `12`. /// * `b` has no `#[default = ...]` attribute. It's default value will `i32`'s default value /// instead - `0`. /// * `c` is an `Option`, and it's default is `Some(Default::default())`. Rust cannot (currently) /// parse `#[default = Some(Default::default())]` and therefore we have to use a special syntax: /// `#[default(Some(Default::default))]` /// * `d` has the `!` token in it, which cannot (currently) be parsed even with `#[default(...)]`, /// so we have to encode it as a string and mark it as `_code = `. /// * `e` is a `String`, so the string literal "four" is automatically converted to it. This /// automatic conversion **only** happens to string (or byte string) literals - and only if /// `_code` is not used. /// * Documentation for the `impl Default` section is generated automatically, specifying the /// default value returned from `::default()`. #[proc_macro_derive(SmartDefault, attributes(default))] pub fn derive_smart_default(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); match body_impl::impl_my_derive(&input) { Ok(output) => output.into(), Err(error) => error.to_compile_error().into(), } } smart-default-0.7.1/src/util.rs000064400000000000000000000011101046102023000145040ustar 00000000000000use syn::parse::Error; use syn::spanned::Spanned; /// Return the value that fulfills the predicate if there is one in the slice. Panic if there is /// more than one. pub fn find_only(iter: impl Iterator, pred: F) -> Result, Error> where T: Spanned, F: Fn(&T) -> Result, { let mut result = None; for item in iter { if pred(&item)? { if result.is_some() { return Err(Error::new(item.span(), "Multiple defaults")); } result = Some(item); } } Ok(result) } smart-default-0.7.1/tests/tests.rs000064400000000000000000000064761046102023000152700ustar 00000000000000use smart_default::SmartDefault; #[test] fn test_unit() { #[derive(PartialEq, SmartDefault)] struct Foo; assert!(Foo::default() == Foo); } #[test] fn test_tuple() { #[derive(PartialEq, SmartDefault)] struct Foo( #[default = 10] i32, #[default = 20] i32, // No default i32, ); assert!(Foo::default() == Foo(10, 20, 0)); } #[test] fn test_struct() { #[derive(PartialEq, SmartDefault)] struct Foo { #[default = 10] x: i32, #[default = 20] y: i32, // No default z: i32, } assert!(Foo::default() == Foo { x: 10, y: 20, z: 0 }); } #[test] fn test_enum_of_units() { #[derive(PartialEq, SmartDefault)] pub enum Foo { #[allow(dead_code)] Bar, #[default] Baz, #[allow(dead_code)] Qux, } assert!(Foo::default() == Foo::Baz); } #[test] fn test_enum_of_tuples() { #[derive(PartialEq, SmartDefault)] pub enum Foo { #[allow(dead_code)] Bar(i32), #[default] Baz(#[default = 10] i32, i32), #[allow(dead_code)] Qux(i32), } assert!(Foo::default() == Foo::Baz(10, 0)); } #[test] fn test_enum_of_structs() { #[derive(PartialEq, SmartDefault)] pub enum Foo { #[allow(dead_code)] Bar { x: i32 }, #[default] Baz { #[default = 10] y: i32, z: i32, }, #[allow(dead_code)] Qux { w: i32 }, } assert!(Foo::default() == Foo::Baz { y: 10, z: 0 }); } #[test] fn test_enum_mixed() { #[derive(PartialEq, SmartDefault)] enum Foo { #[allow(dead_code)] Bar, #[default] Baz(#[default = 10] i32), #[allow(dead_code)] Qux { w: i32 }, } assert!(Foo::default() == Foo::Baz(10)); } #[test] fn test_generics_type_parameters() { #[derive(PartialEq, SmartDefault)] struct Foo where T: Default, { #[default(Some(Default::default()))] x: Option, } assert!(Foo::default() == Foo { x: Some(0) }); } #[test] fn test_generics_lifetime_parameters() { // NOTE: A default value makes no sense with lifetime parameters, since ::default() receives no // paramters and therefore can receive no lifetimes. But it does make sense if you make a variant // without ref fields the default. #[derive(PartialEq, SmartDefault)] enum Foo<'a> { #[default] Bar(i32), #[allow(dead_code)] Baz(&'a str), } assert!(Foo::default() == Foo::Bar(0)); } #[test] fn test_code_hack() { #[derive(PartialEq, SmartDefault)] struct Foo { #[default(_code = "vec![1, 2, 3]")] v: Vec, } assert!(Foo::default().v == [1, 2, 3]); } #[test] fn test_string_conversion() { #[derive(PartialEq, SmartDefault)] struct Foo(#[default = "one"] &'static str, #[default("two")] String); assert!(Foo::default() == Foo("one", "two".to_owned())); } #[test] fn test_non_code_hack_valid_meta() { #[derive(Debug, PartialEq, SmartDefault)] struct Foo { #[default(true)] bar: bool, #[default(Option::None)] baz: Option<()>, } assert_eq!( Foo::default(), Foo { bar: true, baz: None } ); }