Skip to content

Commit

Permalink
feat: add new NewType derive
Browse files Browse the repository at this point in the history
There are a number of der types that could benefit from wrapping in a
newtype. For example, `RelativeDistinguishedName` could benefit from
an `impl Display` to convert it to a string representation. But because
it is just `Vec<AttributeTypeAndValue>` we can't really do this.

Having a systematic way to derive newtypes in these cases is thus
beneficial.

Signed-off-by: Nathaniel McCallum <nathaniel@profian.com>
  • Loading branch information
npmccallum committed Feb 26, 2022
1 parent 63215be commit e20bc6b
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
14 changes: 14 additions & 0 deletions der/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ mod asn1_type;
mod attributes;
mod choice;
mod enumerated;
mod new_type;
mod sequence;
mod tag;
mod value_ord;
Expand All @@ -129,6 +130,7 @@ use crate::{
attributes::{FieldAttrs, TypeAttrs, ATTR_NAME},
choice::DeriveChoice,
enumerated::DeriveEnumerated,
new_type::DeriveNewType,
sequence::DeriveSequence,
tag::{Tag, TagMode, TagNumber},
value_ord::DeriveValueOrd,
Expand Down Expand Up @@ -269,3 +271,15 @@ pub fn derive_value_ord(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
DeriveValueOrd::new(input).to_tokens().into()
}

/// Wraps a der type in a newtype.
///
/// The newtype receives implementations of `der::FixedTag`,
/// `der::DecodeValue`, `der::EncodeValue`, `Deref`, `DerefMut`, and
/// bi-directional `From`.
#[proc_macro_derive(NewType)]
#[proc_macro_error]
pub fn derive_new_type(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
DeriveNewType::new(input).to_tokens().into()
}
118 changes: 118 additions & 0 deletions der/derive/src/new_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Support for deriving newtypes.

use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::{Data, DeriveInput, Fields, FieldsUnnamed, Ident, LifetimeDef, Type};

trait PunctuatedExt<T, P> {
fn only(&self) -> Option<&T>;
}

impl<T, P> PunctuatedExt<T, P> for Punctuated<T, P> {
fn only(&self) -> Option<&T> {
let mut iter = self.iter();

let first = iter.next();
if let Some(..) = iter.next() {
return None;
}

first
}
}

pub(crate) struct DeriveNewType {
ident: Ident,
ltime: Vec<LifetimeDef>,
ftype: Type,
}

impl DeriveNewType {
pub fn new(input: DeriveInput) -> Self {
if let Data::Struct(data) = &input.data {
if let Fields::Unnamed(FieldsUnnamed { unnamed, .. }) = &data.fields {
if let Some(field) = unnamed.only() {
return Self {
ident: input.ident.clone(),
ltime: input.generics.lifetimes().cloned().collect(),
ftype: field.ty.clone(),
};
}
}
}

abort!(input, "only derivable on a newtype");
}

pub fn to_tokens(&self) -> TokenStream {
let ident = &self.ident;
let ftype = &self.ftype;
let ltime = &self.ltime;

let (limpl, ltype, param) = match self.ltime.len() {
0 => (quote! { impl }, quote! { #ident }, quote! { '_ }),
_ => (
quote! { impl<#(#ltime)*> },
quote! { #ident<#(#ltime)*> },
quote! { #(#ltime)* },
),
};

quote! {
#limpl From<#ftype> for #ltype {
#[inline]
fn from(value: #ftype) -> Self {
Self(value)
}
}

#limpl From<#ltype> for #ftype {
#[inline]
fn from(value: #ltype) -> Self {
value.0
}
}

#limpl ::core::ops::Deref for #ltype {
type Target = #ftype;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

#limpl ::core::ops::DerefMut for #ltype {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

#limpl ::der::FixedTag for #ltype {
const TAG: ::der::Tag = <#ftype as ::der::FixedTag>::TAG;
}

#limpl ::der::DecodeValue<#param> for #ltype {
fn decode_value(
decoder: &mut ::der::Decoder<#param>,
header: ::der::Header,
) -> ::der::Result<Self> {
Ok(Self(<#ftype as ::der::DecodeValue>::decode_value(decoder, header)?))
}
}

#limpl ::der::EncodeValue for #ltype {
fn encode_value(&self, encoder: &mut ::der::Encoder<'_>) -> ::der::Result<()> {
self.0.encode_value(encoder)
}

fn value_len(&self) -> ::der::Result<::der::Length> {
self.0.value_len()
}
}
}
}
}
35 changes: 35 additions & 0 deletions der/tests/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,38 @@ mod sequence {
);
}
}

mod new_type {
use der::{asn1::BitString, Decodable, Encodable};
use der_derive::NewType;

#[derive(NewType)]
struct Lifetime<'a>(BitString<'a>);

#[derive(NewType)]
struct NoLifetime(bool);

#[test]
fn decode() {
let bs = BitString::from_bytes(&[0, 1, 2, 3]).unwrap();
let en = bs.to_vec().unwrap();
let lt = Lifetime::from_der(&en).unwrap();
assert_eq!(bs, lt.into());

let en = true.to_vec().unwrap();
let lt = NoLifetime::from_der(&en).unwrap();
assert!(bool::from(lt));
}

#[test]
fn encode() {
let bs = BitString::from_bytes(&[0, 1, 2, 3]).unwrap();
let en = bs.to_vec().unwrap();
let lt = Lifetime::from(bs).to_vec().unwrap();
assert_eq!(en, lt);

let en = true.to_vec().unwrap();
let lt = NoLifetime::from(true).to_vec().unwrap();
assert_eq!(en, lt);
}
}

0 comments on commit e20bc6b

Please sign in to comment.