mentioned_items: record all callee and coerced closure types, whether they are FnDef/Closure or not

They may become FnDef during monomorphization!
This commit is contained in:
Ralf Jung 2024-03-18 11:36:53 +01:00
parent f1ec494c32
commit 0d6a16ac4b
15 changed files with 355 additions and 76 deletions

View File

@ -45,6 +45,7 @@
use std::{iter, mem};
pub use self::query::*;
use self::visit::TyContext;
pub use basic_blocks::BasicBlocks;
mod basic_blocks;
@ -317,15 +318,15 @@ pub fn initial(
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, HashStable, TyEncodable, TyDecodable)]
#[derive(TypeFoldable, TypeVisitable)]
pub enum MentionedItem<'tcx> {
Fn(DefId, GenericArgsRef<'tcx>),
/// A function that gets called. We don't necessarily know its precise type yet, since it can be
/// hidden behind a generic.
Fn(Ty<'tcx>),
/// A type that has its drop shim called.
Drop(Ty<'tcx>),
/// Unsizing casts might require vtables, so we have to record them.
UnsizeCast {
source_ty: Ty<'tcx>,
target_ty: Ty<'tcx>,
},
UnsizeCast { source_ty: Ty<'tcx>, target_ty: Ty<'tcx> },
/// A closure that is coerced to a function pointer.
Closure(DefId, GenericArgsRef<'tcx>),
Closure(Ty<'tcx>),
}
/// The lowered representation of a single function.
@ -610,6 +611,17 @@ pub fn source_info(&self, location: Location) -> &SourceInfo {
}
}
pub fn span_for_ty_context(&self, ty_context: TyContext) -> Span {
match ty_context {
TyContext::UserTy(span) => span,
TyContext::ReturnTy(source_info)
| TyContext::LocalDecl { source_info, .. }
| TyContext::YieldTy(source_info)
| TyContext::ResumeTy(source_info) => source_info.span,
TyContext::Location(loc) => self.source_info(loc).span,
}
}
/// Returns the return type; it always return first element from `local_decls` array.
#[inline]
pub fn return_ty(&self) -> Ty<'tcx> {

View File

@ -723,15 +723,9 @@ fn dest_needs_borrow(place: Place<'_>) -> bool {
// some extra work here to save the monomorphization collector work later. It helps a lot,
// since monomorphization can avoid a lot of work when the "mentioned items" are similar to
// the actually used items. By doing this we can entirely avoid visiting the callee!
let callee_item = {
// We need to reconstruct the `required_item` for the callee so that we can find and
// remove it.
let func_ty = func.ty(caller_body, self.tcx);
match func_ty.kind() {
ty::FnDef(def_id, args) => MentionedItem::Fn(*def_id, args),
_ => bug!(),
}
};
// We need to reconstruct the `required_item` for the callee so that we can find and
// remove it.
let callee_item = MentionedItem::Fn(func.ty(caller_body, self.tcx));
if let Some(idx) =
caller_body.mentioned_items.iter().position(|item| item.node == callee_item)
{

View File

@ -1,6 +1,6 @@
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{self, ConstOperand, Location, MentionedItem, MirPass};
use rustc_middle::ty::{self, adjustment::PointerCoercion, TyCtxt};
use rustc_middle::mir::{self, Location, MentionedItem, MirPass};
use rustc_middle::ty::{adjustment::PointerCoercion, TyCtxt};
use rustc_session::Session;
use rustc_span::source_map::Spanned;
@ -29,27 +29,36 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
}
}
// This visitor is carefully in sync with the one in `rustc_monomorphize::collector`. We are
// visiting the exact same places but then instead of monomorphizing and creating `MonoItems`, we
// have to remain generic and just recording the relevant information in `mentioned_items`, where it
// will then be monomorphized later during "mentioned items" collection.
impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> {
fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, _: Location) {
let const_ = constant.const_;
// This is how function items get referenced: via constants of `FnDef` type. This handles
// both functions that are called and those that are just turned to function pointers.
if let ty::FnDef(def_id, args) = const_.ty().kind() {
debug!("adding to required_items: {def_id:?}");
self.mentioned_items
.push(Spanned { node: MentionedItem::Fn(*def_id, args), span: constant.span });
}
}
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
self.super_terminator(terminator, location);
match terminator.kind {
// We don't need to handle `Call` as we already handled all function type operands in
// `visit_constant`. But we do need to handle `Drop`.
let span = || self.body.source_info(location).span;
match &terminator.kind {
mir::TerminatorKind::Call { func, .. } => {
let callee_ty = func.ty(self.body, self.tcx);
self.mentioned_items
.push(Spanned { node: MentionedItem::Fn(callee_ty), span: span() });
}
mir::TerminatorKind::Drop { place, .. } => {
let ty = place.ty(self.body, self.tcx).ty;
let span = self.body.source_info(location).span;
self.mentioned_items.push(Spanned { node: MentionedItem::Drop(ty), span });
self.mentioned_items.push(Spanned { node: MentionedItem::Drop(ty), span: span() });
}
mir::TerminatorKind::InlineAsm { ref operands, .. } => {
for op in operands {
match *op {
mir::InlineAsmOperand::SymFn { ref value } => {
self.mentioned_items.push(Spanned {
node: MentionedItem::Fn(value.const_.ty()),
span: span(),
});
}
_ => {}
}
}
}
_ => {}
}
@ -57,6 +66,7 @@ fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Loc
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
self.super_rvalue(rvalue, location);
let span = || self.body.source_info(location).span;
match *rvalue {
// We need to detect unsizing casts that required vtables.
mir::Rvalue::Cast(
@ -65,13 +75,14 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
target_ty,
)
| mir::Rvalue::Cast(mir::CastKind::DynStar, ref operand, target_ty) => {
let span = self.body.source_info(location).span;
// This isn't monomorphized yet so we can't tell what the actual types are -- just
// add everything.
self.mentioned_items.push(Spanned {
node: MentionedItem::UnsizeCast {
source_ty: operand.ty(self.body, self.tcx),
target_ty,
},
span,
span: span(),
});
}
// Similarly, record closures that are turned into function pointers.
@ -80,17 +91,19 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
ref operand,
_,
) => {
let span = self.body.source_info(location).span;
let source_ty = operand.ty(self.body, self.tcx);
match *source_ty.kind() {
ty::Closure(def_id, args) => {
self.mentioned_items
.push(Spanned { node: MentionedItem::Closure(def_id, args), span });
}
_ => bug!(),
}
self.mentioned_items
.push(Spanned { node: MentionedItem::Closure(source_ty), span: span() });
}
// And finally, function pointer reification casts.
mir::Rvalue::Cast(
mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer),
ref operand,
_,
) => {
let fn_ty = operand.ty(self.body, self.tcx);
self.mentioned_items.push(Spanned { node: MentionedItem::Fn(fn_ty), span: span() });
}
// Function pointer casts are already handled by `visit_constant` above.
_ => {}
}
}

View File

@ -173,6 +173,26 @@
//! do not use a failing constant. This is reflected via the [`CollectionMode`], which determines
//! whether we are visiting a used item or merely a mentioned item.
//!
//! The collector and "mentioned items" gathering (which lives in `rustc_mir_transform::mentioned_items`)
//! need to stay in sync in the following sense:
//!
//! - For every item that the collector gather that could eventually lead to build failure (most
//! likely due to containing a constant that fails to evaluate), a corresponding mentioned item
//! must be added. This should use the exact same strategy as the ecollector to make sure they are
//! in sync. However, while the collector works on monomorphized types, mentioned items are
//! collected on generic MIR -- so any time the collector checks for a particular type (such as
//! `ty::FnDef`), we have to just onconditionally add this as a mentioned item.
//! - In `visit_mentioned_item`, we then do with that mentioned item exactly what the collector
//! would have done during regular MIR visiting. Basically you can think of the collector having
//! two stages, a pre-monomorphization stage and a post-monomorphization stage (usually quite
//! literally separated by a call to `self.monomorphize`); the pre-monomorphizationn stage is
//! duplicated in mentioned items gathering and the post-monomorphization stage is duplicated in
//! `visit_mentioned_item`.
//! - Finally, as a performance optimization, the collector should fill `used_mentioned_item` during
//! its MIR traversal with exactly what mentioned item gathering would have added in the same
//! situation. This detects mentioned items that have *not* been optimized away and hence don't
//! need a dedicated traversal.
//!
//! Open Issues
//! -----------
//! Some things are not yet fully implemented in the current version of this
@ -904,8 +924,11 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
target_ty,
)
| mir::Rvalue::Cast(mir::CastKind::DynStar, ref operand, target_ty) => {
let target_ty = self.monomorphize(target_ty);
let source_ty = operand.ty(self.body, self.tcx);
// *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items
.insert(MentionedItem::UnsizeCast { source_ty, target_ty });
let target_ty = self.monomorphize(target_ty);
let source_ty = self.monomorphize(source_ty);
let (source_ty, target_ty) =
find_vtable_types_for_unsizing(self.tcx.at(span), source_ty, target_ty);
@ -930,6 +953,8 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
_,
) => {
let fn_ty = operand.ty(self.body, self.tcx);
// *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items.insert(MentionedItem::Fn(fn_ty));
let fn_ty = self.monomorphize(fn_ty);
visit_fn_use(self.tcx, fn_ty, false, span, self.used_items);
}
@ -939,20 +964,17 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
_,
) => {
let source_ty = operand.ty(self.body, self.tcx);
// *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items.insert(MentionedItem::Closure(source_ty));
let source_ty = self.monomorphize(source_ty);
match *source_ty.kind() {
ty::Closure(def_id, args) => {
let instance = Instance::resolve_closure(
self.tcx,
def_id,
args,
ty::ClosureKind::FnOnce,
);
if should_codegen_locally(self.tcx, &instance) {
self.used_items.push(create_fn_mono_item(self.tcx, instance, span));
}
if let ty::Closure(def_id, args) = *source_ty.kind() {
let instance =
Instance::resolve_closure(self.tcx, def_id, args, ty::ClosureKind::FnOnce);
if should_codegen_locally(self.tcx, &instance) {
self.used_items.push(create_fn_mono_item(self.tcx, instance, span));
}
_ => bug!(),
} else {
bug!()
}
}
mir::Rvalue::ThreadLocalRef(def_id) => {
@ -994,9 +1016,7 @@ fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Loc
mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } => {
let callee_ty = func.ty(self.body, tcx);
// *Before* monomorphizing, record that we already handled this mention.
if let ty::FnDef(def_id, args) = callee_ty.kind() {
self.used_mentioned_items.insert(MentionedItem::Fn(*def_id, args));
}
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
let callee_ty = self.monomorphize(callee_ty);
self.check_fn_args_move_size(callee_ty, args, *fn_span, location);
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.used_items)
@ -1012,7 +1032,10 @@ fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Loc
for op in operands {
match *op {
mir::InlineAsmOperand::SymFn { ref value } => {
let fn_ty = self.monomorphize(value.const_.ty());
let fn_ty = value.const_.ty();
// *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items.insert(MentionedItem::Fn(fn_ty));
let fn_ty = self.monomorphize(fn_ty);
visit_fn_use(self.tcx, fn_ty, false, source, self.used_items);
}
mir::InlineAsmOperand::SymStatic { def_id } => {
@ -1076,6 +1099,8 @@ fn visit_drop_use<'tcx>(
visit_instance_use(tcx, instance, is_direct_call, source, output);
}
/// For every call of this function in the visitor, make sure there is a matching call in the
/// `mentioned_items` pass!
fn visit_fn_use<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
@ -1653,13 +1678,13 @@ fn collect_items_of_instance<'tcx>(
// Naively, in "used" collection mode, all functions get added to *both* `used_items` and
// `mentioned_items`. Mentioned items processing will then notice that they have already been
// visited, but at that point each mentioned item has been monomorphized, added to the
// `mentioned_items` worklist, and checked in the global set of visited items. To removes that
// `mentioned_items` worklist, and checked in the global set of visited items. To remove that
// overhead, we have a special optimization that avoids adding items to `mentioned_items` when
// they are already added in `used_items`. We could just scan `used_items`, but that's a linear
// scan and not very efficient. Furthermore we can only do that *after* monomorphizing the
// mentioned item. So instead we collect all pre-monomorphized `MentionedItem` that were already
// added to `used_items` in a hash set, which can efficiently query in the
// `body.mentioned_items` loop below.
// `body.mentioned_items` loop below without even having to monomorphize the item.
let mut used_mentioned_items = FxHashSet::<MentionedItem<'tcx>>::default();
let mut collector = MirUsedCollector {
tcx,
@ -1704,13 +1729,16 @@ fn visit_mentioned_item<'tcx>(
output: &mut MonoItems<'tcx>,
) {
match *item {
MentionedItem::Fn(def_id, args) => {
let instance = Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args);
// `visit_instance_use` was written for "used" item collection but works just as well
// for "mentioned" item collection.
// We can set `is_direct_call`; that just means we'll skip a bunch of shims that anyway
// can't have their own failing constants.
visit_instance_use(tcx, instance, /*is_direct_call*/ true, span, output);
MentionedItem::Fn(ty) => {
if let ty::FnDef(def_id, args) = *ty.kind() {
let instance =
Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args);
// `visit_instance_use` was written for "used" item collection but works just as well
// for "mentioned" item collection.
// We can set `is_direct_call`; that just means we'll skip a bunch of shims that anyway
// can't have their own failing constants.
visit_instance_use(tcx, instance, /*is_direct_call*/ true, span, output);
}
}
MentionedItem::Drop(ty) => {
visit_drop_use(tcx, ty, /*is_direct_call*/ true, span, output);
@ -1727,10 +1755,15 @@ fn visit_mentioned_item<'tcx>(
create_mono_items_for_vtable_methods(tcx, target_ty, source_ty, span, output);
}
}
MentionedItem::Closure(def_id, args) => {
let instance = Instance::resolve_closure(tcx, def_id, args, ty::ClosureKind::FnOnce);
if should_codegen_locally(tcx, &instance) {
output.push(create_fn_mono_item(tcx, instance, span));
MentionedItem::Closure(source_ty) => {
if let ty::Closure(def_id, args) = *source_ty.kind() {
let instance =
Instance::resolve_closure(tcx, def_id, args, ty::ClosureKind::FnOnce);
if should_codegen_locally(tcx, &instance) {
output.push(create_fn_mono_item(tcx, instance, span));
}
} else {
bug!()
}
}
}

View File

@ -16,7 +16,7 @@ note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $DIR/collect-in-dead-closure.rs:23:33
|
LL | let _closure: fn() = || not_called::<T>();
| ^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-assoc-type.rs:9:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-assoc-type.rs:9:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-assoc-type.rs:14:17
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-assoc-type.rs:9:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-assoc-type.rs:9:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-assoc-type.rs:14:17
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,45 @@
#![feature(impl_trait_in_assoc_type)]
//@revisions: noopt opt
//@ build-fail
//@[opt] compile-flags: -O
//! This fails without optimizations, so it should also fail with optimizations.
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
}
fn not_called<T>() {
if false {
let _ = Fail::<T>::C;
}
}
fn callit_not(f: impl Fn()) {
if false {
f();
}
}
// Using `Fn` here is important; with `FnOnce` another shim gets involved which somehow makes this
// easier to collect properly.
trait Hideaway {
type T: Fn();
const C: Self::T;
}
impl Hideaway for () {
type T = impl Fn();
const C: Self::T = not_called::<i32>;
}
fn reveal<T: Hideaway>() {
if false {
callit_not(T::C);
}
}
fn main() {
if false {
reveal::<()>()
}
}

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-generic.rs:8:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-generic.rs:8:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-generic.rs:13:17
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-generic.rs:8:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-generic.rs:8:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-generic.rs:13:17
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,27 @@
//@revisions: noopt opt
//@ build-fail
//@[opt] compile-flags: -O
//! This fails without optimizations, so it should also fail with optimizations.
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
}
fn not_called<T>() {
if false {
let _ = Fail::<T>::C;
}
}
fn callit_not(f: impl Fn()) {
if false {
f();
}
}
fn main() {
if false {
callit_not(not_called::<i32>)
}
}

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `m::Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-opaque-type.rs:10:23
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-opaque-type.rs:10:23
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-opaque-type.rs:17:21
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn m::not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,20 @@
error[E0080]: evaluation of `m::Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn-behind-opaque-type.rs:10:23
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn-behind-opaque-type.rs:10:23
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/collect-in-dead-fn-behind-opaque-type.rs:17:21
|
LL | let _ = Fail::<T>::C;
| ^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn m::not_called::<i32>`
--> $SRC_DIR/core/src/ops/function.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,35 @@
//@revisions: noopt opt
//@ build-fail
//@[opt] compile-flags: -O
//! This fails without optimizations, so it should also fail with optimizations.
#![feature(type_alias_impl_trait)]
mod m {
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `m::Fail::<i32>::C` failed
}
pub type NotCalledFn = impl Fn();
fn not_called<T>() {
if false {
let _ = Fail::<T>::C;
}
}
fn mk_not_called() -> NotCalledFn {
not_called::<i32>
}
}
fn main() {
// This does not involve a constant of `FnDef` type, it generates the value via unsafe
// shenanigans instead. This ensures that we check all `FnDef` types that occur in a function,
// not just those of constants. Furthermore the `FnDef` is behind an opaque type which bust be
// normalized away to reveal the function type.
if false {
let x: m::NotCalledFn = unsafe { std::mem::transmute(()) };
x();
}
}

View File

@ -16,7 +16,7 @@ note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $DIR/collect-in-dead-fn.rs:25:9
|
LL | not_called::<T>();
| ^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error