Skip to content

Commit

Permalink
Update to syn 2
Browse files Browse the repository at this point in the history
Co-Authored-By: John Baublitz <[email protected]>
  • Loading branch information
noamteyssier and jbaublitz committed Sep 10, 2024
1 parent 863406d commit 0eacb31
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 108 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock
*.swp
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Getset, we're ready to go!
A procedural macro for generating the most basic getters and setters on fields.
"""
version = "0.1.2"
version = "0.1.3"
authors = ["Ana Hobden <[email protected]>", "John Baublitz <[email protected]"]
license = "MIT"
edition = "2018"
Expand All @@ -20,6 +20,6 @@ proc-macro = true

[dependencies]
quote = "1"
syn = "1"
syn = "2"
proc-macro2 = { version = "1", default-features = false }
proc-macro-error = "1.0"
proc-macro-error2 = "2"
117 changes: 61 additions & 56 deletions src/generate.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, Span};
use proc_macro_error::{abort, ResultExt};
use syn::{self, ext::IdentExt, spanned::Spanned, Field, Lit, Meta, MetaNameValue, Visibility};
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use proc_macro_error2::abort;
use syn::{
self, ext::IdentExt, spanned::Spanned, Expr, Field, Lit, Meta, MetaNameValue, Visibility,
};

use self::GenMode::*;
use self::GenMode::{Get, GetCopy, GetMut, Set};
use super::parse_attr;

pub struct GenParams {
Expand Down Expand Up @@ -51,65 +52,74 @@ impl GenMode {
}
}

pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> {
match attr {
// `#[get = "pub"]` or `#[set = "pub"]`
Some(Meta::NameValue(MetaNameValue {
lit: Lit::Str(ref s),
path,
..
})) => {
if path.is_ident(meta_name) {
s.value().split(' ').find(|v| *v != "with_prefix").map(|v| {
syn::parse_str(v)
.map_err(|e| syn::Error::new(s.span(), e))
.expect_or_abort("invalid visibility found")
})
} else {
None
}
// Helper function to extract string from Expr
fn expr_to_string(expr: &Expr) -> Option<String> {
if let Expr::Lit(expr_lit) = expr {
if let Lit::Str(s) = &expr_lit.lit {
Some(s.value())
} else {
None
}
_ => None,
} else {
None
}
}

// Helper function to parse visibility
fn parse_vis_str(s: &str, span: proc_macro2::Span) -> Visibility {
match syn::parse_str(s) {
Ok(vis) => vis,
Err(e) => abort!(span, "Invalid visibility found: {}", e),
}
}

// Helper function to parse visibility attribute
pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> {
let meta = attr?;
let Meta::NameValue(MetaNameValue { value, path, .. }) = meta else {
return None;
};

if !path.is_ident(meta_name) {
return None;
}

let value_str = expr_to_string(value)?;
let vis_str = value_str.split(' ').find(|v| *v != "with_prefix")?;

Some(parse_vis_str(vis_str, value.span()))
}

/// Some users want legacy/compatability.
/// Some users want legacy/compatibility.
/// (Getters are often prefixed with `get_`)
fn has_prefix_attr(f: &Field, params: &GenParams) -> bool {
let inner = f
.attrs
.iter()
.filter_map(|v| parse_attr(v, params.mode))
.filter(|meta| {
["get", "get_copy"]
.iter()
.any(|ident| meta.path().is_ident(ident))
})
.last();

// Check it the attr includes `with_prefix`
let wants_prefix = |possible_meta: &Option<Meta>| -> bool {
match possible_meta {
Some(Meta::NameValue(meta)) => {
if let Lit::Str(lit_str) = &meta.lit {
// Naive tokenization to avoid a possible visibility mod named `with_prefix`.
lit_str.value().split(' ').any(|v| v == "with_prefix")
} else {
false
}
// helper function to check if meta has `with_prefix` attribute
let meta_has_prefix = |meta: &Meta| -> bool {
if let Meta::NameValue(name_value) = meta {
if let Some(s) = expr_to_string(&name_value.value) {
return s.split(" ").any(|v| v == "with_prefix");
}
_ => false,
}
false
};

// `with_prefix` can either be on the local or global attr
wants_prefix(&inner) || wants_prefix(&params.global_attr)
let field_attr_has_prefix = f
.attrs
.iter()
.filter_map(|attr| parse_attr(attr, params.mode))
.find(|meta| meta.path().is_ident("get") || meta.path().is_ident("get_copy"))
.as_ref()
.map_or(false, meta_has_prefix);

let global_attr_has_prefix = params.global_attr.as_ref().map_or(false, meta_has_prefix);

field_attr_has_prefix || global_attr_has_prefix
}

pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
let field_name = field
.clone()
.ident
.clone()
.unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name"));

let fn_name = if !has_prefix_attr(field, params)
Expand All @@ -136,11 +146,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
};
let ty = field.ty.clone();

let doc = field.attrs.iter().filter(|v| {
v.parse_meta()
.map(|meta| meta.path().is_ident("doc"))
.unwrap_or(false)
});
let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc"));

let attr = field
.attrs
Expand All @@ -151,7 +157,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {

let visibility = parse_visibility(attr.as_ref(), params.mode.name());
match attr {
// Generate nothing for skipped field.
// Generate nothing for skipped field
Some(meta) if meta.path().is_ident("skip") => quote! {},
Some(_) => match params.mode {
GenMode::Get => {
Expand Down Expand Up @@ -192,7 +198,6 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
}
}
},
// Don't need to do anything.
None => quote! {},
}
}
76 changes: 27 additions & 49 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,101 +175,81 @@ impl Foo {
```
*/

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
extern crate proc_macro2;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt};
use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta};
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
use syn::{parse_macro_input, spanned::Spanned, DataStruct, DeriveInput, Meta};

mod generate;
use crate::generate::{GenMode, GenParams};

mod generate;

#[proc_macro_derive(Getters, attributes(get, with_prefix, getset))]
#[proc_macro_error]
pub fn getters(input: TokenStream) -> TokenStream {
// Parse the string representation
let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
let ast = parse_macro_input!(input as DeriveInput);
let params = GenParams {
mode: GenMode::Get,
global_attr: parse_global_attr(&ast.attrs, GenMode::Get),
};

// Build the impl
let gen = produce(&ast, &params);

// Return the generated impl
gen.into()
produce(&ast, &params).into()
}

#[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))]
#[proc_macro_error]
pub fn copy_getters(input: TokenStream) -> TokenStream {
// Parse the string representation
let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
let ast = parse_macro_input!(input as DeriveInput);
let params = GenParams {
mode: GenMode::GetCopy,
global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy),
};

// Build the impl
let gen = produce(&ast, &params);

// Return the generated impl
gen.into()
produce(&ast, &params).into()
}

#[proc_macro_derive(MutGetters, attributes(get_mut, getset))]
#[proc_macro_error]
pub fn mut_getters(input: TokenStream) -> TokenStream {
// Parse the string representation
let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
let ast = parse_macro_input!(input as DeriveInput);
let params = GenParams {
mode: GenMode::GetMut,
global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut),
};

// Build the impl
let gen = produce(&ast, &params);
// Return the generated impl
gen.into()
produce(&ast, &params).into()
}

#[proc_macro_derive(Setters, attributes(set, getset))]
#[proc_macro_error]
pub fn setters(input: TokenStream) -> TokenStream {
// Parse the string representation
let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for setters");
let ast = parse_macro_input!(input as DeriveInput);
let params = GenParams {
mode: GenMode::Set,
global_attr: parse_global_attr(&ast.attrs, GenMode::Set),
};

// Build the impl
let gen = produce(&ast, &params);

// Return the generated impl
gen.into()
produce(&ast, &params).into()
}

fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option<Meta> {
attrs
.iter()
.filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern
.last()
attrs.iter().filter_map(|v| parse_attr(v, mode)).last()
}

fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> {
fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<syn::Meta> {
use syn::{punctuated::Punctuated, Token};

if attr.path.is_ident("getset") {
let (last, skip, mut collected) = attr
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.unwrap_or_abort()
if attr.path().is_ident("getset") {
let meta_list =
match attr.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated) {
Ok(list) => list,
Err(e) => abort!(attr.span(), "Failed to parse getset attribute: {}", e),
};

let (last, skip, mut collected) = meta_list
.into_iter()
.inspect(|meta| {
if !(meta.path().is_ident("get")
Expand All @@ -289,8 +269,6 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> {
} else if meta.path().is_ident("skip") {
(last, Some(meta), collected)
} else {
// Store inapplicable item for potential error message
// if used with skip.
collected.push(meta);
(last, skip, collected)
}
Expand All @@ -309,14 +287,14 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> {
);
}
} else {
// If skip is not used, return the last occurrence of matching
// setter/getter, if there is any.
last
}
} else if attr.path().is_ident(mode.name()) {
// If skip is not used, return the last occurrence of matching
// setter/getter, if there is any.
attr.meta.clone().into()
} else {
attr.parse_meta()
.ok()
.filter(|meta| meta.path().is_ident(mode.name()))
None
}
}

Expand Down

0 comments on commit 0eacb31

Please sign in to comment.