diff --git a/Cargo.lock b/Cargo.lock index eeb3c99a294a3..9f5d7433f5591 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3752,6 +3752,7 @@ dependencies = [ "rustc_lexer", "rustc_lint_defs", "rustc_macros", + "rustc_middle", "rustc_parse", "rustc_serialize", "rustc_session", diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index e7b393d869d2b..d7271739adacf 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -14,12 +14,14 @@ //! ownership of the original. use std::borrow::Cow; +use std::hash::Hash; use std::{cmp, fmt, iter}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{self, Lrc}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; -use rustc_serialize::{Decodable, Encodable}; +use rustc_serialize::{Decodable, Encodable, Encoder}; +use rustc_span::def_id::{CrateNum, DefIndex}; use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym}; use crate::ast::{AttrStyle, StmtKind}; @@ -295,6 +297,96 @@ pub struct AttrsTarget { #[derive(Clone, Debug, Default, Encodable, Decodable)] pub struct TokenStream(pub(crate) Lrc>); +struct HashEncoder { + hasher: H, +} + +impl Encoder for HashEncoder { + fn emit_usize(&mut self, v: usize) { + self.hasher.write_usize(v) + } + + fn emit_u128(&mut self, v: u128) { + self.hasher.write_u128(v) + } + + fn emit_u64(&mut self, v: u64) { + self.hasher.write_u64(v) + } + + fn emit_u32(&mut self, v: u32) { + self.hasher.write_u32(v) + } + + fn emit_u16(&mut self, v: u16) { + self.hasher.write_u16(v) + } + + fn emit_u8(&mut self, v: u8) { + self.hasher.write_u8(v) + } + + fn emit_isize(&mut self, v: isize) { + self.hasher.write_isize(v) + } + + fn emit_i128(&mut self, v: i128) { + self.hasher.write_i128(v) + } + + fn emit_i64(&mut self, v: i64) { + self.hasher.write_i64(v) + } + + fn emit_i32(&mut self, v: i32) { + self.hasher.write_i32(v) + } + + fn emit_i16(&mut self, v: i16) { + self.hasher.write_i16(v) + } + + fn emit_raw_bytes(&mut self, s: &[u8]) { + self.hasher.write(s) + } +} + +impl SpanEncoder for HashEncoder { + fn encode_span(&mut self, span: Span) { + span.hash(&mut self.hasher) + } + + fn encode_symbol(&mut self, symbol: Symbol) { + symbol.hash(&mut self.hasher) + } + + fn encode_expn_id(&mut self, expn_id: rustc_span::ExpnId) { + expn_id.hash(&mut self.hasher) + } + + fn encode_syntax_context(&mut self, syntax_context: rustc_span::SyntaxContext) { + syntax_context.hash(&mut self.hasher) + } + + fn encode_crate_num(&mut self, crate_num: CrateNum) { + crate_num.hash(&mut self.hasher) + } + + fn encode_def_index(&mut self, def_index: DefIndex) { + def_index.hash(&mut self.hasher) + } + + fn encode_def_id(&mut self, def_id: rustc_span::def_id::DefId) { + def_id.hash(&mut self.hasher) + } +} + +impl Hash for TokenStream { + fn hash(&self, state: &mut H) { + Encodable::encode(self, &mut HashEncoder { hasher: state }); + } +} + /// Indicates whether a token can join with the following token to form a /// compound token. Used for conversions to `proc_macro::Spacing`. Also used to /// guide pretty-printing, which is where the `JointHidden` value (which isn't diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml index eb93972387d40..c2af89c2834cc 100644 --- a/compiler/rustc_expand/Cargo.toml +++ b/compiler/rustc_expand/Cargo.toml @@ -20,6 +20,7 @@ rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_lexer = { path = "../rustc_lexer" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } +rustc_middle = { path = "../rustc_middle" } rustc_parse = { path = "../rustc_parse" } rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 777044e3f33bf..cb5b2504655d7 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -35,4 +35,8 @@ pub mod module; #[allow(rustc::untranslatable_diagnostic)] pub mod proc_macro; +pub fn provide(providers: &mut rustc_middle::util::Providers) { + providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion; +} + rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index dca0516f9f3b3..095cc1ac2af0f 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,11 +1,17 @@ +use std::cell::Cell; +use std::ptr::{self, NonNull}; + use rustc_ast as ast; use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::svh::Svh; use rustc_errors::ErrorGuaranteed; +use rustc_middle::ty::{self, TyCtxt}; use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_session::Session; use rustc_session::config::ProcMacroExecutionStrategy; -use rustc_span::Span; use rustc_span::profiling::SpannedEventArgRecorder; +use rustc_span::{LocalExpnId, Span}; use crate::base::{self, *}; use crate::{errors, proc_macro_server}; @@ -31,9 +37,9 @@ impl pm::bridge::server::MessagePipe for MessagePipe { } } -fn exec_strategy(ecx: &ExtCtxt<'_>) -> impl pm::bridge::server::ExecutionStrategy { +pub fn exec_strategy(sess: &Session) -> impl pm::bridge::server::ExecutionStrategy { pm::bridge::server::MaybeCrossThread::>::new( - ecx.sess.opts.unstable_opts.proc_macro_execution_strategy + sess.opts.unstable_opts.proc_macro_execution_strategy == ProcMacroExecutionStrategy::CrossThread, ) } @@ -55,7 +61,7 @@ impl base::BangProcMacro for BangProcMacro { }); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; - let strategy = exec_strategy(ecx); + let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); self.client.run(&strategy, server, input, proc_macro_backtrace).map_err(|e| { ecx.dcx().emit_err(errors::ProcMacroPanicked { @@ -86,7 +92,7 @@ impl base::AttrProcMacro for AttrProcMacro { }); let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; - let strategy = exec_strategy(ecx); + let strategy = exec_strategy(ecx.sess); let server = proc_macro_server::Rustc::new(ecx); self.client.run(&strategy, server, annotation, annotated, proc_macro_backtrace).map_err( |e| { @@ -114,6 +120,13 @@ impl MultiItemModifier for DeriveProcMacro { item: Annotatable, _is_derive_const: bool, ) -> ExpandResult, Annotatable> { + let _timer = ecx.sess.prof.generic_activity_with_arg_recorder( + "expand_derive_proc_macro_outer", + |recorder| { + recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span); + }, + ); + // We need special handling for statement items // (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`) let is_stmt = matches!(item, Annotatable::Stmt(..)); @@ -124,36 +137,40 @@ impl MultiItemModifier for DeriveProcMacro { // altogether. See #73345. crate::base::ann_pretty_printing_compatibility_hack(&item, &ecx.sess); let input = item.to_tokens(); - let stream = { - let _timer = - ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| { - recorder.record_arg_with_span( - ecx.sess.source_map(), - ecx.expansion_descr(), - span, - ); - }); - let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; - let strategy = exec_strategy(ecx); - let server = proc_macro_server::Rustc::new(ecx); - match self.client.run(&strategy, server, input, proc_macro_backtrace) { - Ok(stream) => stream, - Err(e) => { - ecx.dcx().emit_err({ - errors::ProcMacroDerivePanicked { - span, - message: e.as_str().map(|message| { - errors::ProcMacroDerivePanickedHelp { message: message.into() } - }), - } - }); - return ExpandResult::Ready(vec![]); - } + let res = ty::tls::with(|tcx| { + // FIXME(pr-time): without flattened some (weird) tests fail, but no idea if it's correct/enough + let input = tcx.arena.alloc(input.flattened()) as &TokenStream; + let invoc_id = ecx.current_expansion.id; + let invoc_expn_data = invoc_id.expn_data(); + + assert_eq!(invoc_expn_data.call_site, span); + + // FIXME(pr-time): Is this the correct way to check for incremental compilation (as + // well as for `cache_proc_macros`)? + if tcx.sess.opts.incremental.is_some() && tcx.sess.opts.unstable_opts.cache_proc_macros + { + // FIXME(pr-time): Just using the crate hash to notice when the proc-macro code has + // changed. How to *correctly* depend on exactly the macro definition? + // I.e., depending on the crate hash is just a HACK, and ideally the dependency would be + // more narrow. + let macro_def_id = invoc_expn_data.macro_def_id.unwrap(); + let proc_macro_crate_hash = tcx.crate_hash(macro_def_id.krate); + + let key = (invoc_id, proc_macro_crate_hash, input); + + enter_context((ecx, self.client), move || tcx.derive_macro_expansion(key).cloned()) + } else { + expand_derive_macro(tcx, invoc_id, input, ecx, self.client).cloned() } + }); + + let Ok(output) = res else { + // error will already have been emitted + return ExpandResult::Ready(vec![]); }; let error_count_before = ecx.dcx().err_count(); - let mut parser = Parser::new(&ecx.sess.psess, stream, Some("proc-macro derive")); + let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive")); let mut items = vec![]; loop { @@ -181,3 +198,102 @@ impl MultiItemModifier for DeriveProcMacro { ExpandResult::Ready(items) } } + +pub(super) fn provide_derive_macro_expansion<'tcx>( + tcx: TyCtxt<'tcx>, + key: (LocalExpnId, Svh, &'tcx TokenStream), +) -> Result<&'tcx TokenStream, ()> { + let (invoc_id, _macro_crate_hash, input) = key; + + with_context(|(ecx, client)| expand_derive_macro(tcx, invoc_id, input, ecx, *client)) +} + +type CLIENT = pm::bridge::client::Client; + +fn expand_derive_macro<'tcx>( + tcx: TyCtxt<'tcx>, + invoc_id: LocalExpnId, + input: &'tcx TokenStream, + ecx: &mut ExtCtxt<'_>, + client: CLIENT, +) -> Result<&'tcx TokenStream, ()> { + let invoc_expn_data = invoc_id.expn_data(); + let span = invoc_expn_data.call_site; + let event_arg = invoc_expn_data.kind.descr(); + let _timer = tcx.sess.prof.generic_activity_with_arg_recorder( + "expand_derive_proc_macro_inner", + |recorder| { + recorder.record_arg_with_span(tcx.sess.source_map(), event_arg.clone(), span); + }, + ); + + let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; + let strategy = crate::proc_macro::exec_strategy(tcx.sess); + let server = crate::proc_macro_server::Rustc::new(ecx); + + match client.run(&strategy, server, input.clone(), proc_macro_backtrace) { + Ok(stream) => Ok(tcx.arena.alloc(stream) as &TokenStream), + Err(e) => { + tcx.dcx().emit_err({ + errors::ProcMacroDerivePanicked { + span, + message: e.as_str().map(|message| errors::ProcMacroDerivePanickedHelp { + message: message.into(), + }), + } + }); + Err(()) + } + } +} + +// based on rust/compiler/rustc_middle/src/ty/context/tls.rs +thread_local! { + /// A thread local variable that stores a pointer to the current `CONTEXT`. + static TLV: Cell<(*mut (), Option)> = const { Cell::new((ptr::null_mut(), None)) }; +} + +/// Sets `context` as the new current `CONTEXT` for the duration of the function `f`. +#[inline] +pub(crate) fn enter_context<'a, F, R>(context: (&mut ExtCtxt<'a>, CLIENT), f: F) -> R +where + F: FnOnce() -> R, +{ + let (ectx, client) = context; + let erased = (ectx as *mut _ as *mut (), Some(client)); + TLV.with(|tlv| { + let old = tlv.replace(erased); + let _reset = rustc_data_structures::defer(move || tlv.set(old)); + f() + }) +} + +/// Allows access to the current `CONTEXT`. +/// Panics if there is no `CONTEXT` available. +#[inline] +#[track_caller] +fn with_context(f: F) -> R +where + F: for<'a, 'b> FnOnce(&'b mut (&mut ExtCtxt<'a>, CLIENT)) -> R, +{ + let (ectx, client_opt) = TLV.get(); + let ectx = NonNull::new(ectx).expect("no CONTEXT stored in tls"); + + // We could get an `CONTEXT` pointer from another thread. + // Ensure that `CONTEXT` is `DynSync`. + // FIXME(pr-time): we should not be able to? + // sync::assert_dyn_sync::>(); + + // prevent double entering, as that would allow creating two `&mut ExtCtxt`s + // FIXME(pr-time): probably use a RefCell instead (which checks this properly)? + TLV.with(|tlv| { + let old = tlv.replace((ptr::null_mut(), None)); + let _reset = rustc_data_structures::defer(move || tlv.set(old)); + let ectx = { + let mut casted = ectx.cast::>(); + unsafe { casted.as_mut() } + }; + + f(&mut (ectx, client_opt.unwrap())) + }) +} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index aff66e48fbbee..a5f79579e45a4 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -686,6 +686,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock = LazyLock::new(|| { providers.resolutions = |tcx, ()| tcx.resolver_for_lowering_raw(()).1; providers.early_lint_checks = early_lint_checks; proc_macro_decls::provide(providers); + rustc_expand::provide(providers); rustc_const_eval::provide(providers); rustc_middle::hir::provide(providers); rustc_borrowck::provide(providers); diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index 750531b638e4d..06ee0fdfb3cea 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -114,6 +114,7 @@ macro_rules! arena_types { [decode] specialization_graph: rustc_middle::traits::specialization_graph::Graph, [] crate_inherent_impls: rustc_middle::ty::CrateInherentImpls, [] hir_owner_nodes: rustc_hir::OwnerNodes<'tcx>, + [decode] token_stream: rustc_ast::tokenstream::TokenStream, ]); ) } diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index b72c0e776fe90..f537391d09693 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -1,6 +1,7 @@ use std::intrinsics::transmute_unchecked; use std::mem::MaybeUninit; +use rustc_ast::tokenstream::TokenStream; use rustc_span::ErrorGuaranteed; use crate::query::CyclePlaceholder; @@ -172,6 +173,10 @@ impl EraseType for Result>, CyclePlaceholder> { type Result = [u8; size_of::>, CyclePlaceholder>>()]; } +impl EraseType for Result<&'_ TokenStream, ()> { + type Result = [u8; size_of::>()]; +} + impl EraseType for Option<&'_ T> { type Result = [u8; size_of::>()]; } diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index e243425c0b7ba..09dd0fa0e7b5d 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -1,10 +1,12 @@ //! Defines the set of legal keys that can be used in queries. +use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::svh::Svh; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId, ModDefId}; use rustc_hir::hir_id::{HirId, OwnerId}; use rustc_query_system::dep_graph::DepNodeIndex; use rustc_query_system::query::{DefIdCache, DefaultCache, SingleCache, VecCache}; -use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; +use rustc_span::{DUMMY_SP, Ident, LocalExpnId, Span, Symbol}; use crate::infer::canonical::CanonicalQueryInput; use crate::mir::mono::CollectionMode; @@ -584,6 +586,19 @@ impl Key for (LocalDefId, HirId) { } } +impl<'tcx> Key for (LocalExpnId, Svh, &'tcx TokenStream) { + type Cache = DefaultCache; + + fn default_span(&self, _tcx: TyCtxt<'_>) -> Span { + self.0.expn_data().call_site + } + + #[inline(always)] + fn key_as_def_id(&self) -> Option { + None + } +} + impl<'tcx> Key for (ValidityRequirement, ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) { type Cache = DefaultCache; diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 2c2dffe8b88f0..3157b1064151f 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -14,6 +14,7 @@ use std::sync::Arc; use rustc_arena::TypedArena; use rustc_ast::expand::StrippedCfgItem; use rustc_ast::expand::allocator::AllocatorKind; +use rustc_ast::tokenstream::TokenStream; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sorted_map::SortedMap; @@ -41,7 +42,7 @@ use rustc_session::cstore::{ use rustc_session::lint::LintExpectationId; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::source_map::Spanned; -use rustc_span::{DUMMY_SP, Span, Symbol}; +use rustc_span::{DUMMY_SP, LocalExpnId, Span, Symbol}; use rustc_target::spec::PanicStrategy; use {rustc_abi as abi, rustc_ast as ast, rustc_attr_parsing as attr, rustc_hir as hir}; @@ -105,6 +106,13 @@ pub use plumbing::{IntoQueryParam, TyCtxtAt, TyCtxtEnsure, TyCtxtEnsureWithValue // Queries marked with `fatal_cycle` do not need the latter implementation, // as they will raise an fatal error on query cycles instead. rustc_queries! { + query derive_macro_expansion(key: (LocalExpnId, Svh, &'tcx TokenStream)) -> Result<&'tcx TokenStream, ()> { + // eval_always + // no_hash + desc { "expanding a derive (proc) macro" } + cache_on_disk_if { true } + } + /// This exists purely for testing the interactions between delayed bugs and incremental. query trigger_delayed_bug(key: DefId) { desc { "triggering a delayed bug for testing incremental" } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 4a144ebb89940..5868512fc3b5c 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -785,6 +785,13 @@ impl<'a, 'tcx> Decodable> } } +impl<'a, 'tcx> Decodable> for &'tcx rustc_ast::tokenstream::TokenStream { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + RefDecodable::decode(d) + } +} + macro_rules! impl_ref_decoder { (<$tcx:tt> $($ty:ty,)*) => { $(impl<'a, $tcx> Decodable> for &$tcx [$ty] { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 3772a4a08af01..e97d6792d5785 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1697,6 +1697,8 @@ options! { "emit noalias metadata for box (default: yes)"), branch_protection: Option = (None, parse_branch_protection, [TRACKED], "set options for branch target identification and pointer authentication on AArch64"), + cache_proc_macros: bool = (true, parse_bool, [UNTRACKED], + "cache the results of ALL proc macro invocations (potentially unsound!) (default: YES -- for rustc-perf)"), cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], "instrument control-flow architecture protection"), check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 3cdae437b7d46..6a623269a0f06 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1565,3 +1565,9 @@ impl HashStable for ExpnId { hash.hash_stable(ctx, hasher); } } + +impl HashStable for LocalExpnId { + fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { + self.to_expn_id().hash_stable(hcx, hasher); + } +} diff --git a/tests/incremental/derive_macro_expansion/auxiliary/derive_nothing.rs b/tests/incremental/derive_macro_expansion/auxiliary/derive_nothing.rs new file mode 100644 index 0000000000000..ee0fe7ea10023 --- /dev/null +++ b/tests/incremental/derive_macro_expansion/auxiliary/derive_nothing.rs @@ -0,0 +1,20 @@ +//@ force-host +//@ no-prefer-dynamic + +#![crate_type = "proc-macro"] + +extern crate proc_macro; +use proc_macro::TokenStream; + +#[proc_macro_derive(Nothing)] +pub fn derive(_input: TokenStream) -> TokenStream { + eprintln!("invoked"); + + return r#" + pub mod nothing_mod { + pub fn nothing() { + eprintln!("nothing"); + } + } + "#.parse().unwrap(); +} diff --git a/tests/incremental/derive_macro_expansion/proc_macro_unchanged.rs b/tests/incremental/derive_macro_expansion/proc_macro_unchanged.rs new file mode 100644 index 0000000000000..ad98c9f789f47 --- /dev/null +++ b/tests/incremental/derive_macro_expansion/proc_macro_unchanged.rs @@ -0,0 +1,39 @@ +// This test tests that derive-macro execution is cached. +// HOWEVER, this test can currently only be checked manually, +// by running it (through compiletest) with `-- --nocapture --verbose`. +// The proc-macro (for `Nothing`) prints a message to stderr when invoked, +// and this message should only be present during the first invocation, +// because the cached result should be used for the second invocation. +// FIXME(pr-time): Properly have the test check this, but how? UI-test that tests for `.stderr`? + +//@ aux-build:derive_nothing.rs +//@ revisions:cfail1 cfail2 +//@ compile-flags: -Z query-dep-graph -Zcache-proc-macros=true +//@ build-pass + +#![feature(rustc_attrs)] +#![feature(stmt_expr_attributes)] +#![allow(dead_code)] +#![crate_type = "rlib"] + +#![rustc_partition_codegened(module="proc_macro_unchanged-foo", cfg="cfail1")] +// #![rustc_partition_codegened(module="proc_macro_unchanged-foo", cfg="cfail2")] + +// `foo::nothing_mod` is created by the derive macro and doesn't change +// BUG: this yields the same result with `-Zcache-proc-macros=false` (i.e., uncached), +// not sure how to do this correctly. +#![rustc_partition_reused(module="proc_macro_unchanged-foo-nothing_mod", cfg="cfail2")] + + #[macro_use] + extern crate derive_nothing; + +pub mod foo { + #[derive(Nothing)] + pub struct Foo; + + pub fn use_foo(_f: Foo) { + nothing_mod::nothing(); + + eprintln!("foo used"); + } +}