blob: 97e37cae5048d59209db3438b4365e901801ed19 [file] [log] [blame]
//! Support for deriving the `ValueOrd` trait on enums and structs.
//!
//! This trait is used in conjunction with ASN.1 `SET OF` types to determine
//! the lexicographical order of their DER encodings.
// TODO(tarcieri): enum support
use crate::{FieldAttrs, TypeAttrs};
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use syn::{DeriveInput, Field, Ident, Lifetime, Variant};
/// Derive the `Enumerated` trait for an enum.
pub(crate) struct DeriveValueOrd {
/// Name of the enum.
ident: Ident,
/// Lifetime of the struct.
lifetime: Option<Lifetime>,
/// Fields of structs or enum variants.
fields: Vec<ValueField>,
/// Type of input provided (`enum` or `struct`).
input_type: InputType,
}
impl DeriveValueOrd {
/// Parse [`DeriveInput`].
pub fn new(input: DeriveInput) -> Self {
let ident = input.ident;
let type_attrs = TypeAttrs::parse(&input.attrs);
// TODO(tarcieri): properly handle multiple lifetimes
let lifetime = input
.generics
.lifetimes()
.next()
.map(|lt| lt.lifetime.clone());
let (fields, input_type) = match input.data {
syn::Data::Enum(data) => (
data.variants
.into_iter()
.map(|variant| ValueField::new_enum(variant, &type_attrs))
.collect(),
InputType::Enum,
),
syn::Data::Struct(data) => (
data.fields
.into_iter()
.map(|field| ValueField::new_struct(field, &type_attrs))
.collect(),
InputType::Struct,
),
_ => abort!(
ident,
"can't derive `ValueOrd` on this type: \
only `enum` and `struct` types are allowed",
),
};
Self {
ident,
lifetime,
fields,
input_type,
}
}
/// Lower the derived output into a [`TokenStream`].
pub fn to_tokens(&self) -> TokenStream {
let ident = &self.ident;
// Lifetime parameters
// TODO(tarcieri): support multiple lifetimes
let lt_params = self
.lifetime
.as_ref()
.map(|lt| vec![lt.clone()])
.unwrap_or_default();
let mut body = Vec::new();
for field in &self.fields {
body.push(field.to_tokens());
}
let body = match self.input_type {
InputType::Enum => {
quote! {
#[allow(unused_imports)]
use ::der::ValueOrd;
match (self, other) {
#(#body)*
_ => unreachable!(),
}
}
}
InputType::Struct => {
quote! {
#[allow(unused_imports)]
use ::der::{DerOrd, ValueOrd};
#(#body)*
Ok(::core::cmp::Ordering::Equal)
}
}
};
quote! {
impl<#(#lt_params)*> ::der::ValueOrd for #ident<#(#lt_params)*> {
fn value_cmp(&self, other: &Self) -> ::der::Result<::core::cmp::Ordering> {
#body
}
}
}
}
}
/// What kind of input was provided (i.e. `enum` or `struct`).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum InputType {
/// Input is an `enum`.
Enum,
/// Input is a `struct`.
Struct,
}
struct ValueField {
/// Name of the field
ident: Ident,
/// Field-level attributes.
attrs: FieldAttrs,
is_enum: bool,
}
impl ValueField {
/// Create from an `enum` variant.
fn new_enum(variant: Variant, type_attrs: &TypeAttrs) -> Self {
let ident = variant.ident;
let attrs = FieldAttrs::parse(&variant.attrs, type_attrs);
Self {
ident,
attrs,
is_enum: true,
}
}
/// Create from a `struct` field.
fn new_struct(field: Field, type_attrs: &TypeAttrs) -> Self {
let ident = field
.ident
.as_ref()
.cloned()
.unwrap_or_else(|| abort!(&field, "tuple structs are not supported"));
let attrs = FieldAttrs::parse(&field.attrs, type_attrs);
Self {
ident,
attrs,
is_enum: false,
}
}
/// Lower to [`TokenStream`].
fn to_tokens(&self) -> TokenStream {
let ident = &self.ident;
if self.is_enum {
let binding1 = quote!(Self::#ident(this));
let binding2 = quote!(Self::#ident(other));
quote! {
(#binding1, #binding2) => this.value_cmp(other),
}
} else {
let mut binding1 = quote!(self.#ident);
let mut binding2 = quote!(other.#ident);
if let Some(ty) = &self.attrs.asn1_type {
binding1 = ty.encoder(&binding1);
binding2 = ty.encoder(&binding2);
}
quote! {
match #binding1.der_cmp(&#binding2)? {
::core::cmp::Ordering::Equal => (),
other => return Ok(other),
}
}
}
}
}