diff --git a/src/lib.rs b/src/lib.rs index 7e1c28a..dee66af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,32 @@ //! } //! ``` //! +//! # Overriding visibility for emitted structs and fields +//! Normally, `substruct` will copy the visibility definition over from the +//! base struct. However, you can override it by adding a visibility specifier +//! in front of the struct name within the `#[substruct]` attribute. +//! ``` +//! # use substruct::substruct; +//! # +//! // Both A and B are pub, but C is pub(crate). +//! #[substruct(B, pub(crate) C)] +//! pub struct A { +//! // This field is public in structs A and C, but only pub(crate) in C. +//! #[substruct(pub(crate) B, C)] +//! pub field1: u32, +//! +//! // Making a field private can be done by using pub(self) +//! #[substruct(pub(self) C)] +//! pub field2: u32, +//! +//! // You can also override the visibility when using expressions. +//! // The overridden visibility applies to all structs specified in the +//! // expression. +//! #[substruct(pub(crate) any(B, C))] +//! pub field3: u32, +//! } +//! ``` +//! //! # Managing attributes on generated structs //! Sometimes you may want attributes to only apply to some of the emitted //! structs. To do so, you can use the `#[substruct_attr]` macro to only emit @@ -166,7 +192,7 @@ //! } //! ``` //! -//! > The parent struct as always implicitly included in the set of structs +//! > The parent struct is always implicitly included in the set of structs //! > that each field is emitted for. This means that putting `not(A)` in the //! > the struct above would not exclude the field from `A` (and is, in fact, //! > equivalent to `all()`). @@ -190,8 +216,8 @@ //! } //! ``` //! -//! If multiple documentation overrides apply to a single field, then the first -//! one to apply will be used. +//! If multiple overrides apply to a single field, then the first one to apply +//! will be used. //! //! # Generics //! Generics are currently _mostly_ supported. You can use generics with diff --git a/src/substruct.rs b/src/substruct.rs index 89135cf..6ebebab 100644 --- a/src/substruct.rs +++ b/src/substruct.rs @@ -18,6 +18,7 @@ use crate::expr::Expr; /// ``` struct SubstructInputArg { docs: Vec, + vis: syn::Visibility, expr: Expr, } @@ -36,6 +37,7 @@ impl Parse for SubstructInputArg { Ok(Self { docs: attrs, + vis: input.parse()?, expr: input.parse()?, }) } @@ -78,6 +80,7 @@ impl Parse for SubstructAttrInput { struct TopLevelArg { docs: Vec, + vis: syn::Visibility, } struct Emitter<'a> { @@ -106,7 +109,13 @@ impl<'a> Emitter<'a> { .args .into_iter() .filter_map(|arg| match arg.expr { - Expr::Ident(ident) => Some((ident.clone(), TopLevelArg { docs: arg.docs })), + Expr::Ident(ident) => Some(( + ident.clone(), + TopLevelArg { + docs: arg.docs, + vis: arg.vis, + }, + )), expr => { errors.push(syn::Error::new_spanned( expr, @@ -118,7 +127,13 @@ impl<'a> Emitter<'a> { .collect(); if !args.contains_key(&input.ident) { - args.insert(input.ident.clone(), TopLevelArg { docs: Vec::new() }); + args.insert( + input.ident.clone(), + TopLevelArg { + docs: Vec::new(), + vis: syn::Visibility::Inherited, + }, + ); } Ok(Self { @@ -151,6 +166,17 @@ impl<'a> Emitter<'a> { let mut input = self.input.clone(); input.ident = name.clone(); + if !matches!(tla.vis, syn::Visibility::Inherited) { + if *name == self.input.ident { + self.errors.push(syn::Error::new_spanned( + &tla.vis, + "cannot override the visibility of the base struct", + )); + } + + input.vis = tla.vis.clone(); + } + if !tla.docs.is_empty() { input.attrs.retain(|attr| !attr.path().is_ident("doc")); input.attrs.extend_from_slice(&tla.docs); @@ -169,6 +195,10 @@ impl<'a> Emitter<'a> { syn::Data::Union(data) => self.filter_fields_named(&mut data.fields, name), }; + input + .attrs + .push(syn::parse_quote!(#[allow(clippy::needless_pub_self)])); + input.to_tokens(&mut self.tokens); if input.ident != self.input.ident { @@ -333,6 +363,7 @@ impl<'a> Emitter<'a> { substruct.args.push(SubstructInputArg { docs: Vec::new(), + vis: syn::Visibility::Inherited, expr: Expr::Ident(self.input.ident.clone()), }); @@ -343,6 +374,10 @@ impl<'a> Emitter<'a> { self.filter_attrs(&mut field.attrs, name); + if !matches!(arg.vis, syn::Visibility::Inherited) { + field.vis = arg.vis.clone(); + } + if !arg.docs.is_empty() { field.attrs.retain(|attr| !attr.path().is_ident("doc")); field.attrs.extend_from_slice(&arg.docs); diff --git a/tests/ui/fail/base-visibility-override.rs b/tests/ui/fail/base-visibility-override.rs new file mode 100644 index 0000000..202d154 --- /dev/null +++ b/tests/ui/fail/base-visibility-override.rs @@ -0,0 +1,6 @@ +use substruct::substruct; + +#[substruct(pub(crate) Test)] +pub struct Test {} + +fn main() {} diff --git a/tests/ui/fail/base-visibility-override.stderr b/tests/ui/fail/base-visibility-override.stderr new file mode 100644 index 0000000..aa356b3 --- /dev/null +++ b/tests/ui/fail/base-visibility-override.stderr @@ -0,0 +1,5 @@ +error: cannot override the visibility of the base struct + --> tests/ui/fail/base-visibility-override.rs:3:13 + | +3 | #[substruct(pub(crate) Test)] + | ^^^^^^^^^^ diff --git a/tests/ui/fail/inaccessible-field.rs b/tests/ui/fail/inaccessible-field.rs new file mode 100644 index 0000000..5c47fd3 --- /dev/null +++ b/tests/ui/fail/inaccessible-field.rs @@ -0,0 +1,18 @@ +mod inner { + use substruct::substruct; + + #[substruct(A)] + #[derive(Default)] + pub struct Test { + #[substruct(pub(self) A)] + pub field1: u32, + pub field2: u32, + } +} + +fn main() { + use self::inner::*; + + let value = A::default(); + let _field = value.field1; +} diff --git a/tests/ui/fail/inaccessible-field.stderr b/tests/ui/fail/inaccessible-field.stderr new file mode 100644 index 0000000..c34e86a --- /dev/null +++ b/tests/ui/fail/inaccessible-field.stderr @@ -0,0 +1,5 @@ +error[E0616]: field `field1` of struct `inner::A` is private + --> tests/ui/fail/inaccessible-field.rs:17:24 + | +17 | let _field = value.field1; + | ^^^^^^ private field diff --git a/tests/ui/fail/inaccessible-struct.rs b/tests/ui/fail/inaccessible-struct.rs new file mode 100644 index 0000000..1daf99f --- /dev/null +++ b/tests/ui/fail/inaccessible-struct.rs @@ -0,0 +1,14 @@ +mod inner { + use substruct::substruct; + + #[substruct(pub(self) A)] + #[derive(Default)] + pub struct Test { + #[substruct(A)] + pub field: u32, + } +} + +fn main() { + let _ = crate::inner::A::default(); +} diff --git a/tests/ui/fail/inaccessible-struct.stderr b/tests/ui/fail/inaccessible-struct.stderr new file mode 100644 index 0000000..168f898 --- /dev/null +++ b/tests/ui/fail/inaccessible-struct.stderr @@ -0,0 +1,17 @@ +error[E0603]: struct `A` is private + --> tests/ui/fail/inaccessible-struct.rs:13:27 + | +13 | let _ = crate::inner::A::default(); + | ^ private struct + | +note: the struct `A` is defined here + --> tests/ui/fail/inaccessible-struct.rs:4:17 + | +4 | #[substruct(pub(self) A)] + | _________________^ +5 | | #[derive(Default)] +6 | | pub struct Test { +7 | | #[substruct(A)] +8 | | pub field: u32, +9 | | } + | |_____^ diff --git a/tests/ui/fail/multiple-attr-on-field.rs b/tests/ui/fail/multiple-attr-on-field.rs index e816e9e..2c86bc6 100644 --- a/tests/ui/fail/multiple-attr-on-field.rs +++ b/tests/ui/fail/multiple-attr-on-field.rs @@ -5,7 +5,6 @@ struct A { #[substruct(B)] #[substruct(C)] field: u32, - } fn main() {} diff --git a/tests/ui/fail/multiple-errors.rs b/tests/ui/fail/multiple-errors.rs index 35c7c44..07b8155 100644 --- a/tests/ui/fail/multiple-errors.rs +++ b/tests/ui/fail/multiple-errors.rs @@ -4,7 +4,7 @@ use substruct::substruct; pub struct A { #[substruct_attr(blah, blah, blah)] #[substruct_attr(blah, bleh, blah)] - pub x: u32 + pub x: u32, } fn main() {} diff --git a/tests/ui/pass/expression-vis.rs b/tests/ui/pass/expression-vis.rs new file mode 100644 index 0000000..3b8df8c --- /dev/null +++ b/tests/ui/pass/expression-vis.rs @@ -0,0 +1,20 @@ +mod inner { + use substruct::substruct; + + #[substruct(A, B)] + #[derive(Default)] + pub struct Test { + #[substruct(pub any(A, B))] + field: u32, + } +} + +fn main() { + use crate::inner::*; + + let a = A::default(); + let b = B::default(); + + let _ = a.field; + let _ = b.field; +} diff --git a/tests/ui/pass/no-warn-pub-self.rs b/tests/ui/pass/no-warn-pub-self.rs new file mode 100644 index 0000000..b929bf5 --- /dev/null +++ b/tests/ui/pass/no-warn-pub-self.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] +#![deny(clippy::needless_pub_self)] + +use substruct::substruct; + +#[substruct(pub(self) A)] +pub struct Test { + #[substruct(A)] + pub field: u32, +} + +fn main() {}