├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tylift" 3 | version = "0.3.5" 4 | description = "Lift enum variants to the type-level." 5 | license = "MIT" 6 | repository = "https://github.com/fmease/tylift" 7 | authors = ["León Orell Valerian Liehr "] 8 | readme = "README.md" 9 | keywords = ["lift", "type", "kind", "enum", "macro"] 10 | edition = "2018" 11 | 12 | [features] 13 | span_errors = [] 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | # [[test]] 19 | # name = "tests" 20 | # path = "src/tests.rs" 21 | 22 | [dependencies] 23 | proc-macro2 = "1.0.24" 24 | syn = { version = "1.0.63", features = ["full"] } 25 | quote = "1.0.9" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 fmease 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tylift 2 | 3 | [![crate](https://img.shields.io/crates/v/tylift.svg)](https://crates.io/crates/tylift) 4 | [![documentation](https://docs.rs/tylift/badge.svg)](https://docs.rs/tylift) 5 | [![license](https://img.shields.io/github/license/fmease/tylift.svg)](https://crates.io/crates/tylift/) 6 | 7 | Lift enum variants to the type-level simply by adding the attribute `tylift`. This comes in handy for [type-level programming](https://willcrichton.net/notes/type-level-programming/). 8 | 9 | **Important note**: This library provides mechanisms nearly identical to the experimental 10 | feature [const generics](https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md)/[min const genercis](https://github.com/rust-lang/rust/issues/74878) which has not been fully implemented yet. See the respective section below for more information. 11 | 12 | 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`". 13 | 14 | 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. 15 | 16 | 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. 17 | 18 | ## First Example 19 | 20 | ```rust 21 | use tylift::tylift; 22 | use std::marker::PhantomData; 23 | 24 | #[tylift] 25 | pub enum Mode { 26 | Safe, 27 | Fast, 28 | } 29 | 30 | pub struct Text { 31 | content: String, 32 | _marker: PhantomData, 33 | } 34 | 35 | impl Text { 36 | pub fn into_inner(self) -> String { 37 | self.content 38 | } 39 | } 40 | 41 | impl Text { 42 | pub fn from(content: Vec) -> Option { 43 | Some(Self { 44 | content: String::from_utf8(content).ok()?, 45 | _marker: PhantomData, 46 | }) 47 | } 48 | } 49 | 50 | impl Text { 51 | pub unsafe fn from(content: Vec) -> Self { 52 | Self { 53 | content: unsafe { String::from_utf8_unchecked(content) }, 54 | _marker: PhantomData, 55 | } 56 | } 57 | } 58 | 59 | fn main() { 60 | let safe = Text::::from(vec![0x73, 0x61, 0x66, 0x65]); 61 | let fast = unsafe { Text::::from(vec![0x66, 0x61, 0x73, 0x74]) }; 62 | assert_eq!(safe.map(Text::into_inner), Some("safe".to_owned())); 63 | assert_eq!(fast.into_inner(), "fast".to_owned()); 64 | } 65 | ``` 66 | 67 | ## Installation 68 | 69 | Add these lines to your `Cargo.toml`: 70 | 71 | ```toml 72 | [dependencies] 73 | tylift = "0.3.5" 74 | ``` 75 | 76 | Compatibility with older `rustc` versions is currently not verified. Older versions of this crate (≤ 0.3.2) only 77 | relied on features of `rustc` 1.32. So you might want to check them out. 78 | 79 | ### Cargo Features 80 | 81 | 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`. 82 | 83 | ## More Examples 84 | 85 | Code before the macro expansion: 86 | 87 | ```rust 88 | use tylift::tylift; 89 | 90 | #[tylift] 91 | pub enum Bool { 92 | False, 93 | True, 94 | } 95 | 96 | #[tylift] 97 | pub(crate) enum Nat { 98 | Zero, 99 | Succ(Nat), 100 | } 101 | 102 | #[tylift] 103 | enum BinaryTree { 104 | Leaf, 105 | Branch(BinaryTree, Nat, BinaryTree), 106 | } 107 | 108 | #[tylift(mod)] // put all 3 items into the module `Power` 109 | pub enum Power { 110 | On, 111 | Off, 112 | } 113 | 114 | #[tylift(mod direction)] // put all 3 items into the module `direction` 115 | pub(crate) enum Direction { 116 | /// Higher and higher! 117 | Up, 118 | /// Lower and lower... 119 | Down, 120 | } 121 | ``` 122 | 123 | And after expansion below. It's partially [hygienic](https://en.wikipedia.org/wiki/Hygienic_macro); generated identifiers which are unhygienic because of current limitations of the `proc_macro` API are prefixed with double underscores (`__`) to lower the change of name collisions. 124 | 125 | ```rust 126 | use tylift::tylift; 127 | 128 | pub use __kind_Bool::*; 129 | mod __kind_Bool { 130 | use super::*; 131 | pub trait Bool: sealed::Sealed {} 132 | pub struct False(::core::marker::PhantomData<()>); 133 | impl Bool for False {} 134 | pub struct True(::core::marker::PhantomData<()>); 135 | impl Bool for True {} 136 | mod sealed { 137 | use super::*; 138 | pub trait Sealed {} 139 | impl Sealed for False {} 140 | impl Sealed for True {} 141 | } 142 | } 143 | 144 | pub(crate) use __kind_Nat::*; 145 | mod __kind_Nat { 146 | use super::*; 147 | pub trait Nat: sealed::Sealed {} 148 | pub struct Zero(::core::marker::PhantomData<()>); 149 | impl Nat for Zero {} 150 | pub struct Succ(::core::marker::PhantomData<(T0)>); 151 | impl Nat for Succ {} 152 | mod sealed { 153 | use super::*; 154 | pub trait Sealed {} 155 | impl Sealed for Zero {} 156 | impl Sealed for Succ {} 157 | } 158 | } 159 | 160 | use __kind_BinaryTree::*; 161 | mod __kind_BinaryTree { 162 | use super::*; 163 | pub trait BinaryTree: sealed::Sealed {} 164 | pub struct Leaf(::core::marker::PhantomData<()>); 165 | impl BinaryTree for Leaf {} 166 | pub struct Branch( 167 | ::core::marker::PhantomData<(T0, T1, T2)>, 168 | ); 169 | impl BinaryTree for Branch {} 170 | mod sealed { 171 | use super::*; 172 | pub trait Sealed {} 173 | impl Sealed for Leaf {} 174 | impl Sealed for Branch {} 175 | } 176 | } 177 | 178 | pub mod Power { 179 | use super::*; 180 | pub trait Power: sealed::Sealed {} 181 | pub struct On(::core::marker::PhantomData<()>); 182 | impl Power for On {} 183 | pub struct Off(::core::marker::PhantomData<()>); 184 | impl Power for Off {} 185 | mod sealed { 186 | use super::*; 187 | pub trait Sealed {} 188 | impl Sealed for On {} 189 | impl Sealed for Off {} 190 | } 191 | } 192 | 193 | pub(crate) mod direction { 194 | use super::*; 195 | pub trait Direction: sealed::Sealed {} 196 | /// Higher and higher! 197 | pub struct Up(::core::marker::PhantomData<()>); 198 | impl Direction for Up {} 199 | /// Lower and lower... 200 | pub struct Down(::core::marker::PhantomData<()>); 201 | impl Direction for Down {} 202 | mod sealed { 203 | use super::*; 204 | pub trait Sealed {} 205 | impl Sealed for Up {} 206 | impl Sealed for Down {} 207 | } 208 | } 209 | ``` 210 | 211 | ### Manually Writing a Type-Level Function 212 | 213 | Type-level function `Not` from kind `Bool` to `Bool` (kind defined in previous section): 214 | 215 | ```rust 216 | type Not = ::Result; 217 | 218 | trait NotImpl: Bool { type Result: Bool; } 219 | impl NotImpl for False { type Result = True; } 220 | impl NotImpl for True { type Result = False; } 221 | ``` 222 | 223 | Type-level function `Add` from two `Nat`s to `Nat` (kind defined in previous section): 224 | 225 | ```rust 226 | type Add = >::Result; 227 | 228 | trait AddImpl: Nat { type Result: Nat } 229 | impl AddImpl for Zero { type Result = M; } 230 | impl AddImpl for Succ 231 | // where clause necessary because the type system does not know that 232 | // the trait is sealed (only the module system knows) 233 | where N: AddImpl> 234 | { 235 | type Result = Add>; 236 | } 237 | ``` 238 | 239 | ## tylift Versus Const Generics 240 | 241 | **Advantages** of this crate over const generics: 242 | 243 | * recursive kinds which cannot be represented with const generics right now. 244 | The latter would also require _explicit boxing_ 245 | * compatibility with older rust versions 246 | 247 | Obviously, these are not _that_ convincing arguments. Consider this crate as **a study** rather than something of value. Maybe you can learn from its code. 248 | 249 | **Disadvantages**: 250 | 251 | * requires an additional dependency (`tylift`) with a heavy transitive dependency on `syn` 252 | * worse tooling 253 | * atrociously hairy type-level functions compared to `const fn`s which are compatible with const generics, see [const evaluatable checked](https://github.com/rust-lang/rust/issues/76560) 254 | 255 | ## Future Plans 256 | 257 | * replacing the introductery example with something more reasonable 258 | * creating tests 259 | * adding additional features like 260 | * an attribute to lift functions to type-level ones 261 | * generating reification functions 262 | * removing the feature-gate `span_errors` once `proc_macro_diagnostic` becomes stable 263 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms, unused_must_use)] // never `forbid` for forward compatibility! 2 | 3 | //! This is a libary for making type-level programming more ergonomic. 4 | //! With the attribute `tylift`, one can lift variants of an `enum` to the type-level. 5 | //! 6 | //! ## `cargo` Features 7 | //! 8 | //! The feature-flag `span_errors` drastically improves error messages by taking 9 | //! advantage of the span information of a token. It uses the experimental feature 10 | //! `proc_macro_diagnostic` and thus requires a nightly `rustc`. 11 | 12 | #![cfg_attr(feature = "span_errors", feature(proc_macro_diagnostic))] 13 | 14 | use proc_macro::TokenStream; 15 | use proc_macro2::{Span, TokenStream as TokenStream2}; 16 | use quote::quote; 17 | use syn::{parse_macro_input, Fields, Ident, ItemEnum}; 18 | 19 | fn identifier(identifier: &str) -> Ident { 20 | Ident::new(identifier, Span::mixed_site()) 21 | } 22 | 23 | fn module(vis: &syn::Visibility, name: Ident, content: TokenStream2) -> TokenStream2 { 24 | quote! { #vis mod #name { #content } } 25 | } 26 | 27 | macro_rules! report { 28 | ($location:expr, $message:literal $(, $continuation:tt )?) => { 29 | #[cfg(feature = "span_errors")] 30 | { 31 | $location 32 | .span() 33 | .unwrap() 34 | .error($message) 35 | .emit(); 36 | $( $continuation )? 37 | } 38 | #[cfg(not(feature = "span_errors"))] 39 | return syn::parse::Error::new_spanned(&$location, $message) 40 | .into_compile_error() 41 | .into(); 42 | } 43 | } 44 | 45 | /// The attribute promotes enum variants to their own types. 46 | /// The enum type becomes a [_kind_](https://en.wikipedia.org/wiki/Kind_(type_theory)) 47 | /// – the type of a type – emulated by a trait, replacing the original type declaration. 48 | /// In Rust, the syntax of trait bounds (`:`) beautifully mirror the syntax of type annotations. 49 | /// Thus, the snippet `B: Bool` can also be read as "type parameter `B` of kind `Bool`". 50 | /// 51 | /// Traits representing kinds are _sealed_, which means nobody is able to add new types to 52 | /// the kind. Variants can hold (unnamed) fields of types of a given kind. 53 | /// Attributes (notably documentation comments) applied to the item itself and its variants will 54 | /// be preserved. Expanded code works in `#![no_std]`-environments. 55 | /// 56 | /// As of right now, there is no automated way to _reify_ the lifted variants (i.e. map them to 57 | /// their term-level counterpart). Lifted enum types can _not_ be generic over kinds. 58 | /// Attributes placed in front of fields of a variant (constructor arguments) will not be translated 59 | /// and thus have no effect. 60 | /// 61 | /// Examples: 62 | /// 63 | /// ``` 64 | /// use tylift::tylift; 65 | /// use std::marker::PhantomData; 66 | /// 67 | /// #[tylift] 68 | /// pub enum Mode { 69 | /// Safe, 70 | /// Fast, 71 | /// } 72 | /// 73 | /// pub struct Text { 74 | /// content: String, 75 | /// _marker: PhantomData, 76 | /// } 77 | /// 78 | /// impl Text { 79 | /// pub fn into_inner(self) -> String { 80 | /// self.content 81 | /// } 82 | /// } 83 | /// 84 | /// impl Text { 85 | /// pub fn from(content: Vec) -> Option { 86 | /// Some(Self { 87 | /// content: String::from_utf8(content).ok()?, 88 | /// _marker: PhantomData, 89 | /// }) 90 | /// } 91 | /// } 92 | /// 93 | /// impl Text { 94 | /// pub unsafe fn from(content: Vec) -> Self { 95 | /// Self { 96 | /// content: unsafe { String::from_utf8_unchecked(content) }, 97 | /// _marker: PhantomData, 98 | /// } 99 | /// } 100 | /// } 101 | /// 102 | /// fn main() { 103 | /// let safe = Text::::from(vec![0x73, 0x61, 0x66, 0x65]); 104 | /// let fast = unsafe { Text::::from(vec![0x66, 0x61, 0x73, 0x74]) }; 105 | /// assert_eq!(safe.map(Text::into_inner), Some("safe".to_owned())); 106 | /// assert_eq!(fast.into_inner(), "fast".to_owned()); 107 | /// } 108 | /// 109 | /// #[tylift] 110 | /// pub enum Bool { 111 | /// False, 112 | /// True, 113 | /// } 114 | /// 115 | /// #[tylift] 116 | /// pub(crate) enum Nat { 117 | /// Zero, 118 | /// Succ(Nat), 119 | /// } 120 | /// 121 | /// #[tylift] 122 | /// enum BinaryTree { 123 | /// Leaf, 124 | /// Branch(BinaryTree, Nat, BinaryTree), 125 | /// } 126 | /// 127 | /// #[tylift(mod)] // put the all 3 items into the module `Power` 128 | /// pub enum Power { 129 | /// On, 130 | /// Off, 131 | /// } 132 | /// 133 | /// #[tylift(mod direction)] // put all 3 items into the module `direction` 134 | /// pub(crate) enum Direction { 135 | /// /// Higher and higher! 136 | /// Up, 137 | /// /// Lower and lower... 138 | /// Down, 139 | /// } 140 | /// ``` 141 | #[proc_macro_attribute] 142 | pub fn tylift(attr: TokenStream, item: TokenStream) -> TokenStream { 143 | let arguments = match Arguments::parse(attr) { 144 | Ok(arguments) => arguments, 145 | // task: use report macro instead. blocker: Arguments::parse not being compatible 146 | // with syn/proc_macro2, needs to be adapted first 147 | Err(_span) => panic!("invalid arguments"), 148 | }; 149 | let scoped = arguments.scope.is_some(); 150 | 151 | let item = parse_macro_input!(item as ItemEnum); 152 | 153 | if !item.generics.params.is_empty() { 154 | #[allow(unused_imports)] 155 | use syn::spanned::Spanned; 156 | report!( 157 | item.generics.params, 158 | "type parameters cannot be lifted to the kind-level" 159 | ); 160 | } 161 | 162 | let mut variants = Vec::new(); 163 | 164 | for variant in &item.variants { 165 | if variant.ident == item.ident { 166 | report!( 167 | variant.ident, 168 | "name of variant matches name of enum", 169 | continue 170 | ); 171 | } 172 | let mut field_names = Vec::new(); 173 | let mut field_types = Vec::new(); 174 | match &variant.fields { 175 | Fields::Named(_) => { 176 | report!( 177 | variant.ident, 178 | "variant must not have named fields", 179 | continue 180 | ); 181 | } 182 | Fields::Unnamed(unnamed) => { 183 | for (index, field) in unnamed.unnamed.iter().enumerate() { 184 | field_names.push(identifier(&format!("T{}", index))); 185 | field_types.push(&field.ty); 186 | } 187 | } 188 | _ => {} 189 | } 190 | variants.push((&variant.attrs, &variant.ident, field_names, field_types)); 191 | } 192 | 193 | let attributes = &item.attrs; 194 | let visibility = &item.vis; 195 | let kind = &item.ident; 196 | let clause = item.generics.where_clause; 197 | 198 | let kind_module = match arguments.scope { 199 | // note: obviously, we'd like to have `Span::def_site()` to fully hide this module from outsiders 200 | // but it's still unstable unfortunately :/ 201 | None => identifier(&format!("__kind_{}", kind)), 202 | Some(None) => kind.clone(), 203 | // no From impl exists... 204 | Some(Some(ident)) => identifier(&ident.to_string()), 205 | }; 206 | let sealed_module = identifier("sealed"); 207 | let sealed_trait = identifier("Sealed"); 208 | 209 | let mut output_stream = quote! {}; 210 | if !scoped { 211 | output_stream.extend(quote! { #visibility use #kind_module::*; }); 212 | }; 213 | let mut kind_module_stream = quote! { 214 | use super::*; 215 | #(#attributes)* 216 | pub trait #kind: #sealed_module::#sealed_trait #clause {} 217 | }; 218 | let mut sealed_module_stream = quote! { 219 | use super::*; 220 | pub trait #sealed_trait {} 221 | }; 222 | for (attributes, name, field_names, field_types) in &variants { 223 | let &attributes = attributes; 224 | let parameters = quote! { <#(#field_names: #field_types),*> }; 225 | let arguments = quote! { <#(#field_names),*> }; 226 | kind_module_stream.extend(quote! { 227 | #(#attributes)* 228 | pub struct #name #parameters (::core::marker::PhantomData <(#(#field_names),*)>); 229 | impl #parameters #kind for #name #arguments {} 230 | }); 231 | sealed_module_stream.extend(quote! { 232 | impl #parameters #sealed_trait for #name #arguments {} 233 | }); 234 | } 235 | kind_module_stream.extend(module( 236 | &syn::Visibility::Inherited, 237 | sealed_module, 238 | sealed_module_stream, 239 | )); 240 | output_stream.extend(module(visibility, kind_module, kind_module_stream)); 241 | output_stream.into() 242 | } 243 | 244 | #[derive(Default)] 245 | struct Arguments { 246 | /// The module under which the kind and its inhabitants live/are scoped. 247 | scope: Option>, 248 | } 249 | 250 | impl Arguments { 251 | // we should probably use the syn::parse::Parse API 252 | fn parse(tokens: TokenStream) -> Result { 253 | let mut tokens = tokens.into_iter(); 254 | let mut arguments = Self::default(); 255 | 256 | if let Some(token) = tokens.next() { 257 | use proc_macro::TokenTree; 258 | 259 | if let TokenTree::Ident(identifier) = token { 260 | if identifier.to_string() != "mod" { 261 | return Err(identifier.span()); 262 | } 263 | arguments.scope = Some(None); 264 | } else { 265 | return Err(token.span()); 266 | } 267 | 268 | if let Some(token) = tokens.next() { 269 | if let TokenTree::Ident(identifier) = token { 270 | arguments.scope = Some(Some(identifier)); 271 | } else { 272 | return Err(token.span()); 273 | } 274 | } 275 | 276 | if let Some(token) = tokens.next() { 277 | return Err(token.span()); 278 | } 279 | } 280 | 281 | Ok(arguments) 282 | } 283 | } 284 | --------------------------------------------------------------------------------