summary refs log tree commit diff
path: root/enumn
diff options
context:
space:
mode:
authorDavid Tolnay <dtolnay@chromium.org>2018-12-06 01:58:21 -0800
committerchrome-bot <chrome-bot@chromium.org>2018-12-07 17:35:43 -0800
commitf97991985d6921375eb5a533c49fd7df5a75d2cd (patch)
tree52278731797f91cb0d42b5c81ba545acaaae9b53 /enumn
parent1d4d44a8e229d63aa16d05615ed33100f949863e (diff)
downloadcrosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar.gz
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar.bz2
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar.lz
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar.xz
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.tar.zst
crosvm-f97991985d6921375eb5a533c49fd7df5a75d2cd.zip
macros: Derive macro to generate integer to enum conversion
This CL adds a procedural macro to generate functions for converting a
primitive integer into the corresponding variant of an enum.

Loosely based on https://docs.rs/enum-primitive-derive but implemented
against a newer version of Syn and without the dependency on num-traits.

The generated function is named `n` and has the following signature:

    impl YourEnum {
        pub fn n(value: Repr) -> Option<Self>;
    }

where `Repr` is an integer type of the right size as described in more
detail below.

EXAMPLE

    extern crate enumn;

    #[derive(PartialEq, Debug, enumn::N)]
    enum Status {
        LegendaryTriumph,
        QualifiedSuccess,
        FortuitousRevival,
        IndeterminateStalemate,
        RecoverableSetback,
        DireMisadventure,
        AbjectFailure,
    }

    fn main() {
        let s = Status::n(1);
        assert_eq!(s, Some(Status::QualifiedSuccess));

        let s = Status::n(9);
        assert_eq!(s, None);
    }

SIGNATURE

The generated signature depends on whether the enum has a `#[repr(..)]`
attribute. If a `repr` is specified, the input to `n` will be required
to be of that type.

    #[derive(enumn::N)]
    #[repr(u8)]
    enum E {
        /* ... */
    }

    // expands to:
    impl E {
        pub fn n(value: u8) -> Option<Self> {
            /* ... */
        }
    }

On the other hand if no `repr` is specified then we get a signature that
is generic over a variety of possible types.

    impl E {
        pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
            /* ... */
        }
    }

DISCRIMINANTS

The conversion respects explictly specified enum discriminants. Consider
this enum:

    #[derive(enumn::N)]
    enum Letter {
        A = 65,
        B = 66,
    }

Here `Letter::n(65)` would return `Some(Letter::A)`.

TEST=`cargo test` against the new crate

