Skip to main content

rustc_expand/
proc_macro.rs

1use rustc_ast as ast;
2use rustc_ast::tokenstream::TokenStream;
3use rustc_data_structures::AtomicRef;
4use rustc_data_structures::profiling::TimingGuard;
5use rustc_errors::ErrorGuaranteed;
6use rustc_parse::parser::{AllowConstBlockItems, ForceCollect, Parser};
7use rustc_proc_macro as pm;
8use rustc_session::Session;
9use rustc_session::config::ProcMacroExecutionStrategy;
10use rustc_span::profiling::SpannedEventArgRecorder;
11use rustc_span::{LocalExpnId, Span};
12
13use crate::base::{self, *};
14use crate::{diagnostics, proc_macro_server};
15
16fn exec_strategy(sess: &Session) -> impl pm::bridge::server::ExecutionStrategy + 'static {
17    pm::bridge::server::MaybeCrossThread {
18        cross_thread: sess.opts.unstable_opts.proc_macro_execution_strategy
19            == ProcMacroExecutionStrategy::CrossThread,
20    }
21}
22
23fn record_expand_proc_macro<'a>(
24    ecx: &ExtCtxt<'a>,
25    name: &'static str,
26    span: Span,
27) -> TimingGuard<'a> {
28    ecx.sess.prof.generic_activity_with_arg_recorder(name, |recorder| {
29        recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span);
30    })
31}
32
33pub struct BangProcMacro {
34    pub client: pm::bridge::client::Client,
35}
36
37impl base::BangProcMacro for BangProcMacro {
38    fn expand(
39        &self,
40        ecx: &mut ExtCtxt<'_>,
41        span: Span,
42        input: TokenStream,
43    ) -> Result<TokenStream, ErrorGuaranteed> {
44        let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span);
45
46        let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
47        let strategy = exec_strategy(ecx.sess);
48        let server = proc_macro_server::Rustc::new(ecx);
49        self.client.run1(&strategy, server, input, proc_macro_backtrace).map_err(|e| {
50            ecx.dcx().emit_err(diagnostics::ProcMacroPanicked {
51                span,
52                message: e
53                    .into_string()
54                    .map(|message| diagnostics::ProcMacroPanickedHelp { message }),
55            })
56        })
57    }
58}
59
60pub struct AttrProcMacro {
61    pub client: pm::bridge::client::Client,
62}
63
64impl base::AttrProcMacro for AttrProcMacro {
65    fn expand(
66        &self,
67        ecx: &mut ExtCtxt<'_>,
68        span: Span,
69        annotation: TokenStream,
70        annotated: TokenStream,
71    ) -> Result<TokenStream, ErrorGuaranteed> {
72        let _timer = record_expand_proc_macro(ecx, "expand_proc_macro", span);
73
74        let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
75        let strategy = exec_strategy(ecx.sess);
76        let server = proc_macro_server::Rustc::new(ecx);
77        self.client.run2(&strategy, server, annotation, annotated, proc_macro_backtrace).map_err(
78            |e| {
79                ecx.dcx().emit_err(diagnostics::CustomAttributePanicked {
80                    span,
81                    message: e
82                        .into_string()
83                        .map(|message| diagnostics::CustomAttributePanickedHelp { message }),
84                })
85            },
86        )
87    }
88}
89
90pub struct DeriveProcMacro {
91    pub client: DeriveClient,
92}
93
94impl MultiItemModifier for DeriveProcMacro {
95    fn expand(
96        &self,
97        ecx: &mut ExtCtxt<'_>,
98        span: Span,
99        _meta_item: &ast::MetaItem,
100        item: Annotatable,
101        _is_derive_const: bool,
102    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
103        let _timer = record_expand_proc_macro(ecx, "expand_derive_proc_macro_outer", span);
104
105        // We need special handling for statement items
106        // (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`)
107        let is_stmt = #[allow(non_exhaustive_omitted_patterns)] match item {
    Annotatable::Stmt(..) => true,
    _ => false,
}matches!(item, Annotatable::Stmt(..));
108
109        let input = item.to_tokens();
110
111        let invoc_id = ecx.current_expansion.id;
112
113        let res = if ecx.sess.opts.incremental.is_some()
114            && ecx.sess.opts.unstable_opts.cache_proc_macros
115        {
116            (*EXPAND_DERIVE_MACRO_CACHED)(invoc_id, input, ecx, self.client)
117        } else {
118            expand_derive_macro(invoc_id, input, ecx, self.client)
119        };
120
121        let Ok(output) = res else {
122            // error will already have been emitted
123            return ExpandResult::Ready(::alloc::vec::Vec::new()vec![]);
124        };
125
126        let error_count_before = ecx.dcx().err_count();
127        let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive"));
128        let mut items = ::alloc::vec::Vec::new()vec![];
129
130        loop {
131            match parser.parse_item(
132                ForceCollect::No,
133                if is_stmt { AllowConstBlockItems::No } else { AllowConstBlockItems::Yes },
134            ) {
135                Ok(None) => break,
136                Ok(Some(item)) => {
137                    if is_stmt {
138                        items.push(Annotatable::Stmt(Box::new(ecx.stmt_item(span, item))));
139                    } else {
140                        items.push(Annotatable::Item(item));
141                    }
142                }
143                Err(err) => {
144                    err.emit();
145                    break;
146                }
147            }
148        }
149
150        // fail if there have been errors emitted
151        if ecx.dcx().err_count() > error_count_before {
152            ecx.dcx().emit_err(diagnostics::ProcMacroDeriveTokens { span });
153        }
154
155        ExpandResult::Ready(items)
156    }
157}
158
159type DeriveClient = pm::bridge::client::Client;
160
161pub fn expand_derive_macro(
162    invoc_id: LocalExpnId,
163    input: TokenStream,
164    ecx: &mut ExtCtxt<'_>,
165    client: DeriveClient,
166) -> Result<TokenStream, ()> {
167    let _timer =
168        ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| {
169            let invoc_expn_data = invoc_id.expn_data();
170            let span = invoc_expn_data.call_site;
171            let event_arg = invoc_expn_data.kind.descr();
172            recorder.record_arg_with_span(ecx.sess.source_map(), event_arg, span);
173        });
174
175    let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
176    let strategy = exec_strategy(ecx.sess);
177    let server = proc_macro_server::Rustc::new(ecx);
178
179    match client.run1(&strategy, server, input, proc_macro_backtrace) {
180        Ok(stream) => Ok(stream),
181        Err(e) => {
182            let invoc_expn_data = invoc_id.expn_data();
183            let span = invoc_expn_data.call_site;
184            ecx.dcx().emit_err({
185                diagnostics::ProcMacroDerivePanicked {
186                    span,
187                    message: e
188                        .into_string()
189                        .map(|message| diagnostics::ProcMacroDerivePanickedHelp { message }),
190                }
191            });
192            Err(())
193        }
194    }
195}
196
197pub static EXPAND_DERIVE_MACRO_CACHED: AtomicRef<
198    fn(LocalExpnId, TokenStream, &mut ExtCtxt<'_>, DeriveClient) -> Result<TokenStream, ()>,
199> = AtomicRef::new(
200    &(|_, _, _: &mut ExtCtxt<'_>, _| -> Result<_, _> {
201        {
    ::core::panicking::panic_fmt(format_args!("`EXPAND_DERIVE_MACRO_CACHED` callback was not setup; it must be set in `rustc_interface::callbacks`"));
}panic!(
202            "`EXPAND_DERIVE_MACRO_CACHED` callback was not setup; it must be set in `rustc_interface::callbacks`"
203        )
204    } as _),
205);