#![warn(missing_docs)]
#![allow(uncommon_codepoints)]
pub use unsynn::*;
use std::ops::Deref;
keyword! {
pub KPub = "pub";
pub KStruct = "struct";
pub KEnum = "enum";
pub KDoc = "doc";
pub KRepr = "repr";
pub KCrate = "crate";
pub KIn = "in";
pub KConst = "const";
pub KWhere = "where";
pub KMut = "mut";
pub KFacet = "facet";
}
impl KWhere {
pub fn span(&self) -> proc_macro2::Span {
self.0.deref().span()
}
}
operator! {
pub Eq = "=";
pub Semi = ";";
pub Apostrophe = "'";
pub DoubleSemicolon = "::";
}
pub type VerbatimUntil<C> = Many<Cons<Except<C>, AngleTokenTree>>;
pub type ModPath = Cons<Option<PathSep>, PathSepDelimited<Ident>>;
pub type Bounds = Cons<Colon, VerbatimUntil<Either<Comma, Eq, Gt>>>;
unsynn! {
#[derive(Clone)]
pub struct AngleTokenTree(
#[allow(clippy::type_complexity)] pub Either<Cons<Lt, Vec<Cons<Except<Gt>, AngleTokenTree>>, Gt>, TokenTree>,
);
pub enum AdtDecl {
Struct(Struct),
Enum(Enum),
}
pub enum Vis {
PubIn(Cons<KPub, ParenthesisGroupContaining<Cons<Option<KIn>, ModPath>>>),
Pub(KPub),
}
pub struct Attribute {
pub _pound: Pound,
pub body: BracketGroupContaining<AttributeInner>,
}
pub enum AttributeInner {
Facet(FacetAttr),
Doc(DocInner),
Repr(ReprInner),
Any(Vec<TokenTree>),
}
pub struct FacetAttr {
pub _facet: KFacet,
pub inner: ParenthesisGroupContaining<CommaDelimitedVec<FacetInner>>,
}
pub enum FacetInner {
Namespaced(NamespacedAttr),
Where(WhereAttr),
Simple(SimpleAttr),
}
pub struct WhereAttr {
pub _kw_where: KWhere,
pub bounds: VerbatimUntil<EndOfStream>,
}
pub struct NamespacedAttr {
pub ns: Ident,
pub _sep: PathSep,
pub key: Ident,
pub args: Option<AttrArgs>,
}
pub struct SimpleAttr {
pub key: Ident,
pub args: Option<AttrArgs>,
}
pub enum AttrArgs {
Parens(ParenthesisGroupContaining<Vec<TokenTree>>),
Equals(AttrEqualsArgs),
}
pub struct AttrEqualsArgs {
pub _eq: Eq,
pub value: VerbatimUntil<Comma>,
}
pub struct DocInner {
pub _kw_doc: KDoc,
pub _eq: Eq,
pub value: LiteralString,
}
pub struct ReprInner {
pub _kw_repr: KRepr,
pub attr: ParenthesisGroupContaining<CommaDelimitedVec<Ident>>,
}
pub struct Struct {
pub attributes: Vec<Attribute>,
pub _vis: Option<Vis>,
pub _kw_struct: KStruct,
pub name: Ident,
pub generics: Option<GenericParams>,
pub kind: StructKind,
}
pub struct GenericParams {
pub _lt: Lt,
pub params: CommaDelimitedVec<GenericParam>,
pub _gt: Gt,
}
pub enum GenericParam {
Lifetime {
name: Lifetime,
bounds: Option<Cons<Colon, VerbatimUntil<Either<Comma, Gt>>>>,
},
Const {
_const: KConst,
name: Ident,
_colon: Colon,
typ: VerbatimUntil<Either<Comma, Gt, Eq>>,
default: Option<Cons<Eq, VerbatimUntil<Either<Comma, Gt>>>>,
},
Type {
name: Ident,
bounds: Option<Bounds>,
default: Option<Cons<Eq, VerbatimUntil<Either<Comma, Gt>>>>,
},
}
#[derive(Clone)]
pub struct WhereClauses {
pub _kw_where: KWhere,
pub clauses: CommaDelimitedVec<WhereClause>,
}
#[derive(Clone)]
pub struct WhereClause {
pub _pred: VerbatimUntil<Cons<Colon, Except<Colon>>>,
pub _colon: Colon,
pub bounds: VerbatimUntil<Either<Comma, Semicolon, BraceGroup>>,
}
pub enum StructKind {
Struct {
clauses: Option<WhereClauses>,
fields: BraceGroupContaining<CommaDelimitedVec<StructField>>,
},
TupleStruct {
fields: ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>,
clauses: Option<WhereClauses>,
semi: Semi,
},
UnitStruct {
clauses: Option<WhereClauses>,
semi: Semi,
},
}
pub struct Lifetime {
pub _apostrophe: PunctJoint<'\''>,
pub name: Ident,
}
pub enum Expr {
Integer(LiteralInteger),
}
pub enum ConstOrMut {
Const(KConst),
Mut(KMut),
}
pub struct StructField {
pub attributes: Vec<Attribute>,
pub _vis: Option<Vis>,
pub name: Ident,
pub _colon: Colon,
pub typ: VerbatimUntil<Comma>,
}
pub struct TupleField {
pub attributes: Vec<Attribute>,
pub vis: Option<Vis>,
pub typ: VerbatimUntil<Comma>,
}
pub struct Enum {
pub attributes: Vec<Attribute>,
pub _vis: Option<Vis>,
pub _kw_enum: KEnum,
pub name: Ident,
pub generics: Option<GenericParams>,
pub clauses: Option<WhereClauses>,
pub body: BraceGroupContaining<CommaDelimitedVec<EnumVariantLike>>,
}
pub struct EnumVariantLike {
pub variant: EnumVariantData,
pub discriminant: Option<Cons<Eq, VerbatimUntil<Comma>>>
}
pub enum EnumVariantData {
Tuple(TupleVariant),
Struct(StructEnumVariant),
Unit(UnitVariant),
}
pub struct UnitVariant {
pub attributes: Vec<Attribute>,
pub name: Ident,
}
pub struct TupleVariant {
pub attributes: Vec<Attribute>,
pub name: Ident,
pub fields: ParenthesisGroupContaining<CommaDelimitedVec<TupleField>>,
}
pub struct StructEnumVariant {
pub attributes: Vec<Attribute>,
pub name: Ident,
pub fields: BraceGroupContaining<CommaDelimitedVec<StructField>>,
}
pub enum LifetimeOrTt {
Lifetime(Lifetime),
TokenTree(TokenTree),
}
}
impl core::fmt::Display for AngleTokenTree {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match &self.0 {
Either::First(it) => {
write!(f, "<")?;
for it in it.second.iter() {
write!(f, "{}", it.second)?;
}
write!(f, ">")?;
}
Either::Second(it) => write!(f, "{it}")?,
Either::Third(Invalid) => unreachable!(),
Either::Fourth(Invalid) => unreachable!(),
};
Ok(())
}
}
pub struct VerbatimDisplay<'a, C>(
pub &'a VerbatimUntil<C>,
);
impl<C> core::fmt::Display for VerbatimDisplay<'_, C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for tt in self.0.iter() {
write!(f, "{}", tt.value.second)?;
}
Ok(())
}
}
impl core::fmt::Display for ConstOrMut {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConstOrMut::Const(_) => write!(f, "const"),
ConstOrMut::Mut(_) => write!(f, "mut"),
}
}
}
impl core::fmt::Display for Lifetime {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "'{}", self.name)
}
}
impl core::fmt::Display for WhereClauses {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "where ")?;
for clause in self.clauses.iter() {
write!(f, "{},", clause.value)?;
}
Ok(())
}
}
impl core::fmt::Display for WhereClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}: {}",
VerbatimDisplay(&self._pred),
VerbatimDisplay(&self.bounds)
)
}
}
impl core::fmt::Display for Expr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Expr::Integer(int) => write!(f, "{}", int.value()),
}
}
}
impl Struct {
pub fn facet_attributes(&self) -> impl Iterator<Item = &FacetInner> {
self.attributes
.iter()
.filter_map(|attr| match &attr.body.content {
AttributeInner::Facet(f) => Some(f.inner.content.as_slice()),
_ => None,
})
.flatten()
.map(|d| &d.value)
}
}
#[derive(Clone)]
pub struct LifetimeName(
pub Ident,
);
impl quote::ToTokens for LifetimeName {
fn to_tokens(&self, tokens: &mut TokenStream) {
use proc_macro2::{Punct, Spacing, TokenTree};
let punct = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
let name = &self.0;
tokens.extend(quote::quote! {
#punct #name
});
}
}
mod renamerule;
pub use renamerule::*;
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_struct_with_field_doc_comments() {
let input = quote! {
#[derive(Facet)]
pub struct User {
#[doc = " The user's unique identifier"]
pub id: u64,
}
};
let mut it = input.to_token_iter();
let parsed = it.parse::<Struct>().expect("Failed to parse struct");
assert_eq!(parsed.name.to_string(), "User");
if let StructKind::Struct { fields, .. } = &parsed.kind {
assert_eq!(fields.content.len(), 1);
let id_field = &fields.content[0].value;
assert_eq!(id_field.name.to_string(), "id");
let mut doc_found = false;
for attr in &id_field.attributes {
match &attr.body.content {
AttributeInner::Doc(doc_inner) => {
assert_eq!(doc_inner.value, " The user's unique identifier");
doc_found = true;
}
_ => {
}
}
}
assert!(doc_found, "Should have found a doc comment");
} else {
panic!("Expected a regular struct with named fields");
}
}
}