Fix a FP in missing_const_for_fn

where a function that calls a standard library function whose constness
is unstable is considered as being able to be a const function
This commit is contained in:
rail 2021-04-13 17:13:49 +12:00 committed by rail-rain
parent e9728b80ce
commit 26a1989041
7 changed files with 92 additions and 14 deletions

View File

@ -138,7 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
let mir = cx.tcx.optimized_mir(def_id); let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir) { if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) { if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) {
cx.tcx.sess.span_err(span, &err); cx.tcx.sess.span_err(span, &err);
} }

View File

@ -9,6 +9,7 @@
// (Currently there is no way to opt into sysroot crates without `extern crate`.) // (Currently there is no way to opt into sysroot crates without `extern crate`.)
extern crate rustc_ast; extern crate rustc_ast;
extern crate rustc_ast_pretty; extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_data_structures; extern crate rustc_data_structures;
extern crate rustc_errors; extern crate rustc_errors;
extern crate rustc_hir; extern crate rustc_hir;

View File

@ -1,3 +1,8 @@
// This code used to be a part of `rustc` but moved to Clippy as a result of
// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some
// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
// differ from the time of `rustc` even if the name stays the same.
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_middle::mir::{ use rustc_middle::mir::{
@ -6,6 +11,7 @@ use rustc_middle::mir::{
}; };
use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
use rustc_semver::RustcVersion;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::Span; use rustc_span::Span;
use rustc_target::spec::abi::Abi::RustIntrinsic; use rustc_target::spec::abi::Abi::RustIntrinsic;
@ -13,7 +19,7 @@ use std::borrow::Cow;
type McfResult = Result<(), (Span, Cow<'static, str>)>; type McfResult = Result<(), (Span, Cow<'static, str>)>;
pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult { pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&RustcVersion>) -> McfResult {
let def_id = body.source.def_id(); let def_id = body.source.def_id();
let mut current = def_id; let mut current = def_id;
loop { loop {
@ -70,7 +76,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
)?; )?;
for bb in body.basic_blocks() { for bb in body.basic_blocks() {
check_terminator(tcx, body, bb.terminator())?; check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements { for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt)?; check_statement(tcx, body, def_id, stmt)?;
} }
@ -268,7 +274,12 @@ fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'t
Ok(()) Ok(())
} }
fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult { fn check_terminator(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
terminator: &Terminator<'tcx>,
msrv: Option<&RustcVersion>,
) -> McfResult {
let span = terminator.source_info.span; let span = terminator.source_info.span;
match &terminator.kind { match &terminator.kind {
TerminatorKind::FalseEdge { .. } TerminatorKind::FalseEdge { .. }
@ -305,7 +316,7 @@ fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Termin
} => { } => {
let fn_ty = func.ty(body, tcx); let fn_ty = func.ty(body, tcx);
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) { if !is_const_fn(tcx, fn_def_id, msrv) {
return Err(( return Err((
span, span,
format!( format!(
@ -350,3 +361,24 @@ fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Termin
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
} }
} }
fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<&RustcVersion>) -> bool {
rustc_mir::const_eval::is_const_fn(tcx, def_id)
&& if let Some(const_stab) = tcx.lookup_const_stability(def_id) {
if let rustc_attr::StabilityLevel::Stable { since } = const_stab.level {
// Checking MSRV is manually necessary because `rustc` has no such concept. This entire
// function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
crate::meets_msrv(
msrv,
&RustcVersion::parse(&since.as_str())
.expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"),
)
} else {
// `rustc_mir::const_eval::is_const_fn` should return false for unstably const functions.
unreachable!();
}
} else {
true
}
}

View File

@ -0,0 +1,8 @@
// This file provides a const function that is unstably const forever.
#![feature(staged_api)]
#![stable(feature = "1", since = "1.0.0")]
#[stable(feature = "1", since = "1.0.0")]
#[rustc_const_unstable(feature = "foo", issue = "none")]
pub const fn unstably_const_fn() {}

View File

@ -2,9 +2,14 @@
//! compilation error. //! compilation error.
//! The .stderr output of this test should be empty. Otherwise it's a bug somewhere. //! The .stderr output of this test should be empty. Otherwise it's a bug somewhere.
// aux-build:helper.rs
#![warn(clippy::missing_const_for_fn)] #![warn(clippy::missing_const_for_fn)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(start, const_generics)] #![feature(start, const_generics)]
#![feature(custom_inner_attributes)]
extern crate helper;
struct Game; struct Game;
@ -101,3 +106,17 @@ fn const_generic_return<T, const N: usize>(t: &[T]) -> &[T; N] {
unsafe { &*p } unsafe { &*p }
} }
// Do not lint this because it calls a function whose constness is unstable.
fn unstably_const_fn() {
helper::unstably_const_fn()
}
mod const_fn_stabilized_after_msrv {
#![clippy::msrv = "1.46.0"]
// Do not lint this because `u8::is_ascii_digit` is stabilized as a const function in 1.47.0.
fn const_fn_stabilized_after_msrv(byte: u8) {
byte.is_ascii_digit();
}
}

View File

@ -1,6 +1,7 @@
#![warn(clippy::missing_const_for_fn)] #![warn(clippy::missing_const_for_fn)]
#![allow(incomplete_features, clippy::let_and_return)] #![allow(incomplete_features, clippy::let_and_return)]
#![feature(const_generics)] #![feature(const_generics)]
#![feature(custom_inner_attributes)]
use std::mem::transmute; use std::mem::transmute;
@ -70,5 +71,14 @@ mod with_drop {
} }
} }
mod const_fn_stabilized_before_msrv {
#![clippy::msrv = "1.47.0"]
// This could be const because `u8::is_ascii_digit` is a stable const function in 1.47.
fn const_fn_stabilized_before_msrv(byte: u8) {
byte.is_ascii_digit();
}
}
// Should not be const // Should not be const
fn main() {} fn main() {}

View File

@ -1,5 +1,5 @@
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:13:5 --> $DIR/could_be_const.rs:14:5
| |
LL | / pub fn new() -> Self { LL | / pub fn new() -> Self {
LL | | Self { guess: 42 } LL | | Self { guess: 42 }
@ -9,7 +9,7 @@ LL | | }
= note: `-D clippy::missing-const-for-fn` implied by `-D warnings` = note: `-D clippy::missing-const-for-fn` implied by `-D warnings`
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:17:5 --> $DIR/could_be_const.rs:18:5
| |
LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] {
LL | | b LL | | b
@ -17,7 +17,7 @@ LL | | }
| |_____^ | |_____^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:23:1 --> $DIR/could_be_const.rs:24:1
| |
LL | / fn one() -> i32 { LL | / fn one() -> i32 {
LL | | 1 LL | | 1
@ -25,7 +25,7 @@ LL | | }
| |_^ | |_^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:28:1 --> $DIR/could_be_const.rs:29:1
| |
LL | / fn two() -> i32 { LL | / fn two() -> i32 {
LL | | let abc = 2; LL | | let abc = 2;
@ -34,7 +34,7 @@ LL | | }
| |_^ | |_^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:34:1 --> $DIR/could_be_const.rs:35:1
| |
LL | / fn string() -> String { LL | / fn string() -> String {
LL | | String::new() LL | | String::new()
@ -42,7 +42,7 @@ LL | | }
| |_^ | |_^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:39:1 --> $DIR/could_be_const.rs:40:1
| |
LL | / unsafe fn four() -> i32 { LL | / unsafe fn four() -> i32 {
LL | | 4 LL | | 4
@ -50,7 +50,7 @@ LL | | }
| |_^ | |_^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:44:1 --> $DIR/could_be_const.rs:45:1
| |
LL | / fn generic<T>(t: T) -> T { LL | / fn generic<T>(t: T) -> T {
LL | | t LL | | t
@ -58,12 +58,20 @@ LL | | }
| |_^ | |_^
error: this could be a `const fn` error: this could be a `const fn`
--> $DIR/could_be_const.rs:67:9 --> $DIR/could_be_const.rs:68:9
| |
LL | / pub fn b(self, a: &A) -> B { LL | / pub fn b(self, a: &A) -> B {
LL | | B LL | | B
LL | | } LL | | }
| |_________^ | |_________^
error: aborting due to 8 previous errors error: this could be a `const fn`
--> $DIR/could_be_const.rs:78:5
|
LL | / fn const_fn_stabilized_before_msrv(byte: u8) {
LL | | byte.is_ascii_digit();
LL | | }
| |_____^
error: aborting due to 9 previous errors