1use 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 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 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 Expr::Unsafe(ExprUnsafe { block, .. }) | Expr::Block(ExprBlock { block, .. }) => {
162 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 Expr::Return(_) | Expr::Break(_) | Expr::Continue(_) => Ok(quote! {!}),
179
180 Expr::Cast(ExprCast { ty, .. }) => Ok(quote! {#ty}),
184
185 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#[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}