mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Do not deduce parameter attributes during CTFE
`fn_abi_of_instance` can look at function bodies to deduce codegen optimization attributes (namely `ArgAttribute::ReadOnly` and `ArgAttribute::CapturesNone`) of indirectly-passed parameters. This can lead to cycles when performed during typeck, when such attributes are irrelevant. This patch breaks a subquery out from `fn_abi_of_instance`, `fn_abi_of_instance_no_deduced_attrs`, which returns the ABI before such parameter attributes are deduced; and that new subquery is used in CTFE instead.
This commit is contained in:
@@ -361,7 +361,7 @@ pub fn init_stack_frame(
|
||||
} 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 @@ pub(super) fn init_drop_in_place_call(
|
||||
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)?);
|
||||
|
||||
@@ -153,16 +153,16 @@ pub fn fn_abi_of_fn_ptr(
|
||||
}
|
||||
|
||||
/// 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<Ty<'tcx>>,
|
||||
) -> <Self as FnAbiOfHelpers<'tcx>>::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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ fn eval_callee_and_args(
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1801,11 +1801,32 @@
|
||||
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 `fn_abi_of_instance`.
|
||||
///
|
||||
/// NB: that includes virtual calls, which are represented by "direct calls"
|
||||
/// to an `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::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 `<dyn Trait as Trait>::fn`).
|
||||
query fn_abi_of_instance_no_deduced_attrs(
|
||||
key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>
|
||||
) -> 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`.
|
||||
///
|
||||
/// * that includes virtual calls, which are represented by "direct calls" to an
|
||||
/// `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
|
||||
query fn_abi_of_instance(
|
||||
key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>
|
||||
) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> {
|
||||
|
||||
@@ -1369,11 +1369,56 @@ fn fn_abi_of_fn_ptr(
|
||||
)
|
||||
}
|
||||
|
||||
/// 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 `<dyn Trait as Trait>::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 `<dyn Trait as Trait>::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<Ty<'tcx>>,
|
||||
) -> 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 `<dyn Trait as Trait>::fn`).
|
||||
#[inline]
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
fn fn_abi_of_instance(
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
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,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE(eddyb) this is private to avoid using it from outside of
|
||||
@@ -243,30 +248,95 @@ 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<DefId>,
|
||||
caller_location: Option<Ty<'tcx>>,
|
||||
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<Ty<'tcx>>)>,
|
||||
) -> 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<Ty<'tcx>>)>,
|
||||
) -> 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<Ty<'tcx>>)>,
|
||||
) -> 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_no_deduced_attrs<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
|
||||
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
|
||||
let desc = FnAbiDesc::for_instance(tcx, query);
|
||||
fn_abi_new_uncached(desc)
|
||||
}
|
||||
|
||||
fn fn_abi_of_instance<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
|
||||
) -> 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),
|
||||
)
|
||||
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 +553,21 @@ fn fn_arg_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<ty::Instance<'tcx>>,
|
||||
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 +643,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 +654,6 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
||||
cx: &LayoutCx<'tcx>,
|
||||
fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>,
|
||||
abi: ExternAbi,
|
||||
fn_def_id: Option<DefId>,
|
||||
) {
|
||||
if abi == ExternAbi::Unadjusted {
|
||||
// The "unadjusted" ABI passes aggregates in "direct" mode. That's fragile but needed for
|
||||
@@ -620,34 +674,46 @@ fn unadjust<'tcx>(arg: &mut ArgAbi<'tcx, Ty<'tcx>>) {
|
||||
for arg in fn_abi.args.iter_mut() {
|
||||
unadjust(arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let tcx = cx.tcx();
|
||||
|
||||
if abi.is_rustic_abi() {
|
||||
} else 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);
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
// 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 abi.is_rustic_abi()
|
||||
&& tcx.sess.opts.optimize != OptLevel::No
|
||||
&& tcx.sess.opts.incremental.is_none()
|
||||
{
|
||||
tcx.deduced_param_attrs(fn_def_id)
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
if deduced.is_empty() {
|
||||
fn_abi
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply deduced optimization attributes to a parameter using an indirect pass mode.
|
||||
///
|
||||
/// `deduced` is a possibly truncated list of deduced attributes for a return place and arguments.
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user