diff --git a/compiler/rustc_mir/src/transform/check_consts/mod.rs b/compiler/rustc_mir/src/transform/check_consts/mod.rs index 8d4efd8ae8052..4cab3e48dacff 100644 --- a/compiler/rustc_mir/src/transform/check_consts/mod.rs +++ b/compiler/rustc_mir/src/transform/check_consts/mod.rs @@ -12,6 +12,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Symbol; pub use self::qualifs::Qualif; +pub use self::validation::non_const_fn_could_be_made_stable_const_fn; mod ops; pub mod post_drop_elaboration; diff --git a/compiler/rustc_mir/src/transform/check_consts/ops.rs b/compiler/rustc_mir/src/transform/check_consts/ops.rs index e14dcf92b89d2..3ba2363f5672c 100644 --- a/compiler/rustc_mir/src/transform/check_consts/ops.rs +++ b/compiler/rustc_mir/src/transform/check_consts/ops.rs @@ -1,6 +1,6 @@ //! Concrete error types for all operations which may be invalid in a certain const context. -use rustc_errors::{struct_span_err, Applicability}; +use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_session::config::nightly_options; @@ -10,49 +10,6 @@ use rustc_span::{Span, Symbol}; use super::ConstCx; -/// Emits an error and returns `true` if `op` is not allowed in the given const context. -pub fn non_const(ccx: &ConstCx<'_, '_>, op: O, span: Span) -> bool { - debug!("illegal_op: op={:?}", op); - - let gate = match op.status_in_item(ccx) { - Status::Allowed => return false, - - Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => { - let unstable_in_stable = ccx.is_const_stable_const_fn() - && !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate); - - if unstable_in_stable { - ccx.tcx.sess - .struct_span_err( - span, - &format!("const-stable function cannot use `#[feature({})]`", gate.as_str()), - ) - .span_suggestion( - ccx.body.span, - "if it is not part of the public API, make this function unstably const", - concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(), - Applicability::HasPlaceholders, - ) - .note("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks") - .emit(); - } - - return unstable_in_stable; - } - - Status::Unstable(gate) => Some(gate), - Status::Forbidden => None, - }; - - if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - ccx.tcx.sess.miri_unleashed_feature(span, gate); - return false; - } - - op.emit_error(ccx, span); - true -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Status { Allowed, diff --git a/compiler/rustc_mir/src/transform/check_consts/post_drop_elaboration.rs b/compiler/rustc_mir/src/transform/check_consts/post_drop_elaboration.rs index 0c171bbc464a2..537dc4268263a 100644 --- a/compiler/rustc_mir/src/transform/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_mir/src/transform/check_consts/post_drop_elaboration.rs @@ -4,7 +4,7 @@ use rustc_middle::mir::{self, BasicBlock, Location}; use rustc_middle::ty::TyCtxt; use rustc_span::Span; -use super::ops; +use super::ops::{self, NonConstOp}; use super::qualifs::{NeedsDrop, Qualif}; use super::validation::Qualifs; use super::ConstCx; @@ -56,7 +56,7 @@ impl std::ops::Deref for CheckLiveDrops<'mir, 'tcx> { impl CheckLiveDrops<'mir, 'tcx> { fn check_live_drop(&self, span: Span) { - ops::non_const(self.ccx, ops::LiveDrop { dropped_at: None }, span); + ops::LiveDrop { dropped_at: None }.emit_error(self.ccx, span) } } diff --git a/compiler/rustc_mir/src/transform/check_consts/qualifs.rs b/compiler/rustc_mir/src/transform/check_consts/qualifs.rs index 3f4b3ca2eedb4..8f8815c1d3834 100644 --- a/compiler/rustc_mir/src/transform/check_consts/qualifs.rs +++ b/compiler/rustc_mir/src/transform/check_consts/qualifs.rs @@ -244,8 +244,11 @@ where }; // Check the qualifs of the value of `const` items. - if let ty::ConstKind::Unevaluated(def, _, promoted) = constant.literal.val { - assert!(promoted.is_none()); + if let ty::ConstKind::Unevaluated(def, _, _promoted) = constant.literal.val { + // FIXME(rust-lang/rust-clippy#6080): Const-checking should never see promoteds, but clippy + // is broken. + // assert!(promoted.is_none()); + // Don't peek inside trait associated constants. if cx.tcx.trait_of_item(def.did).is_none() { let qualifs = if let Some((did, param_did)) = def.as_const_arg() { diff --git a/compiler/rustc_mir/src/transform/check_consts/validation.rs b/compiler/rustc_mir/src/transform/check_consts/validation.rs index 7ea3c1d5a6f85..992e55be09047 100644 --- a/compiler/rustc_mir/src/transform/check_consts/validation.rs +++ b/compiler/rustc_mir/src/transform/check_consts/validation.rs @@ -1,8 +1,8 @@ //! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations. -use rustc_errors::struct_span_err; -use rustc_hir::{self as hir, LangItem}; -use rustc_hir::{def_id::DefId, HirId}; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, HirId, LangItem}; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; @@ -11,13 +11,13 @@ use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::{ self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt, TypeAndMut, }; -use rustc_span::{sym, Span}; +use rustc_span::{sym, Span, Symbol}; use rustc_trait_selection::traits::error_reporting::InferCtxtExt; use rustc_trait_selection::traits::{self, TraitEngine}; use std::ops::Deref; -use super::ops::{self, NonConstOp}; +use super::ops::{self, NonConstOp, Status}; use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{is_lang_panic_fn, ConstCx, Qualif}; @@ -25,6 +25,34 @@ use crate::const_eval::is_unstable_const_fn; use crate::dataflow::impls::MaybeMutBorrowedLocals; use crate::dataflow::{self, Analysis}; +/// Returns `true` if the given `fn` could be made into a `const fn` without depending on any +/// unstable features. +/// +/// This is used by clippy. Do not use it for const-checking. +pub fn non_const_fn_could_be_made_stable_const_fn( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, + body: &Body<'tcx>, +) -> bool { + let const_kind = tcx.hir().body_const_context(def_id); + + // Only run this on non-const `fn`s. + assert!(const_kind.is_none()); + + let ccx = ConstCx { + body, + tcx, + def_id, + const_kind: Some(hir::ConstContext::ConstFn), + param_env: tcx.param_env(def_id), + }; + + let mut checker = Validator::new(&ccx); + checker.silence_errors = true; + checker.check_body(); + checker.passes_checks_without_unstable_features +} + // We are using `MaybeMutBorrowedLocals` as a proxy for whether an item may have been mutated // through a pointer prior to the given point. This is okay even though `MaybeMutBorrowedLocals` // kills locals upon `StorageDead` because a local will never be used after a `StorageDead`. @@ -179,7 +207,12 @@ pub struct Validator<'mir, 'tcx> { /// The span of the current statement. span: Span, - const_checking_stopped: bool, + /// True if we shouldn't emit errors when we find them. + /// + /// This allows items to be speculatively const-checked. + silence_errors: bool, + + passes_checks_without_unstable_features: bool, } impl Deref for Validator<'mir, 'tcx> { @@ -196,7 +229,8 @@ impl Validator<'mir, 'tcx> { span: ccx.body.span, ccx, qualifs: Default::default(), - const_checking_stopped: false, + passes_checks_without_unstable_features: true, + silence_errors: false, } } @@ -266,15 +300,48 @@ impl Validator<'mir, 'tcx> { /// Emits an error at the given `span` if an expression cannot be evaluated in the current /// context. pub fn check_op_spanned(&mut self, op: O, span: Span) { - // HACK: This is for strict equivalence with the old `qualify_min_const_fn` pass, which - // only emitted one error per function. It should be removed and the test output updated. - if self.const_checking_stopped { + debug!("illegal_op: op={:?}", op); + + let ccx = self.ccx; + + let gate = match op.status_in_item(ccx) { + Status::Allowed => return, + Status::Unstable(gate) => Some(gate), + Status::Forbidden => None, + }; + + self.passes_checks_without_unstable_features = false; + + if self.silence_errors { + return; + } + + // Unless we are const-checking a const-stable function, return before emitting an error if + // the user has enabled the requisite feature gate. + if let Some(gate) = gate { + if ccx.tcx.features().enabled(gate) { + let unstable_in_stable = ccx.is_const_stable_const_fn() + && !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate); + + if unstable_in_stable { + error_unstable_in_stable(ccx, gate, span); + } + + return; + } + } + + if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { + ccx.tcx.sess.miri_unleashed_feature(span, gate); return; } - let err_emitted = ops::non_const(self.ccx, op, span); - if err_emitted && O::STOPS_CONST_CHECKING { - self.const_checking_stopped = true; + op.emit_error(ccx, span); + + // HACK: This is for strict equivalence with the old `qualify_min_const_fn` pass, which + // only emitted one error per function. It should be removed and the test output updated. + if O::STOPS_CONST_CHECKING { + self.silence_errors = true; } } @@ -774,12 +841,6 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { // projections that cannot be `NeedsDrop`. TerminatorKind::Drop { place: dropped_place, .. } | TerminatorKind::DropAndReplace { place: dropped_place, .. } => { - // If we are checking live drops after drop-elaboration, don't emit duplicate - // errors here. - if super::post_drop_elaboration::checking_enabled(self.ccx) { - return; - } - let mut err_span = self.span; // Check to see if the type of this place can ever have a drop impl. If not, this @@ -791,7 +852,7 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { return; } - let needs_drop = if let Some(local) = dropped_place.as_local() { + let local_needs_drop = if let Some(local) = dropped_place.as_local() { // Use the span where the local was declared as the span of the drop error. err_span = self.body.local_decls[local].source_info.span; self.qualifs.needs_drop(self.ccx, local, location) @@ -799,12 +860,22 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { true }; - if needs_drop { - self.check_op_spanned( - ops::LiveDrop { dropped_at: Some(terminator.source_info.span) }, - err_span, - ); + if !local_needs_drop { + return; } + + self.passes_checks_without_unstable_features = false; + + // If we are checking live drops after drop-elaboration, don't emit duplicate + // errors here. + if super::post_drop_elaboration::checking_enabled(self.ccx) { + return; + } + + self.check_op_spanned( + ops::LiveDrop { dropped_at: Some(terminator.source_info.span) }, + err_span, + ); } TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm), @@ -866,3 +937,20 @@ fn place_as_reborrow( } }) } + +fn error_unstable_in_stable(ccx: &ConstCx<'_, '_>, gate: Symbol, span: Span) { + ccx.tcx + .sess + .struct_span_err( + span, + &format!("const-stable function cannot use `#[feature({})]`", gate.as_str()), + ) + .span_suggestion( + ccx.body.span, + "if it is not part of the public API, make this function unstably const", + concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(), + Applicability::HasPlaceholders, + ) + .note("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks") + .emit(); +} diff --git a/compiler/rustc_mir/src/transform/mod.rs b/compiler/rustc_mir/src/transform/mod.rs index abe2dc496a630..5703e96e6f16f 100644 --- a/compiler/rustc_mir/src/transform/mod.rs +++ b/compiler/rustc_mir/src/transform/mod.rs @@ -36,7 +36,6 @@ pub mod match_branches; pub mod no_landing_pads; pub mod nrvo; pub mod promote_consts; -pub mod qualify_min_const_fn; pub mod remove_noop_landing_pads; pub mod required_consts; pub mod rustc_peek; diff --git a/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs b/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs deleted file mode 100644 index f15a7f7c2c889..0000000000000 --- a/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs +++ /dev/null @@ -1,462 +0,0 @@ -use rustc_hir as hir; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::*; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; -use rustc_span::symbol::{sym, Symbol}; -use rustc_span::Span; -use rustc_target::spec::abi::Abi::RustIntrinsic; -use std::borrow::Cow; - -type McfResult = Result<(), (Span, Cow<'static, str>)>; - -pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult { - // Prevent const trait methods from being annotated as `stable`. - if tcx.features().staged_api { - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - if crate::const_eval::is_parent_const_impl_raw(tcx, hir_id) { - return Err((body.span, "trait methods cannot be stable const fn".into())); - } - } - - let mut current = def_id; - loop { - let predicates = tcx.predicates_of(current); - for (predicate, _) in predicates.predicates { - match predicate.skip_binders() { - ty::PredicateAtom::RegionOutlives(_) - | ty::PredicateAtom::TypeOutlives(_) - | ty::PredicateAtom::WellFormed(_) - | ty::PredicateAtom::Projection(_) - | ty::PredicateAtom::ConstEvaluatable(..) - | ty::PredicateAtom::ConstEquate(..) - | ty::PredicateAtom::TypeWellFormedFromEnv(..) => continue, - ty::PredicateAtom::ObjectSafe(_) => { - bug!("object safe predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::ClosureKind(..) => { - bug!("closure kind predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::Subtype(_) => { - bug!("subtype predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::Trait(pred, constness) => { - if Some(pred.def_id()) == tcx.lang_items().sized_trait() { - continue; - } - match pred.self_ty().kind() { - ty::Param(ref p) => { - // Allow `T: ?const Trait` - if constness == hir::Constness::NotConst - && feature_allowed(tcx, def_id, sym::const_trait_bound_opt_out) - { - continue; - } - - let generics = tcx.generics_of(current); - let def = generics.type_param(p, tcx); - let span = tcx.def_span(def.def_id); - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - // other kinds of bounds are either tautologies - // or cause errors in other passes - _ => continue, - } - } - } - } - match predicates.parent { - Some(parent) => current = parent, - None => break, - } - } - - for local in &body.local_decls { - check_ty(tcx, local.ty, local.source_info.span, def_id)?; - } - // impl trait is gone in MIR, so check the return type manually - check_ty( - tcx, - tcx.fn_sig(def_id).output().skip_binder(), - body.local_decls.iter().next().unwrap().source_info.span, - def_id, - )?; - - for bb in body.basic_blocks() { - check_terminator(tcx, body, def_id, bb.terminator())?; - for stmt in &bb.statements { - check_statement(tcx, body, def_id, stmt)?; - } - } - Ok(()) -} - -fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult { - for arg in ty.walk() { - let ty = match arg.unpack() { - GenericArgKind::Type(ty) => ty, - - // No constraints on lifetimes or constants, except potentially - // constants' types, but `walk` will get to them as well. - GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue, - }; - - match ty.kind() { - ty::Ref(_, _, hir::Mutability::Mut) => { - if !feature_allowed(tcx, fn_def_id, sym::const_mut_refs) { - return Err((span, "mutable references in const fn are unstable".into())); - } - } - ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())), - ty::FnPtr(..) => { - if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) { - return Err((span, "function pointers in const fn are unstable".into())); - } - } - ty::Dynamic(preds, _) => { - for pred in preds.iter() { - match pred.skip_binder() { - ty::ExistentialPredicate::AutoTrait(_) - | ty::ExistentialPredicate::Projection(_) => { - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - ty::ExistentialPredicate::Trait(trait_ref) => { - if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() { - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - } - } - } - } - _ => {} - } - } - Ok(()) -} - -fn check_rvalue( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - def_id: DefId, - rvalue: &Rvalue<'tcx>, - span: Span, -) -> McfResult { - match rvalue { - Rvalue::ThreadLocalRef(_) => { - Err((span, "cannot access thread local storage in const fn".into())) - } - Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => { - check_operand(tcx, operand, span, def_id, body) - } - Rvalue::Len(place) - | Rvalue::Discriminant(place) - | Rvalue::Ref(_, _, place) - | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span, def_id, body), - Rvalue::Cast(CastKind::Misc, operand, cast_ty) => { - use rustc_middle::ty::cast::CastTy; - let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast"); - let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); - match (cast_in, cast_out) { - (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => { - Err((span, "casting pointers to ints is unstable in const fn".into())) - } - _ => check_operand(tcx, operand, span, def_id, body), - } - } - Rvalue::Cast( - CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), - operand, - _, - ) => check_operand(tcx, operand, span, def_id, body), - Rvalue::Cast( - CastKind::Pointer( - PointerCast::UnsafeFnPointer - | PointerCast::ClosureFnPointer(_) - | PointerCast::ReifyFnPointer, - ), - _, - _, - ) => Err((span, "function pointer casts are not allowed in const fn".into())), - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => { - let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) { - deref_ty.ty - } else { - // We cannot allow this for now. - return Err(( - span, - "unsizing casts are only allowed for references right now".into(), - )); - }; - let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); - if let ty::Slice(_) | ty::Str = unsized_ty.kind() { - check_operand(tcx, op, span, def_id, body)?; - // Casting/coercing things to slices is fine. - Ok(()) - } else { - // We just can't allow trait objects until we have figured out trait method calls. - Err((span, "unsizing casts are not allowed in const fn".into())) - } - } - // binops are fine on integers - Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => { - check_operand(tcx, lhs, span, def_id, body)?; - check_operand(tcx, rhs, span, def_id, body)?; - let ty = lhs.ty(body, tcx); - if ty.is_integral() || ty.is_bool() || ty.is_char() { - Ok(()) - } else { - Err((span, "only int, `bool` and `char` operations are stable in const fn".into())) - } - } - Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()), - Rvalue::NullaryOp(NullOp::Box, _) => { - Err((span, "heap allocations are not allowed in const fn".into())) - } - Rvalue::UnaryOp(_, operand) => { - let ty = operand.ty(body, tcx); - if ty.is_integral() || ty.is_bool() { - check_operand(tcx, operand, span, def_id, body) - } else { - Err((span, "only int and `bool` operations are stable in const fn".into())) - } - } - Rvalue::Aggregate(_, operands) => { - for operand in operands { - check_operand(tcx, operand, span, def_id, body)?; - } - Ok(()) - } - } -} - -fn check_statement( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - def_id: DefId, - statement: &Statement<'tcx>, -) -> McfResult { - let span = statement.source_info.span; - match &statement.kind { - StatementKind::Assign(box (place, rval)) => { - check_place(tcx, *place, span, def_id, body)?; - check_rvalue(tcx, body, def_id, rval, span) - } - - StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, def_id, body), - - // just an assignment - StatementKind::SetDiscriminant { place, .. } => { - check_place(tcx, **place, span, def_id, body) - } - - StatementKind::LlvmInlineAsm { .. } => { - Err((span, "cannot use inline assembly in const fn".into())) - } - - // These are all NOPs - StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Retag { .. } - | StatementKind::AscribeUserType(..) - | StatementKind::Coverage(..) - | StatementKind::Nop => Ok(()), - } -} - -fn check_operand( - tcx: TyCtxt<'tcx>, - operand: &Operand<'tcx>, - span: Span, - def_id: DefId, - body: &Body<'tcx>, -) -> McfResult { - match operand { - Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, def_id, body), - Operand::Constant(c) => match c.check_static_ptr(tcx) { - Some(_) => Err((span, "cannot access `static` items in const fn".into())), - None => Ok(()), - }, - } -} - -fn check_place( - tcx: TyCtxt<'tcx>, - place: Place<'tcx>, - span: Span, - def_id: DefId, - body: &Body<'tcx>, -) -> McfResult { - let mut cursor = place.projection.as_ref(); - while let &[ref proj_base @ .., elem] = cursor { - cursor = proj_base; - match elem { - ProjectionElem::Field(..) => { - let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty; - if let Some(def) = base_ty.ty_adt_def() { - // No union field accesses in `const fn` - if def.is_union() { - if !feature_allowed(tcx, def_id, sym::const_fn_union) { - return Err((span, "accessing union fields is unstable".into())); - } - } - } - } - ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Downcast(..) - | ProjectionElem::Subslice { .. } - | ProjectionElem::Deref - | ProjectionElem::Index(_) => {} - } - } - - Ok(()) -} - -/// Returns `true` if the given feature gate is allowed within the function with the given `DefId`. -fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().enabled(feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - -/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`. -pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - -fn check_terminator( - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - def_id: DefId, - terminator: &Terminator<'tcx>, -) -> McfResult { - let span = terminator.source_info.span; - match &terminator.kind { - TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } - | TerminatorKind::Goto { .. } - | TerminatorKind::Return - | TerminatorKind::Resume - | TerminatorKind::Unreachable => Ok(()), - - TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, def_id, body), - TerminatorKind::DropAndReplace { place, value, .. } => { - check_place(tcx, *place, span, def_id, body)?; - check_operand(tcx, value, span, def_id, body) - } - - TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => { - check_operand(tcx, discr, span, def_id, body) - } - - TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), - TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { - Err((span, "const fn generators are unstable".into())) - } - - TerminatorKind::Call { - func, - args, - from_hir_call: _, - destination: _, - cleanup: _, - fn_span: _, - } => { - let fn_ty = func.ty(body, tcx); - if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { - // Allow unstable const if we opt in by using #[allow_internal_unstable] - // on function or macro declaration. - if !crate::const_eval::is_min_const_fn(tcx, fn_def_id) - && !crate::const_eval::is_unstable_const_fn(tcx, fn_def_id) - .map(|feature| { - span.allows_unstable(feature) - || lib_feature_allowed(tcx, def_id, feature) - }) - .unwrap_or(false) - { - return Err(( - span, - format!( - "can only call other `const fn` within a `const fn`, \ - but `{:?}` is not stable as `const fn`", - func, - ) - .into(), - )); - } - - // HACK: This is to "unstabilize" the `transmute` intrinsic - // within const fns. `transmute` is allowed in all other const contexts. - // This won't really scale to more intrinsics or functions. Let's allow const - // transmutes in const fn before we add more hacks to this. - if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic - && tcx.item_name(fn_def_id) == sym::transmute - && !feature_allowed(tcx, def_id, sym::const_fn_transmute) - { - return Err(( - span, - "can only call `transmute` from const items, not `const fn`".into(), - )); - } - - check_operand(tcx, func, span, fn_def_id, body)?; - - for arg in args { - check_operand(tcx, arg, span, fn_def_id, body)?; - } - Ok(()) - } else { - Err((span, "can only call other const fns within const fn".into())) - } - } - - TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { - check_operand(tcx, cond, span, def_id, body) - } - - TerminatorKind::InlineAsm { .. } => { - Err((span, "cannot use inline assembly in const fn".into())) - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs index 1ad184dfc460b..eec4fd55152a5 100644 --- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -1,13 +1,11 @@ -use crate::utils::{fn_has_unsatisfiable_preds, has_drop, is_entrypoint_fn, span_lint, trait_ref_of_method}; +use crate::utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, span_lint, trait_ref_of_method}; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_mir::transform::qualify_min_const_fn::is_min_const_fn; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; -use rustc_typeck::hir_ty_to_ty; declare_clippy_lint! { /// **What it does:** @@ -108,7 +106,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { FnKind::Method(_, sig, ..) => { if trait_ref_of_method(cx, hir_id).is_some() || already_const(sig.header) - || method_accepts_dropable(cx, sig.decl.inputs) { return; } @@ -116,28 +113,17 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { FnKind::Closure(..) => return, } + + // FIXME(#6080): Const-checking is only guaranteed to be correct when run on unoptimized MIR. + // This needs to be changed to `mir_const`. let mir = cx.tcx.optimized_mir(def_id); - if let Err((span, err)) = is_min_const_fn(cx.tcx, def_id.to_def_id(), &mir) { - if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) { - cx.tcx.sess.span_err(span, &err); - } - } else { + if rustc_mir::transform::check_consts::non_const_fn_could_be_made_stable_const_fn(cx.tcx, def_id, &mir) { span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); } } } -/// Returns true if any of the method parameters is a type that implements `Drop`. The method -/// can't be made const then, because `drop` can't be const-evaluated. -fn method_accepts_dropable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool { - // If any of the params are droppable, return true - param_tys.iter().any(|hir_ty| { - let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty); - has_drop(cx, ty_ty) - }) -} - // We don't have to lint on something that's already `const` #[must_use] fn already_const(header: hir::FnHeader) -> bool {