diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs index 34339b0634194..a27809ca1f2ec 100644 --- a/src/librustc_mir/transform/promote_consts.rs +++ b/src/librustc_mir/transform/promote_consts.rs @@ -332,6 +332,14 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let operand = Operand::Copy(promoted_place(ty, span)); mem::replace(&mut args[index], operand) } + // We expected a `TerminatorKind::Call` for which we'd like to promote an + // argument. `qualify_consts` saw a `TerminatorKind::Call` here, but + // we are seeing a `Goto`. That means that the `promote_temps` method + // already promoted this call away entirely. This case occurs when calling + // a function requiring a constant argument and as that constant value + // providing a value whose computation contains another call to a function + // requiring a constant argument. + TerminatorKind::Goto { .. } => return, _ => bug!() } } diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 17fe78d325cf1..e7d694ffba91c 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -812,7 +812,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { let fn_ty = func.ty(self.mir, self.tcx); let mut callee_def_id = None; - let (mut is_shuffle, mut is_const_fn) = (false, false); + let mut is_shuffle = false; + let mut is_const_fn = false; + let mut is_promotable_const_fn = false; if let ty::FnDef(def_id, _) = fn_ty.sty { callee_def_id = Some(def_id); match self.tcx.fn_sig(def_id).abi() { @@ -873,6 +875,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { // functions without #[rustc_promotable] if self.tcx.is_promotable_const_fn(def_id) { is_const_fn = true; + is_promotable_const_fn = true; + } else if self.tcx.is_const_fn(def_id) { + is_const_fn = true; } } else { // stable const fn or unstable const fns with their feature gate @@ -974,7 +979,17 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { if !constant_arguments.contains(&i) { return } - if this.qualif.is_empty() { + // Since the argument is required to be constant, + // we care about constness, not promotability. + // If we checked for promotability, we'd miss out on + // the results of function calls (which are never promoted + // in runtime code) + // This is not a problem, because the argument explicitly + // requests constness, in contrast to regular promotion + // which happens even without the user requesting it. + // We can error out with a hard error if the argument is not + // constant here. + if (this.qualif - Qualif::NOT_PROMOTABLE).is_empty() { this.promotion_candidates.push(candidate); } else { this.tcx.sess.span_err(this.span, @@ -1003,7 +1018,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { // Be conservative about the returned value of a const fn. let tcx = self.tcx; let ty = dest.ty(self.mir, tcx).to_ty(tcx); - self.qualif = Qualif::empty(); + if is_const_fn && !is_promotable_const_fn && self.mode == Mode::Fn { + self.qualif = Qualif::NOT_PROMOTABLE; + } else { + self.qualif = Qualif::empty(); + } self.add_type(ty); } self.assign(dest, location); diff --git a/src/test/ui/consts/const-eval/double_promotion.rs b/src/test/ui/consts/const-eval/double_promotion.rs new file mode 100644 index 0000000000000..0e75ea8e66b3c --- /dev/null +++ b/src/test/ui/consts/const-eval/double_promotion.rs @@ -0,0 +1,17 @@ +// compile-pass + +#![feature(const_fn, rustc_attrs)] + +#[rustc_args_required_const(0)] +pub const fn a(value: u8) -> u8 { + value +} + +#[rustc_args_required_const(0)] +pub fn b(_: u8) { + unimplemented!() +} + +fn main() { + let _ = b(a(0)); +}