From 0972c3b56596b51f9cfdf99a5ad74c754e94d3aa Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 13 Dec 2024 17:40:07 +0000 Subject: [PATCH] Check for MSRV attributes in late passes using the HIR --- book/src/development/adding_lints.md | 31 ++-- clippy_config/src/conf.rs | 2 +- clippy_dev/src/main.rs | 6 +- clippy_dev/src/new_lint.rs | 82 ++++++----- clippy_utils/src/lib.rs | 17 +-- clippy_utils/src/msrvs.rs | 139 +++++++++++------- clippy_utils/src/paths.rs | 3 +- clippy_utils/src/qualify_min_const_fn.rs | 123 +++++++++------- .../ui-internal/invalid_msrv_attr_impl.fixed | 11 +- tests/ui-internal/invalid_msrv_attr_impl.rs | 8 +- .../ui-internal/invalid_msrv_attr_impl.stderr | 26 +--- .../ui/msrv_attributes_without_early_lints.rs | 12 ++ 12 files changed, 246 insertions(+), 214 deletions(-) create mode 100644 tests/ui/msrv_attributes_without_early_lints.rs diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 60135e96c5a5..0b9010f01071 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -460,7 +460,7 @@ pub struct ManualStrip { impl ManualStrip { pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv.clone() } + Self { msrv: conf.msrv } } } ``` @@ -469,24 +469,13 @@ The project's MSRV can then be matched against the feature MSRV in the LintPass using the `Msrv::meets` method. ``` rust -if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) { +if !self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) { return; } ``` -The project's MSRV can also be specified as an attribute, which overrides -the value from `clippy.toml`. This can be accounted for using the -`extract_msrv_attr!(LintContext)` macro and passing -`LateContext`/`EarlyContext`. - -```rust,ignore -impl<'tcx> LateLintPass<'tcx> for ManualStrip { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - ... - } - extract_msrv_attr!(LateContext); -} -``` +Early lint passes should instead use `MsrvStack` coupled with +`extract_msrv_attr!()` Once the `msrv` is added to the lint, a relevant test case should be added to the lint's test file, `tests/ui/manual_strip.rs` in this example. It should @@ -512,8 +501,16 @@ in `clippy_config/src/conf.rs`: ```rust define_Conf! { - /// Lint: LIST, OF, LINTS, . The minimum rust version that the project supports - (msrv: Option = None), + #[lints( + allow_attributes, + allow_attributes_without_reason, + .. + , + .. + unused_trait_names, + use_self, + )] + msrv: Msrv = Msrv::default(), ... } ``` diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index e4571204f46a..a61acbaa96bc 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -670,7 +670,7 @@ define_Conf! { unused_trait_names, use_self, )] - msrv: Msrv = Msrv::empty(), + msrv: Msrv = Msrv::default(), /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. #[lints(large_types_passed_by_value)] pass_by_value_size_limit: u64 = 256, diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index fcdee073f88d..074dea4ab77b 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -35,7 +35,7 @@ fn main() { category, r#type, msrv, - } => match new_lint::create(&pass, &name, &category, r#type.as_deref(), msrv) { + } => match new_lint::create(pass, &name, &category, r#type.as_deref(), msrv) { Ok(()) => update_lints::update(utils::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {e}"), }, @@ -147,9 +147,9 @@ enum DevCommand { #[command(name = "new_lint")] /// Create a new lint and run `cargo dev update_lints` NewLint { - #[arg(short, long, value_parser = ["early", "late"], conflicts_with = "type", default_value = "late")] + #[arg(short, long, conflicts_with = "type", default_value = "late")] /// Specify whether the lint runs during the early or late pass - pass: String, + pass: new_lint::Pass, #[arg( short, long, diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index cf6e44245660..96e12706c9e2 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,13 +1,28 @@ use crate::utils::{clippy_project_root, clippy_version}; +use clap::ValueEnum; use indoc::{formatdoc, writedoc}; -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; use std::fs::{self, OpenOptions}; -use std::io::prelude::*; +use std::io::{self, Write as _}; use std::path::{Path, PathBuf}; -use std::{fmt, io}; + +#[derive(Clone, Copy, PartialEq, ValueEnum)] +pub enum Pass { + Early, + Late, +} + +impl fmt::Display for Pass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Pass::Early => "early", + Pass::Late => "late", + }) + } +} struct LintData<'a> { - pass: &'a str, + pass: Pass, name: &'a str, category: &'a str, ty: Option<&'a str>, @@ -35,7 +50,7 @@ impl Context for io::Result { /// # Errors /// /// This function errors out if the files couldn't be created or written to. -pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> { +pub fn create(pass: Pass, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> { if category == "cargo" && ty.is_none() { // `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo` ty = Some("cargo"); @@ -56,7 +71,7 @@ pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?; } - if pass == "early" { + if pass == Pass::Early { println!( "\n\ NOTE: Use a late pass unless you need something specific from\n\ @@ -136,23 +151,17 @@ fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { let mut lib_rs = fs::read_to_string(path).context("reading")?; let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment"); + let ctor_arg = if lint.pass == Pass::Late { "_" } else { "" }; + let lint_pass = lint.pass; + let module_name = lint.name; + let camel_name = to_camel_case(lint.name); let new_lint = if enable_msrv { format!( "store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf)));\n ", - lint_pass = lint.pass, - ctor_arg = if lint.pass == "late" { "_" } else { "" }, - module_name = lint.name, - camel_name = to_camel_case(lint.name), ) } else { - format!( - "store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n ", - lint_pass = lint.pass, - ctor_arg = if lint.pass == "late" { "_" } else { "" }, - module_name = lint.name, - camel_name = to_camel_case(lint.name), - ) + format!("store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n ",) }; lib_rs.insert_str(comment_start, &new_lint); @@ -242,11 +251,16 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let mut result = String::new(); let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass { - "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"), - "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"), - _ => { - unreachable!("`pass_type` should only ever be `early` or `late`!"); - }, + Pass::Early => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"), + Pass::Late => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"), + }; + let (msrv_ty, msrv_ctor, extract_msrv) = match lint.pass { + Pass::Early => ( + "MsrvStack", + "MsrvStack::new(conf.msrv)", + "\n extract_msrv_attr!();\n", + ), + Pass::Late => ("Msrv", "conf.msrv", ""), }; let lint_name = lint.name; @@ -258,10 +272,10 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let _: fmt::Result = writedoc!( result, r" - use clippy_utils::msrvs::{{self, Msrv}}; + use clippy_utils::msrvs::{{self, {msrv_ty}}}; use clippy_config::Conf; {pass_import} - use rustc_lint::{{{context_import}, {pass_type}, LintContext}}; + use rustc_lint::{{{context_import}, {pass_type}}}; use rustc_session::impl_lint_pass; " @@ -285,20 +299,18 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { result, r" pub struct {name_camel} {{ - msrv: Msrv, + msrv: {msrv_ty}, }} impl {name_camel} {{ pub fn new(conf: &'static Conf) -> Self {{ - Self {{ msrv: conf.msrv.clone() }} + Self {{ msrv: {msrv_ctor} }} }} }} impl_lint_pass!({name_camel} => [{name_upper}]); - impl {pass_type}{pass_lifetimes} for {name_camel} {{ - extract_msrv_attr!({context_import}); - }} + impl {pass_type}{pass_lifetimes} for {name_camel} {{{extract_msrv}}} // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed. // TODO: Update msrv config comment in `clippy_config/src/conf.rs` @@ -375,9 +387,9 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R let mod_file_path = ty_dir.join("mod.rs"); let context_import = setup_mod_file(&mod_file_path, lint)?; - let pass_lifetimes = match context_import { - "LateContext" => "<'_>", - _ => "", + let (pass_lifetimes, msrv_ty, msrv_ref, msrv_cx) = match context_import { + "LateContext" => ("<'_>", "Msrv", "", "cx, "), + _ => ("", "MsrvStack", "&", ""), }; let name_upper = lint.name.to_uppercase(); @@ -387,14 +399,14 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R let _: fmt::Result = writedoc!( lint_file_contents, r#" - use clippy_utils::msrvs::{{self, Msrv}}; + use clippy_utils::msrvs::{{self, {msrv_ty}}}; use rustc_lint::{{{context_import}, LintContext}}; use super::{name_upper}; // TODO: Adjust the parameters as necessary - pub(super) fn check(cx: &{context_import}{pass_lifetimes}, msrv: &Msrv) {{ - if !msrv.meets(todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{ + pub(super) fn check(cx: &{context_import}{pass_lifetimes}, msrv: {msrv_ref}{msrv_ty}) {{ + if !msrv.meets({msrv_cx}todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{ return; }} todo!(); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 3e9429399b31..ba7346979460 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -137,24 +137,13 @@ use rustc_middle::hir::nested_filter; #[macro_export] macro_rules! extract_msrv_attr { - (LateContext) => { - fn check_attributes(&mut self, cx: &rustc_lint::LateContext<'_>, attrs: &[rustc_hir::Attribute]) { + () => { + fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) { let sess = rustc_lint::LintContext::sess(cx); self.msrv.check_attributes(sess, attrs); } - fn check_attributes_post(&mut self, cx: &rustc_lint::LateContext<'_>, attrs: &[rustc_hir::Attribute]) { - let sess = rustc_lint::LintContext::sess(cx); - self.msrv.check_attributes_post(sess, attrs); - } - }; - (EarlyContext) => { - fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) { - let sess = rustc_lint::LintContext::sess(cx); - self.msrv.check_attributes(sess, attrs); - } - - fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) { + fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) { let sess = rustc_lint::LintContext::sess(cx); self.msrv.check_attributes_post(sess, attrs); } diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index f9cf29cbdf46..5bb2b12988a6 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -1,11 +1,14 @@ +use rustc_ast::Attribute; use rustc_ast::attr::AttributeExt; use rustc_attr_parsing::{RustcVersion, parse_version}; +use rustc_lint::LateContext; use rustc_session::Session; use rustc_span::{Symbol, sym}; use serde::Deserialize; -use smallvec::{SmallVec, smallvec}; -use std::fmt; +use smallvec::SmallVec; +use std::iter::once; +use std::sync::atomic::{AtomicBool, Ordering}; macro_rules! msrv_aliases { ($($major:literal,$minor:literal,$patch:literal { @@ -73,21 +76,15 @@ msrv_aliases! { 1,15,0 { MAYBE_BOUND_IN_WHERE } } -/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` -#[derive(Debug, Clone)] -pub struct Msrv { - stack: SmallVec<[RustcVersion; 2]>, -} +/// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic +/// optimization we can skip traversing the HIR in [`Msrv::meets`] if we never saw an MSRV attribute +/// during the early lint passes +static SEEN_MSRV_ATTR: AtomicBool = AtomicBool::new(false); -impl fmt::Display for Msrv { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(msrv) = self.current() { - write!(f, "{msrv}") - } else { - f.write_str("1.0.0") - } - } -} +/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` in late +/// lint passes, use [`MsrvStack`] for early passes +#[derive(Copy, Clone, Debug, Default)] +pub struct Msrv(Option); impl<'de> Deserialize<'de> for Msrv { fn deserialize(deserializer: D) -> Result @@ -96,14 +93,36 @@ impl<'de> Deserialize<'de> for Msrv { { let v = String::deserialize(deserializer)?; parse_version(Symbol::intern(&v)) - .map(|v| Msrv { stack: smallvec![v] }) + .map(|v| Self(Some(v))) .ok_or_else(|| serde::de::Error::custom("not a valid Rust version")) } } impl Msrv { - pub fn empty() -> Msrv { - Msrv { stack: SmallVec::new() } + /// Returns the MSRV at the current node + /// + /// If the crate being linted uses an `#[clippy::msrv]` attribute this will search the parent + /// nodes for that attribute, prefer to run this check after cheaper pattern matching operations + pub fn current(self, cx: &LateContext<'_>) -> Option { + if SEEN_MSRV_ATTR.load(Ordering::Relaxed) { + let start = cx.last_node_with_lint_attrs; + if let Some(msrv_attr) = once(start) + .chain(cx.tcx.hir_parent_id_iter(start)) + .find_map(|id| parse_attrs(cx.tcx.sess, cx.tcx.hir().attrs(id))) + { + return Some(msrv_attr); + } + } + + self.0 + } + + /// Checks if a required version from [this module](self) is met at the current node + /// + /// If the crate being linted uses an `#[clippy::msrv]` attribute this will search the parent + /// nodes for that attribute, prefer to run this check after cheaper pattern matching operations + pub fn meets(self, cx: &LateContext<'_>, required: RustcVersion) -> bool { + self.current(cx).is_none_or(|msrv| msrv >= required) } pub fn read_cargo(&mut self, sess: &Session) { @@ -111,8 +130,8 @@ impl Msrv { .ok() .and_then(|v| parse_version(Symbol::intern(&v))); - match (self.current(), cargo_msrv) { - (None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv], + match (self.0, cargo_msrv) { + (None, Some(cargo_msrv)) => self.0 = Some(cargo_msrv), (Some(clippy_msrv), Some(cargo_msrv)) => { if clippy_msrv != cargo_msrv { sess.dcx().warn(format!( @@ -123,6 +142,21 @@ impl Msrv { _ => {}, } } +} + +/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` in early +/// lint passes, use [`Msrv`] for late passes +#[derive(Debug, Clone)] +pub struct MsrvStack { + stack: SmallVec<[RustcVersion; 2]>, +} + +impl MsrvStack { + pub fn new(initial: Msrv) -> Self { + Self { + stack: SmallVec::from_iter(initial.0), + } + } pub fn current(&self) -> Option { self.stack.last().copied() @@ -132,42 +166,43 @@ impl Msrv { self.current().is_none_or(|msrv| msrv >= required) } - fn parse_attr(sess: &Session, attrs: &[impl AttributeExt]) -> Option { - let sym_msrv = Symbol::intern("msrv"); - let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym_msrv])); - - if let Some(msrv_attr) = msrv_attrs.next() { - if let Some(duplicate) = msrv_attrs.next_back() { - sess.dcx() - .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times") - .with_span_note(msrv_attr.span(), "first definition found here") - .emit(); - } - - if let Some(msrv) = msrv_attr.value_str() { - if let Some(version) = parse_version(msrv) { - return Some(version); - } - - sess.dcx() - .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version")); - } else { - sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute"); - } - } - - None - } - - pub fn check_attributes(&mut self, sess: &Session, attrs: &[impl AttributeExt]) { - if let Some(version) = Self::parse_attr(sess, attrs) { + pub fn check_attributes(&mut self, sess: &Session, attrs: &[Attribute]) { + if let Some(version) = parse_attrs(sess, attrs) { + SEEN_MSRV_ATTR.store(true, Ordering::Relaxed); self.stack.push(version); } } - pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[impl AttributeExt]) { - if Self::parse_attr(sess, attrs).is_some() { + pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[Attribute]) { + if parse_attrs(sess, attrs).is_some() { self.stack.pop(); } } } + +fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option { + let sym_msrv = Symbol::intern("msrv"); + let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym_msrv])); + + if let Some(msrv_attr) = msrv_attrs.next() { + if let Some(duplicate) = msrv_attrs.next_back() { + sess.dcx() + .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times") + .with_span_note(msrv_attr.span(), "first definition found here") + .emit(); + } + + if let Some(msrv) = msrv_attr.value_str() { + if let Some(version) = parse_version(msrv) { + return Some(version); + } + + sess.dcx() + .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version")); + } else { + sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute"); + } + } + + None +} diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 452bb4ce4c77..51d06ad9b1aa 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -19,7 +19,6 @@ pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"]; pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; -pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"]; pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"]; pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"]; pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"]; @@ -33,7 +32,7 @@ pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"]; pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"]; // Paths in clippy itself -pub const MSRV: [&str; 3] = ["clippy_utils", "msrvs", "Msrv"]; +pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"]; // Paths in external crates #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index c7890f33f27e..8e6f4d4a317e 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -11,6 +11,7 @@ use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::Obligation; +use rustc_lint::LateContext; use rustc_middle::mir::{ Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, @@ -25,16 +26,16 @@ use std::borrow::Cow; type McfResult = Result<(), (Span, Cow<'static, str>)>; -pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) -> McfResult { +pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Msrv) -> McfResult { let def_id = body.source.def_id(); for local in &body.local_decls { - check_ty(tcx, local.ty, local.source_info.span, msrv)?; + check_ty(cx, local.ty, local.source_info.span, msrv)?; } // impl trait is gone in MIR, so check the return type manually check_ty( - tcx, - tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(), + cx, + cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, msrv, )?; @@ -43,16 +44,16 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) // Cleanup blocks are ignored entirely by const eval, so we can too: // https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382 if !bb.is_cleanup { - check_terminator(tcx, body, bb.terminator(), msrv)?; + check_terminator(cx, body, bb.terminator(), msrv)?; for stmt in &bb.statements { - check_statement(tcx, body, def_id, stmt, msrv)?; + check_statement(cx, body, def_id, stmt, msrv)?; } } } Ok(()) } -fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> McfResult { +fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, msrv: Msrv) -> McfResult { for arg in ty.walk() { let ty = match arg.unpack() { GenericArgKind::Type(ty) => ty, @@ -63,7 +64,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M }; match ty.kind() { - ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(msrvs::CONST_MUT_REFS) => { + ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(cx, msrvs::CONST_MUT_REFS) => { return Err((span, "mutable references in const fn are unstable".into())); }, ty::Alias(ty::Opaque, ..) => return Err((span, "`impl Trait` in const fn is unstable".into())), @@ -82,7 +83,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M )); }, ty::ExistentialPredicate::Trait(trait_ref) => { - if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() { + if Some(trait_ref.def_id) != cx.tcx.lang_items().sized_trait() { return Err(( span, "trait bounds other than `Sized` \ @@ -101,19 +102,19 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M } fn check_rvalue<'tcx>( - tcx: TyCtxt<'tcx>, + cx: &LateContext<'tcx>, body: &Body<'tcx>, def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span, - msrv: &Msrv, + msrv: Msrv, ) -> McfResult { match rvalue { Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => { - check_place(tcx, *place, span, body, msrv) + check_place(cx, *place, span, body, msrv) }, - Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body, msrv), + Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), Rvalue::Repeat(operand, _) | Rvalue::Use(operand) | Rvalue::WrapUnsafeBinder(operand, _) @@ -128,7 +129,7 @@ fn check_rvalue<'tcx>( | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _), operand, _, - ) => check_operand(tcx, operand, span, body, msrv), + ) => check_operand(cx, operand, span, body, msrv), Rvalue::Cast( CastKind::PointerCoercion( PointerCoercion::UnsafeFnPointer @@ -144,9 +145,11 @@ fn check_rvalue<'tcx>( // 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_for_codegen(pointee_ty, ty::TypingEnv::post_analysis(tcx, def_id)); + let unsized_ty = cx + .tcx + .struct_tail_for_codegen(pointee_ty, ty::TypingEnv::post_analysis(cx.tcx, def_id)); if let ty::Slice(_) | ty::Str = unsized_ty.kind() { - check_operand(tcx, op, span, body, msrv)?; + check_operand(cx, op, span, body, msrv)?; // Casting/coercing things to slices is fine. Ok(()) } else { @@ -167,9 +170,9 @@ fn check_rvalue<'tcx>( )), // binops are fine on integers Rvalue::BinaryOp(_, box (lhs, rhs)) => { - check_operand(tcx, lhs, span, body, msrv)?; - check_operand(tcx, rhs, span, body, msrv)?; - let ty = lhs.ty(body, tcx); + check_operand(cx, lhs, span, body, msrv)?; + check_operand(cx, rhs, span, body, msrv)?; + let ty = lhs.ty(body, cx.tcx); if ty.is_integral() || ty.is_bool() || ty.is_char() { Ok(()) } else { @@ -185,16 +188,16 @@ fn check_rvalue<'tcx>( ) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { - let ty = operand.ty(body, tcx); + let ty = operand.ty(body, cx.tcx); if ty.is_integral() || ty.is_bool() { - check_operand(tcx, operand, span, body, msrv) + check_operand(cx, operand, span, body, msrv) } 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, body, msrv)?; + check_operand(cx, operand, span, body, msrv)?; } Ok(()) }, @@ -202,33 +205,33 @@ fn check_rvalue<'tcx>( } fn check_statement<'tcx>( - tcx: TyCtxt<'tcx>, + cx: &LateContext<'tcx>, body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>, - msrv: &Msrv, + msrv: Msrv, ) -> McfResult { let span = statement.source_info.span; match &statement.kind { StatementKind::Assign(box (place, rval)) => { - check_place(tcx, *place, span, body, msrv)?; - check_rvalue(tcx, body, def_id, rval, span, msrv) + check_place(cx, *place, span, body, msrv)?; + check_rvalue(cx, body, def_id, rval, span, msrv) }, - StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body, msrv), + StatementKind::FakeRead(box (_, place)) => check_place(cx, *place, span, body, msrv), // just an assignment StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { - check_place(tcx, **place, span, body, msrv) + check_place(cx, **place, span, body, msrv) }, - StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body, msrv), + StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(cx, op, span, body, msrv), StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping( rustc_middle::mir::CopyNonOverlapping { dst, src, count }, )) => { - check_operand(tcx, dst, span, body, msrv)?; - check_operand(tcx, src, span, body, msrv)?; - check_operand(tcx, count, span, body, msrv) + check_operand(cx, dst, span, body, msrv)?; + check_operand(cx, src, span, body, msrv)?; + check_operand(cx, count, span, body, msrv) }, // These are all NOPs StatementKind::StorageLive(_) @@ -244,16 +247,16 @@ fn check_statement<'tcx>( } fn check_operand<'tcx>( - tcx: TyCtxt<'tcx>, + cx: &LateContext<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>, - msrv: &Msrv, + msrv: Msrv, ) -> McfResult { match operand { Operand::Move(place) => { if !place.projection.as_ref().is_empty() - && !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) + && !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body) { return Err(( span, @@ -261,29 +264,35 @@ fn check_operand<'tcx>( )); } - check_place(tcx, *place, span, body, msrv) + check_place(cx, *place, span, body, msrv) }, - Operand::Copy(place) => check_place(tcx, *place, span, body, msrv), - Operand::Constant(c) => match c.check_static_ptr(tcx) { + Operand::Copy(place) => check_place(cx, *place, span, body, msrv), + Operand::Constant(c) => match c.check_static_ptr(cx.tcx) { Some(_) => Err((span, "cannot access `static` items in const fn".into())), None => Ok(()), }, } } -fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>, msrv: &Msrv) -> McfResult { +fn check_place<'tcx>( + cx: &LateContext<'tcx>, + place: Place<'tcx>, + span: Span, + body: &Body<'tcx>, + msrv: Msrv, +) -> McfResult { for (base, elem) in place.as_ref().iter_projections() { match elem { ProjectionElem::Field(..) => { - if base.ty(body, tcx).ty.is_union() && !msrv.meets(msrvs::CONST_FN_UNION) { + if base.ty(body, cx.tcx).ty.is_union() && !msrv.meets(cx, msrvs::CONST_FN_UNION) { return Err((span, "accessing union fields is unstable".into())); } }, - ProjectionElem::Deref => match base.ty(body, tcx).ty.kind() { + ProjectionElem::Deref => match base.ty(body, cx.tcx).ty.kind() { ty::RawPtr(_, hir::Mutability::Mut) => { return Err((span, "dereferencing raw mut pointer in const fn is unstable".into())); }, - ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(msrvs::CONST_RAW_PTR_DEREF) => { + ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(cx, msrvs::CONST_RAW_PTR_DEREF) => { return Err((span, "dereferencing raw const pointer in const fn is unstable".into())); }, _ => (), @@ -302,10 +311,10 @@ fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &B } fn check_terminator<'tcx>( - tcx: TyCtxt<'tcx>, + cx: &LateContext<'tcx>, body: &Body<'tcx>, terminator: &Terminator<'tcx>, - msrv: &Msrv, + msrv: Msrv, ) -> McfResult { let span = terminator.source_info.span; match &terminator.kind { @@ -317,7 +326,7 @@ fn check_terminator<'tcx>( | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Unreachable => Ok(()), TerminatorKind::Drop { place, .. } => { - if !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) { + if !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body) { return Err(( span, "cannot drop locals with a non constant destructor in const fn".into(), @@ -325,7 +334,7 @@ fn check_terminator<'tcx>( } Ok(()) }, - TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body, msrv), + TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(cx, discr, span, body, msrv), TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } => { Err((span, "const fn coroutines are unstable".into())) }, @@ -339,9 +348,9 @@ fn check_terminator<'tcx>( fn_span: _, } | TerminatorKind::TailCall { func, args, fn_span: _ } => { - let fn_ty = func.ty(body, tcx); + let fn_ty = func.ty(body, cx.tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { - if !is_stable_const_fn(tcx, fn_def_id, msrv) { + if !is_stable_const_fn(cx, fn_def_id, msrv) { return Err(( span, format!( @@ -356,17 +365,17 @@ fn check_terminator<'tcx>( // 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.is_intrinsic(fn_def_id, sym::transmute) { + if cx.tcx.is_intrinsic(fn_def_id, sym::transmute) { return Err(( span, "can only call `transmute` from const items, not `const fn`".into(), )); } - check_operand(tcx, func, span, body, msrv)?; + check_operand(cx, func, span, body, msrv)?; for arg in args { - check_operand(tcx, &arg.node, span, body, msrv)?; + check_operand(cx, &arg.node, span, body, msrv)?; } Ok(()) } else { @@ -379,14 +388,14 @@ fn check_terminator<'tcx>( msg: _, target: _, unwind: _, - } => check_operand(tcx, cond, span, body, msrv), + } => check_operand(cx, cond, span, body, msrv), TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), } } -fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool { - tcx.is_const_fn(def_id) - && tcx.lookup_const_stability(def_id).is_none_or(|const_stab| { +fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bool { + cx.tcx.is_const_fn(def_id) + && cx.tcx.lookup_const_stability(def_id).is_none_or(|const_stab| { if let rustc_attr_parsing::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_stable_const_fn`. @@ -398,10 +407,10 @@ fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool { StableSince::Err => return false, }; - msrv.meets(const_stab_rust_version) + msrv.meets(cx, const_stab_rust_version) } else { // Unstable const fn, check if the feature is enabled. - tcx.features().enabled(const_stab.feature) && msrv.current().is_none() + cx.tcx.features().enabled(const_stab.feature) && msrv.current(cx).is_none() } }) } diff --git a/tests/ui-internal/invalid_msrv_attr_impl.fixed b/tests/ui-internal/invalid_msrv_attr_impl.fixed index 928596d08091..7011ef518f20 100644 --- a/tests/ui-internal/invalid_msrv_attr_impl.fixed +++ b/tests/ui-internal/invalid_msrv_attr_impl.fixed @@ -9,7 +9,7 @@ extern crate rustc_middle; #[macro_use] extern crate rustc_session; use clippy_utils::extract_msrv_attr; -use clippy_utils::msrvs::Msrv; +use clippy_utils::msrvs::MsrvStack; use rustc_hir::Expr; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; @@ -20,18 +20,13 @@ declare_lint! { } struct Pass { - msrv: Msrv, + msrv: MsrvStack, } impl_lint_pass!(Pass => [TEST_LINT]); -impl LateLintPass<'_> for Pass { - extract_msrv_attr!(LateContext); - fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {} -} - impl EarlyLintPass for Pass { - extract_msrv_attr!(EarlyContext); + extract_msrv_attr!(); fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {} } diff --git a/tests/ui-internal/invalid_msrv_attr_impl.rs b/tests/ui-internal/invalid_msrv_attr_impl.rs index 50b28648ccc9..323061decd23 100644 --- a/tests/ui-internal/invalid_msrv_attr_impl.rs +++ b/tests/ui-internal/invalid_msrv_attr_impl.rs @@ -9,7 +9,7 @@ extern crate rustc_middle; #[macro_use] extern crate rustc_session; use clippy_utils::extract_msrv_attr; -use clippy_utils::msrvs::Msrv; +use clippy_utils::msrvs::MsrvStack; use rustc_hir::Expr; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; @@ -20,15 +20,11 @@ declare_lint! { } struct Pass { - msrv: Msrv, + msrv: MsrvStack, } impl_lint_pass!(Pass => [TEST_LINT]); -impl LateLintPass<'_> for Pass { - fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {} -} - impl EarlyLintPass for Pass { fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {} } diff --git a/tests/ui-internal/invalid_msrv_attr_impl.stderr b/tests/ui-internal/invalid_msrv_attr_impl.stderr index 8b69af122e45..8ba42e4bb2b2 100644 --- a/tests/ui-internal/invalid_msrv_attr_impl.stderr +++ b/tests/ui-internal/invalid_msrv_attr_impl.stderr @@ -1,8 +1,8 @@ -error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation +error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation --> tests/ui-internal/invalid_msrv_attr_impl.rs:28:1 | -LL | impl LateLintPass<'_> for Pass { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | impl EarlyLintPass for Pass { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> tests/ui-internal/invalid_msrv_attr_impl.rs:1:9 @@ -10,23 +10,11 @@ note: the lint level is defined here LL | #![deny(clippy::internal)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::missing_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]` -help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation +help: add `extract_msrv_attr!()` to the `EarlyLintPass` implementation | -LL ~ impl LateLintPass<'_> for Pass { -LL + extract_msrv_attr!(LateContext); +LL + impl EarlyLintPass for Pass { +LL + extract_msrv_attr!(); | -error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation - --> tests/ui-internal/invalid_msrv_attr_impl.rs:32:1 - | -LL | impl EarlyLintPass for Pass { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation - | -LL ~ impl EarlyLintPass for Pass { -LL + extract_msrv_attr!(EarlyContext); - | - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error diff --git a/tests/ui/msrv_attributes_without_early_lints.rs b/tests/ui/msrv_attributes_without_early_lints.rs new file mode 100644 index 000000000000..dec62c15079d --- /dev/null +++ b/tests/ui/msrv_attributes_without_early_lints.rs @@ -0,0 +1,12 @@ +#![allow(clippy::all, clippy::pedantic, clippy::restriction, clippy::nursery)] +#![forbid(clippy::ptr_as_ptr)] + +/// MSRV checking in late passes skips checking the parent nodes if no early pass sees a +/// `#[clippy::msrv]` attribute +/// +/// Here we ensure that even if all early passes are allowed (above) the attribute is still detected +/// in late lints such as `clippy::ptr_as_ptr` +#[clippy::msrv = "1.37"] +fn f(p: *const i32) { + let _ = p as *const i64; +}