you_can_build_macros/
lib.rs1#![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
10macro_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#[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}