• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Support for deriving the `ValueOrd` trait on enums and structs.
2 //!
3 //! This trait is used in conjunction with ASN.1 `SET OF` types to determine
4 //! the lexicographical order of their DER encodings.
5 
6 // TODO(tarcieri): enum support
7 
8 use crate::{FieldAttrs, TypeAttrs};
9 use proc_macro2::TokenStream;
10 use proc_macro_error::abort;
11 use quote::quote;
12 use syn::{DeriveInput, Field, Ident, Lifetime, Variant};
13 
14 /// Derive the `Enumerated` trait for an enum.
15 pub(crate) struct DeriveValueOrd {
16     /// Name of the enum.
17     ident: Ident,
18 
19     /// Lifetime of the struct.
20     lifetime: Option<Lifetime>,
21 
22     /// Fields of structs or enum variants.
23     fields: Vec<ValueField>,
24 
25     /// Type of input provided (`enum` or `struct`).
26     input_type: InputType,
27 }
28 
29 impl DeriveValueOrd {
30     /// Parse [`DeriveInput`].
new(input: DeriveInput) -> Self31     pub fn new(input: DeriveInput) -> Self {
32         let ident = input.ident;
33         let type_attrs = TypeAttrs::parse(&input.attrs);
34 
35         // TODO(tarcieri): properly handle multiple lifetimes
36         let lifetime = input
37             .generics
38             .lifetimes()
39             .next()
40             .map(|lt| lt.lifetime.clone());
41 
42         let (fields, input_type) = match input.data {
43             syn::Data::Enum(data) => (
44                 data.variants
45                     .into_iter()
46                     .map(|variant| ValueField::new_enum(variant, &type_attrs))
47                     .collect(),
48                 InputType::Enum,
49             ),
50             syn::Data::Struct(data) => (
51                 data.fields
52                     .into_iter()
53                     .map(|field| ValueField::new_struct(field, &type_attrs))
54                     .collect(),
55                 InputType::Struct,
56             ),
57             _ => abort!(
58                 ident,
59                 "can't derive `ValueOrd` on this type: \
60                  only `enum` and `struct` types are allowed",
61             ),
62         };
63 
64         Self {
65             ident,
66             lifetime,
67             fields,
68             input_type,
69         }
70     }
71 
72     /// Lower the derived output into a [`TokenStream`].
to_tokens(&self) -> TokenStream73     pub fn to_tokens(&self) -> TokenStream {
74         let ident = &self.ident;
75 
76         // Lifetime parameters
77         // TODO(tarcieri): support multiple lifetimes
78         let lt_params = self
79             .lifetime
80             .as_ref()
81             .map(|lt| vec![lt.clone()])
82             .unwrap_or_default();
83 
84         let mut body = Vec::new();
85 
86         for field in &self.fields {
87             body.push(field.to_tokens());
88         }
89 
90         let body = match self.input_type {
91             InputType::Enum => {
92                 quote! {
93                     #[allow(unused_imports)]
94                     use ::der::ValueOrd;
95                     match (self, other) {
96                         #(#body)*
97                         _ => unreachable!(),
98                     }
99                 }
100             }
101             InputType::Struct => {
102                 quote! {
103                     #[allow(unused_imports)]
104                     use ::der::{DerOrd, ValueOrd};
105 
106                     #(#body)*
107 
108                     Ok(::core::cmp::Ordering::Equal)
109                 }
110             }
111         };
112 
113         quote! {
114             impl<#(#lt_params)*> ::der::ValueOrd for #ident<#(#lt_params)*> {
115                 fn value_cmp(&self, other: &Self) -> ::der::Result<::core::cmp::Ordering> {
116                     #body
117                 }
118             }
119         }
120     }
121 }
122 
123 /// What kind of input was provided (i.e. `enum` or `struct`).
124 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
125 enum InputType {
126     /// Input is an `enum`.
127     Enum,
128 
129     /// Input is a `struct`.
130     Struct,
131 }
132 
133 struct ValueField {
134     /// Name of the field
135     ident: Ident,
136 
137     /// Field-level attributes.
138     attrs: FieldAttrs,
139 
140     is_enum: bool,
141 }
142 
143 impl ValueField {
144     /// Create from an `enum` variant.
new_enum(variant: Variant, type_attrs: &TypeAttrs) -> Self145     fn new_enum(variant: Variant, type_attrs: &TypeAttrs) -> Self {
146         let ident = variant.ident;
147 
148         let attrs = FieldAttrs::parse(&variant.attrs, type_attrs);
149         Self {
150             ident,
151             attrs,
152             is_enum: true,
153         }
154     }
155 
156     /// Create from a `struct` field.
new_struct(field: Field, type_attrs: &TypeAttrs) -> Self157     fn new_struct(field: Field, type_attrs: &TypeAttrs) -> Self {
158         let ident = field
159             .ident
160             .as_ref()
161             .cloned()
162             .unwrap_or_else(|| abort!(&field, "tuple structs are not supported"));
163 
164         let attrs = FieldAttrs::parse(&field.attrs, type_attrs);
165         Self {
166             ident,
167             attrs,
168             is_enum: false,
169         }
170     }
171 
172     /// Lower to [`TokenStream`].
to_tokens(&self) -> TokenStream173     fn to_tokens(&self) -> TokenStream {
174         let ident = &self.ident;
175 
176         if self.is_enum {
177             let binding1 = quote!(Self::#ident(this));
178             let binding2 = quote!(Self::#ident(other));
179             quote! {
180                 (#binding1, #binding2) => this.value_cmp(other),
181             }
182         } else {
183             let mut binding1 = quote!(self.#ident);
184             let mut binding2 = quote!(other.#ident);
185 
186             if let Some(ty) = &self.attrs.asn1_type {
187                 binding1 = ty.encoder(&binding1);
188                 binding2 = ty.encoder(&binding2);
189             }
190 
191             quote! {
192                 match #binding1.der_cmp(&#binding2)? {
193                     ::core::cmp::Ordering::Equal => (),
194                     other => return Ok(other),
195                 }
196             }
197         }
198     }
199 }
200