Skip to content
Open
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ debug = []
full = [ "eq", "debug" ]

[dependencies]
thiserror = "2.0"
thisenum-impl = { version = "0.2.2", path = "impl" }
thiserror = "~2.0"
thisenum-impl = { version = "0.2.1", path = "impl" }

[dev-dependencies]

Expand Down
12 changes: 6 additions & 6 deletions impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ full = [ "eq", "debug" ]
proc-macro = true

[dependencies]
syn = "1.0"
paste = "1.0"
quote = "1.0"
unzip-n = "0.1"
thiserror = "1.0"
proc-macro2 = "1.0"
paste = "~1.0"
quote = "~1.0"
unzip-n = "~0.1"
thiserror = "~2.0"
proc-macro2 = "~1.0"
syn = { version = "~2.0", features = ["full", "extra-traits"] }

[dev-dependencies]

Expand Down
72 changes: 72 additions & 0 deletions impl/src/ast/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// --------------------------------------------------
// external
// --------------------------------------------------
use quote::ToTokens;

// --------------------------------------------------
// local
// --------------------------------------------------
use crate::Ctxt;
use crate::symbol;

#[derive(Debug)]
/// Represents struct attribute information
pub(crate) struct Container {
pub armtype: Option<syn::Type>,
}
/// [`Container`] implementation
impl Container {
/// Extract out the `#[armtype(...)]` attributes from a container.
///
/// Only container implemented is struct.
pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self {
// --------------------------------------------------
// init
// --------------------------------------------------
let mut armtype = None;

// --------------------------------------------------
// loop through attrs
// --------------------------------------------------
for attr in &item.attrs {
// --------------------------------------------------
// only look for `armtype`
// --------------------------------------------------
if attr.path() != symbol::ARMTYPE {
continue;
}
// --------------------------------------------------
// parse out `armtype` attributes
// --------------------------------------------------
match &attr.meta {
// --------------------------------------------------
// handle all: `syn::MetaList`
// e.g. `armtype(<..>,),`
// --------------------------------------------------
syn::Meta::List(list) => match symbol::Symbol::from(&list.path) {

symbol::ARMTYPE => match armtype {
Some(_) => cx.error_spanned_by(&list, err!(DuplicateArmtypeEnumAttribute)),
None => armtype = syn::parse2(list.tokens.clone())
.map_err(|err| cx.syn_error(err))
.ok(),
},

_ => cx.error_spanned_by(&list.path, err!(UnknownContainerAttribute(list.path))),
},

_ => cx.error_spanned_by(&attr.meta.path(), err!(MalformedContainerAttribute)),
}
}

// --------------------------------------------------
// return
// --------------------------------------------------
Container {
armtype: armtype.or_else(|| {
cx.error_spanned_by(&item.ident, err!(MissingArmtypeEnumAttribute));
None
})
}
}
}
92 changes: 92 additions & 0 deletions impl/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// --------------------------------------------------
// mods
// --------------------------------------------------
mod variant;
mod container;

// --------------------------------------------------
// re-exports
// --------------------------------------------------
use container::*;

// --------------------------------------------------
// external
// --------------------------------------------------
use syn::{punctuated::Punctuated, Token};

// --------------------------------------------------
// local
// --------------------------------------------------
use crate::Ctxt;

/// A source data structure annotated with `#[derive(Const)]` parsed into an internal representation.
pub(crate) struct MainContainer<'a> {
/// The struct or enum name (without generics).
pub ident: syn::Ident,
/// Attributes on the structure, parsed for `thisenum`.
pub attrs: container::Container,
/// The contents of the enum
pub data: Vec<MainVariant<'a>>,
/// Any generics on the enum
pub generics: &'a syn::Generics,
/// Original input.
pub _original: &'a syn::DeriveInput,
}

/// A variant of an enum.
pub(crate) struct MainVariant<'a> {
pub ident: syn::Ident,
pub attrs: variant::Variant,
pub original: &'a syn::Variant,
}

/// [`MainContainer`] implementation
impl<'a> MainContainer<'a> {
/// Convert the raw [`syn`] ast into a parsed container object, collecting errors in `cx`.
pub fn from_ast(
cx: &Ctxt,
item: &'a syn::DeriveInput,
) -> Option<MainContainer<'a>> {

let attrs = Container::from_ast(cx, item);

let data = match &item.data {
syn::Data::Enum(data) => Some(enum_from_ast(cx, &data.variants)),

syn::Data::Struct(_) => {
cx.error_spanned_by(item, err!(UnsupportedContainer("struct")));
return None;
}

syn::Data::Union(_) => {
cx.error_spanned_by(item, err!(UnsupportedContainer("union")));
return None;
}
}?;

Some(MainContainer {
ident: item.ident.clone(),
attrs,
data,
generics: &item.generics,
_original: item,
})
}
}

#[inline(always)]
fn enum_from_ast<'a>(
cx: &Ctxt,
variants: &'a Punctuated<syn::Variant, Token![,]>,
) -> Vec<MainVariant<'a>> {
variants
.iter()
.map(|variant| {
MainVariant {
ident: variant.ident.clone(),
attrs: variant::Variant::from_ast(cx, variant),
original: variant,
}
})
.collect()
}
72 changes: 72 additions & 0 deletions impl/src/ast/variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// --------------------------------------------------
// external
// --------------------------------------------------
use quote::ToTokens;