Change-Id: I4286a816828c83507b35185fe497455ee30ae9e8
Reviewed-on: https://chromium-review.googlesource.com/1365114
Commit-Ready: David Tolnay <dtolnay@chromium.org>
Tested-by: David Tolnay <dtolnay@chromium.org>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'enumn')
-rw-r--r--enumn/Cargo.toml12
-rw-r--r--enumn/src/lib.rs205
-rw-r--r--enumn/src/tests.rs69
3 files changed, 286 insertions, 0 deletions
diff --git a/enumn/Cargo.toml b/enumn/Cargo.toml
new file mode 100644
index 0000000..84649ca
--- /dev/null
+++ b/enumn/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "enumn"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "0.4"
+quote = "0.6"
+syn = "0.15"
diff --git a/enumn/src/lib.rs b/enumn/src/lib.rs
new file mode 100644
index 0000000..f4c30a7
--- /dev/null
+++ b/enumn/src/lib.rs
@@ -0,0 +1,205 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Convert number to enum.
+//!
+//! This crate provides a derive macro to generate a function for converting a
+//! primitive integer into the corresponding variant of an enum.
+//!
+//! The generated function is named `n` and has the following signature:
+//!
+//! ```rust
+//! # const IGNORE: &str = stringify! {
+//! impl YourEnum {
+//!     pub fn n(value: Repr) -> Option<Self>;
+//! }
+//! # };
+//! ```
+//!
+//! where `Repr` is an integer type of the right size as described in more
+//! detail below.
+//!
+//! # Example
+//!
+//! ```rust
+//! extern crate enumn;
+//!
+//! #[derive(PartialEq, Debug, enumn::N)]
+//! enum Status {
+//!     LegendaryTriumph,
+//!     QualifiedSuccess,
+//!     FortuitousRevival,
+//!     IndeterminateStalemate,
+//!     RecoverableSetback,
+//!     DireMisadventure,
+//!     AbjectFailure,
+//! }
+//!
+//! fn main() {
+//!     let s = Status::n(1);
+//!     assert_eq!(s, Some(Status::QualifiedSuccess));
+//!
+//!     let s = Status::n(9);
+//!     assert_eq!(s, None);
+//! }
+//! ```
+//!
+//! # Signature
+//!
+//! The generated signature depends on whether the enum has a `#[repr(..)]`
+//! attribute. If a `repr` is specified, the input to `n` will be required to be
+//! of that type.
+//!
+//! ```rust
+//! #[derive(enumn::N)]
+//! #[repr(u8)]
+//! enum E {
+//!     /* ... */
+//!     # IGNORE
+//! }
+//!
+//! // expands to:
+//! impl E {
+//!     pub fn n(value: u8) -> Option<Self> {
+//!         /* ... */
+//!         # unimplemented!()
+//!     }
+//! }
+//! ```
+//!
+//! On the other hand if no `repr` is specified then we get a signature that is
+//! generic over a variety of possible types.
+//!
+//! ```rust
+//! # enum E {}
+//! #
+//! impl E {
+//!     pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
+//!         /* ... */
+//!         # unimplemented!()
+//!     }
+//! }
+//! ```
+//!
+//! # Discriminants
+//!
+//! The conversion respects explictly specified enum discriminants. Consider
+//! this enum:
+//!
+//! ```rust
+//! #[derive(enumn::N)]
+//! enum Letter {
+//!     A = 65,
+//!     B = 66,
+//! }
+//! ```
+//!
+//! Here `Letter::n(65)` would return `Some(Letter::A)`.
+
+#![recursion_limit = "128"]
+
+extern crate proc_macro;
+extern crate proc_macro2;
+extern crate quote;
+extern crate syn;
+
+#[cfg(test)]
+mod tests;
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::Error;
+use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Meta, NestedMeta};
+
+fn testable_derive(input: DeriveInput) -> proc_macro2::TokenStream {
+    let variants = match input.data {
+        Data::Enum(data) => data.variants,
+        Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
+    };
+
+    for variant in &variants {
+        match variant.fields {
+            Fields::Unit => {}
+            Fields::Named(_) | Fields::Unnamed(_) => {
+                let span = variant.ident.span();
+                let err = Error::new(span, "enumn: variant with data is not supported");
+                return err.to_compile_error();
+            }
+        }
+    }
+
+    // Parse repr attribute like #[repr(u16)].
+    let mut repr = None;
+    for attr in input.attrs {
+        if let Ok(Meta::List(list)) = attr.parse_meta() {
+            if list.ident == "repr" {
+                if let Some(NestedMeta::Meta(Meta::Word(word))) = list.nested.into_iter().next() {
+                    match word.to_string().as_str() {
+                        "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32"
+                        | "i64" | "i128" | "isize" => {
+                            repr = Some(word);
+                        }
+                        _ => {}
+                    }
+                }
+            }
+        }
+    }
+
+    let signature;
+    let value;
+    match repr {
+        Some(ref repr) => {
+            signature = quote! {
+                fn n(value: #repr)
+            };
+            value = quote!(value);
+        }
+        None => {
+            repr = Some(parse_quote!(i64));
+            signature = quote! {
+                fn n<REPR: Into<i64>>(value: REPR)
+            };
+            value = quote! {
+                <REPR as Into<i64>>::into(value)
+            };
+        }
+    }
+
+    let ident = input.ident;
+    let declare_discriminants = variants.iter().map(|variant| {
+        let variant = &variant.ident;
+        quote! {
+            const #variant: #repr = #ident::#variant as #repr;
+        }
+    });
+    let match_discriminants = variants.iter().map(|variant| {
+        let variant = &variant.ident;
+        quote! {
+            discriminant::#variant => Some(#ident::#variant),
+        }
+    });
+
+    quote! {
+        impl #ident {
+            pub #signature -> Option<Self> {
+                struct discriminant;
+                impl discriminant {
+                    #(#declare_discriminants)*
+                }
+                match #value {
+                    #(#match_discriminants)*
+                    _ => None,
+                }
+            }
+        }
+    }
+}
+
+#[proc_macro_derive(N)]
+pub fn derive(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let expanded = testable_derive(input);
+    TokenStream::from(expanded)
+}
diff --git a/enumn/src/tests.rs b/enumn/src/tests.rs
new file mode 100644
index 0000000..b7e0cda
--- /dev/null
+++ b/enumn/src/tests.rs
@@ -0,0 +1,69 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use quote::quote;
+use syn::{parse_quote, DeriveInput};
+
+#[test]
+fn test_repr() {
+    let input: DeriveInput = parse_quote! {
+        #[repr(u8)]
+        enum E {
+            A,
+            B,
+            C,
+        }
+    };
+    let actual = ::testable_derive(input);
+    let expected = quote! {
+        impl E {
+            pub fn n(value: u8) -> Option<Self> {
+                struct discriminant;
+                impl discriminant {
+                    const A: u8 = E::A as u8;
+                    const B: u8 = E::B as u8;
+                    const C: u8 = E::C as u8;
+                }
+                match value {
+                    discriminant::A => Some(E::A),
+                    discriminant::B => Some(E::B),
+                    discriminant::C => Some(E::C),
+                    _ => None,
+                }
+            }
+        }
+    };
+    assert_eq!(actual.to_string(), expected.to_string());
+}
+
+#[test]
+fn test_no_repr() {
+    let input: DeriveInput = parse_quote! {
+        enum E {
+            A,
+            B,
+            C,
+        }
+    };
+    let actual = ::testable_derive(input);
+    let expected = quote! {
+        impl E {
+            pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
+                struct discriminant;
+                impl discriminant {
+                    const A: i64 = E::A as i64;
+                    const B: i64 = E::B as i64;
+                    const C: i64 = E::C as i64;
+                }
+                match <REPR as Into<i64>>::into(value) {
+                    discriminant::A => Some(E::A),
+                    discriminant::B => Some(E::B),
+                    discriminant::C => Some(E::C),
+                    _ => None,
+                }
+            }
+        }
+    };
+    assert_eq!(actual.to_string(), expected.to_string());
+}