Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #133

Merged
merged 6 commits into from
Oct 8, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions strum_macros/src/helpers/case_style.rs
@@ -1,4 +1,5 @@
use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase};
use syn::Ident;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CaseStyle {
Expand Down Expand Up @@ -53,7 +54,7 @@ pub trait CaseStyleHelpers {
fn convert_case(&self, case_style: Option<CaseStyle>) -> String;
}

impl CaseStyleHelpers for syn::Ident {
impl CaseStyleHelpers for Ident {
fn convert_case(&self, case_style: Option<CaseStyle>) -> String {
let ident_string = self.to_string();
if let Some(case_style) = case_style {
Expand Down Expand Up @@ -86,7 +87,7 @@ impl CaseStyleHelpers for syn::Ident {

#[test]
fn test_convert_case() {
let id = syn::Ident::new("test_me", proc_macro2::Span::call_site());
let id = Ident::new("test_me", proc_macro2::Span::call_site());
assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
}
62 changes: 37 additions & 25 deletions strum_macros/src/helpers/has_metadata.rs
@@ -1,44 +1,56 @@
///Represents a type that can have strum metadata associated with it.
use syn::{Attribute, DeriveInput, Meta, NestedMeta, Variant};

/// Represents a type that can have strum metadata associated with it.
pub trait HasMetadata {
/// Get all the metadata associated with a specific "tag".
/// All of strum's metadata is nested inside a path such as
/// #[strum(...)] so this let's us quickly filter down to only our metadata.
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta>;
fn get_metadata(&self, ident: &str) -> syn::Result<Vec<Meta>>;
}

fn get_metadata_inner<'a>(
ident: &str,
it: impl IntoIterator<Item = &'a syn::Attribute>,
) -> Vec<syn::Meta> {
it.into_iter()
.filter(|attr| attr.path.is_ident(ident))
.map(|attr| attr.parse_meta().unwrap())
.filter_map(|meta| match meta {
syn::Meta::List(syn::MetaList { path, nested, .. }) => {
if path.is_ident(ident) {
Some(nested)
} else {
None
it: impl IntoIterator<Item = &'a Attribute>,
) -> syn::Result<Vec<Meta>> {
let mut res = Vec::new();

for attr in it {
if !attr.path.is_ident(ident) {
continue;
}

let meta = attr.parse_meta()?;
let nested = match meta {
Meta::List(syn::MetaList { nested, .. }) => nested,
_ => {
return Err(syn::Error::new_spanned(
meta,
"unrecognized strum attribute form",
))
}
};

for nested_meta in nested {
match nested_meta {
NestedMeta::Meta(meta) => res.push(meta),
NestedMeta::Lit(lit) => {
return Err(syn::Error::new_spanned(lit, "unexpected literal"))
}
}
_ => None,
})
.flat_map(|id| id)
.map(|nested| match nested {
syn::NestedMeta::Meta(meta) => meta,
_ => panic!("unexpected literal parsing strum attributes"),
})
.collect()
}
}

Ok(res)
}

impl HasMetadata for syn::Variant {
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta> {
impl HasMetadata for Variant {
fn get_metadata(&self, ident: &str) -> syn::Result<Vec<Meta>> {
get_metadata_inner(ident, &self.attrs)
}
}

impl HasMetadata for syn::DeriveInput {
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta> {
impl HasMetadata for DeriveInput {
fn get_metadata(&self, ident: &str) -> syn::Result<Vec<Meta>> {
get_metadata_inner(ident, &self.attrs)
}
}
54 changes: 27 additions & 27 deletions strum_macros/src/helpers/meta_helpers.rs
@@ -1,63 +1,63 @@
use syn::{Meta, MetaList, NestedMeta};
use syn::{Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path};

pub trait MetaHelpers {
fn expect_metalist(&self, msg: &str) -> &MetaList;
fn expect_path(&self, msg: &str) -> &syn::Path;
fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue;
fn expect_metalist(&self, msg: &str) -> syn::Result<&MetaList>;
fn expect_path(&self, msg: &str) -> syn::Result<&Path>;
fn expect_namevalue(&self, msg: &str) -> syn::Result<&MetaNameValue>;
}

impl MetaHelpers for syn::Meta {
fn expect_metalist(&self, msg: &str) -> &MetaList {
impl MetaHelpers for Meta {
fn expect_metalist(&self, msg: &str) -> syn::Result<&MetaList> {
match self {
Meta::List(list) => list,
_ => panic!("{}", msg),
Meta::List(list) => Ok(list),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}

fn expect_path(&self, msg: &str) -> &syn::Path {
fn expect_path(&self, msg: &str) -> syn::Result<&Path> {
match self {
Meta::Path(path) => path,
_ => panic!("{}", msg),
Meta::Path(path) => Ok(path),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}

fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue {
fn expect_namevalue(&self, msg: &str) -> syn::Result<&MetaNameValue> {
match self {
Meta::NameValue(pair) => pair,
_ => panic!("{}", msg),
Meta::NameValue(pair) => Ok(pair),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}
}

pub trait NestedMetaHelpers {
fn expect_meta(&self, msg: &str) -> &syn::Meta;
fn expect_lit(&self, msg: &str) -> &syn::Lit;
fn expect_meta(&self, msg: &str) -> syn::Result<&Meta>;
fn expect_lit(&self, msg: &str) -> syn::Result<&Lit>;
}

impl NestedMetaHelpers for NestedMeta {
fn expect_meta(&self, msg: &str) -> &Meta {
fn expect_meta(&self, msg: &str) -> syn::Result<&Meta> {
match self {
syn::NestedMeta::Meta(m) => m,
_ => panic!("{}", msg),
NestedMeta::Meta(m) => Ok(m),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}
fn expect_lit(&self, msg: &str) -> &syn::Lit {
fn expect_lit(&self, msg: &str) -> syn::Result<&Lit> {
match self {
syn::NestedMeta::Lit(l) => l,
_ => panic!("{}", msg),
NestedMeta::Lit(l) => Ok(l),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}
}

pub trait LitHelpers {
fn expect_string(&self, msg: &str) -> String;
fn expect_string(&self, msg: &str) -> syn::Result<String>;
}

impl LitHelpers for syn::Lit {
fn expect_string(&self, msg: &str) -> String {
impl LitHelpers for Lit {
fn expect_string(&self, msg: &str) -> syn::Result<String> {
match self {
syn::Lit::Str(s) => s.value(),
_ => panic!("{}", msg),
Lit::Str(s) => Ok(s.value()),
_ => Err(syn::Error::new_spanned(self, msg)),
}
}
}
35 changes: 20 additions & 15 deletions strum_macros/src/helpers/type_props.rs
@@ -1,38 +1,39 @@
use std::convert::From;
use std::default::Default;
use syn::{DeriveInput, Lit, Meta, Path};

use crate::helpers::case_style::CaseStyle;
use crate::helpers::has_metadata::HasMetadata;
use crate::helpers::{MetaHelpers, NestedMetaHelpers};

pub trait HasTypeProperties {
fn get_type_properties(&self) -> StrumTypeProperties;
fn get_type_properties(&self) -> syn::Result<StrumTypeProperties>;
}

#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct StrumTypeProperties {
pub case_style: Option<CaseStyle>,
pub discriminant_derives: Vec<syn::Path>,
pub discriminant_name: Option<syn::Path>,
pub discriminant_others: Vec<syn::Meta>,
pub discriminant_derives: Vec<Path>,
pub discriminant_name: Option<Path>,
pub discriminant_others: Vec<Meta>,
}

impl HasTypeProperties for syn::DeriveInput {
fn get_type_properties(&self) -> StrumTypeProperties {
impl HasTypeProperties for DeriveInput {
fn get_type_properties(&self) -> syn::Result<StrumTypeProperties> {
let mut output = StrumTypeProperties::default();

let strum_meta = self.get_metadata("strum");
let discriminants_meta = self.get_metadata("strum_discriminants");
let strum_meta = self.get_metadata("strum")?;
let discriminants_meta = self.get_metadata("strum_discriminants")?;

for meta in strum_meta {
let meta = match meta {
syn::Meta::NameValue(mv) => mv,
Meta::NameValue(mv) => mv,
_ => panic!("strum on types only supports key-values"),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return an Err here was well?

};

if meta.path.is_ident("serialize_all") {
let style = match meta.lit {
syn::Lit::Str(s) => s.value(),
Lit::Str(s) => s.value(),
_ => panic!("expected string value for 'serialize_all'"),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well.

};

Expand All @@ -48,12 +49,16 @@ impl HasTypeProperties for syn::DeriveInput {

for meta in discriminants_meta {
match meta {
syn::Meta::List(ref ls) => {
Meta::List(ref ls) => {
if ls.path.is_ident("derive") {
let paths = ls
.nested
.iter()
.map(|meta| meta.expect_meta("unexpected literal").path().clone());
.map(|meta| {
let meta = meta.expect_meta("unexpected literal")?;
Ok(meta.path().clone())
})
.collect::<syn::Result<Vec<_>>>()?;

output.discriminant_derives.extend(paths);
} else if ls.path.is_ident("name") {
Expand All @@ -63,8 +68,8 @@ impl HasTypeProperties for syn::DeriveInput {

let value = ls.nested.first().expect("unexpected error");
let name = value
.expect_meta("unexpected literal")
.expect_path("name must be an identifier");
.expect_meta("unexpected literal")?
.expect_path("name must be an identifier")?;

if output.discriminant_name.is_some() {
panic!("multiple occurrences of 'name'");
Expand All @@ -81,6 +86,6 @@ impl HasTypeProperties for syn::DeriveInput {
}
}

output
Ok(output)
}
}
33 changes: 17 additions & 16 deletions strum_macros/src/helpers/variant_props.rs
@@ -1,12 +1,13 @@
use std::collections::HashMap;
use std::default::Default;
use syn::{Ident, Meta, Variant};

use crate::helpers::case_style::{CaseStyle, CaseStyleHelpers};
use crate::helpers::has_metadata::HasMetadata;
use crate::helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers};

pub trait HasStrumVariantProperties {
fn get_variant_properties(&self) -> StrumVariantProperties;
fn get_variant_properties(&self) -> syn::Result<StrumVariantProperties>;
}

#[derive(Clone, Eq, PartialEq, Debug, Default)]
Expand All @@ -18,7 +19,7 @@ pub struct StrumVariantProperties {
pub string_props: HashMap<String, String>,
serialize: Vec<String>,
to_string: Option<String>,
ident: Option<syn::Ident>,
ident: Option<Ident>,
}

impl StrumVariantProperties {
Expand Down Expand Up @@ -58,34 +59,34 @@ impl StrumVariantProperties {
}
}

impl HasStrumVariantProperties for syn::Variant {
fn get_variant_properties(&self) -> StrumVariantProperties {
impl HasStrumVariantProperties for Variant {
fn get_variant_properties(&self) -> syn::Result<StrumVariantProperties> {
let mut output = StrumVariantProperties::default();
output.ident = Some(self.ident.clone());

for meta in self.get_metadata("strum") {
for meta in self.get_metadata("strum")? {
match meta {
syn::Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => {
Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => {
if path.is_ident("message") {
if output.message.is_some() {
panic!("message is set twice on the same variant");
}

output.message = Some(lit.expect_string("expected string"));
output.message = Some(lit.expect_string("expected string")?);
} else if path.is_ident("detailed_message") {
if output.detailed_message.is_some() {
panic!("detailed message set twice on the same variant");
}

output.detailed_message = Some(lit.expect_string("expected string"));
output.detailed_message = Some(lit.expect_string("expected string")?);
} else if path.is_ident("serialize") {
output.serialize.push(lit.expect_string("expected string"));
output.serialize.push(lit.expect_string("expected string")?);
} else if path.is_ident("to_string") {
if output.to_string.is_some() {
panic!("to_string is set twice on the same variant");
}

output.to_string = Some(lit.expect_string("expected string"));
output.to_string = Some(lit.expect_string("expected string")?);
} else if path.is_ident("disabled") {
panic!("this method is deprecated. Prefer #[strum(disabled)] instead of #[strum(disabled=\"true\")]");
} else if path.is_ident("default") {
Expand All @@ -94,7 +95,7 @@ impl HasStrumVariantProperties for syn::Variant {
panic!("unrecognized value in strum(..) attribute");
}
}
syn::Meta::Path(p) => {
Meta::Path(p) => {
if p.is_ident("disabled") {
output.is_disabled = true;
} else if p.is_ident("default") {
Expand All @@ -103,20 +104,20 @@ impl HasStrumVariantProperties for syn::Variant {
panic!("unrecognized value in strum(..) attribute");
}
}
syn::Meta::List(syn::MetaList { path, nested, .. }) => {
Meta::List(syn::MetaList { path, nested, .. }) => {
if path.is_ident("props") {
for p in nested {
let p = p
.expect_meta("unexpected literal found in props")
.expect_namevalue("props must be key-value pairs");
.expect_meta("unexpected literal found in props")?
.expect_namevalue("props must be key-value pairs")?;

let key = p
.path
.get_ident()
.expect("key must be an identifier")
.to_string();

let value = p.lit.expect_string("expected string");
let value = p.lit.expect_string("expected string")?;
output.string_props.insert(key, value);
}
} else {
Expand All @@ -126,6 +127,6 @@ impl HasStrumVariantProperties for syn::Variant {
}
}

output
Ok(output)
}
}