typeof_literal/
lib.rs

1/*!
2`typeof_literal` is a macro that can get the type of any literal or
3composite-of-literals expression. It can be used to help with trait
4implementations or function definitions that are generated by macros, where the
5macro has access to some literal value but still needs to name the type in a
6function return or trait associated type.
7
8# Example
9
10```
11use typeof_literal::typeof_literal;
12
13/// This macro creates a `const fn` called `$name` that returns `$value`.
14macro_rules! maker {
15    ($name:ident => $value:expr) => {
16        const fn $name() -> typeof_literal!{$value} {
17            $value
18        }
19    }
20}
21
22maker! {ten => 10i16}
23let value: i16 = ten();
24assert_eq!(value, 10);
25
26maker! {hello_world => "Hello, World"}
27assert_eq!(hello_world(), "Hello, World");
28
29// It works on composite types
30maker! {tuple => (123, [0u8; 2], [1i64, 2, 3, 4], "Hello")};
31assert_eq!(tuple(), (123i32, [0, 0], [1, 2, 3, 4], "Hello"));
32
33// It also works on common macro literal-ish expressions
34maker! {concatenated => concat!(1, 2, 3, "abc")}
35assert_eq!(concatenated(), "123abc");
36
37// We support blocks and other nested expressions, and can use `as` to support
38// *any* arbitrary expression
39maker! {computed => {
40    let x = 1;
41    let y = 2;
42    (x + y) as i32
43}}
44assert_eq!(computed(), 3);
45```
46
47In these examples, the `maker` macro is used to create a function that returns
48a literal value. `typeof_literal` allows us to automatically know the type of
49that literal, so that we can use it for the function's return type, instead of
50forcing the caller to include it with something like
51`maker!(int => 123 as i64)`.
52
53Currently it only works on primitive literals and composites of literals. In
54the future we may add support for simple binary operations like `1 + 2`, struct
55literals, etc.
56*/
57
58use std::fmt::{self, Display, Formatter};
59
60use proc_macro::TokenStream;
61use proc_macro2::Span;
62use quote::{format_ident, quote};
63use syn::{
64    parse_macro_input, spanned::Spanned, Expr, ExprBlock, ExprCast, ExprGroup, ExprParen,
65    ExprUnsafe, Lit, Stmt,
66};
67
68struct Error {
69    location: Span,
70    kind: ErrorKind,
71}
72
73enum ErrorKind {
74    UnsupportedExpression,
75    EmptyArray,
76    UnrecognizedLiteral,
77}
78
79impl Display for ErrorKind {
80    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81        match *self {
82            ErrorKind::UnsupportedExpression => write!(
83                f,
84                "`typeof_literal` only works on literals and trivial composites of literals"
85            ),
86            ErrorKind::EmptyArray => write!(f, "can't determine the type of an empty array"),
87            ErrorKind::UnrecognizedLiteral => {
88                write!(f, "this is a literal but syn couldn't identify it")
89            }
90        }
91    }
92}
93
94fn default_str<'a>(input: &'a str, default: &'a str) -> &'a str {
95    match input {
96        "" => default,
97        input => input,
98    }
99}
100
101fn typename(expr: &Expr) -> Result<proc_macro2::TokenStream, Error> {
102    let span = expr.span();
103    let error = move |kind| Error {
104        location: span,
105        kind,
106    };
107
108    match expr {
109        // The whole point of this crate:
110        Expr::Lit(literal) => Ok(match literal.lit {
111            Lit::Str(_) => quote! {&'static str},
112            Lit::ByteStr(_) => quote! {&'static [u8]},
113            Lit::Byte(_) => quote! {u8},
114            Lit::Char(_) => quote! {char},
115            Lit::Int(ref i) => {
116                let ty = format_ident!("{}", default_str(i.suffix(), "i32"));
117                quote! {#ty}
118            }
119            Lit::Float(ref f) => {
120                let ty = format_ident!("{}", default_str(f.suffix(), "f64"));
121                quote! {#ty}
122            }
123            Lit::Bool(_) => quote! {bool},
124            _ => return Err(error(ErrorKind::UnrecognizedLiteral)),
125        }),
126
127        // Fun extras:
128        // Composite types: tuple, arrays, reference
129        Expr::Array(array) => {
130            let inner_expr = array.elems.first().ok_or(error(ErrorKind::EmptyArray))?;
131
132            let inner = typename(inner_expr)?;
133            let len = array.elems.len();
134
135            Ok(quote! {[#inner; #len]})
136        }
137
138        Expr::Repeat(array) => {
139            let inner = typename(&array.expr)?;
140            let len = &array.len;
141            Ok(quote! {[#inner; #len]})
142        }
143
144        Expr::Reference(reference) => {
145            let inner = typename(&reference.expr)?;
146            Ok(quote! {&'static #inner})
147        }
148
149        Expr::Tuple(t) => {
150            let types = t
151                .elems
152                .iter()
153                .map(typename)
154                .collect::<Result<Vec<_>, _>>()?;
155
156            Ok(quote! {(#(#types,)*)})
157        }
158
159        // Block-like things; just look to see if there's a literal in
160        // the tail position
161        Expr::Unsafe(ExprUnsafe { block, .. }) | Expr::Block(ExprBlock { block, .. }) => {
162            // Find the expression without a semicolon. Don't bother trying
163            // to find `return` expressions.
164            let tail = block.stmts.iter().find_map(|statement| match statement {
165                Stmt::Expr(expr, None) => Some(expr),
166                _ => None,
167            });
168
169            match tail {
170                Some(expr) => typename(expr),
171                None => Ok(quote! {()}),
172            }
173        }
174
175        Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => typename(expr),
176
177        // Control flow stuff has type `!`
178        Expr::Return(_) | Expr::Break(_) | Expr::Continue(_) => Ok(quote! {!}),
179
180        // Casts are great cause they give us the literal type. Not really
181        // clear why you're using this crate in this case, but we're not
182        // here to judge.
183        Expr::Cast(ExprCast { ty, .. }) => Ok(quote! {#ty}),
184
185        // Assignments are valid expressions of unit type
186        Expr::Assign(_) => Ok(quote! {()}),
187
188        Expr::Macro(mac) => match mac.mac.path.get_ident() {
189            Some(ident)
190                if ident == "concat"
191                    || ident == "stringify"
192                    || ident == "env"
193                    || ident == "file"
194                    || ident == "include_file" =>
195            {
196                Ok(quote! {&'static str})
197            }
198            Some(ident) if ident == "cfg" => Ok(quote! {bool}),
199            Some(ident) if ident == "option_env" => Ok(quote! {Option<&'static str>}),
200            Some(ident) if ident == "line" || ident == "column" => Ok(quote! {u32}),
201            Some(ident) if ident == "include_bytes" => Ok(quote! {&'static [u8]}),
202
203            _ => Err(error(ErrorKind::UnsupportedExpression)),
204        },
205
206        _ => Err(error(ErrorKind::UnsupportedExpression)),
207    }
208}
209
210/**
211Macro that returns the *type* of any literal expression. See the
212[module docs][crate] for details.
213*/
214#[proc_macro]
215pub fn typeof_literal(expr: TokenStream) -> TokenStream {
216    let expr = parse_macro_input!(expr as Expr);
217
218    match typename(&expr) {
219        Ok(name) => name,
220        Err(err) => syn::Error::new(err.location, err.kind).into_compile_error(),
221    }
222    .into()
223}