1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#![warn(rust_2018_idioms, unused_must_use)] // never `forbid` for forward compatibility!

//! This is a libary for making type-level programming more ergonomic.
//! With the attribute `tylift`, one can lift variants of an `enum` to the type-level.
//!
//! ## `cargo` Features
//!
//! The feature-flag `span_errors` drastically improves error messages by taking
//! advantage of the span information of a token. It uses the experimental feature
//! `proc_macro_diagnostic` and thus requires a nightly `rustc`.

#![cfg_attr(feature = "span_errors", feature(proc_macro_diagnostic))]

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{parse_macro_input, Fields, Ident, ItemEnum};

fn identifier(identifier: &str) -> Ident {
    Ident::new(identifier, Span::mixed_site())
}

fn module(vis: &syn::Visibility, name: Ident, content: TokenStream2) -> TokenStream2 {
    quote! { #vis mod #name { #content } }
}

macro_rules! report {
    ($location:expr, $message:literal $(, $continuation:tt )?) => {
        #[cfg(feature = "span_errors")]
        {
            $location
                .span()
                .unwrap()
                .error($message)
                .emit();
            $( $continuation )?
        }
        #[cfg(not(feature = "span_errors"))]
        return syn::parse::Error::new_spanned(&$location, $message)
            .into_compile_error()
            .into();
    }
}

/// The attribute promotes enum variants to their own types.
/// The enum type becomes a [_kind_](https://en.wikipedia.org/wiki/Kind_(type_theory))
/// – the type of a type – emulated by a trait, replacing the original type declaration.
/// In Rust, the syntax of trait bounds (`:`) beautifully mirror the syntax of type annotations.
/// Thus, the snippet `B: Bool` can also be read as "type parameter `B` of kind `Bool`".
///
/// Traits representing kinds are _sealed_, which means nobody is able to add new types to
/// the kind. Variants can hold (unnamed) fields of types of a given kind.
/// Attributes (notably documentation comments) applied to the item itself and its variants will
/// be preserved. Expanded code works in `#![no_std]`-environments.
///
/// As of right now, there is no automated way to _reify_ the lifted variants (i.e. map them to
/// their term-level counterpart). Lifted enum types can _not_ be generic over kinds.
/// Attributes placed in front of fields of a variant (constructor arguments) will not be translated
/// and thus have no effect.
///
/// Examples:
///
/// ```
/// use tylift::tylift;
/// use std::marker::PhantomData;
///
/// #[tylift]
/// pub enum Mode {
///     Safe,
///     Fast,
/// }
///
/// pub struct Text<M: Mode> {
///     content: String,
///     _marker: PhantomData<M>,
/// }
///
/// impl<M: Mode> Text<M> {
///     pub fn into_inner(self) -> String {
///         self.content
///     }
/// }
///
/// impl Text<Safe> {
///     pub fn from(content: Vec<u8>) -> Option<Self> {
///         Some(Self {
///             content: String::from_utf8(content).ok()?,
///             _marker: PhantomData,
///         })
///     }
/// }
///
/// impl Text<Fast> {
///     pub unsafe fn from(content: Vec<u8>) -> Self {
///         Self {
///             content: unsafe { String::from_utf8_unchecked(content) },
///             _marker: PhantomData,
///         }
///     }
/// }
///
/// fn main() {
///     let safe = Text::<Safe>::from(vec![0x73, 0x61, 0x66, 0x65]);
///     let fast = unsafe { Text::<Fast>::from(vec![0x66, 0x61, 0x73, 0x74]) };
///     assert_eq!(safe.map(Text::into_inner), Some("safe".to_owned()));
///     assert_eq!(fast.into_inner(), "fast".to_owned());
/// }
///
/// #[tylift]
/// pub enum Bool {
///     False,
///     True,
/// }
///
/// #[tylift]
/// pub(crate) enum Nat {
///     Zero,
///     Succ(Nat),
/// }
///
/// #[tylift]
/// enum BinaryTree {
///     Leaf,
///     Branch(BinaryTree, Nat, BinaryTree),
/// }
///
/// #[tylift(mod)] // put the all 3 items into the module `Power`
/// pub enum Power {
///     On,
///     Off,
/// }
///
/// #[tylift(mod direction)] // put all 3 items into the module `direction`
/// pub(crate) enum Direction {
///     /// Higher and higher!
///     Up,
///     /// Lower and lower...
///     Down,
/// }
/// ```
#[proc_macro_attribute]
pub fn tylift(attr: TokenStream, item: TokenStream) -> TokenStream {
    let arguments = match Arguments::parse(attr) {
        Ok(arguments) => arguments,
        // task: use report macro instead. blocker: Arguments::parse not being compatible
        // with syn/proc_macro2, needs to be adapted first
        Err(_span) => panic!("invalid arguments"),
    };
    let scoped = arguments.scope.is_some();

    let item = parse_macro_input!(item as ItemEnum);

    if !item.generics.params.is_empty() {
        #[allow(unused_imports)]
        use syn::spanned::Spanned;
        report!(
            item.generics.params,
            "type parameters cannot be lifted to the kind-level"
        );
    }

    let mut variants = Vec::new();

    for variant in &item.variants {
        if variant.ident == item.ident {
            report!(
                variant.ident,
                "name of variant matches name of enum",
                continue
            );
        }
        let mut field_names = Vec::new();
        let mut field_types = Vec::new();
        match &variant.fields {
            Fields::Named(_) => {
                report!(
                    variant.ident,
                    "variant must not have named fields",
                    continue
                );
            }
            Fields::Unnamed(unnamed) => {
                for (index, field) in unnamed.unnamed.iter().enumerate() {
                    field_names.push(identifier(&format!("T{}", index)));
                    field_types.push(&field.ty);
                }
            }
            _ => {}
        }
        variants.push((&variant.attrs, &variant.ident, field_names, field_types));
    }

    let attributes = &item.attrs;
    let visibility = &item.vis;
    let kind = &item.ident;
    let clause = item.generics.where_clause;

    let kind_module = match arguments.scope {
        // note: obviously, we'd like to have `Span::def_site()` to fully hide this module from outsiders
        // but it's still unstable unfortunately :/
        None => identifier(&format!("__kind_{}", kind)),
        Some(None) => kind.clone(),
        // no From impl exists...
        Some(Some(ident)) => identifier(&ident.to_string()),
    };
    let sealed_module = identifier("sealed");
    let sealed_trait = identifier("Sealed");

    let mut output_stream = quote! {};
    if !scoped {
        output_stream.extend(quote! { #visibility use #kind_module::*; });
    };
    let mut kind_module_stream = quote! {
        use super::*;
        #(#attributes)*
        pub trait #kind: #sealed_module::#sealed_trait #clause {}
    };
    let mut sealed_module_stream = quote! {
        use super::*;
        pub trait #sealed_trait {}
    };
    for (attributes, name, field_names, field_types) in &variants {
        let &attributes = attributes;
        let parameters = quote! { <#(#field_names: #field_types),*> };
        let arguments = quote! { <#(#field_names),*> };
        kind_module_stream.extend(quote! {
            #(#attributes)*
            pub struct #name #parameters (::core::marker::PhantomData <(#(#field_names),*)>);
            impl #parameters #kind for #name #arguments {}
        });
        sealed_module_stream.extend(quote! {
            impl #parameters #sealed_trait for #name #arguments {}
        });
    }
    kind_module_stream.extend(module(
        &syn::Visibility::Inherited,
        sealed_module,
        sealed_module_stream,
    ));
    output_stream.extend(module(visibility, kind_module, kind_module_stream));
    output_stream.into()
}

#[derive(Default)]
struct Arguments {
    /// The module under which the kind and its inhabitants live/are scoped.
    scope: Option<Option<proc_macro::Ident>>,
}

impl Arguments {
    // we should probably use the syn::parse::Parse API
    fn parse(tokens: TokenStream) -> Result<Self, proc_macro::Span> {
        let mut tokens = tokens.into_iter();
        let mut arguments = Self::default();

        if let Some(token) = tokens.next() {
            use proc_macro::TokenTree;

            if let TokenTree::Ident(identifier) = token {
                if identifier.to_string() != "mod" {
                    return Err(identifier.span());
                }
                arguments.scope = Some(None);
            } else {
                return Err(token.span());
            }

            if let Some(token) = tokens.next() {
                if let TokenTree::Ident(identifier) = token {
                    arguments.scope = Some(Some(identifier));
                } else {
                    return Err(token.span());
                }
            }

            if let Some(token) = tokens.next() {
                return Err(token.span());
            }
        }

        Ok(arguments)
    }
}