aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/extensions/routeify.rs
blob: 27077ffcfec365bc4b7a55c311b5e65540c68a4c (plain) (blame)
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
//! The `#[routeify]` extension — transforms `expr?` into `route!(expr)`.
//!
//! Designed as an extension for the Mingling attribute macro system, intended
//! to be used with `#[chain(routeify)]` or standalone as `#[routeify]`.
//!
//! # How it works
//!
//! The macro parses the function AST and replaces every `Expr::Try` node with an
//! equivalent `route!(expr)` invocation.

use proc_macro::TokenStream;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::visit_mut::VisitMut;
use syn::{parse_macro_input, Expr, ItemFn};

struct RouteifyTransform;

impl VisitMut for RouteifyTransform {
    fn visit_expr_mut(&mut self, expr: &mut Expr) {
        syn::visit_mut::visit_expr_mut(self, expr);

        if let Expr::Try(try_expr) = expr {
            let inner = &*try_expr.expr;
            let inner_tokens = inner.to_token_stream();

            // Set the span of the generated `route` ident to the `?` token's span,
            // so that rust-analyzer resolves the `?` position to the `route!` macro
            // instead of the standard Try trait, showing the route macro's docs on hover.
            let q_span = try_expr.question_token.span();
            let route_ident = proc_macro2::Ident::new("route", q_span);

            if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! {
                ::mingling::macros::#route_ident!(#inner_tokens)
            }) {
                *expr = macro_expr;
            }
        }
    }
}

pub(crate) fn routeify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut input_fn = parse_macro_input!(item as ItemFn);
    RouteifyTransform.visit_item_fn_mut(&mut input_fn);
    input_fn.to_token_stream().into()
}