Skip to content

Commit

Permalink
start to structure code a bit better
Browse files Browse the repository at this point in the history
  • Loading branch information
GlenDC committed Mar 31, 2024
1 parent 84b0f1c commit 48979b2
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 28 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"

Expand Down
54 changes: 54 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![allow(dead_code)]

use {
proc_macro2::{Span, TokenStream},
quote::ToTokens,
std::cell::RefCell,
};

/// A type for collecting procedural macro errors.
#[derive(Default)]
pub struct Errors {
errors: RefCell<Vec<syn::Error>>,
}

impl Errors {
/// Issue an error relating to a particular `Spanned` structure.
pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {
self.err_span(spanned.span(), msg);
}

/// Issue an error relating to a particular `Span`.
pub fn err_span(&self, span: Span, msg: &str) {
self.push(syn::Error::new(span, msg));
}

/// Issue an error spanning over the given syntax tree node.
pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {
self.push(syn::Error::new_spanned(tokens, msg));
}

/// Push a `syn::Error` onto the list of errors to issue.
pub fn push(&self, err: syn::Error) {
self.errors.borrow_mut().push(err);
}

/// Convert a `syn::Result` to an `Option`, logging the error if present.
pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> {
match r {
Ok(v) => Some(v),
Err(e) => {
self.push(e);
None
}
}
}
}

impl ToTokens for Errors {
/// Convert the errors into tokens that, when emit, will cause
/// the user of the macro to receive compiler errors.
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error()));
}
}
77 changes: 57 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,64 @@
#![forbid(unsafe_code)]

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{Data::Struct, DataStruct, DeriveInput, Fields::Named, FieldsNamed};

#[proc_macro_derive(VennDB)]
pub fn venndb(item: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse2(item.into()).unwrap();

let name_db = format_ident!("{}DB", ast.ident);

let _fields = match ast.data {
Struct(DataStruct {
fields: Named(FieldsNamed { ref named, .. }),
..
}) => named,
_ => {
return syn::Error::new_spanned(ast, "Only Structs with named fields are supported")
.to_compile_error()
.into()
use errors::Errors;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};

mod errors;

/// Entrypoint for `#[derive(VennDB)]`.
#[proc_macro_derive(VennDB, attributes(venndb))]
pub fn venndb(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let gen = impl_from_args(&ast);
gen.into()
}

/// Transform the input into a token stream containing any generated implementations,
/// as well as all errors that occurred.
fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
let errors = &Errors::default();
let mut output_tokens = match &input.data {
syn::Data::Struct(ds) => impl_from_args_struct(errors, &input.ident, &input.generics, ds),
syn::Data::Enum(_) => {
errors.err(input, "`#[derive(VennDB)]` cannot be applied to enums");
TokenStream::new()
}
syn::Data::Union(_) => {
errors.err(input, "`#[derive(VennDB)]` cannot be applied to unions");
TokenStream::new()
}
};
errors.to_tokens(&mut output_tokens);
output_tokens
}

/// Implements `VennDB` for a `#[derive(VennDB)]` struct.
fn impl_from_args_struct(
errors: &Errors,
name: &syn::Ident,
_generic_args: &syn::Generics,
ds: &syn::DataStruct,
) -> TokenStream {
let _fields = match &ds.fields {
syn::Fields::Named(fields) => fields,
syn::Fields::Unnamed(_) => {
errors.err(
&ds.struct_token,
"`#![derive(VennDB)]` is not currently supported on tuple structs",
);
return TokenStream::new();
}
syn::Fields::Unit => {
errors.err(
&ds.struct_token,
"#![derive(VennDB)]` cannot be applied to unit structs",
);
return TokenStream::new();
}
};

let name_db = format_ident!("{}DB", name);

quote! {
#[non_exhaustive]
Expand All @@ -38,5 +76,4 @@ pub fn venndb(item: TokenStream) -> TokenStream {
}
}
}
.into()
}
9 changes: 3 additions & 6 deletions venndb-usage/tests/fails/derive_enum.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
error: Only Structs with named fields are supported
error: `#[derive(VennDB)]` cannot be applied to enums
--> tests/fails/derive_enum.rs:4:1
|
4 | / enum MyEnum {
5 | | A,
6 | | B,
7 | | }
| |_^
4 | enum MyEnum {
| ^^^^
4 changes: 2 additions & 2 deletions venndb-usage/tests/fails/derive_tuple_struct.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Only Structs with named fields are supported
error: `#![derive(VennDB)]` is not currently supported on tuple structs
--> tests/fails/derive_tuple_struct.rs:4:1
|
4 | struct MyStruct(u32);
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^

0 comments on commit 48979b2

Please sign in to comment.