you_can_build_macros/
lib.rs

1//! internal proc macros for [`::you_can`]
2#![cfg_attr(rustc_is_unstable, feature(proc_macro_diagnostic, proc_macro_span))]
3
4use {
5    proc_macro::{Span, TokenStream},
6    quote::{quote, quote_spanned, ToTokens},
7    syn::{fold::Fold, parse_quote_spanned, spanned::Spanned, visit::Visit},
8};
9
10/// Runs one of two branches depending on whether we're running on a stable
11/// version of the compiler (stable, beta), or an unstable version (nightly,
12/// dev, or anywhere that `RUSTC_BOOTSTRAP=1`).
13macro_rules! if_unstable {
14    { then { $($then:tt)* } else { $($else:tt)* } } => {
15        #[allow(unreachable_code)]
16        if cfg!(rustc_is_unstable) {
17            #[cfg(not(rustc_is_unstable))] {
18                unreachable!();
19            }
20            #[cfg(rustc_is_unstable)] {
21                $($then)*
22            }
23        } else {
24            #[cfg(rustc_is_unstable)] {
25                unreachable!();
26            }
27            $($else)*
28        }
29    }
30}
31
32#[proc_macro_attribute]
33pub fn turn_off_the_borrow_checker(_attribute: TokenStream, input: TokenStream) -> TokenStream {
34    let mut suppressor = BorrowCheckerSuppressor {
35        suppressed_references: vec![],
36    };
37
38    let output = if let Ok(as_file) = syn::parse(input.clone()) {
39        suppressor.fold_file(as_file).to_token_stream()
40    } else if let Ok(as_expr) = syn::parse(input.clone()) {
41        suppressor.fold_expr(as_expr).to_token_stream()
42    } else if let Ok(as_stmt) = syn::parse(input) {
43        suppressor.fold_stmt(as_stmt).to_token_stream()
44    } else {
45        return quote! { compile_error!("unsupported use of #[turn_off_the_borrow_checker]") }
46            .into();
47    };
48
49    if_unstable! {
50        then {
51            proc_macro::Diagnostic::spanned(
52                vec![Span::call_site().parent().unwrap_or_else(Span::call_site)],
53                proc_macro::Level::Warning,
54                "this suppresses the borrow checker in an unsafe, unsound, and unstable way \
55                that produces undefined behaviour. this is not suitable for any purpose beyond \
56                educational experimentation.",
57            ).emit();
58
59            if suppressor.suppressed_references.len() > 1 {
60                proc_macro::Diagnostic::spanned(
61                    suppressor.suppressed_references,
62                    proc_macro::Level::Warning,
63                    "the borrow checker is suppressed for these references.",
64                ).emit();
65            }
66
67            output.into_token_stream().into()
68        } else {
69            static DANGER: std::sync::Once = std::sync::Once::new();
70            DANGER.call_once(|| {
71                eprintln!();
72                eprintln!(" DANGER   This project is using the the #[you_can::turn_off_the_borrow_checker]");
73                eprintln!(" DANGER   macro, which is inherently unsafe, unsound, and unstable. This is not");
74                eprintln!(" DANGER   suitable for any purpose beyond educational experimentation.");
75                eprintln!();
76            });
77
78            quote_spanned! {
79                Span::call_site().into() =>
80                #[warn(unsafe_code)]
81                #output
82            }.into_token_stream().into()
83        }
84    }
85}
86
87/// Replaces all references (&T or &mut T) with unbounded references by wrapping
88/// them in calls to you_can::borrow_unchecked().
89#[derive(Debug, Default)]
90struct BorrowCheckerSuppressor {
91    suppressed_references: Vec<Span>,
92}
93
94impl Fold for BorrowCheckerSuppressor {
95    fn fold_expr(&mut self, node: syn::Expr) -> syn::Expr {
96        match node {
97            syn::Expr::Reference(node) => {
98                let node = syn::fold::fold_expr_reference(self, node);
99                self.suppressed_references.push(node.span().unwrap());
100                syn::Expr::Block(parse_quote_spanned! { node.span() =>
101                    {
102                        let r#ref = #node;
103                        unsafe { ::you_can::borrow_unchecked(r#ref) }
104                    }
105                })
106            },
107            _ => syn::fold::fold_expr(self, node),
108        }
109    }
110
111    fn fold_expr_if(&mut self, mut node: syn::ExprIf) -> syn::ExprIf {
112        if matches!(*node.cond, syn::Expr::Let(_)) {
113            let mut ref_collector = RefCollector::default();
114            ref_collector.visit_expr(&node.cond);
115            let refs = ref_collector.refs;
116            self.suppressed_references.extend(ref_collector.spans);
117            let then_stmts = node.then_branch.stmts.clone();
118            node.then_branch = parse_quote_spanned! { node.span() =>
119                {
120                    #(let #refs = unsafe { ::you_can::borrow_unchecked(#refs) };)*
121                    #(#then_stmts)*
122                }
123            };
124        }
125        syn::fold::fold_expr_if(self, node)
126    }
127
128    fn fold_arm(&mut self, mut node: syn::Arm) -> syn::Arm {
129        let mut ref_collector = RefCollector::default();
130        ref_collector.visit_pat(&node.pat);
131        let refs = ref_collector.refs;
132        self.suppressed_references.extend(ref_collector.spans);
133        let body = node.body.clone();
134        node.body = parse_quote_spanned! { node.span() =>
135            {
136                #(let #refs = unsafe { ::you_can::borrow_unchecked(#refs) };)*
137                #body
138            }
139        };
140        syn::fold::fold_arm(self, node)
141    }
142}
143
144#[derive(Debug, Default)]
145struct RefCollector {
146    refs: Vec<syn::Ident>,
147    spans: Vec<Span>,
148}
149
150impl<'ast> Visit<'ast> for RefCollector {
151    fn visit_pat_ident(&mut self, node: &'ast syn::PatIdent) {
152        if node.by_ref.is_some() {
153            self.refs.push(node.ident.clone());
154            self.spans.push(node.span().unwrap());
155        }
156    }
157}