Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()`).
Expand 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
Expand Down
39 changes: 37 additions & 2 deletions src/substruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::expr::Expr;
/// ```
struct SubstructInputArg {
docs: Vec<syn::Attribute>,
vis: syn::Visibility,
expr: Expr,
}

Expand All @@ -36,6 +37,7 @@ impl Parse for SubstructInputArg {

Ok(Self {
docs: attrs,
vis: input.parse()?,
expr: input.parse()?,
})
}
Expand Down Expand Up @@ -78,6 +80,7 @@ impl Parse for SubstructAttrInput {

struct TopLevelArg {
docs: Vec<syn::Attribute>,
vis: syn::Visibility,
}

struct Emitter<'a> {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -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()),
});

Expand All @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/fail/base-visibility-override.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use substruct::substruct;

#[substruct(pub(crate) Test)]
pub struct Test {}

fn main() {}
5 changes: 5 additions & 0 deletions tests/ui/fail/base-visibility-override.stderr
Original file line number Diff line number Diff line change
@@ -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)]
| ^^^^^^^^^^
18 changes: 18 additions & 0 deletions tests/ui/fail/inaccessible-field.rs
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions tests/ui/fail/inaccessible-field.stderr
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/ui/fail/inaccessible-struct.rs
Original file line number Diff line number Diff line change
@@ -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();
}
17 changes: 17 additions & 0 deletions tests/ui/fail/inaccessible-struct.stderr
Original file line number Diff line number Diff line change
@@ -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 | | }
| |_____^
1 change: 0 additions & 1 deletion tests/ui/fail/multiple-attr-on-field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ struct A {
#[substruct(B)]
#[substruct(C)]
field: u32,

}

fn main() {}
2 changes: 1 addition & 1 deletion tests/ui/fail/multiple-errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
20 changes: 20 additions & 0 deletions tests/ui/pass/expression-vis.rs
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 12 additions & 0 deletions tests/ui/pass/no-warn-pub-self.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading