typenum_uuid/
lib.rs

1//! A crate to generate typesystem-level UUIDs
2//!
3//! By default, all of the macros produce `::typenum::Unsigned`
4//! types.
5//! If `typenum` is located somewhere else, you can append
6//! `| path::to::typenum` to the macro argument, and it will use
7//! that prefix instead:
8//! ```
9//! # use typenum_uuid::uuid_new_v4;
10//! use ::typenum as some_other_name;
11//! type ID = uuid_new_v4!( | some_other_name );
12//! ```
13//! This feature is most useful when exporting macros from a crate
14//! that relies on `typenum`: the UUIDs can be made to use your
15//! crate's re-export of `typenum`, in case your users have an
16//! incompatible version.
17
18use uuid::Uuid;
19use std::iter;
20
21extern crate proc_macro;
22use proc_macro::*;
23
24/// Appends an identifier to a type path
25///
26/// Roughly equivalent to the macro_rules expansion:
27/// ```ignore
28/// $prefix :: $id
29/// ```
30fn prefixed_ident(prefix: &TokenStream, id: &str)->impl Iterator<Item=TokenTree> {
31    prefix.clone().into_iter().chain(
32        vec![
33            Punct::new(':', Spacing::Joint).into(),
34            Punct::new(':', Spacing::Alone).into(),
35            Ident::new(id, Span::call_site()).into()
36        ].into_iter()
37    )
38}
39
40/// A mirror of how `typenum` describes unsigned integers:
41/// recursively with the least significant bit in the outermost
42/// type
43enum TypenumUint {
44    Lsb(Box<TypenumUint>, bool),
45    Term,
46}
47
48impl From<u128> for TypenumUint {
49    fn from(x:u128)->Self {
50        if x == 0 { return Self::Term; }
51        else { Self::Lsb( Box::new(Self::from(x >> 1)), (x & 1) != 0 ) }
52    }
53}
54
55impl TypenumUint {
56    /// Write `self` into `ts`.
57    ///
58    /// `prefix` is the location of the `typenum` crate.
59    fn write_ts(&self, prefix: &TokenStream, ts: &mut TokenStream) {
60        match self {
61            Self::Term => ts.extend(prefixed_ident(prefix, "UTerm")),
62            Self::Lsb(high, bit) => {
63                ts.extend(prefixed_ident(prefix, "UInt"));
64                ts.extend(iter::once::<TokenTree>(
65                    Punct::new('<', Spacing::Alone).into()
66                ));
67                high.write_ts(prefix, ts);
68                ts.extend(iter::once::<TokenTree>(
69                    Punct::new(',', Spacing::Alone).into()
70                ));
71                ts.extend(prefixed_ident(prefix, if *bit { "B1" } else { "B0" }));
72                ts.extend(iter::once::<TokenTree>(
73                    Punct::new('>', Spacing::Alone).into()
74                ));
75            }
76        }
77    }
78}
79
80/// Convert a Uuid object into a TokenStream
81///
82/// The resulting stream contains a type declaration that implements
83/// `typenum::Unsigned`.  If the `i128` feature of `typenum` is enabled,
84/// `<result as Unsigned>::to_u128()` will equal `uuid.as_u128()`
85///
86/// `prefix` should be the path to the `typenum` crate at the macro
87/// expansion point.
88fn uuid_to_tokenstream(uuid: Uuid, prefix: TokenStream)->TokenStream {
89    let mut result = TokenStream::new();
90    TypenumUint::from(uuid.as_u128()).write_ts(&prefix, &mut result);
91    result
92}
93
94/// Separate local from global macro arguments.
95///
96/// The macros in this crate all allow `| path::to::typenum` to be
97/// appended to the regular arguments in order to specify where to
98/// find `typenum`.  This function is responsible for finding and
99/// interpreting this, and using the default value of `::typenum`
100/// if none is given.
101fn split_off_prefix(args: TokenStream) -> (TokenStream, TokenStream) {
102    let mut args = args.into_iter();
103    let local = (&mut args).take_while(
104        |tt| match tt {
105            TokenTree::Punct(ref p) if p.as_char() == '|' => false,
106            _ => true
107        }
108    ).collect();
109    let mut prefix:TokenStream = args.collect();
110    if prefix.is_empty() {
111        let x:Vec<TokenTree> = vec![
112            Punct::new(':', Spacing::Joint).into(),
113            Punct::new(':', Spacing::Alone).into(),
114            Ident::new("typenum", Span::call_site()).into()
115        ];
116        prefix = x.into_iter().collect();
117    }
118    (local, prefix)
119}
120
121/// Construct a new random UUID
122///
123/// This macro constructs a new random (v4) UUID at compile
124/// time and returns it as a `typenum::Unsigned` type.
125/// Tis can be used to enable the type system to perform
126/// a limited form of negative reasoning on type identities.
127///
128/// For example:
129/// ```
130/// #![recursion_limit = "256"]
131/// use typenum::{Unsigned,IsEqual,True,False};
132/// use typenum_uuid::uuid_new_v4;
133///
134/// trait Id { type ID: typenum::Unsigned; }
135///
136/// trait Different<B:Id> {}
137/// impl<A:Id, B:Id> Different<B> for A
138///     where A::ID: IsEqual<B::ID, Output=False> {}
139///
140/// struct T1;
141/// impl Id for T1 { type ID = uuid_new_v4!(); }
142///
143/// struct T2;
144/// impl Id for T2 { type ID = uuid_new_v4!(); }
145///
146/// fn must_be_different<A:Id, B:Id + Different<A>>(a:A, b:B) {};
147///
148/// must_be_different(T1, T2);
149/// // must_be_different(T1, T1);  // Compile Error
150/// ```
151#[proc_macro]
152pub fn uuid_new_v4(args: TokenStream)->TokenStream {
153    let (args, prefix) = split_off_prefix(args);
154    assert!(args.is_empty(), "v4 UUIDs take no arguments");
155    uuid_to_tokenstream(Uuid::new_v4(), prefix)
156}
157
158#[proc_macro]
159pub fn uuid_new_v4_literal(args: TokenStream)->TokenStream {
160    let (args, _prefix) = split_off_prefix(args);
161    assert!(args.is_empty(), "v4 UUIDs take no arguments");
162    TokenTree::Literal(Literal::u128_suffixed(Uuid::new_v4().as_u128())).into()
163}
164
165/// Construct a typenum UUID
166///
167/// This macro parses its argument as a UUID 
168/// and returns it as a `typenum::Unsigned` type:
169///
170/// ```edition2018
171/// # use typenum_uuid::uuid;
172/// type Id = uuid!(a65ff38d-b5b2-48d0-b03a-bdf468523d2e);
173/// ```
174#[proc_macro]
175pub fn uuid(args: TokenStream)->TokenStream {
176    let (args, prefix) = split_off_prefix(args);
177    let args:String = args.to_string()
178        .chars().filter(|c| !c.is_whitespace()).collect();
179    uuid_to_tokenstream(Uuid::parse_str(&*args).unwrap(), prefix)
180}