diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index b8205bda68525..25458a7cbd116 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -361,7 +361,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } else { ty::List::empty() }; - let callee_fn_abi = self.fn_abi_of_instance(instance, extra_tys)?; + let callee_fn_abi = self.fn_abi_of_instance_no_deduced_attrs(instance, extra_tys)?; if caller_fn_abi.conv != callee_fn_abi.conv { throw_ub_custom!( @@ -899,7 +899,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { enter_trace_span!(M, resolve::resolve_drop_in_place, ty = ?place.layout.ty); ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty) }; - let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?; + let fn_abi = self.fn_abi_of_instance_no_deduced_attrs(instance, ty::List::empty())?; let arg = self.mplace_to_ref(&place)?; let ret = MPlaceTy::fake_alloc_zst(self.layout_of(self.tcx.types.unit)?); diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 3a887f8afaa57..8737550907e82 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -153,16 +153,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// This inherent method takes priority over the trait method with the same name in FnAbiOf, - /// and allows wrapping the actual [FnAbiOf::fn_abi_of_instance] with a tracing span. - /// See [FnAbiOf::fn_abi_of_instance] for the original documentation. + /// and allows wrapping the actual [FnAbiOf::fn_abi_of_instance_no_deduced_attrs] with a tracing span. + /// See [FnAbiOf::fn_abi_of_instance_no_deduced_attrs] for the original documentation. #[inline(always)] - pub fn fn_abi_of_instance( + pub fn fn_abi_of_instance_no_deduced_attrs( &self, instance: ty::Instance<'tcx>, extra_args: &'tcx ty::List>, ) -> >::FnAbiOfResult { let _trace = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args); - FnAbiOf::fn_abi_of_instance(self, instance, extra_args) + FnAbiOf::fn_abi_of_instance_no_deduced_attrs(self, instance, extra_args) } } diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 126d0fc9b7a1b..083fd97aec4dc 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -470,7 +470,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let instance = self.resolve(def_id, args)?; ( FnVal::Instance(instance), - self.fn_abi_of_instance(instance, extra_args)?, + self.fn_abi_of_instance_no_deduced_attrs(instance, extra_args)?, instance.def.requires_caller_location(*self.tcx), ) } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 0ff38a0f36041..2b1842466a83f 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1801,12 +1801,38 @@ rustc_queries! { desc { "computing call ABI of `{}` function pointers", key.value.0 } } - /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for - /// direct calls to an `fn`. + /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls* + /// to an `fn`. Indirectly-passed parameters in the returned ABI might not include all possible + /// codegen optimization attributes (such as `ReadOnly` or `CapturesNone`), as deducing these + /// requires inspection of function bodies that can lead to cycles when performed during typeck. + /// Post typeck, you should prefer the optimized ABI returned by `TyCtxt::fn_abi_of_instance`. + /// + /// NB: the ABI returned by this query must not differ from that returned by + /// `fn_abi_of_instance_raw` in any other way. + /// + /// * that includes virtual calls, which are represented by "direct calls" to an + /// `InstanceKind::Virtual` instance (of `::fn`). + query fn_abi_of_instance_no_deduced_attrs( + key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)> + ) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> { + desc { "computing unadjusted call ABI of `{}`", key.value.0 } + } + + /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls* + /// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable + /// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of + /// which requires inspection of function bodies that can lead to cycles when performed during + /// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by + /// `fn_abi_of_instance_no_deduced_attrs`. + /// + /// For performance reasons, you should prefer to call the inherent `TyCtxt::fn_abi_of_instance` + /// method rather than invoke this query: it delegates to this query if necessary, but where + /// possible delegates instead to the `fn_abi_of_instance_no_deduced_attrs` query (thus avoiding + /// unnecessary query system overhead). /// - /// NB: that includes virtual calls, which are represented by "direct calls" - /// to an `InstanceKind::Virtual` instance (of `::fn`). - query fn_abi_of_instance( + /// * that includes virtual calls, which are represented by "direct calls" to an + /// `InstanceKind::Virtual` instance (of `::fn`). + query fn_abi_of_instance_raw( key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)> ) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> { desc { "computing call ABI of `{}`", key.value.0 } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 0dcbafed3e900..de0b26ede0e06 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -1369,11 +1369,56 @@ pub trait FnAbiOf<'tcx>: FnAbiOfHelpers<'tcx> { ) } - /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for - /// direct calls to an `fn`. + /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls* + /// to an `fn`. Indirectly-passed parameters in the returned ABI might not include all possible + /// codegen optimization attributes (such as `ReadOnly` or `CapturesNone`), as deducing these + /// requires inspection of function bodies that can lead to cycles when performed during typeck. + /// Post typeck, you should prefer the optimized ABI returned by `fn_abi_of_instance`. /// - /// NB: that includes virtual calls, which are represented by "direct calls" - /// to an `InstanceKind::Virtual` instance (of `::fn`). + /// NB: the ABI returned by this query must not differ from that returned by + /// `fn_abi_of_instance` in any other way. + /// + /// * that includes virtual calls, which are represented by "direct calls" to an + /// `InstanceKind::Virtual` instance (of `::fn`). + #[inline] + #[tracing::instrument(level = "debug", skip(self))] + fn fn_abi_of_instance_no_deduced_attrs( + &self, + instance: ty::Instance<'tcx>, + extra_args: &'tcx ty::List>, + ) -> Self::FnAbiOfResult { + // FIXME(eddyb) get a better `span` here. + let span = self.layout_tcx_at_span(); + let tcx = self.tcx().at(span); + + MaybeResult::from( + tcx.fn_abi_of_instance_no_deduced_attrs( + self.typing_env().as_query_input((instance, extra_args)), + ) + .map_err(|err| { + // HACK(eddyb) at least for definitions of/calls to `Instance`s, + // we can get some kind of span even if one wasn't provided. + // However, we don't do this early in order to avoid calling + // `def_span` unconditionally (which may have a perf penalty). + let span = if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) }; + self.handle_fn_abi_err( + *err, + span, + FnAbiRequest::OfInstance { instance, extra_args }, + ) + }), + ) + } + + /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls* + /// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable + /// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of + /// which requires inspection of function bodies that can lead to cycles when performed during + /// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by + /// `fn_abi_of_instance_no_deduced_attrs`. + /// + /// * that includes virtual calls, which are represented by "direct calls" to an + /// `InstanceKind::Virtual` instance (of `::fn`). #[inline] #[tracing::instrument(level = "debug", skip(self))] fn fn_abi_of_instance( diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index d576b749f5bca..9a78b35488503 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -48,9 +48,11 @@ use rustc_macros::{ TypeVisitable, extension, }; use rustc_serialize::{Decodable, Encodable}; +use rustc_session::config::OptLevel; pub use rustc_session::lint::RegisteredTools; use rustc_span::hygiene::MacroKind; use rustc_span::{DUMMY_SP, ExpnId, ExpnKind, Ident, Span, Symbol}; +use rustc_target::callconv::FnAbi; pub use rustc_type_ir::data_structures::{DelayedMap, DelayedSet}; pub use rustc_type_ir::fast_reject::DeepRejectCtxt; #[allow( @@ -120,7 +122,7 @@ use crate::ty; use crate::ty::codec::{TyDecoder, TyEncoder}; pub use crate::ty::diagnostics::*; use crate::ty::fast_reject::SimplifiedType; -use crate::ty::layout::LayoutError; +use crate::ty::layout::{FnAbiError, LayoutError}; use crate::ty::util::Discr; use crate::ty::walk::TypeWalker; @@ -2167,6 +2169,34 @@ impl<'tcx> TyCtxt<'tcx> { !self.associated_types_for_impl_traits_in_associated_fn(trait_item_def_id).is_empty() } + + /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls* + /// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable + /// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of + /// which requires inspection of function bodies that can lead to cycles when performed during + /// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by + /// `fn_abi_of_instance_no_deduced_attrs`. + /// + /// For performance reasons, you should prefer to call this inherent method rather than invoke + /// the `fn_abi_of_instance_raw` query: it delegates to that query if necessary, but where + /// possible delegates instead to the `fn_abi_of_instance_no_deduced_attrs` query (thus avoiding + /// unnecessary query system overhead). + /// + /// * that includes virtual calls, which are represented by "direct calls" to an + /// `InstanceKind::Virtual` instance (of `::fn`). + #[inline] + pub fn fn_abi_of_instance( + self, + query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)>, + ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { + // Only deduce attrs in full, optimized builds. Otherwise, avoid the query system overhead + // of ever invoking the `fn_abi_of_instance_raw` query. + if self.sess.opts.optimize != OptLevel::No && self.sess.opts.incremental.is_none() { + self.fn_abi_of_instance_raw(query) + } else { + self.fn_abi_of_instance_no_deduced_attrs(query) + } + } } pub fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 07e9b731d21ac..446eba34cedde 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -12,7 +12,6 @@ use rustc_middle::ty::layout::{ FnAbiError, HasTyCtxt, HasTypingEnv, LayoutCx, LayoutOf, TyAndLayout, fn_can_unwind, }; use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt}; -use rustc_session::config::OptLevel; use rustc_span::DUMMY_SP; use rustc_span::def_id::DefId; use rustc_target::callconv::{ @@ -21,7 +20,12 @@ use rustc_target::callconv::{ use tracing::debug; pub(crate) fn provide(providers: &mut Providers) { - *providers = Providers { fn_abi_of_fn_ptr, fn_abi_of_instance, ..*providers }; + *providers = Providers { + fn_abi_of_fn_ptr, + fn_abi_of_instance_no_deduced_attrs, + fn_abi_of_instance_raw, + ..*providers + }; } // NOTE(eddyb) this is private to avoid using it from outside of @@ -243,30 +247,97 @@ fn fn_sig_for_fn_abi<'tcx>( } } +/// Describes a function for determination of its ABI. +struct FnAbiDesc<'tcx> { + layout_cx: LayoutCx<'tcx>, + sig: ty::FnSig<'tcx>, + + /// The function's definition, if its body can be used to deduce parameter attributes. + determined_fn_def_id: Option, + caller_location: Option>, + is_virtual_call: bool, + extra_args: &'tcx [Ty<'tcx>], +} + +impl<'tcx> FnAbiDesc<'tcx> { + fn for_fn_ptr( + tcx: TyCtxt<'tcx>, + query: ty::PseudoCanonicalInput<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List>)>, + ) -> Self { + let ty::PseudoCanonicalInput { typing_env, value: (sig, extra_args) } = query; + Self { + layout_cx: LayoutCx::new(tcx, typing_env), + sig: tcx.normalize_erasing_regions( + typing_env, + tcx.instantiate_bound_regions_with_erased(sig), + ), + // Parameter attributes can never be deduced for indirect calls, as there is no + // function body available to use. + determined_fn_def_id: None, + caller_location: None, + is_virtual_call: false, + extra_args, + } + } + + fn for_instance( + tcx: TyCtxt<'tcx>, + query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)>, + ) -> Self { + let ty::PseudoCanonicalInput { typing_env, value: (instance, extra_args) } = query; + let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..)); + let is_tls_shim_call = matches!(instance.def, ty::InstanceKind::ThreadLocalShim(_)); + Self { + layout_cx: LayoutCx::new(tcx, typing_env), + sig: tcx.normalize_erasing_regions( + typing_env, + fn_sig_for_fn_abi(tcx, instance, typing_env), + ), + // Parameter attributes can be deduced from the bodies of neither: + // - virtual calls, as they might call other functions from the vtable; nor + // - TLS shims, as they would refer to the underlying static. + determined_fn_def_id: (!is_virtual_call && !is_tls_shim_call) + .then(|| instance.def_id()), + caller_location: instance + .def + .requires_caller_location(tcx) + .then(|| tcx.caller_location_ty()), + is_virtual_call, + extra_args, + } + } +} + fn fn_abi_of_fn_ptr<'tcx>( tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List>)>, ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { - let ty::PseudoCanonicalInput { typing_env, value: (sig, extra_args) } = query; - fn_abi_new_uncached( - &LayoutCx::new(tcx, typing_env), - tcx.instantiate_bound_regions_with_erased(sig), - extra_args, - None, - ) + let desc = FnAbiDesc::for_fn_ptr(tcx, query); + fn_abi_new_uncached(desc) } -fn fn_abi_of_instance<'tcx>( +fn fn_abi_of_instance_no_deduced_attrs<'tcx>( tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)>, ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { - let ty::PseudoCanonicalInput { typing_env, value: (instance, extra_args) } = query; - fn_abi_new_uncached( - &LayoutCx::new(tcx, typing_env), - fn_sig_for_fn_abi(tcx, instance, typing_env), - extra_args, - Some(instance), - ) + let desc = FnAbiDesc::for_instance(tcx, query); + fn_abi_new_uncached(desc) +} + +fn fn_abi_of_instance_raw<'tcx>( + tcx: TyCtxt<'tcx>, + query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)>, +) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { + // The `fn_abi_of_instance_no_deduced_attrs` query may have been called during CTFE, so we + // delegate to it here in order to reuse (and, if necessary, augment) its result. + tcx.fn_abi_of_instance_no_deduced_attrs(query).map(|fn_abi| { + let params = FnAbiDesc::for_instance(tcx, query); + // If the function's body can be used to deduce parameter attributes, then adjust such + // "no deduced attrs" ABI; otherwise, return that ABI unadjusted. + params.determined_fn_def_id.map_or(fn_abi, |fn_def_id| { + fn_abi_adjust_for_deduced_attrs(¶ms.layout_cx, fn_abi, params.sig.abi, fn_def_id) + }) + }) } // Handle safe Rust thin and wide pointers. @@ -483,27 +554,21 @@ fn fn_abi_sanity_check<'tcx>( fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret); } -#[tracing::instrument(level = "debug", skip(cx, instance))] +#[tracing::instrument( + level = "debug", + skip(cx, caller_location, determined_fn_def_id, is_virtual_call) +)] fn fn_abi_new_uncached<'tcx>( - cx: &LayoutCx<'tcx>, - sig: ty::FnSig<'tcx>, - extra_args: &[Ty<'tcx>], - instance: Option>, + FnAbiDesc { + layout_cx: ref cx, + sig, + determined_fn_def_id, + caller_location, + is_virtual_call, + extra_args, + }: FnAbiDesc<'tcx>, ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { let tcx = cx.tcx(); - let (caller_location, determined_fn_def_id, is_virtual_call) = if let Some(instance) = instance - { - let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..)); - let is_tls_shim_call = matches!(instance.def, ty::InstanceKind::ThreadLocalShim(_)); - ( - instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty()), - if is_virtual_call || is_tls_shim_call { None } else { Some(instance.def_id()) }, - is_virtual_call, - ) - } else { - (None, None, false) - }; - let sig = tcx.normalize_erasing_regions(cx.typing_env, sig); let abi_map = AbiMap::from_target(&tcx.sess.target); let conv = abi_map.canonize_abi(sig.abi, sig.c_variadic).unwrap(); @@ -579,16 +644,7 @@ fn fn_abi_new_uncached<'tcx>( sig.abi, ), }; - fn_abi_adjust_for_abi( - cx, - &mut fn_abi, - sig.abi, - // If this is a virtual call, we cannot pass the `fn_def_id`, as it might call other - // functions from vtable. And for a tls shim, passing the `fn_def_id` would refer to - // the underlying static. Internally, `deduced_param_attrs` attempts to infer attributes - // by visit the function body. - determined_fn_def_id, - ); + fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi); debug!("fn_abi_new_uncached = {:?}", fn_abi); fn_abi_sanity_check(cx, &fn_abi, sig.abi); Ok(tcx.arena.alloc(fn_abi)) @@ -599,7 +655,6 @@ fn fn_abi_adjust_for_abi<'tcx>( cx: &LayoutCx<'tcx>, fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>, abi: ExternAbi, - fn_def_id: Option, ) { if abi == ExternAbi::Unadjusted { // The "unadjusted" ABI passes aggregates in "direct" mode. That's fragile but needed for @@ -620,31 +675,35 @@ fn fn_abi_adjust_for_abi<'tcx>( for arg in fn_abi.args.iter_mut() { unadjust(arg); } - return; + } else if abi.is_rustic_abi() { + fn_abi.adjust_for_rust_abi(cx); + } else { + fn_abi.adjust_for_foreign_abi(cx, abi); } +} +#[tracing::instrument(level = "trace", skip(cx))] +fn fn_abi_adjust_for_deduced_attrs<'tcx>( + cx: &LayoutCx<'tcx>, + fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, + abi: ExternAbi, + fn_def_id: DefId, +) -> &'tcx FnAbi<'tcx, Ty<'tcx>> { let tcx = cx.tcx(); - - if abi.is_rustic_abi() { - fn_abi.adjust_for_rust_abi(cx); - - // Look up the deduced parameter attributes for this function, if we have its def ID and - // we're optimizing in non-incremental mode. We'll tag its parameters with those attributes - // as appropriate. - let deduced = - if tcx.sess.opts.optimize != OptLevel::No && tcx.sess.opts.incremental.is_none() { - fn_def_id.map(|fn_def_id| tcx.deduced_param_attrs(fn_def_id)).unwrap_or_default() - } else { - &[] - }; - if !deduced.is_empty() { - apply_deduced_attributes(cx, deduced, 0, &mut fn_abi.ret); - for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() { - apply_deduced_attributes(cx, deduced, arg_idx + 1, arg); - } - } + // Look up the deduced parameter attributes for this function, if we have its def ID. + // We'll tag its parameters with those attributes as appropriate. + let deduced = if abi.is_rustic_abi() { tcx.deduced_param_attrs(fn_def_id) } else { &[] }; + if deduced.is_empty() { + fn_abi } else { - fn_abi.adjust_for_foreign_abi(cx, abi); + let mut fn_abi = fn_abi.clone(); + apply_deduced_attributes(cx, deduced, 0, &mut fn_abi.ret); + for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() { + apply_deduced_attributes(cx, deduced, arg_idx + 1, arg); + } + debug!("fn_abi_adjust_for_deduced_attrs = {:?}", fn_abi); + fn_abi_sanity_check(cx, &fn_abi, abi); + tcx.arena.alloc(fn_abi) } } diff --git a/tests/ui/coroutine/avoid-query-cycle-in-ctfe.rs b/tests/ui/coroutine/avoid-query-cycle-in-ctfe.rs new file mode 100644 index 0000000000000..4ae3d97daa448 --- /dev/null +++ b/tests/ui/coroutine/avoid-query-cycle-in-ctfe.rs @@ -0,0 +1,18 @@ +//! Items whose type depends on CTFE (such as the async closure/coroutine beneath, whose type +//! depends upon evaluating `do_nothing`) should not cause a query cycle owing to the deduction of +//! the function's parameter attributes, which are only required for codegen and not for CTFE. +//! +//! Regression test for https://github.com/rust-lang/rust/issues/151748 +//@ compile-flags: -O +//@ edition: 2018 +//@ check-pass + +fn main() { + let _ = async || { + let COMPLEX_CONSTANT = (); + }; +} + +const fn do_nothing() {} + +const COMPLEX_CONSTANT: () = do_nothing();