// --------------------------------------------------
// local
// --------------------------------------------------
use crate::Ctxt;
use crate::symbol;

#[derive(Debug)]
/// Represents variant attribute information
pub(crate) struct Variant {
pub value: Option<syn::Expr>,
}
/// [`Variant`] implementation
impl Variant {
/// Extract out the `#[value(...)]` attributes from an enum variant.
pub fn from_ast(
cx: &Ctxt,
variant: &syn::Variant,
) -> Self {
let mut result = Self { value: None };
// --------------------------------------------------
// return if no attrs on variant
// --------------------------------------------------
if variant.attrs.is_empty() {
return result;
}

// --------------------------------------------------
// loop through attrs
// --------------------------------------------------
for attr in &variant.attrs {
// --------------------------------------------------
// only look for `value`
// --------------------------------------------------
if attr.path() != symbol::VALUE {
continue;
}
// --------------------------------------------------
// parse out `value` attributes
// --------------------------------------------------
match attr.meta {
// --------------------------------------------------
// handle all: `syn::NameValue`
// e.g. `value = <..>,`
// --------------------------------------------------
syn::Meta::NameValue(ref value) => match symbol::Symbol::from(&value.path) {
symbol::VALUE => match result.value {
Some(_) => cx.error_spanned_by(attr, err!(DuplicateValueVariantAttribute)),
None => result.value = Some(value.value.clone()),
},

_ => cx.error_spanned_by(attr, err!(UnknownVariantAttribute(value.path))),
}

_ => cx.error_spanned_by(attr, err!(MalformedValueAttribute)),
}
}

if result.value.is_none() {
cx.error_spanned_by(variant, err!(MissingValueVariantAttribute));
}

// --------------------------------------------------
// return
// --------------------------------------------------
result
}
}
69 changes: 69 additions & 0 deletions impl/src/ctxt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Taken from: [https://github.com/serde-rs/serde/blob/930401b0dd58a809fce34da091b8aa3d6083cb33/serde_derive/src/internals/ctxt.rs]

use quote::ToTokens;
use std::cell::RefCell;
use std::fmt::Display;

/// A type to collect errors together and format them.
///
/// Dropping this object will cause a panic. It must be consumed using `check`.
///
/// References can be shared since this type uses run-time exclusive mut checking.
#[derive(Default)]
pub(crate) struct Ctxt {
// The contents will be set to `None` during checking. This is so that checking can be
// enforced.
errors: RefCell<Option<Vec<syn::Error>>>,
}

impl Ctxt {
/// Create a new context object.
///
/// This object contains no errors, but will still trigger a panic if it is not `check`ed.
pub fn new() -> Self {
Ctxt {
errors: RefCell::new(Some(Vec::new())),
}
}

/// Add an error to the context object with a tokenenizable object.
///
/// The object is used for spanning in error messages.
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
self.errors
.borrow_mut()
.as_mut()
.unwrap()
// Curb monomorphization from generating too many identical methods.
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
}

/// Add one of Syn's parse errors.
pub fn syn_error(&self, err: syn::Error) {
self.errors.borrow_mut().as_mut().unwrap().push(err);
}

/// Consume this object, producing a formatted error string if there are errors.
pub fn check(self) -> syn::Result<()> {
let mut errors = self.errors.borrow_mut().take().unwrap().into_iter();

let mut combined = match errors.next() {
Some(first) => first,
None => return Ok(()),
};

for rest in errors {
combined.combine(rest);
}

Err(combined)
}
}

impl Drop for Ctxt {
fn drop(&mut self) {
if !std::thread::panicking() && self.errors.borrow().is_some() {
panic!("forgot to check for errors");
}
}
}
49 changes: 49 additions & 0 deletions impl/src/err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Taken from `tinyklv`
#[doc(hidden)]
/// A quick way to add enum variants of [`crate::Error`] to the [`crate::Ctxt`]
/// error or [`syn::Error`] by transforming it into a [`str`].
macro_rules! err {
// --------------------------------------------------
// 1+ expr; 1+ literals
// --------------------------------------------------
($variant:ident($($expr:expr),* ; $($litstr:literal),*)) => {
$crate::Error::$variant(
$($expr.to_token_stream().to_string()),*,
$($litstr.to_string()),*
).as_str()
};

// --------------------------------------------------
// 1+ literals
// --------------------------------------------------
($variant:ident($($litstr:literal),*)) => {
$crate::Error::$variant(
$($litstr.to_string()),*
).as_str()
};

// --------------------------------------------------
// 1+ expressions
// --------------------------------------------------
($variant:ident($($expr:expr),*)) => {
$crate::Error::$variant(
$($expr.to_token_stream().to_string()),*
).as_str()
};

// --------------------------------------------------
// @String operator
// --------------------------------------------------
($variant:ident(@String $expr:expr)) => {
$crate::Error::$variant(
$expr
).as_str()
};

// --------------------------------------------------
// no arguments
// --------------------------------------------------
($variant:ident) => {
$crate::Error::$variant.as_str()
};
}
Loading