Auto merge of #149909 - matthiaskrgr:rollup-596c34w, r=matthiaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang/rust#149655 (bootstrap: add rustc-dev install target)
 - rust-lang/rust#149789 (Cleanup in the attribute parsers)
 - rust-lang/rust#149791 (Remove uses of `cfg({any()/all()})`)
 - rust-lang/rust#149792 (Suggest `cfg(false)` instead of `cfg(FALSE)`)
 - rust-lang/rust#149883 (add regression test for `proc_macro` error subdiagnostics)
 - rust-lang/rust#149884 (Clippy subtree update)
 - rust-lang/rust#149896 (Add myself(makai410) to the review rotation)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-12-12 13:00:47 +00:00
commit 3391c01336
203 changed files with 4948 additions and 1726 deletions

View file

@ -431,6 +431,7 @@ Lzu Tao <taolzu@gmail.com>
Maik Klein <maikklein@googlemail.com>
Maja Kądziołka <maya@compilercrim.es> <github@compilercrim.es>
Maja Kądziołka <maya@compilercrim.es> <kuba@kadziolka.net>
Makai <m4kai410@gmail.com>
Malo Jaffré <jaffre.malo@gmail.com>
Manish Goregaokar <manishsmail@gmail.com>
Mara Bos <m-ou.se@m-ou.se>

View file

@ -621,7 +621,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clippy"
version = "0.1.93"
version = "0.1.94"
dependencies = [
"anstream",
"askama",
@ -648,7 +648,7 @@ dependencies = [
[[package]]
name = "clippy_config"
version = "0.1.93"
version = "0.1.94"
dependencies = [
"clippy_utils",
"itertools",
@ -671,7 +671,7 @@ dependencies = [
[[package]]
name = "clippy_lints"
version = "0.1.93"
version = "0.1.94"
dependencies = [
"arrayvec",
"cargo_metadata 0.18.1",
@ -703,7 +703,7 @@ dependencies = [
[[package]]
name = "clippy_utils"
version = "0.1.93"
version = "0.1.94"
dependencies = [
"arrayvec",
"itertools",
@ -1107,7 +1107,7 @@ dependencies = [
[[package]]
name = "declare_clippy_lint"
version = "0.1.93"
version = "0.1.94"
[[package]]
name = "derive-where"

View file

@ -17,9 +17,9 @@ impl<S: Stage> CombineAttributeParser<S> for AllowInternalUnstableParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_unstable(cx, args, <Self as CombineAttributeParser<S>>::PATH[0])
.into_iter()
@ -39,9 +39,9 @@ impl<S: Stage> CombineAttributeParser<S> for UnstableFeatureBoundParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
if !cx.features().staged_api() {
cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span });
@ -67,17 +67,17 @@ impl<S: Stage> CombineAttributeParser<S> for AllowConstFnUnstableParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_unstable(cx, args, <Self as CombineAttributeParser<S>>::PATH[0])
}
}
fn parse_unstable<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
symbol: Symbol,
) -> impl IntoIterator<Item = Symbol> {
let mut res = Vec::new();

View file

@ -35,9 +35,9 @@ const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
"https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
);
pub fn parse_cfg<'c, S: Stage>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
pub fn parse_cfg<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> Option<CfgEntry> {
let ArgParser::List(list) = args else {
cx.expected_list(cx.attr_span);
@ -52,7 +52,7 @@ pub fn parse_cfg<'c, S: Stage>(
pub fn parse_cfg_entry<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
item: &MetaItemOrLitParser<'_>,
item: &MetaItemOrLitParser,
) -> Result<CfgEntry, ErrorGuaranteed> {
Ok(match item {
MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
@ -98,7 +98,7 @@ pub fn parse_cfg_entry<S: Stage>(
fn parse_cfg_entry_version<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
meta_span: Span,
) -> Result<CfgEntry, ErrorGuaranteed> {
try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
@ -130,7 +130,7 @@ fn parse_cfg_entry_version<S: Stage>(
fn parse_cfg_entry_target<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
meta_span: Span,
) -> Result<CfgEntry, ErrorGuaranteed> {
if let Some(features) = cx.features_option()

View file

@ -23,7 +23,7 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
]);
const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;
@ -84,7 +84,7 @@ impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
]);
const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(args) = args.list() else {
cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]);
return None;
@ -135,7 +135,7 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -164,7 +164,7 @@ impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -196,7 +196,7 @@ impl<S: Stage> SingleAttributeParser<S> for ObjcSelectorParser {
AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -472,10 +472,10 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
}
}
fn parse_tf_attribute<'c, S: Stage>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = (Symbol, Span)> + 'c {
fn parse_tf_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = (Symbol, Span)> {
let mut features = Vec::new();
let ArgParser::List(list) = args else {
cx.expected_list(cx.attr_span);
@ -529,10 +529,10 @@ impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
};
const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_tf_attribute(cx, args)
}
@ -567,10 +567,10 @@ impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
Allow(Target::Method(MethodKind::TraitImpl)),
]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_tf_attribute(cx, args)
}
}
@ -599,7 +599,7 @@ impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;

View file

@ -11,7 +11,7 @@ impl<S: Stage> SingleAttributeParser<S> for CrateNameParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(n) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -35,7 +35,7 @@ impl<S: Stage> SingleAttributeParser<S> for RecursionLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -58,7 +58,7 @@ impl<S: Stage> SingleAttributeParser<S> for MoveSizeLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -81,7 +81,7 @@ impl<S: Stage> SingleAttributeParser<S> for TypeLengthLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -104,7 +104,7 @@ impl<S: Stage> SingleAttributeParser<S> for PatternComplexityLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -154,7 +154,7 @@ impl<S: Stage> SingleAttributeParser<S> for WindowsSubsystemParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
const TEMPLATE: AttributeTemplate = template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(
args.span().unwrap_or(cx.inner_span),

View file

@ -16,10 +16,10 @@ impl<S: Stage> CombineAttributeParser<S> for DebuggerViualizerParser {
type Item = DebugVisualizer;
const CONVERT: ConvertFn<Self::Item> = |v, _| AttributeKind::DebuggerVisualizer(v);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let Some(l) = args.list() else {
cx.expected_list(args.span().unwrap_or(cx.attr_span));
return None;

View file

@ -12,7 +12,7 @@ fn get<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
name: Symbol,
param_span: Span,
arg: &ArgParser<'_>,
arg: &ArgParser,
item: &Option<Symbol>,
) -> Option<Symbol> {
if item.is_some() {
@ -68,7 +68,7 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
NameValueStr: "reason"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let features = cx.features();
let mut since = None;

View file

@ -10,7 +10,7 @@ use thin_vec::ThinVec;
use super::prelude::{ALL_TARGETS, AllowedTargets};
use super::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute,
DocKeywordNotKeyword,
@ -43,10 +43,10 @@ fn check_attribute<S: Stage>(
false
}
fn parse_keyword_and_attribute<'c, S, F>(
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
fn parse_keyword_and_attribute<S, F>(
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
attr_value: &mut Option<(Symbol, Span)>,
callback: F,
) where
@ -82,10 +82,10 @@ pub(crate) struct DocParser {
}
impl DocParser {
fn parse_single_test_doc_attr_item<'c, S: Stage>(
fn parse_single_test_doc_attr_item<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
mip: &'c MetaItemParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser,
) {
let path = mip.path();
let args = mip.args();
@ -132,9 +132,9 @@ impl DocParser {
}
}
fn add_alias<'c, S: Stage>(
fn add_alias<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
cx: &mut AcceptContext<'_, '_, S>,
alias: Symbol,
span: Span,
) {
@ -167,11 +167,11 @@ impl DocParser {
self.attribute.aliases.insert(alias, span);
}
fn parse_alias<'c, S: Stage>(
fn parse_alias<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {
@ -197,11 +197,11 @@ impl DocParser {
}
}
fn parse_inline<'c, S: Stage>(
fn parse_inline<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
inline: DocInline,
) {
if let Err(span) = args.no_args() {
@ -212,11 +212,7 @@ impl DocParser {
self.attribute.inline.push((inline, path.span()));
}
fn parse_cfg<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
) {
fn parse_cfg<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
// This function replaces cases like `cfg(all())` with `true`.
fn simplify_cfg(cfg_entry: &mut CfgEntry) {
match cfg_entry {
@ -236,11 +232,11 @@ impl DocParser {
}
}
fn parse_auto_cfg<'c, S: Stage>(
fn parse_auto_cfg<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {
@ -343,10 +339,10 @@ impl DocParser {
}
}
fn parse_single_doc_attr_item<'c, S: Stage>(
fn parse_single_doc_attr_item<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser,
) {
let path = mip.path();
let args = mip.args();
@ -506,10 +502,10 @@ impl DocParser {
}
}
fn accept_single_doc_attr<'c, S: Stage>(
fn accept_single_doc_attr<S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {

View file

@ -15,7 +15,7 @@ impl<S: Stage> SingleAttributeParser<S> for DummyParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really
fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::Dummy)
}
}

View file

@ -34,7 +34,7 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
"https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
match args {
ArgParser::NoArgs => Some(AttributeKind::Inline(InlineAttr::Hint, cx.attr_span)),
ArgParser::List(list) => {
@ -77,7 +77,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
const TEMPLATE: AttributeTemplate = template!(Word, List: &["reason"], NameValueStr: "reason");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let reason = match args {
ArgParser::NoArgs => None,
ArgParser::List(list) => {

View file

@ -33,7 +33,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
"https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -62,10 +62,10 @@ impl<S: Stage> CombineAttributeParser<S> for LinkParser {
], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let items = match args {
ArgParser::List(list) => list,
// This is an edgecase added because making this a hard error would break too many crates
@ -242,7 +242,7 @@ impl<S: Stage> CombineAttributeParser<S> for LinkParser {
impl LinkParser {
fn parse_link_name<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
name: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -267,7 +267,7 @@ impl LinkParser {
}
fn parse_link_kind<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
kind: &mut Option<NativeLibKind>,
cx: &mut AcceptContext<'_, '_, S>,
sess: &Session,
@ -347,7 +347,7 @@ impl LinkParser {
}
fn parse_link_modifiers<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
modifiers: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -368,7 +368,7 @@ impl LinkParser {
}
fn parse_link_cfg<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
cfg: &mut Option<CfgEntry>,
cx: &mut AcceptContext<'_, '_, S>,
sess: &Session,
@ -400,7 +400,7 @@ impl LinkParser {
}
fn parse_link_wasm_import_module<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
wasm_import_module: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -421,7 +421,7 @@ impl LinkParser {
}
fn parse_link_import_name_type<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
import_name_type: &mut Option<(PeImportNameType, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -478,7 +478,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
"https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -551,7 +551,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkOrdinalParser {
"https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ordinal = parse_single_integer(cx, args)?;
// According to the table at
@ -607,7 +607,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkageParser {
"weak_odr",
]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(name_value) = args.name_value() else {
cx.expected_name_value(cx.attr_span, Some(sym::linkage));
return None;

View file

@ -148,7 +148,7 @@ impl<S: Stage> SingleAttributeParser<S> for MacroExportParser {
Error(Target::Crate),
]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let local_inner_macros = match args {
ArgParser::NoArgs => false,
ArgParser::List(list) => {

View file

@ -62,7 +62,7 @@ pub(crate) mod traits;
pub(crate) mod transparency;
pub(crate) mod util;
type AcceptFn<T, S> = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess, S>, &ArgParser<'_>);
type AcceptFn<T, S> = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess, S>, &ArgParser);
type AcceptMapping<T, S> = &'static [(&'static [Symbol], AttributeTemplate, AcceptFn<T, S>)];
/// An [`AttributeParser`] is a type which searches for syntactic attributes.
@ -133,7 +133,7 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
const TEMPLATE: AttributeTemplate;
/// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`]
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind>;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind>;
}
/// Use in combination with [`SingleAttributeParser`].
@ -282,7 +282,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
const TEMPLATE: AttributeTemplate = template!(Word);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
}
@ -315,10 +315,10 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
const TEMPLATE: AttributeTemplate;
/// Converts a single syntactical attribute to a number of elements of the semantic attribute, or [`AttributeKind`]
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c;
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item>;
}
/// Use in combination with [`CombineAttributeParser`].

View file

@ -29,7 +29,7 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
"https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::MustUse {
span: cx.attr_span,
reason: match args {

View file

@ -13,7 +13,7 @@ impl<S: Stage> SingleAttributeParser<S> for PathParser {
"https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -30,7 +30,7 @@ impl<S: Stage> SingleAttributeParser<S> for ProcMacroDeriveParser {
"https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let (trait_name, helper_attrs) = parse_derive_like(cx, args, true)?;
Some(AttributeKind::ProcMacroDerive {
trait_name: trait_name.expect("Trait name is mandatory, so it is present"),
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcBuiltinMacroParser {
const TEMPLATE: AttributeTemplate =
template!(List: &["TraitName", "TraitName, attributes(name1, name2, ...)"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let (builtin_name, helper_attrs) = parse_derive_like(cx, args, false)?;
Some(AttributeKind::RustcBuiltinMacro { builtin_name, helper_attrs, span: cx.attr_span })
}
@ -57,7 +57,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcBuiltinMacroParser {
fn parse_derive_like<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
trait_name_mandatory: bool,
) -> Option<(Option<Symbol>, ThinVec<Symbol>)> {
let Some(list) = args.list() else {

View file

@ -25,7 +25,7 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;
@ -70,7 +70,7 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
fn extract_value<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
key: Symbol,
arg: &ArgParser<'_>,
arg: &ArgParser,
span: Span,
out_val: &mut Option<(Symbol, Span)>,
failed: &mut bool,

View file

@ -26,10 +26,10 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
"https://doc.rust-lang.org/reference/type-layout.html#representations"
);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let mut reprs = Vec::new();
let Some(list) = args.list() else {
@ -98,10 +98,7 @@ fn int_type_of_word(s: Symbol) -> Option<IntType> {
}
}
fn parse_repr<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
param: &MetaItemParser<'_>,
) -> Option<ReprAttr> {
fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -> Option<ReprAttr> {
use ReprAttr::*;
// FIXME(jdonszelmann): invert the parsing here to match on the word first and then the
@ -192,7 +189,7 @@ enum AlignKind {
fn parse_repr_align<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
param_span: Span,
align_kind: AlignKind,
) -> Option<ReprAttr> {
@ -278,11 +275,7 @@ impl AlignParser {
const PATH: &'static [Symbol] = &[sym::rustc_align];
const TEMPLATE: AttributeTemplate = template!(List: &["<alignment in bytes>"]);
fn parse<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) {
fn parse<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
match args {
ArgParser::NoArgs | ArgParser::NameValue(_) => {
cx.expected_list(cx.attr_span);
@ -339,11 +332,7 @@ impl AlignStaticParser {
const PATH: &'static [Symbol] = &[sym::rustc_align_static];
const TEMPLATE: AttributeTemplate = AlignParser::TEMPLATE;
fn parse<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) {
fn parse<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
self.0.parse(cx, args)
}
}

View file

@ -19,7 +19,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeStartPars
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["start"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeStart(Box::new(n), cx.attr_span))
}
@ -34,7 +34,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeEndParser
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["end"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeEnd(Box::new(n), cx.attr_span))
}
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcObjectLifetimeDefaultParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(Word);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return None;
@ -68,7 +68,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcSimdMonomorphizeLaneLimitParser
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -267,7 +267,7 @@ impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
/// `name = value`
fn insert_value_into_option_or_error<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
param: &MetaItemParser<'_>,
param: &MetaItemParser,
item: &mut Option<Symbol>,
name: Ident,
) -> Option<()> {
@ -289,7 +289,7 @@ fn insert_value_into_option_or_error<S: Stage>(
/// its stability information.
pub(crate) fn parse_stability<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<(Symbol, StabilityLevel)> {
let mut feature = None;
let mut since = None;
@ -365,7 +365,7 @@ pub(crate) fn parse_stability<S: Stage>(
/// attribute, and return the feature name and its stability information.
pub(crate) fn parse_unstability<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<(Symbol, StabilityLevel)> {
let mut feature = None;
let mut reason = None;

View file

@ -15,7 +15,7 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
"https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::Ignore {
span: cx.attr_span,
reason: match args {
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for ShouldPanicParser {
"https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::ShouldPanic {
span: cx.attr_span,
reason: match args {

View file

@ -18,7 +18,7 @@ impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
const TEMPLATE: AttributeTemplate = template!(List: &["array, boxed_slice"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let mut array = false;
let mut boxed_slice = false;
let Some(args) = args.list() else {

View file

@ -17,7 +17,7 @@ impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
const TEMPLATE: AttributeTemplate =
template!(NameValueStr: ["transparent", "semitransparent", "opaque"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -40,7 +40,7 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
/// `args` is the parser for the attribute arguments.
pub(crate) fn parse_single_integer<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<u128> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);

View file

@ -75,7 +75,7 @@ use crate::attributes::traits::{
};
use crate::attributes::transparency::TransparencyParser;
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
use crate::parser::{ArgParser, PathParser};
use crate::parser::{ArgParser, RefPathParser};
use crate::session_diagnostics::{
AttributeParseError, AttributeParseErrorReason, ParsedDescription, UnknownMetaItem,
};
@ -95,7 +95,7 @@ pub(super) struct GroupTypeInnerAccept<S: Stage> {
}
type AcceptFn<S> =
Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser<'a>) + Send + Sync>;
Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser) + Send + Sync>;
type FinalizeFn<S> =
Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, S>) -> Option<AttributeKind>>;
@ -713,7 +713,7 @@ pub(crate) struct FinalizeContext<'p, 'sess, S: Stage> {
///
/// Usually, you should use normal attribute parsing logic instead,
/// especially when making a *denylist* of other attributes.
pub(crate) all_attrs: &'p [PathParser<'p>],
pub(crate) all_attrs: &'p [RefPathParser<'p>],
}
impl<'p, 'sess: 'p, S: Stage> Deref for FinalizeContext<'p, 'sess, S> {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::convert::identity;
use rustc_ast as ast;
use rustc_ast::token::DocFragmentKind;
@ -13,7 +13,7 @@ use rustc_session::lint::BuiltinLintDiag;
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage};
use crate::parser::{ArgParser, MetaItemParser, PathParser};
use crate::parser::{ArgParser, PathParser, RefPathParser};
use crate::session_diagnostics::ParsedDescription;
use crate::{Early, Late, OmitDoc, ShouldEmit};
@ -136,7 +136,7 @@ impl<'sess> AttributeParser<'sess, Early> {
target_node_id: NodeId,
features: Option<&'sess Features>,
emit_errors: ShouldEmit,
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> Option<T>,
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser) -> Option<T>,
template: &AttributeTemplate,
) -> Option<T> {
let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
@ -144,22 +144,23 @@ impl<'sess> AttributeParser<'sess, Early> {
};
let parts =
normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
let meta_parser = MetaItemParser::from_attr(normal_attr, &parts, &sess.psess, emit_errors)?;
let path = meta_parser.path();
let args = meta_parser.args();
let path = AttrPath::from_ast(&normal_attr.item.path, identity);
let args =
ArgParser::from_attr_args(&normal_attr.item.args, &parts, &sess.psess, emit_errors)?;
Self::parse_single_args(
sess,
attr.span,
normal_attr.item.span(),
attr.style,
path.get_attribute_path(),
path,
Some(normal_attr.item.unsafety),
ParsedDescription::Attribute,
target_span,
target_node_id,
features,
emit_errors,
args,
&args,
parse_fn,
template,
)
@ -267,7 +268,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
mut emit_lint: impl FnMut(AttributeLint<S::Id>),
) -> Vec<Attribute> {
let mut attributes = Vec::new();
let mut attr_paths = Vec::new();
let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
for attr in attrs {
// If we're only looking for a single attribute, skip all the ones we don't care about.
@ -301,7 +302,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
}))
}
ast::AttrKind::Normal(n) => {
attr_paths.push(PathParser(Cow::Borrowed(&n.item.path)));
attr_paths.push(PathParser(&n.item.path));
let attr_path = AttrPath::from_ast(&n.item.path, lower_span);
self.check_attribute_safety(
@ -316,15 +317,14 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
let Some(parser) = MetaItemParser::from_attr(
n,
let Some(args) = ArgParser::from_attr_args(
&n.item.args,
&parts,
&self.sess.psess,
self.stage.should_emit(),
) else {
continue;
};
let args = parser.args();
// Special-case handling for `#[doc = "..."]`: if we go through with
// `DocParser`, the order of doc comments will be messed up because `///`
@ -342,7 +342,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
// blob
// a
if is_doc_attribute
&& let ArgParser::NameValue(nv) = args
&& let ArgParser::NameValue(nv) = &args
// If not a string key/value, it should emit an error, but to make
// things simpler, it's handled in `DocParser` because it's simpler to
// emit an error with `AcceptContext`.
@ -373,7 +373,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attr_path: attr_path.clone(),
};
(accept.accept_fn)(&mut cx, args);
(accept.accept_fn)(&mut cx, &args);
if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) {
Self::check_target(&accept.allowed_targets, target, &mut cx);
}

View file

@ -3,12 +3,12 @@
//!
//! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs`
use std::borrow::Cow;
use std::borrow::Borrow;
use std::fmt::{Debug, Display};
use rustc_ast::token::{self, Delimiter, MetaVarKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path, StmtKind, UnOp};
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, StmtKind, UnOp};
use rustc_ast_pretty::pprust;
use rustc_errors::{Diag, PResult};
use rustc_hir::{self as hir, AttrPath};
@ -26,9 +26,12 @@ use crate::session_diagnostics::{
};
#[derive(Clone, Debug)]
pub struct PathParser<'a>(pub Cow<'a, Path>);
pub struct PathParser<P: Borrow<Path>>(pub P);
impl<'a> PathParser<'a> {
pub type OwnedPathParser = PathParser<Path>;
pub type RefPathParser<'p> = PathParser<&'p Path>;
impl<P: Borrow<Path>> PathParser<P> {
pub fn get_attribute_path(&self) -> hir::AttrPath {
AttrPath {
segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(),
@ -36,16 +39,16 @@ impl<'a> PathParser<'a> {
}
}
pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> {
self.0.segments.iter().map(|seg| &seg.ident)
pub fn segments(&self) -> impl Iterator<Item = &Ident> {
self.0.borrow().segments.iter().map(|seg| &seg.ident)
}
pub fn span(&self) -> Span {
self.0.span
self.0.borrow().span
}
pub fn len(&self) -> usize {
self.0.segments.len()
self.0.borrow().segments.len()
}
pub fn segments_is(&self, segments: &[Symbol]) -> bool {
@ -76,21 +79,21 @@ impl<'a> PathParser<'a> {
}
}
impl Display for PathParser<'_> {
impl<P: Borrow<Path>> Display for PathParser<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", pprust::path_to_string(&self.0))
write!(f, "{}", pprust::path_to_string(self.0.borrow()))
}
}
#[derive(Clone, Debug)]
#[must_use]
pub enum ArgParser<'a> {
pub enum ArgParser {
NoArgs,
List(MetaItemListParser<'a>),
List(MetaItemListParser),
NameValue(NameValueParser),
}
impl<'a> ArgParser<'a> {
impl ArgParser {
pub fn span(&self) -> Option<Span> {
match self {
Self::NoArgs => None,
@ -100,7 +103,7 @@ impl<'a> ArgParser<'a> {
}
pub fn from_attr_args<'sess>(
value: &'a AttrArgs,
value: &AttrArgs,
parts: &[Symbol],
psess: &'sess ParseSess,
should_emit: ShouldEmit,
@ -144,7 +147,7 @@ impl<'a> ArgParser<'a> {
///
/// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list
/// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list
pub fn list(&self) -> Option<&MetaItemListParser<'a>> {
pub fn list(&self) -> Option<&MetaItemListParser> {
match self {
Self::List(l) => Some(l),
Self::NameValue(_) | Self::NoArgs => None,
@ -184,17 +187,17 @@ impl<'a> ArgParser<'a> {
///
/// Choose which one you want using the provided methods.
#[derive(Debug, Clone)]
pub enum MetaItemOrLitParser<'a> {
MetaItemParser(MetaItemParser<'a>),
pub enum MetaItemOrLitParser {
MetaItemParser(MetaItemParser),
Lit(MetaItemLit),
Err(Span, ErrorGuaranteed),
}
impl<'sess> MetaItemOrLitParser<'sess> {
pub fn parse_single(
impl MetaItemOrLitParser {
pub fn parse_single<'sess>(
parser: &mut Parser<'sess>,
should_emit: ShouldEmit,
) -> PResult<'sess, MetaItemOrLitParser<'static>> {
) -> PResult<'sess, MetaItemOrLitParser> {
let mut this = MetaItemListParserContext { parser, should_emit };
this.parse_meta_item_inner()
}
@ -216,7 +219,7 @@ impl<'sess> MetaItemOrLitParser<'sess> {
}
}
pub fn meta_item(&self) -> Option<&MetaItemParser<'sess>> {
pub fn meta_item(&self) -> Option<&MetaItemParser> {
match self {
MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
_ => None,
@ -238,12 +241,12 @@ impl<'sess> MetaItemOrLitParser<'sess> {
///
/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html>
#[derive(Clone)]
pub struct MetaItemParser<'a> {
path: PathParser<'a>,
args: ArgParser<'a>,
pub struct MetaItemParser {
path: OwnedPathParser,
args: ArgParser,
}
impl<'a> Debug for MetaItemParser<'a> {
impl Debug for MetaItemParser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaItemParser")
.field("path", &self.path)
@ -252,28 +255,12 @@ impl<'a> Debug for MetaItemParser<'a> {
}
}
impl<'a> MetaItemParser<'a> {
/// Create a new parser from a [`NormalAttr`], which is stored inside of any
/// [`ast::Attribute`](rustc_ast::Attribute)
pub fn from_attr<'sess>(
attr: &'a NormalAttr,
parts: &[Symbol],
psess: &'sess ParseSess,
should_emit: ShouldEmit,
) -> Option<Self> {
Some(Self {
path: PathParser(Cow::Borrowed(&attr.item.path)),
args: ArgParser::from_attr_args(&attr.item.args, parts, psess, should_emit)?,
})
}
}
impl<'a> MetaItemParser<'a> {
impl MetaItemParser {
pub fn span(&self) -> Span {
if let Some(other) = self.args.span() {
self.path.span().with_hi(other.hi())
self.path.borrow().span().with_hi(other.hi())
} else {
self.path.span()
self.path.borrow().span()
}
}
@ -282,12 +269,12 @@ impl<'a> MetaItemParser<'a> {
/// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
/// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
/// - `#[inline]`: `inline` is a single segment path
pub fn path(&self) -> &PathParser<'a> {
pub fn path(&self) -> &OwnedPathParser {
&self.path
}
/// Gets just the args parser, without caring about the path.
pub fn args(&self) -> &ArgParser<'a> {
pub fn args(&self) -> &ArgParser {
&self.args
}
@ -297,7 +284,7 @@ impl<'a> MetaItemParser<'a> {
/// - `#[inline]`: `inline` is a word
/// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
/// and not a word and should instead be parsed using [`path`](Self::path)
pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser> {
self.path().word_is(sym).then(|| self.args())
}
}
@ -421,7 +408,7 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
Ok(lit)
}
fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser<'static>> {
fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser> {
if let Some(MetaVarKind::Meta { has_meta_form }) = self.parser.token.is_metavar_seq() {
return if has_meta_form {
let attr_item = self
@ -457,10 +444,10 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
ArgParser::NoArgs
};
Ok(MetaItemParser { path: PathParser(Cow::Owned(path)), args })
Ok(MetaItemParser { path: PathParser(path), args })
}
fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser> {
if let Some(token_lit) = self.parser.eat_token_lit() {
// If a literal token is parsed, we commit to parsing a MetaItemLit for better errors
Ok(MetaItemOrLitParser::Lit(self.unsuffixed_meta_item_from_lit(token_lit)?))
@ -547,7 +534,7 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
psess: &'sess ParseSess,
span: Span,
should_emit: ShouldEmit,
) -> PResult<'sess, MetaItemListParser<'static>> {
) -> PResult<'sess, MetaItemListParser> {
let mut parser = Parser::new(psess, tokens, None);
let mut this = MetaItemListParserContext { parser: &mut parser, should_emit };
@ -570,14 +557,14 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
}
#[derive(Debug, Clone)]
pub struct MetaItemListParser<'a> {
sub_parsers: ThinVec<MetaItemOrLitParser<'a>>,
pub struct MetaItemListParser {
sub_parsers: ThinVec<MetaItemOrLitParser>,
pub span: Span,
}
impl<'a> MetaItemListParser<'a> {
impl MetaItemListParser {
pub(crate) fn new<'sess>(
tokens: &'a TokenStream,
tokens: &TokenStream,
span: Span,
psess: &'sess ParseSess,
should_emit: ShouldEmit,
@ -586,7 +573,7 @@ impl<'a> MetaItemListParser<'a> {
}
/// Lets you pick and choose as what you want to parse each element in the list
pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser> {
self.sub_parsers.iter()
}
@ -601,7 +588,7 @@ impl<'a> MetaItemListParser<'a> {
/// Returns Some if the list contains only a single element.
///
/// Inside the Some is the parser to parse this single element.
pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
pub fn single(&self) -> Option<&MetaItemOrLitParser> {
let mut iter = self.mixed();
iter.next().filter(|_| iter.next().is_none())
}

View file

@ -167,7 +167,7 @@ pub(crate) fn compile_fn(
context.clear();
context.func = codegened_func.func;
#[cfg(any())] // This is never true
#[cfg(false)]
let _clif_guard = {
use std::fmt::Write;

View file

@ -883,6 +883,7 @@ lint_unexpected_cfg_add_build_rs_println = or consider adding `{$build_rs_printl
lint_unexpected_cfg_add_cargo_feature = consider using a Cargo feature instead
lint_unexpected_cfg_add_cargo_toml_lint_cfg = or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:{$cargo_toml_lint_cfg}
lint_unexpected_cfg_add_cmdline_arg = to expect this configuration use `{$cmdline_arg}`
lint_unexpected_cfg_boolean = you may have meant to use `{$literal}` (notice the capitalization). Doing so makes this predicate evaluate to `{$literal}` unconditionally
lint_unexpected_cfg_cargo_update = the {$macro_kind} `{$macro_name}` may come from an old version of the `{$crate_name}` crate, try updating your dependency with `cargo update -p {$crate_name}`
lint_unexpected_cfg_define_features = consider defining some features in `Cargo.toml`

View file

@ -138,6 +138,16 @@ pub(super) fn unexpected_cfg_name(
let is_from_external_macro = name_span.in_external_macro(sess.source_map());
let mut is_feature_cfg = name == sym::feature;
fn miscapitalized_boolean(name: Symbol) -> Option<bool> {
if name.as_str().eq_ignore_ascii_case("false") {
Some(false)
} else if name.as_str().eq_ignore_ascii_case("true") {
Some(true)
} else {
None
}
}
let code_sugg = if is_feature_cfg && is_from_cargo {
lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
// Suggest correct `version("..")` predicate syntax
@ -148,6 +158,21 @@ pub(super) fn unexpected_cfg_name(
between_name_and_value: name_span.between(value_span),
after_value: value_span.shrink_to_hi(),
}
// Suggest a literal `false` instead
// Detect miscapitalized `False`/`FALSE` etc, ensuring that this isn't `r#false`
} else if value.is_none()
// If this is a miscapitalized False/FALSE, suggest the boolean literal instead
&& let Some(boolean) = miscapitalized_boolean(name)
// Check this isn't a raw identifier
&& sess
.source_map()
.span_to_snippet(name_span)
.map_or(true, |snippet| !snippet.contains("r#"))
{
lints::unexpected_cfg_name::CodeSuggestion::BooleanLiteral {
span: name_span,
literal: boolean,
}
// Suggest the most probable if we found one
} else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
is_feature_cfg |= best_match == sym::feature;

View file

@ -2401,6 +2401,17 @@ pub(crate) mod unexpected_cfg_name {
#[subdiagnostic]
expected_names: Option<ExpectedNames>,
},
#[suggestion(
lint_unexpected_cfg_boolean,
applicability = "machine-applicable",
style = "verbose",
code = "{literal}"
)]
BooleanLiteral {
#[primary_span]
span: Span,
literal: bool,
},
}
#[derive(Subdiagnostic)]

View file

@ -591,7 +591,7 @@ impl () {}
/// # pub unsafe fn malloc(_size: usize) -> *mut core::ffi::c_void { core::ptr::NonNull::dangling().as_ptr() }
/// # pub unsafe fn free(_ptr: *mut core::ffi::c_void) {}
/// # }
/// # #[cfg(any())]
/// # #[cfg(false)]
/// #[allow(unused_extern_crates)]
/// extern crate libc;
///

View file

@ -892,8 +892,8 @@ impl Step for Std {
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustcDev {
/// The compiler that will build rustc which will be shipped in this component.
build_compiler: Compiler,
target: TargetSelection,
pub build_compiler: Compiler,
pub target: TargetSelection,
}
impl RustcDev {

View file

@ -279,6 +279,17 @@ install!((self, builder, _config),
});
install_sh(builder, "rustc", self.build_compiler, Some(self.target), &tarball);
};
RustcDev, alias = "rustc-dev", Self::should_build(_config), IS_HOST: true, {
if let Some(tarball) = builder.ensure(dist::RustcDev {
build_compiler: self.build_compiler, target: self.target
}) {
install_sh(builder, "rustc-dev", self.build_compiler, Some(self.target), &tarball);
} else {
builder.info(
&format!("skipping Install RustcDev stage{} ({})", self.build_compiler.stage + 1, self.target),
);
}
};
RustcCodegenCranelift, alias = "rustc-codegen-cranelift", Self::should_build(_config), IS_HOST: true, {
if let Some(tarball) = builder.ensure(dist::CraneliftCodegenBackend {
compilers: RustcPrivateCompilers::from_build_compiler(builder, self.build_compiler, self.target),

View file

@ -998,6 +998,7 @@ impl<'a> Builder<'a> {
// binary path, we must install rustc before the tools. Otherwise, the rust-installer will
// install the same binaries twice for each tool, leaving backup files (*.old) as a result.
install::Rustc,
install::RustcDev,
install::Cargo,
install::RustAnalyzer,
install::Rustfmt,

View file

@ -2912,6 +2912,7 @@ mod snapshot {
[build] rustc 1 <x86_64-unknown-linux-gnu> -> rust-analyzer-proc-macro-srv 2 <x86_64-unknown-linux-gnu>
[build] rustc 0 <x86_64-unknown-linux-gnu> -> GenerateCopyright 1 <x86_64-unknown-linux-gnu>
[dist] rustc <x86_64-unknown-linux-gnu>
[dist] rustc 1 <x86_64-unknown-linux-gnu> -> rustc-dev 2 <x86_64-unknown-linux-gnu>
[build] rustc 1 <x86_64-unknown-linux-gnu> -> cargo 2 <x86_64-unknown-linux-gnu>
[dist] rustc 1 <x86_64-unknown-linux-gnu> -> cargo 2 <x86_64-unknown-linux-gnu>
[build] rustc 1 <x86_64-unknown-linux-gnu> -> rust-analyzer 2 <x86_64-unknown-linux-gnu>

View file

@ -6,7 +6,83 @@ document.
## Unreleased / Beta / In Rust Nightly
[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master)
[d9fb15c...master](https://github.com/rust-lang/rust-clippy/compare/d9fb15c...master)
## Rust 1.92
Current stable, released 2025-12-11
[View all 124 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-09-05T18%3A24%3A03Z..2025-10-16T14%3A13%3A43Z+base%3Amaster)
### New Lints
* Added [`unnecessary_option_map_or_else`] to `suspicious`
[#14662](https://github.com/rust-lang/rust-clippy/pull/14662)
* Added [`replace_box`] to `perf`
[#14953](https://github.com/rust-lang/rust-clippy/pull/14953)
* Added [`volatile_composites`] to `nursery`
[#15686](https://github.com/rust-lang/rust-clippy/pull/15686)
* Added [`self_only_used_in_recursion`] to `pedantic`
[#14787](https://github.com/rust-lang/rust-clippy/pull/14787)
* Added [`redundant_iter_cloned`] to `perf`
[#15277](https://github.com/rust-lang/rust-clippy/pull/15277)
### Moves and Deprecations
* Renamed [`unchecked_duration_subtraction`] to [`unchecked_time_subtraction`]
[#13800](https://github.com/rust-lang/rust-clippy/pull/13800)
### Enhancements
* [`mutex_atomic`] and [`mutex_integer`] overhauled to only lint definitions, not uses; added suggestions
and better help messages
[#15632](https://github.com/rust-lang/rust-clippy/pull/15632)
* [`manual_rotate`] now recognizes non-const rotation amounts
[#15402](https://github.com/rust-lang/rust-clippy/pull/15402)
* [`multiple_inherent_impl`] added `inherent-impl-lint-scope` config option (`module`, `file`,
or `crate`)
[#15843](https://github.com/rust-lang/rust-clippy/pull/15843)
* [`use_self`] now checks structs and enums
[#15566](https://github.com/rust-lang/rust-clippy/pull/15566)
* [`while_let_loop`] extended to lint on `loop { let else }`
[#15701](https://github.com/rust-lang/rust-clippy/pull/15701)
* [`mut_mut`] overhauled with structured suggestions and improved documentation
[#15417](https://github.com/rust-lang/rust-clippy/pull/15417)
* [`nonstandard_macro_braces`] now suggests trailing semicolon when needed
[#15593](https://github.com/rust-lang/rust-clippy/pull/15593)
* [`ptr_offset_with_cast`] now respects MSRV when suggesting fix, and lints more cases
[#15613](https://github.com/rust-lang/rust-clippy/pull/15613)
* [`cast_sign_loss`] and [`cast_possible_wrap`] added suggestions using `cast_{un,}signed()` methods
(MSRV 1.87+)
[#15384](https://github.com/rust-lang/rust-clippy/pull/15384)
* [`unchecked_time_subtraction`] extended to include `Duration - Duration` operations
[#13800](https://github.com/rust-lang/rust-clippy/pull/13800)
* [`filter_next`] now suggests replacing `filter().next_back()` with `rfind()` for
`DoubleEndedIterator`
[#15748](https://github.com/rust-lang/rust-clippy/pull/15748)
### False Positive Fixes
* [`unnecessary_safety_comment`] fixed FPs with comments above attributes
[#15678](https://github.com/rust-lang/rust-clippy/pull/15678)
* [`manual_unwrap_or`] fixed FP edge case
[#15812](https://github.com/rust-lang/rust-clippy/pull/15812)
* [`needless_continue`] fixed FP when match type is not unit or never
[#15547](https://github.com/rust-lang/rust-clippy/pull/15547)
* [`if_then_some_else_none`] fixed FP when return exists in block expr
[#15783](https://github.com/rust-lang/rust-clippy/pull/15783)
* [`new_without_default`] fixed to copy `#[cfg]` onto `impl Default` and fixed FP on private type
with trait impl
[#15720](https://github.com/rust-lang/rust-clippy/pull/15720)
[#15782](https://github.com/rust-lang/rust-clippy/pull/15782)
* [`question_mark`] fixed FP on variables used after
[#15644](https://github.com/rust-lang/rust-clippy/pull/15644)
* [`needless_return`] fixed FP with `cfg`d code after `return`
[#15669](https://github.com/rust-lang/rust-clippy/pull/15669)
* [`useless_attribute`] fixed FP on `deprecated_in_future`
[#15645](https://github.com/rust-lang/rust-clippy/pull/15645)
* [`double_parens`] fixed FP when macros are involved
[#15420](https://github.com/rust-lang/rust-clippy/pull/15420)
## Rust 1.91
@ -6280,6 +6356,7 @@ Released 2018-09-13
[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`decimal_bitwise_operands`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_bitwise_operands
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
[`default_constructed_unit_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs
@ -6541,6 +6618,7 @@ Released 2018-09-13
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
@ -6686,6 +6764,7 @@ Released 2018-09-13
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
[`needless_return_with_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return_with_question_mark
[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
[`needless_type_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_type_cast
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
@ -6765,6 +6844,7 @@ Released 2018-09-13
[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr
[`ptr_cast_constness`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_cast_constness
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
[`ptr_offset_by_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_by_literal
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
[`pub_underscore_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields
@ -7081,6 +7161,7 @@ Released 2018-09-13
[`allow-expect-in-consts`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-consts
[`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests
[`allow-indexing-slicing-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-indexing-slicing-in-tests
[`allow-large-stack-frames-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-large-stack-frames-in-tests
[`allow-mixed-uninlined-format-args`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-mixed-uninlined-format-args
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
[`allow-panic-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-panic-in-tests

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.93"
version = "0.1.94"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -4,7 +4,7 @@
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View file

@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View file

@ -111,6 +111,16 @@ Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
* [`indexing_slicing`](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing)
## `allow-large-stack-frames-in-tests`
Whether functions inside `#[cfg(test)]` modules or test functions should be checked.
**Default Value:** `true`
---
**Affected lints:**
* [`large_stack_frames`](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_frames)
## `allow-mixed-uninlined-format-args`
Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
publish = false

View file

@ -373,6 +373,9 @@ define_Conf! {
/// Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
#[lints(indexing_slicing)]
allow_indexing_slicing_in_tests: bool = false,
/// Whether functions inside `#[cfg(test)]` modules or test functions should be checked.
#[lints(large_stack_frames)]
allow_large_stack_frames_in_tests: bool = true,
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
#[lints(uninlined_format_args)]
allow_mixed_uninlined_format_args: bool = true,

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.93"
version = "0.1.94"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -19,6 +19,7 @@ mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod needless_type_cast;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
@ -813,6 +814,32 @@ declare_clippy_lint! {
"casting a primitive method pointer to any integer type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bindings (constants, statics, or let bindings) that are defined
/// with one numeric type but are consistently cast to a different type in all usages.
///
/// ### Why is this bad?
/// If a binding is always cast to a different type when used, it would be clearer
/// and more efficient to define it with the target type from the start.
///
/// ### Example
/// ```no_run
/// const SIZE: u16 = 15;
/// let arr: [u8; SIZE as usize] = [0; SIZE as usize];
/// ```
///
/// Use instead:
/// ```no_run
/// const SIZE: usize = 15;
/// let arr: [u8; SIZE] = [0; SIZE];
/// ```
#[clippy::version = "1.93.0"]
pub NEEDLESS_TYPE_CAST,
pedantic,
"binding defined with one type but always cast to another"
}
pub struct Casts {
msrv: Msrv,
}
@ -851,6 +878,7 @@ impl_lint_pass!(Casts => [
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
CONFUSING_METHOD_TO_NUMERIC_CAST,
NEEDLESS_TYPE_CAST,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -920,4 +948,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
}
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &rustc_hir::Body<'tcx>) {
needless_type_cast::check(cx, body);
}
}

View file

@ -0,0 +1,289 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::visitors::{Descend, for_each_expr, for_each_expr_without_closures};
use core::ops::ControlFlow;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BlockCheckMode, Body, Expr, ExprKind, HirId, LetStmt, PatKind, StmtKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty::{Ty, TypeVisitableExt};
use rustc_span::Span;
use super::NEEDLESS_TYPE_CAST;
struct BindingInfo<'a> {
source_ty: Ty<'a>,
ty_span: Span,
}
struct UsageInfo<'a> {
cast_to: Option<Ty<'a>>,
in_generic_context: bool,
}
pub(super) fn check<'a>(cx: &LateContext<'a>, body: &Body<'a>) {
let mut bindings: FxHashMap<HirId, BindingInfo<'a>> = FxHashMap::default();
for_each_expr_without_closures(body.value, |expr| {
match expr.kind {
ExprKind::Block(block, _) => {
for stmt in block.stmts {
if let StmtKind::Let(let_stmt) = stmt.kind {
collect_binding_from_local(cx, let_stmt, &mut bindings);
}
}
},
ExprKind::Let(let_expr) => {
collect_binding_from_let(cx, let_expr, &mut bindings);
},
_ => {},
}
ControlFlow::<()>::Continue(())
});
#[allow(rustc::potential_query_instability)]
let mut binding_vec: Vec<_> = bindings.into_iter().collect();
binding_vec.sort_by_key(|(_, info)| info.ty_span.lo());
for (hir_id, binding_info) in binding_vec {
check_binding_usages(cx, body, hir_id, &binding_info);
}
}
fn collect_binding_from_let<'a>(
cx: &LateContext<'a>,
let_expr: &rustc_hir::LetExpr<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_expr.ty.is_none()
|| let_expr.span.from_expansion()
|| has_generic_return_type(cx, let_expr.init)
|| contains_unsafe(let_expr.init)
{
return;
}
if let PatKind::Binding(_, hir_id, _, _) = let_expr.pat.kind
&& let Some(ty_hir) = let_expr.ty
{
let ty = cx.typeck_results().pat_ty(let_expr.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}
fn collect_binding_from_local<'a>(
cx: &LateContext<'a>,
let_stmt: &LetStmt<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_stmt.ty.is_none()
|| let_stmt.span.from_expansion()
|| let_stmt
.init
.is_some_and(|init| has_generic_return_type(cx, init) || contains_unsafe(init))
{
return;
}
if let PatKind::Binding(_, hir_id, _, _) = let_stmt.pat.kind
&& let Some(ty_hir) = let_stmt.ty
{
let ty = cx.typeck_results().pat_ty(let_stmt.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}
fn contains_unsafe(expr: &Expr<'_>) -> bool {
for_each_expr_without_closures(expr, |e| {
if let ExprKind::Block(block, _) = e.kind
&& let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
.is_some()
}
fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match &expr.kind {
ExprKind::Block(block, _) => {
if let Some(tail_expr) = block.expr {
return has_generic_return_type(cx, tail_expr);
}
false
},
ExprKind::If(_, then_block, else_expr) => {
has_generic_return_type(cx, then_block) || else_expr.is_some_and(|e| has_generic_return_type(cx, e))
},
ExprKind::Match(_, arms, _) => arms.iter().any(|arm| has_generic_return_type(cx, arm.body)),
ExprKind::Loop(block, label, ..) => for_each_expr_without_closures(*block, |e| {
match e.kind {
ExprKind::Loop(..) => {
// Unlabeled breaks inside nested loops target the inner loop, not ours
return ControlFlow::Continue(Descend::No);
},
ExprKind::Break(dest, Some(break_expr)) => {
let targets_this_loop =
dest.label.is_none() || dest.label.map(|l| l.ident) == label.map(|l| l.ident);
if targets_this_loop && has_generic_return_type(cx, break_expr) {
return ControlFlow::Break(());
}
},
_ => {},
}
ControlFlow::Continue(Descend::Yes)
})
.is_some(),
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
false
},
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = res {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
}
false
},
_ => false,
}
}
fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
let has_type_params = |def_id| {
cx.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
};
match res {
Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id),
// Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))),
_ => false,
}
}
fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool {
let mut current_id = cast_expr.hir_id;
loop {
let parent_id = cx.tcx.parent_hir_id(current_id);
if parent_id == current_id {
return false;
}
let parent = cx.tcx.hir_node(parent_id);
match parent {
rustc_hir::Node::Expr(parent_expr) => {
match &parent_expr.kind {
ExprKind::Closure(_) => return false,
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if is_generic_res(cx, res) {
return true;
}
}
},
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& cx
.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
{
return true;
}
},
_ => {},
}
current_id = parent_id;
},
_ => return false,
}
}
}
fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) {
let mut usages = Vec::new();
for_each_expr(cx, body.value, |expr| {
if let ExprKind::Path(ref qpath) = expr.kind
&& !expr.span.from_expansion()
&& let Res::Local(id) = cx.qpath_res(qpath, expr.hir_id)
&& id == hir_id
{
let parent_id = cx.tcx.parent_hir_id(expr.hir_id);
let parent = cx.tcx.hir_node(parent_id);
let usage = if let rustc_hir::Node::Expr(parent_expr) = parent
&& let ExprKind::Cast(..) = parent_expr.kind
&& !parent_expr.span.from_expansion()
{
UsageInfo {
cast_to: Some(cx.typeck_results().expr_ty(parent_expr)),
in_generic_context: is_cast_in_generic_context(cx, parent_expr),
}
} else {
UsageInfo {
cast_to: None,
in_generic_context: false,
}
};
usages.push(usage);
}
ControlFlow::<()>::Continue(())
});
let Some(first_target) = usages
.first()
.and_then(|u| u.cast_to)
.filter(|&t| t != binding_info.source_ty)
.filter(|&t| usages.iter().all(|u| u.cast_to == Some(t) && !u.in_generic_context))
else {
return;
};
span_lint_and_sugg(
cx,
NEEDLESS_TYPE_CAST,
binding_info.ty_span,
format!(
"this binding is defined as `{}` but is always cast to `{}`",
binding_info.source_ty, first_target
),
"consider defining it as",
first_target.to_string(),
Applicability::MaybeIncorrect,
);
}

View file

@ -70,6 +70,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::NEEDLESS_TYPE_CAST_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
@ -245,8 +246,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::large_stack_frames::LARGE_STACK_FRAMES_INFO,
crate::legacy_numeric_constants::LEGACY_NUMERIC_CONSTANTS_INFO,
crate::len_without_is_empty::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::LEN_ZERO_INFO,
crate::let_if_seq::USELESS_LET_IF_SEQ_INFO,
crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO,
@ -300,6 +301,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
crate::manual_ilog2::MANUAL_ILOG2_INFO,
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
@ -444,6 +446,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::OR_THEN_UNWRAP_INFO,
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
crate::methods::PATH_ENDS_WITH_EXT_INFO,
crate::methods::PTR_OFFSET_BY_LITERAL_INFO,
crate::methods::PTR_OFFSET_WITH_CAST_INFO,
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
crate::methods::READONLY_WRITE_LOCK_INFO,
@ -578,6 +581,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::operators::ASSIGN_OP_PATTERN_INFO,
crate::operators::BAD_BIT_MASK_INFO,
crate::operators::CMP_OWNED_INFO,
crate::operators::DECIMAL_BITWISE_OPERANDS_INFO,
crate::operators::DOUBLE_COMPARISONS_INFO,
crate::operators::DURATION_SUBSEC_INFO,
crate::operators::EQ_OP_INFO,

View file

@ -34,7 +34,7 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [
#[clippy::version = "pre 1.29.0"]
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
#[clippy::version = "1.91.0"]
("clippy::string_to_string", "`clippy:implicit_clone` covers those cases"),
("clippy::string_to_string", "`clippy::implicit_clone` covers those cases"),
#[clippy::version = "pre 1.29.0"]
("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
#[clippy::version = "pre 1.29.0"]

View file

@ -3,11 +3,12 @@ use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_appl
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
SpanlessEq, can_move_expr_to_closure_no_visit, higher, is_expr_final_block_expr, is_expr_used_or_unified,
peel_hir_expr_while,
SpanlessEq, can_move_expr_to_closure_no_visit, desugar_await, higher, is_expr_final_block_expr,
is_expr_used_or_unified, paths, peel_hir_expr_while,
};
use core::fmt::{self, Write};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirIdSet;
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
use rustc_hir::{Block, Expr, ExprKind, HirId, Pat, Stmt, StmtKind, UnOp};
@ -382,6 +383,8 @@ struct InsertSearcher<'cx, 'tcx> {
loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
/// Whether the map is a non-async-aware `MutexGuard`.
map_is_mutex_guard: bool,
}
impl<'tcx> InsertSearcher<'_, 'tcx> {
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@ -524,15 +527,22 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
self.is_single_insert = false;
self.visit_non_tail_expr(cond_expr);
// Each branch may contain it's own insert expression.
// Each branch may contain its own insert expression.
let mut is_map_used = self.visit_cond_arm(then_expr);
is_map_used |= self.visit_cond_arm(else_expr);
self.is_map_used = is_map_used;
},
ExprKind::Match(scrutinee_expr, arms, _) => {
// If the map is a non-async-aware `MutexGuard` and
// `.await` expression appears alongside map insertion in the same `then` or `else` block,
// we cannot suggest using `entry()` because it would hold the lock across the await point,
// triggering `await_holding_lock` and risking deadlock.
if self.map_is_mutex_guard && desugar_await(expr).is_some() {
self.can_use_entry = false;
}
self.is_single_insert = false;
self.visit_non_tail_expr(scrutinee_expr);
// Each branch may contain it's own insert expression.
// Each branch may contain its own insert expression.
let mut is_map_used = self.is_map_used;
for arm in arms {
self.visit_pat(arm.pat);
@ -725,16 +735,32 @@ fn find_insert_calls<'tcx>(
edits: Vec::new(),
loops: Vec::new(),
locals: HirIdSet::default(),
map_is_mutex_guard: false,
};
// Check if the map is a non-async-aware `MutexGuard`
if let rustc_middle::ty::Adt(adt, _) = cx.typeck_results().expr_ty(contains_expr.map).kind()
&& is_mutex_guard(cx, adt.did())
{
s.map_is_mutex_guard = true;
}
s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure;
let is_single_insert = s.is_single_insert;
if !s.can_use_entry {
return None;
}
let is_key_used_and_no_copy = s.is_key_used && !is_copy(cx, cx.typeck_results().expr_ty(contains_expr.key));
let edits = s.edits;
s.can_use_entry.then_some(InsertSearchResults {
edits,
allow_insert_closure,
is_single_insert,
Some(InsertSearchResults {
edits: s.edits,
allow_insert_closure: s.allow_insert_closure,
is_single_insert: s.is_single_insert,
is_key_used_and_no_copy,
})
}
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
match cx.tcx.get_diagnostic_name(def_id) {
Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
}
}

View file

@ -2,15 +2,16 @@ use std::{fmt, ops};
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::fn_has_unsatisfiable_preds;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::source::{HasSession, SpanRangeExt};
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_in_test};
use rustc_errors::Diag;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl};
use rustc_lexer::is_ident;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -83,12 +84,14 @@ declare_clippy_lint! {
pub struct LargeStackFrames {
maximum_allowed_size: u64,
allow_large_stack_frames_in_tests: bool,
}
impl LargeStackFrames {
pub fn new(conf: &'static Conf) -> Self {
Self {
maximum_allowed_size: conf.stack_size_threshold,
allow_large_stack_frames_in_tests: conf.allow_large_stack_frames_in_tests,
}
}
}
@ -152,67 +155,122 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
let mir = cx.tcx.optimized_mir(def_id);
let typing_env = mir.typing_env(cx.tcx);
let sizes_of_locals = || {
mir.local_decls.iter().filter_map(|local| {
let sizes_of_locals = mir
.local_decls
.iter()
.filter_map(|local| {
let layout = cx.tcx.layout_of(typing_env.as_query_input(local.ty)).ok()?;
Some((local, layout.size.bytes()))
})
};
.collect::<Vec<_>>();
let frame_size = sizes_of_locals().fold(Space::Used(0), |sum, (_, size)| sum + size);
let frame_size = sizes_of_locals
.iter()
.fold(Space::Used(0), |sum, (_, size)| sum + *size);
let limit = self.maximum_allowed_size;
if frame_size.exceeds_limit(limit) {
// Point at just the function name if possible, because lints that span
// the entire body and don't have to are less legible.
let fn_span = match fn_kind {
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
FnKind::Closure => entire_fn_span,
let (fn_span, fn_name) = match fn_kind {
FnKind::ItemFn(ident, _, _) => (ident.span, format!("function `{}`", ident.name)),
FnKind::Method(ident, _) => (ident.span, format!("method `{}`", ident.name)),
FnKind::Closure => (entire_fn_span, "closure".to_string()),
};
// Don't lint inside tests if configured to not do so.
if self.allow_large_stack_frames_in_tests && is_in_test(cx.tcx, cx.tcx.local_def_id_to_hir_id(local_def_id))
{
return;
}
let explain_lint = |diag: &mut Diag<'_, ()>, ctxt: SyntaxContext| {
// Point out the largest individual contribution to this size, because
// it is the most likely to be unintentionally large.
if let Some((local, size)) = sizes_of_locals.iter().max_by_key(|&(_, size)| size)
&& let local_span = local.source_info.span
&& local_span.ctxt() == ctxt
{
let size = Space::Used(*size); // pluralizes for us
let ty = local.ty;
// TODO: Is there a cleaner, robust way to ask this question?
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
// and that doesn't get us the true name in scope rather than the span text either.
if let Some(name) = local_span.get_source_text(cx)
&& is_ident(&name)
{
// If the local is an ordinary named variable,
// print its name rather than relying solely on the span.
diag.span_label(
local_span,
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
);
} else {
diag.span_label(
local_span,
format!("this is the largest part, at {size} for type `{ty}`"),
);
}
}
// Explain why we are linting this and not other functions.
diag.note(format!(
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
));
// Explain why the user should care, briefly.
diag.note_once(
"allocating large amounts of stack space can overflow the stack \
and cause the program to abort",
);
};
if fn_span.from_expansion() {
// Don't lint on the main function generated by `--test` target
if cx.sess().is_test_crate() && is_entrypoint_fn(cx, local_def_id.to_def_id()) {
return;
}
let is_from_external_macro = fn_span.in_external_macro(cx.sess().source_map());
span_lint_and_then(
cx,
LARGE_STACK_FRAMES,
fn_span.source_callsite(),
format!(
"{} generated by this macro may allocate a lot of stack space",
if is_from_external_macro {
cx.tcx.def_descr(local_def_id.into())
} else {
fn_name.as_str()
}
),
|diag| {
if is_from_external_macro {
return;
}
diag.span_label(
fn_span,
format!(
"this {} has a stack frame size of {frame_size}",
cx.tcx.def_descr(local_def_id.into())
),
);
explain_lint(diag, fn_span.ctxt());
},
);
return;
}
span_lint_and_then(
cx,
LARGE_STACK_FRAMES,
fn_span,
format!("this function may allocate {frame_size} on the stack"),
|diag| {
// Point out the largest individual contribution to this size, because
// it is the most likely to be unintentionally large.
if let Some((local, size)) = sizes_of_locals().max_by_key(|&(_, size)| size) {
let local_span: Span = local.source_info.span;
let size = Space::Used(size); // pluralizes for us
let ty = local.ty;
// TODO: Is there a cleaner, robust way to ask this question?
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
// and that doesn't get us the true name in scope rather than the span text either.
if let Some(name) = local_span.get_source_text(cx)
&& is_ident(&name)
{
// If the local is an ordinary named variable,
// print its name rather than relying solely on the span.
diag.span_label(
local_span,
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
);
} else {
diag.span_label(
local_span,
format!("this is the largest part, at {size} for type `{ty}`"),
);
}
}
// Explain why we are linting this and not other functions.
diag.note(format!(
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
));
// Explain why the user should care, briefly.
diag.note_once(
"allocating large amounts of stack space can overflow the stack \
and cause the program to abort",
);
explain_lint(diag, SyntaxContext::root());
},
);
}

View file

@ -0,0 +1,342 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::res::MaybeDef;
use clippy_utils::{fulfill_or_allowed, get_parent_as_impl, sym};
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability,
Node, OpaqueTyOrigin, PathSegment, PrimTy, QPath, TraitItemId, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FnSig, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::kw;
use rustc_span::{Ident, Span, Symbol};
use rustc_trait_selection::traits::supertrait_def_ids;
declare_clippy_lint! {
/// ### What it does
/// Checks for items that implement `.len()` but not
/// `.is_empty()`.
///
/// ### Why is this bad?
/// It is good custom to have both methods, because for
/// some data structures, asking about the length will be a costly operation,
/// whereas `.is_empty()` can usually answer in constant time. Also it used to
/// lead to false positives on the [`len_zero`](#len_zero) lint currently that
/// lint will ignore such entities.
///
/// ### Example
/// ```ignore
/// impl X {
/// pub fn len(&self) -> usize {
/// ..
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub LEN_WITHOUT_IS_EMPTY,
style,
"traits or impls with a public `len` method but no corresponding `is_empty` method"
}
declare_lint_pass!(LenWithoutIsEmpty => [LEN_WITHOUT_IS_EMPTY]);
impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Trait(_, _, _, ident, _, _, trait_items) = item.kind
&& !item.span.from_expansion()
{
check_trait_items(cx, item, ident, trait_items);
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
&& let Some(output) = LenOutput::new(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
let (name, kind) = match cx.tcx.hir_node(ty_hir_id) {
Node::ForeignItem(x) => (x.ident.name, "extern type"),
Node::Item(x) => match x.kind {
ItemKind::Struct(ident, ..) => (ident.name, "struct"),
ItemKind::Enum(ident, ..) => (ident.name, "enum"),
ItemKind::Union(ident, ..) => (ident.name, "union"),
_ => (x.kind.ident().unwrap().name, "type"),
},
_ => return,
};
check_for_is_empty(
cx,
sig.span,
sig.decl.implicit_self,
output,
ty_id,
name,
kind,
item.hir_id(),
ty_hir_id,
);
}
}
}
fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) {
fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool {
cx.tcx.item_name(item.owner_id) == name
&& matches!(
cx.tcx.fn_arg_idents(item.owner_id),
[Some(Ident {
name: kw::SelfLower,
..
})],
)
}
// fill the set with current and super traits
fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
if set.insert(traitt) {
for supertrait in supertrait_def_ids(cx.tcx, traitt) {
fill_trait_set(supertrait, set, cx);
}
}
}
if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
&& trait_items.iter().any(|&i| is_named_self(cx, i, sym::len))
{
let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty_method_found = current_and_super_traits
.items()
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty))
.any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1);
if !is_empty_method_found {
span_lint(
cx,
LEN_WITHOUT_IS_EMPTY,
visited_trait.span,
format!(
"trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
ident.name
),
);
}
}
}
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
if let ty::Alias(_, alias_ty) = ty.kind()
&& let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir_get_if_local(alias_ty.def_id)
&& let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin
&& let [GenericBound::Trait(trait_ref)] = &opaque.bounds
&& let Some(segment) = trait_ref.trait_ref.path.segments.last()
&& let Some(generic_args) = segment.args
&& let [constraint] = generic_args.constraints
&& let Some(ty) = constraint.ty()
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment] = path.segments
{
return Some(segment);
}
None
}
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
if let Some(generic_args) = segment.args
&& let [GenericArg::Type(ty), ..] = &generic_args.args
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment, ..] = &path.segments
&& matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
{
true
} else {
false
}
}
#[derive(Debug, Clone, Copy)]
enum LenOutput {
Integral,
Option(DefId),
Result(DefId),
}
impl LenOutput {
fn new<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Self> {
if let Some(segment) = extract_future_output(cx, sig.output()) {
let res = segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
return Some(Self::Integral);
}
if let Res::Def(_, def_id) = res
&& let Some(res) = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::Option) => Some(Self::Option(def_id)),
Some(sym::Result) => Some(Self::Result(def_id)),
_ => None,
}
&& is_first_generic_integral(segment)
{
return Some(res);
}
return None;
}
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(Self::Integral),
ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => subs.type_at(0).is_integral().then(|| Self::Option(adt.did())),
Some(sym::Result) => subs.type_at(0).is_integral().then(|| Self::Result(adt.did())),
_ => None,
},
_ => None,
}
}
fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, is_empty_output: Ty<'tcx>) -> bool {
if let Some(segment) = extract_future_output(cx, is_empty_output) {
return match (self, segment.res) {
(_, Res::PrimTy(PrimTy::Bool)) => true,
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
(Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
_ => false,
};
}
match (self, is_empty_output.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
(Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
_ => false,
}
}
}
/// The expected signature of `is_empty`, based on that of `len`
fn expected_is_empty_sig(len_output: LenOutput, len_self_kind: ImplicitSelfKind) -> String {
let self_ref = match len_self_kind {
ImplicitSelfKind::RefImm => "&",
ImplicitSelfKind::RefMut => "&(mut) ",
_ => "",
};
match len_output {
LenOutput::Integral => format!("expected signature: `({self_ref}self) -> bool`"),
LenOutput::Option(_) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
},
LenOutput::Result(..) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
},
}
}
/// Checks if the given signature matches the expectations for `is_empty`
fn check_is_empty_sig<'tcx>(
cx: &LateContext<'tcx>,
is_empty_sig: FnSig<'tcx>,
len_self_kind: ImplicitSelfKind,
len_output: LenOutput,
) -> bool {
if let [is_empty_self_arg, is_empty_output] = &**is_empty_sig.inputs_and_output
&& len_output.matches_is_empty_output(cx, *is_empty_output)
{
match (is_empty_self_arg.kind(), len_self_kind) {
// if `len` takes `&self`, `is_empty` should do so as well
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::RefImm)
// if `len` takes `&mut self`, `is_empty` may take that _or_ `&self` (#16190)
| (ty::Ref(_, _, Mutability::Mut | Mutability::Not), ImplicitSelfKind::RefMut) => true,
// if len takes `self`, `is_empty` should do so as well
// XXX: we might want to relax this to allow `&self` and `&mut self`
(_, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut) if !is_empty_self_arg.is_ref() => true,
_ => false,
}
} else {
false
}
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
#[expect(clippy::too_many_arguments)]
fn check_for_is_empty(
cx: &LateContext<'_>,
len_span: Span,
len_self_kind: ImplicitSelfKind,
len_output: LenOutput,
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
len_method_hir_id: HirId,
ty_decl_hir_id: HirId,
) {
// Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
// find the correct inherent impls.
let impl_ty = if let Some(adt) = cx.tcx.type_of(impl_ty).skip_binder().ty_adt_def() {
adt.did()
} else {
return;
};
let is_empty = cx
.tcx
.inherent_impls(impl_ty)
.iter()
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty))
.find(|item| item.is_fn());
let (msg, is_empty_span, is_empty_expected_sig) = match is_empty {
None => (
format!("{item_kind} `{item_name}` has a public `len` method, but no `is_empty` method"),
None,
None,
),
Some(is_empty) if !cx.effective_visibilities.is_exported(is_empty.def_id.expect_local()) => (
format!("{item_kind} `{item_name}` has a public `len` method, but a private `is_empty` method"),
Some(cx.tcx.def_span(is_empty.def_id)),
None,
),
Some(is_empty)
if !(is_empty.is_method()
&& check_is_empty_sig(
cx,
cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
len_self_kind,
len_output,
)) =>
{
(
format!(
"{item_kind} `{item_name}` has a public `len` method, but the `is_empty` method has an unexpected signature",
),
Some(cx.tcx.def_span(is_empty.def_id)),
Some(expected_is_empty_sig(len_output, len_self_kind)),
)
},
Some(_) => return,
};
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, len_span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(expected_sig) = is_empty_expected_sig {
db.note(expected_sig);
}
});
}
}

View file

@ -1,27 +1,20 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::ty::implements_trait;
use clippy_utils::{fulfill_or_allowed, get_parent_as_impl, parent_item_name, peel_ref_operators, sym};
use clippy_utils::{parent_item_name, peel_ref_operators, sym};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind,
Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, RustcVersion,
StabilityLevel, StableSince, TraitItemId, TyKind,
};
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, PatExprKind, PatKind, RustcVersion, StabilityLevel, StableSince};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FnSig, Ty};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::kw;
use rustc_span::{Ident, Span, Symbol};
use rustc_trait_selection::traits::supertrait_def_ids;
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -58,32 +51,6 @@ declare_clippy_lint! {
"checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for items that implement `.len()` but not
/// `.is_empty()`.
///
/// ### Why is this bad?
/// It is good custom to have both methods, because for
/// some data structures, asking about the length will be a costly operation,
/// whereas `.is_empty()` can usually answer in constant time. Also it used to
/// lead to false positives on the [`len_zero`](#len_zero) lint currently that
/// lint will ignore such entities.
///
/// ### Example
/// ```ignore
/// impl X {
/// pub fn len(&self) -> usize {
/// ..
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub LEN_WITHOUT_IS_EMPTY,
style,
"traits or impls with a public `len` method but no corresponding `is_empty` method"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for comparing to an empty slice such as `""` or `[]`,
@ -126,7 +93,7 @@ pub struct LenZero {
msrv: Msrv,
}
impl_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
impl_lint_pass!(LenZero => [LEN_ZERO, COMPARISON_TO_EMPTY]);
impl LenZero {
pub fn new(conf: &'static Conf) -> Self {
@ -135,54 +102,6 @@ impl LenZero {
}
impl<'tcx> LateLintPass<'tcx> for LenZero {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Trait(_, _, _, ident, _, _, trait_items) = item.kind
&& !item.span.from_expansion()
{
check_trait_items(cx, item, ident, trait_items);
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
&& let Some(output) =
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
let (name, kind) = match cx.tcx.hir_node(ty_hir_id) {
Node::ForeignItem(x) => (x.ident.name, "extern type"),
Node::Item(x) => match x.kind {
ItemKind::Struct(ident, ..) => (ident.name, "struct"),
ItemKind::Enum(ident, ..) => (ident.name, "enum"),
ItemKind::Union(ident, ..) => (ident.name, "union"),
_ => (x.kind.ident().unwrap().name, "type"),
},
_ => return,
};
check_for_is_empty(
cx,
sig.span,
sig.decl.implicit_self,
output,
ty_id,
name,
kind,
item.hir_id(),
ty_hir_id,
);
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Let(lt) = expr.kind
&& match lt.pat.kind {
@ -356,256 +275,6 @@ fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span {
}
}
fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) {
fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool {
cx.tcx.item_name(item.owner_id) == name
&& matches!(
cx.tcx.fn_arg_idents(item.owner_id),
[Some(Ident {
name: kw::SelfLower,
..
})],
)
}
// fill the set with current and super traits
fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
if set.insert(traitt) {
for supertrait in supertrait_def_ids(cx.tcx, traitt) {
fill_trait_set(supertrait, set, cx);
}
}
}
if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
&& trait_items.iter().any(|&i| is_named_self(cx, i, sym::len))
{
let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty_method_found = current_and_super_traits
.items()
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty))
.any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1);
if !is_empty_method_found {
span_lint(
cx,
LEN_WITHOUT_IS_EMPTY,
visited_trait.span,
format!(
"trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
ident.name
),
);
}
}
}
#[derive(Debug, Clone, Copy)]
enum LenOutput {
Integral,
Option(DefId),
Result(DefId),
}
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
if let ty::Alias(_, alias_ty) = ty.kind()
&& let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir_get_if_local(alias_ty.def_id)
&& let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin
&& let [GenericBound::Trait(trait_ref)] = &opaque.bounds
&& let Some(segment) = trait_ref.trait_ref.path.segments.last()
&& let Some(generic_args) = segment.args
&& let [constraint] = generic_args.constraints
&& let Some(ty) = constraint.ty()
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment] = path.segments
{
return Some(segment);
}
None
}
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
if let Some(generic_args) = segment.args
&& let [GenericArg::Type(ty), ..] = &generic_args.args
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment, ..] = &path.segments
&& matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
{
true
} else {
false
}
}
fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
if let Some(segment) = extract_future_output(cx, sig.output()) {
let res = segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
return Some(LenOutput::Integral);
}
if let Res::Def(_, def_id) = res
&& let Some(res) = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::Option) => Some(LenOutput::Option(def_id)),
Some(sym::Result) => Some(LenOutput::Result(def_id)),
_ => None,
}
&& is_first_generic_integral(segment)
{
return Some(res);
}
return None;
}
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())),
Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())),
_ => None,
},
_ => None,
}
}
impl LenOutput {
fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if let Some(segment) = extract_future_output(cx, ty) {
return match (self, segment.res) {
(_, Res::PrimTy(PrimTy::Bool)) => true,
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
(Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
_ => false,
};
}
match (self, ty.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
(Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
_ => false,
}
}
fn expected_sig(self, self_kind: ImplicitSelfKind) -> String {
let self_ref = match self_kind {
ImplicitSelfKind::RefImm => "&",
ImplicitSelfKind::RefMut => "&mut ",
_ => "",
};
match self {
Self::Integral => format!("expected signature: `({self_ref}self) -> bool`"),
Self::Option(_) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
},
Self::Result(..) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
},
}
}
}
/// Checks if the given signature matches the expectations for `is_empty`
fn check_is_empty_sig<'tcx>(
cx: &LateContext<'tcx>,
sig: FnSig<'tcx>,
self_kind: ImplicitSelfKind,
len_output: LenOutput,
) -> bool {
match &**sig.inputs_and_output {
[arg, res] if len_output.matches_is_empty_output(cx, *res) => {
matches!(
(arg.kind(), self_kind),
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::RefImm)
| (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::RefMut)
) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
},
_ => false,
}
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
#[expect(clippy::too_many_arguments)]
fn check_for_is_empty(
cx: &LateContext<'_>,
span: Span,
self_kind: ImplicitSelfKind,
output: LenOutput,
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
len_method_hir_id: HirId,
ty_decl_hir_id: HirId,
) {
// Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
// find the correct inherent impls.
let impl_ty = if let Some(adt) = cx.tcx.type_of(impl_ty).skip_binder().ty_adt_def() {
adt.did()
} else {
return;
};
let is_empty = cx
.tcx
.inherent_impls(impl_ty)
.iter()
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty))
.find(|item| item.is_fn());
let (msg, is_empty_span, self_kind) = match is_empty {
None => (
format!(
"{item_kind} `{}` has a public `len` method, but no `is_empty` method",
item_name.as_str(),
),
None,
None,
),
Some(is_empty) if !cx.effective_visibilities.is_exported(is_empty.def_id.expect_local()) => (
format!(
"{item_kind} `{}` has a public `len` method, but a private `is_empty` method",
item_name.as_str(),
),
Some(cx.tcx.def_span(is_empty.def_id)),
None,
),
Some(is_empty)
if !(is_empty.is_method()
&& check_is_empty_sig(
cx,
cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
self_kind,
output,
)) =>
{
(
format!(
"{item_kind} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
item_name.as_str(),
),
Some(cx.tcx.def_span(is_empty.def_id)),
Some(self_kind),
)
},
Some(_) => return,
};
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(self_kind) = self_kind {
db.note(output.expected_sig(self_kind));
}
});
}
}
fn is_empty_string(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(lit, _) = lit.node

View file

@ -182,6 +182,7 @@ mod large_include_file;
mod large_stack_arrays;
mod large_stack_frames;
mod legacy_numeric_constants;
mod len_without_is_empty;
mod len_zero;
mod let_if_seq;
mod let_underscore;
@ -201,6 +202,7 @@ mod manual_clamp;
mod manual_float_methods;
mod manual_hash_one;
mod manual_ignore_case_cmp;
mod manual_ilog2;
mod manual_is_ascii_check;
mod manual_is_power_of_two;
mod manual_let_else;
@ -538,6 +540,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)),
Box::new(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default()),
Box::new(move |_| Box::new(len_zero::LenZero::new(conf))),
Box::new(|_| Box::new(len_without_is_empty::LenWithoutIsEmpty)),
Box::new(move |_| Box::new(attrs::Attributes::new(conf))),
Box::new(|_| Box::new(blocks_in_conditions::BlocksInConditions)),
Box::new(|_| Box::new(unicode::Unicode)),
@ -848,6 +851,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);

View file

@ -43,26 +43,26 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
};
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// passed by reference. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
let iterator_by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
{
".by_ref()"
make_iterator_snippet(cx, iter_expr, &iterator)
} else {
""
iterator.into_owned()
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("{loop_label}for {loop_var} in {iterator}{by_ref}"),
format!("{loop_label}for {loop_var} in {iterator_by_ref}"),
applicability,
);
}
@ -355,3 +355,22 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
.is_break()
}
}
/// Constructs the transformed iterator expression for the suggestion.
/// Returns `iterator.by_ref()` unless the last deref adjustment targets an unsized type,
/// in which case it applies all derefs (e.g., `&mut **iterator` or `&mut ***iterator`).
fn make_iterator_snippet<'tcx>(cx: &LateContext<'tcx>, iter_expr: &Expr<'tcx>, iterator: &str) -> String {
if let Some((n, adjust)) = cx
.typeck_results()
.expr_adjustments(iter_expr)
.iter()
.take_while(|x| matches!(x.kind, Adjust::Deref(_)))
.enumerate()
.last()
&& !adjust.target.is_sized(cx.tcx, cx.typing_env())
{
format!("&mut {:*<n$}{iterator}", '*', n = n + 1)
} else {
format!("{iterator}.by_ref()")
}
}

View file

@ -0,0 +1,115 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_from_proc_macro, sym};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions like `N - x.leading_zeros()` (where `N` is one less than bit width
/// of `x`) or `x.ilog(2)`, which are manual reimplementations of `x.ilog2()`
///
/// ### Why is this bad?
/// Manual reimplementations of `ilog2` increase code complexity for little benefit.
///
/// ### Example
/// ```no_run
/// let x: u32 = 5;
/// let log = 31 - x.leading_zeros();
/// let log = x.ilog(2);
/// ```
/// Use instead:
/// ```no_run
/// let x: u32 = 5;
/// let log = x.ilog2();
/// let log = x.ilog2();
/// ```
#[clippy::version = "1.93.0"]
pub MANUAL_ILOG2,
pedantic,
"manually reimplementing `ilog2`"
}
pub struct ManualIlog2 {
msrv: Msrv,
}
impl ManualIlog2 {
pub fn new(conf: &Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
impl LateLintPass<'_> for ManualIlog2 {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if expr.span.in_external_macro(cx.sess().source_map()) {
return;
}
match expr.kind {
// `BIT_WIDTH - 1 - n.leading_zeros()`
ExprKind::Binary(op, left, right)
if left.span.eq_ctxt(right.span)
&& op.node == BinOpKind::Sub
&& let ExprKind::Lit(lit) = left.kind
&& let LitKind::Int(Pu128(val), _) = lit.node
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
&& leading_zeros.ident.name == sym::leading_zeros
&& let ty = cx.typeck_results().expr_ty(recv)
&& let Some(bit_width) = match ty.kind() {
ty::Uint(uint_ty) => uint_ty.bit_width(),
ty::Int(_) => {
// On non-positive integers, `ilog2` would panic, which might be a sign that the author does
// in fact want to calculate something different, so stay on the safer side and don't
// suggest anything.
return;
},
_ => return,
}
&& val == u128::from(bit_width) - 1
&& self.msrv.meets(cx, msrvs::ILOG2)
&& !is_from_proc_macro(cx, expr) =>
{
emit(cx, recv, expr);
},
// `n.ilog(2)`
ExprKind::MethodCall(ilog, recv, [two], _)
if expr.span.eq_ctxt(two.span)
&& ilog.ident.name == sym::ilog
&& let ExprKind::Lit(lit) = two.kind
&& let LitKind::Int(Pu128(2), _) = lit.node
&& cx.typeck_results().expr_ty_adjusted(recv).is_integral()
/* no need to check MSRV here, as `ilog` and `ilog2` were introduced simultaneously */
&& !is_from_proc_macro(cx, expr) =>
{
emit(cx, recv, expr);
},
_ => {},
}
}
}
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
span_lint_and_sugg(
cx,
MANUAL_ILOG2,
full_expr.span,
"manually reimplementing `ilog2`",
"try",
format!("{recv}.ilog2()"),
app,
);
}

View file

@ -1,7 +1,8 @@
//! Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::has_let_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment};
use rustc_ast::LitKind;
@ -43,18 +44,23 @@ pub(crate) fn check_if_let<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
"if let .. else expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
"`if let .. else` expression looks like `matches!` macro",
|diag| {
diag.span_suggestion_verbose(
expr.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
}
}
@ -87,7 +93,10 @@ pub(super) fn check_match<'tcx>(
// ```rs
// matches!(e, Either::Left $(if $guard)|+)
// ```
middle_arms.is_empty()
//
// But if the guard _is_ present, it may not be an `if-let` guard, as `matches!` doesn't
// support these (currently?)
(middle_arms.is_empty() && first_arm.guard.is_none_or(|g| !has_let_expr(g)))
// - (added in #6216) There are middle arms
//
@ -169,18 +178,23 @@ pub(super) fn check_match<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
e.span,
"match expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
|diag| {
diag.span_suggestion_verbose(
e.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
true
} else {

View file

@ -4,18 +4,20 @@ use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::{self as hir, Expr};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::LayoutOf;
use rustc_span::Symbol;
pub fn check(
pub fn check_unwrap_or(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
arith_lhs: &hir::Expr<'_>,
arith_rhs: &hir::Expr<'_>,
unwrap_arg: &hir::Expr<'_>,
arith: &str,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
unwrap_arg: &Expr<'_>,
arith: Symbol,
) {
let ty = cx.typeck_results().expr_ty(arith_lhs);
if !ty.is_integral() {
@ -26,35 +28,75 @@ pub fn check(
return;
};
if ty.is_signed() {
use self::MinMax::{Max, Min};
use self::Sign::{Neg, Pos};
let Some(checked_arith) = CheckedArith::new(arith) else {
return;
};
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
}
pub(super) fn check_sub_unwrap_or_default(
cx: &LateContext<'_>,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
) {
let ty = cx.typeck_results().expr_ty(arith_lhs);
if !ty.is_integral() {
return;
}
let mm = if ty.is_signed() {
return; // iN::default() is 0, which is neither MIN nor MAX
} else {
MinMax::Min // uN::default() is 0, which is also the MIN
};
let checked_arith = CheckedArith::Sub;
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
}
fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
ty: Ty<'_>,
mm: MinMax,
checked_arith: CheckedArith,
) {
use self::MinMax::{Max, Min};
use self::Sign::{Neg, Pos};
use CheckedArith::{Add, Mul, Sub};
if ty.is_signed() {
let Some(sign) = lit_sign(arith_rhs) else {
return;
};
match (arith, sign, mm) {
("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
match (checked_arith, sign, mm) {
(Add, Pos, Max) | (Add, Neg, Min) | (Sub, Neg, Max) | (Sub, Pos, Min) => (),
// "mul" is omitted because lhs can be negative.
_ => return,
}
} else {
match (mm, arith) {
(MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
match (mm, checked_arith) {
(Max, Add | Mul) | (Min, Sub) => (),
_ => return,
}
}
let mut applicability = Applicability::MachineApplicable;
let saturating_arith = checked_arith.as_saturating();
span_lint_and_sugg(
cx,
super::MANUAL_SATURATING_ARITHMETIC,
expr.span,
"manual saturating arithmetic",
format!("consider using `saturating_{arith}`"),
format!("consider using `{saturating_arith}`"),
format!(
"{}.saturating_{arith}({})",
"{}.{saturating_arith}({})",
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
),
@ -62,13 +104,40 @@ pub fn check(
);
}
#[derive(Clone, Copy)]
enum CheckedArith {
Add,
Sub,
Mul,
}
impl CheckedArith {
fn new(sym: Symbol) -> Option<Self> {
let res = match sym {
sym::checked_add => Self::Add,
sym::checked_sub => Self::Sub,
sym::checked_mul => Self::Mul,
_ => return None,
};
Some(res)
}
fn as_saturating(self) -> &'static str {
match self {
Self::Add => "saturating_add",
Self::Sub => "saturating_sub",
Self::Mul => "saturating_mul",
}
}
}
#[derive(PartialEq, Eq)]
enum MinMax {
Min,
Max,
}
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
fn is_min_or_max(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<MinMax> {
// `T::max_value()` `T::min_value()` inherent methods
if let hir::ExprKind::Call(func, []) = &expr.kind
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind
@ -106,7 +175,7 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
(0, if bits == 128 { !0 } else { (1 << bits) - 1 })
};
let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
let check_lit = |expr: &Expr<'_>, check_min: bool| {
if let hir::ExprKind::Lit(lit) = &expr.kind
&& let ast::LitKind::Int(value, _) = lit.node
{
@ -141,7 +210,7 @@ enum Sign {
Neg,
}
fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
fn lit_sign(expr: &Expr<'_>) -> Option<Sign> {
if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
if let hir::ExprKind::Lit(..) = &inner.kind {
return Some(Sign::Neg);

View file

@ -94,6 +94,7 @@ mod or_fun_call;
mod or_then_unwrap;
mod path_buf_push_overwrite;
mod path_ends_with_ext;
mod ptr_offset_by_literal;
mod ptr_offset_with_cast;
mod range_zip_with_len;
mod read_line_without_trim;
@ -1728,6 +1729,40 @@ declare_clippy_lint! {
"Check for offset calculations on raw pointers to zero-sized types"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of the `offset` pointer method with an integer
/// literal.
///
/// ### Why is this bad?
/// The `add` and `sub` methods more accurately express the intent.
///
/// ### Example
/// ```no_run
/// let vec = vec![b'a', b'b', b'c'];
/// let ptr = vec.as_ptr();
///
/// unsafe {
/// ptr.offset(-8);
/// }
/// ```
///
/// Could be written:
///
/// ```no_run
/// let vec = vec![b'a', b'b', b'c'];
/// let ptr = vec.as_ptr();
///
/// unsafe {
/// ptr.sub(8);
/// }
/// ```
#[clippy::version = "1.92.0"]
pub PTR_OFFSET_BY_LITERAL,
pedantic,
"unneeded pointer offset"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of the `offset` pointer method with a `usize` casted to an
@ -4635,7 +4670,7 @@ declare_clippy_lint! {
/// let x = vec![String::new()];
/// let _ = x.iter().map(|x| x.len());
/// ```
#[clippy::version = "1.90.0"]
#[clippy::version = "1.92.0"]
pub REDUNDANT_ITER_CLONED,
perf,
"detects redundant calls to `Iterator::cloned`"
@ -4659,7 +4694,7 @@ declare_clippy_lint! {
/// let x: Option<u32> = Some(4);
/// let y = x.unwrap_or_else(|| 2 * k);
/// ```
#[clippy::version = "1.88.0"]
#[clippy::version = "1.92.0"]
pub UNNECESSARY_OPTION_MAP_OR_ELSE,
suspicious,
"making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
@ -4803,6 +4838,7 @@ impl_lint_pass!(Methods => [
UNINIT_ASSUMED_INIT,
MANUAL_SATURATING_ARITHMETIC,
ZST_OFFSET,
PTR_OFFSET_BY_LITERAL,
PTR_OFFSET_WITH_CAST,
FILETYPE_IS_FILE,
OPTION_AS_REF_DEREF,
@ -5426,6 +5462,7 @@ impl Methods {
zst_offset::check(cx, expr, recv);
ptr_offset_with_cast::check(cx, name, expr, recv, arg, self.msrv);
ptr_offset_by_literal::check(cx, expr, self.msrv);
},
(sym::ok_or_else, [arg]) => {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or");
@ -5567,14 +5604,7 @@ impl Methods {
(sym::unwrap_or, [u_arg]) => {
match method_call(recv) {
Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(
cx,
expr,
lhs,
rhs,
u_arg,
&arith.as_str()[const { "checked_".len() }..],
);
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
},
Some((sym::map, m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
@ -5595,6 +5625,9 @@ impl Methods {
},
(sym::unwrap_or_default, []) => {
match method_call(recv) {
Some((sym::checked_sub, lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check_sub_unwrap_or_default(cx, expr, lhs, rhs);
},
Some((sym::map, m_recv, [arg], span, _)) => {
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
},

View file

@ -0,0 +1,138 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::sym;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
use rustc_lint::LateContext;
use std::cmp::Ordering;
use std::fmt;
use super::PTR_OFFSET_BY_LITERAL;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Msrv) {
// `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions
// became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable.
if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) {
return;
}
let ExprKind::MethodCall(method_name, recv, [arg_expr], _) = expr.kind else {
return;
};
let method = match method_name.ident.name {
sym::offset => Method::Offset,
sym::wrapping_offset => Method::WrappingOffset,
_ => return,
};
if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() {
return;
}
// Check if the argument to the method call is a (negated) literal.
let Some((literal, literal_text)) = expr_as_literal(cx, arg_expr) else {
return;
};
match method.suggestion(literal) {
None => {
let msg = format!("use of `{method}` with zero");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.span_suggestion(
expr.span.with_lo(recv.span.hi()),
format!("remove the call to `{method}`"),
String::new(),
Applicability::MachineApplicable,
);
});
},
Some(method_suggestion) => {
let msg = format!("use of `{method}` with a literal");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.multipart_suggestion(
format!("use `{method_suggestion}` instead"),
vec![
(method_name.ident.span, method_suggestion.to_string()),
(arg_expr.span, literal_text),
],
Applicability::MachineApplicable,
);
});
},
}
}
fn get_literal_bits<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<u128> {
match expr.kind {
ExprKind::Lit(Lit {
node: LitKind::Int(packed_u128, _),
..
}) => Some(packed_u128.get()),
_ => None,
}
}
// If the given expression is a (negated) literal, return its value.
fn expr_as_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(i128, String)> {
if let Some(literal_bits) = get_literal_bits(expr) {
// The value must fit in a isize, so we can't have overflow here.
return Some((literal_bits.cast_signed(), format_isize_literal(cx, expr)?));
}
if let ExprKind::Unary(UnOp::Neg, inner) = expr.kind
&& let Some(literal_bits) = get_literal_bits(inner)
{
return Some((-(literal_bits.cast_signed()), format_isize_literal(cx, inner)?));
}
None
}
fn format_isize_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<String> {
let text = expr.span.get_source_text(cx)?;
let text = peel_parens_str(&text);
Some(text.trim_end_matches("isize").trim_end_matches('_').to_string())
}
fn peel_parens_str(snippet: &str) -> &str {
let mut s = snippet.trim();
while let Some(next) = s.strip_prefix("(").and_then(|suf| suf.strip_suffix(")")) {
s = next.trim();
}
s
}
#[derive(Copy, Clone)]
enum Method {
Offset,
WrappingOffset,
}
impl Method {
fn suggestion(self, literal: i128) -> Option<&'static str> {
match Ord::cmp(&literal, &0) {
Ordering::Greater => match self {
Method::Offset => Some("add"),
Method::WrappingOffset => Some("wrapping_add"),
},
// `ptr.offset(0)` is equivalent to `ptr`, so no adjustment is needed
Ordering::Equal => None,
Ordering::Less => match self {
Method::Offset => Some("sub"),
Method::WrappingOffset => Some("wrapping_sub"),
},
}
}
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Offset => write!(f, "offset"),
Self::WrappingOffset => write!(f, "wrapping_offset"),
}
}
}

View file

@ -5,7 +5,7 @@ use clippy_utils::comparisons::{Rel, normalize_comparison};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::{If, Range};
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call};
use clippy_utils::source::snippet;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{eq_expr_value, hash_expr};
use rustc_ast::{BinOpKind, LitKind, RangeLimits};
@ -67,16 +67,13 @@ declare_clippy_lint! {
}
declare_lint_pass!(MissingAssertsForIndexing => [MISSING_ASSERTS_FOR_INDEXING]);
fn report_lint<F>(cx: &LateContext<'_>, full_span: Span, msg: &'static str, indexes: &[Span], f: F)
fn report_lint<F>(cx: &LateContext<'_>, index_spans: Vec<Span>, msg: &'static str, f: F)
where
F: FnOnce(&mut Diag<'_, ()>),
{
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, full_span, msg, |diag| {
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, index_spans, msg, |diag| {
f(diag);
for span in indexes {
diag.span_note(*span, "slice indexed here");
}
diag.note("asserting the length before indexing will elide bounds checks");
diag.note_once("asserting the length before indexing will elide bounds checks");
});
}
@ -213,15 +210,6 @@ impl<'hir> IndexEntry<'hir> {
| IndexEntry::IndexWithoutAssert { slice, .. } => slice,
}
}
pub fn index_spans(&self) -> Option<&[Span]> {
match self {
IndexEntry::StrayAssert { .. } => None,
IndexEntry::AssertWithIndex { indexes, .. } | IndexEntry::IndexWithoutAssert { indexes, .. } => {
Some(indexes)
},
}
}
}
/// Extracts the upper index of a slice indexing expression.
@ -354,63 +342,47 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
/// Inspects indexes and reports lints.
///
/// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.values() {
fn report_indexes(cx: &LateContext<'_>, map: UnindexMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.into_values() {
for entry in bucket {
let Some(full_span) = entry
.index_spans()
.and_then(|spans| spans.first().zip(spans.last()))
.map(|(low, &high)| low.to(high))
else {
continue;
};
match *entry {
match entry {
IndexEntry::AssertWithIndex {
highest_index,
is_first_highest,
asserted_len,
ref indexes,
indexes,
comparison,
assert_span,
slice,
macro_call,
} if indexes.len() > 1 && !is_first_highest => {
let mut app = Applicability::MachineApplicable;
let slice_str = snippet_with_applicability(cx, slice.span, "_", &mut app);
// if we have found an `assert!`, let's also check that it's actually right
// and if it covers the highest index and if not, suggest the correct length
let sugg = match comparison {
// `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
// The user probably meant `v.len() > 5`
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => Some(
format!("assert!({}.len() > {highest_index})", snippet(cx, slice.span, "..")),
),
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `5 < v.len()` == `v.len() > 5`
LengthComparison::IntLessThanLength if asserted_len < highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
LengthComparison::IntLessThanLength if asserted_len < highest_index => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `5 <= v.len() == `v.len() >= 5`
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `highest_index` here is rather a length, so we need to add 1 to it
LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call {
sym::assert_eq_macro => Some(format!(
"assert_eq!({}.len(), {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
sym::debug_assert_eq_macro => Some(format!(
"debug_assert_eq!({}.len(), {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
_ => Some(format!(
"assert!({}.len() == {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
sym::assert_eq_macro => {
Some(format!("assert_eq!({slice_str}.len(), {})", highest_index + 1))
},
sym::debug_assert_eq_macro => {
Some(format!("debug_assert_eq!({slice_str}.len(), {})", highest_index + 1))
},
_ => Some(format!("assert!({slice_str}.len() == {})", highest_index + 1)),
},
_ => None,
};
@ -418,22 +390,21 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>
if let Some(sugg) = sugg {
report_lint(
cx,
full_span,
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
indexes,
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
|diag| {
diag.span_suggestion(
diag.span_suggestion_verbose(
assert_span,
"provide the highest index that is indexed with",
sugg,
Applicability::MachineApplicable,
app,
);
},
);
}
},
IndexEntry::IndexWithoutAssert {
ref indexes,
indexes,
highest_index,
is_first_highest,
slice,
@ -442,9 +413,8 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>
// adding an `assert!` that covers the highest index
report_lint(
cx,
full_span,
"indexing into a slice multiple times without an `assert`",
indexes,
"indexing into a slice multiple times without an `assert`",
|diag| {
diag.help(format!(
"consider asserting the length before indexing: `assert!({}.len() > {highest_index});`",
@ -469,6 +439,6 @@ impl LateLintPass<'_> for MissingAssertsForIndexing {
ControlFlow::<!, ()>::Continue(())
});
report_indexes(cx, &map);
report_indexes(cx, map);
}
}

View file

@ -115,35 +115,41 @@ impl EarlyLintPass for MacroBraces {
}
fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBraces) -> Option<MacroInfo> {
let unnested_or_local = || {
!span.ctxt().outer_expn_data().call_site.from_expansion()
let unnested_or_local = |span: Span| {
!span.from_expansion()
|| span
.macro_backtrace()
.last()
.is_some_and(|e| e.macro_def_id.is_some_and(DefId::is_local))
};
let callsite_span = span.ctxt().outer_expn_data().call_site;
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind
let mut ctxt = span.ctxt();
while !ctxt.is_root() {
let expn_data = ctxt.outer_expn_data();
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = expn_data.kind
&& let name = mac_name.as_str()
&& let Some(&braces) = mac_braces.macro_braces.get(name)
&& let Some(snip) = callsite_span.get_source_text(cx)
&& let Some(snip) = expn_data.call_site.get_source_text(cx)
// we must check only invocation sites
// https://github.com/rust-lang/rust-clippy/issues/7422
&& let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!'))
&& let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next()
&& old_open_brace != braces.0
&& unnested_or_local()
&& !mac_braces.done.contains(&callsite_span)
{
Some(MacroInfo {
callsite_span,
callsite_snippet: snip,
old_open_brace,
braces,
})
} else {
None
&& unnested_or_local(expn_data.call_site)
&& !mac_braces.done.contains(&expn_data.call_site)
{
return Some(MacroInfo {
callsite_span: expn_data.call_site,
callsite_snippet: snip,
old_open_brace,
braces,
});
}
ctxt = expn_data.call_site.ctxt();
}
None
}
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) {

View file

@ -0,0 +1,76 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::numeric_literal;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::SpanRangeExt;
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use super::DECIMAL_BITWISE_OPERANDS;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, left: &'tcx Expr<'_>, right: &'tcx Expr<'_>) {
if !matches!(op, BinOpKind::BitAnd | BinOpKind::BitOr | BinOpKind::BitXor) {
return;
}
for expr in [left, right] {
check_expr(cx, expr);
}
}
fn check_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
match &expr.kind {
ExprKind::Block(block, _) => {
if let Some(block_expr) = block.expr {
check_expr(cx, block_expr);
}
},
ExprKind::Cast(cast_expr, _) => {
check_expr(cx, cast_expr);
},
ExprKind::Unary(_, unary_expr) => {
check_expr(cx, unary_expr);
},
ExprKind::AddrOf(_, _, addr_of_expr) => {
check_expr(cx, addr_of_expr);
},
ExprKind::Lit(lit) => {
if let LitKind::Int(Pu128(val), _) = lit.node
&& !is_single_digit(val)
&& !is_power_of_twoish(val)
&& let Some(src) = lit.span.get_source_text(cx)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
&& num_lit.is_decimal()
{
emit_lint(cx, lit.span, num_lit.suffix, val);
}
},
_ => (),
}
}
fn is_power_of_twoish(val: u128) -> bool {
val.is_power_of_two() || val.wrapping_add(1).is_power_of_two()
}
fn is_single_digit(val: u128) -> bool {
val <= 9
}
fn emit_lint(cx: &LateContext<'_>, span: Span, suffix: Option<&str>, val: u128) {
span_lint_and_help(
cx,
DECIMAL_BITWISE_OPERANDS,
span,
"using decimal literal for bitwise operation",
None,
format!(
"use binary ({}), hex ({}), or octal ({}) notation for better readability",
numeric_literal::format(&format!("{val:#b}"), suffix, false),
numeric_literal::format(&format!("{val:#x}"), suffix, false),
numeric_literal::format(&format!("{val:#o}"), suffix, false),
),
);
}

View file

@ -3,6 +3,7 @@ mod assign_op_pattern;
mod bit_mask;
mod cmp_owned;
mod const_comparisons;
mod decimal_bitwise_operands;
mod double_comparison;
mod duration_subsec;
mod eq_op;
@ -935,6 +936,28 @@ declare_clippy_lint! {
"use of disallowed default division and remainder operations"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for decimal literals used as bit masks in bitwise operations.
///
/// ### Why is this bad?
/// Using decimal literals for bit masks can make the code less readable and obscure the intended bit pattern.
/// Binary, hexadecimal, or octal literals make the bit pattern more explicit and easier to understand at a glance.
///
/// ### Example
/// ```rust,no_run
/// let a = 14 & 6; // Bit pattern is not immediately clear
/// ```
/// Use instead:
/// ```rust,no_run
/// let a = 0b1110 & 0b0110;
/// ```
#[clippy::version = "1.93.0"]
pub DECIMAL_BITWISE_OPERANDS,
pedantic,
"use binary, hex, or octal literals for bitwise operations"
}
pub struct Operators {
arithmetic_context: numeric_arithmetic::Context,
verbose_bit_mask_threshold: u64,
@ -984,6 +1007,7 @@ impl_lint_pass!(Operators => [
MANUAL_IS_MULTIPLE_OF,
MANUAL_DIV_CEIL,
INVALID_UPCAST_COMPARISONS,
DECIMAL_BITWISE_OPERANDS
]);
impl<'tcx> LateLintPass<'tcx> for Operators {
@ -1003,6 +1027,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv);
decimal_bitwise_operands::check(cx, op.node, lhs, rhs);
}
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
bit_mask::check(cx, e, op.node, lhs, rhs);
@ -1028,6 +1053,9 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
},
ExprKind::AssignOp(op, lhs, rhs) => {
let bin_op = op.node.into();
if !e.span.from_expansion() {
decimal_bitwise_operands::check(cx, bin_op, lhs, rhs);
}
self.arithmetic_context.check_binary(cx, e, bin_op, lhs, rhs);
misrefactored_assign_op::check(cx, e, bin_op, lhs, rhs);
modulo_arithmetic::check(cx, e, bin_op, lhs, rhs, false);

View file

@ -18,8 +18,11 @@ mod wrong_transmute;
use clippy_config::Conf;
use clippy_utils::is_in_const_context;
use clippy_utils::msrvs::Msrv;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::sym;
@ -490,6 +493,32 @@ impl Transmute {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
/// When transmuting, a struct containing a single field works like the field.
/// This function extracts the field type and the expression to get the field.
fn extract_struct_field<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
outer_type: Ty<'tcx>,
outer: &'tcx Expr<'tcx>,
) -> (Ty<'tcx>, Sugg<'tcx>) {
let mut applicability = Applicability::MachineApplicable;
let outer_sugg = Sugg::hir_with_context(cx, outer, e.span.ctxt(), "..", &mut applicability);
if let ty::Adt(struct_def, struct_args) = *outer_type.kind()
&& struct_def.is_struct()
&& let mut fields = struct_def.all_fields()
&& let Some(first) = fields.next()
&& fields.next().is_none()
&& first.vis.is_accessible_from(cx.tcx.parent_module(outer.hir_id), cx.tcx)
{
(
first.ty(cx.tcx, struct_args),
Sugg::NonParen(format!("{}.{}", outer_sugg.maybe_paren(), first.name).into()),
)
} else {
(outer_type, outer_sugg)
}
}
}
impl<'tcx> LateLintPass<'tcx> for Transmute {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
@ -516,14 +545,17 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
return;
}
// A struct having a single pointer can be treated like a pointer.
let (from_field_ty, from_field_expr) = Self::extract_struct_field(cx, e, from_ty, arg);
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmuting_null::check(cx, e, arg, to_ty)
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| transmute_ptr_to_ref::check(cx, e, from_field_ty, to_ty, from_field_expr.clone(), path, self.msrv)
| missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
| transmute_ptr_to_ptr::check(cx, e, from_field_ty, to_ty, from_field_expr, self.msrv)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg)
| (unsound_collection_transmute::check(cx, e, from_ty, to_ty)

View file

@ -14,11 +14,9 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
arg: sugg::Sugg<'_>,
msrv: Msrv,
) -> bool {
let mut applicability = Applicability::MachineApplicable;
let arg_sugg = sugg::Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut applicability);
match (from_ty.kind(), to_ty.kind()) {
(ty::RawPtr(from_pointee_ty, from_mutbl), ty::RawPtr(to_pointee_ty, to_mutbl)) => {
span_lint_and_then(
@ -34,7 +32,7 @@ pub(super) fn check<'tcx>(
diag.span_suggestion_verbose(
e.span,
"use `pointer::cast` instead",
format!("{}.cast::<{to_pointee_ty}>()", arg_sugg.maybe_paren()),
format!("{}.cast::<{to_pointee_ty}>()", arg.maybe_paren()),
Applicability::MaybeIncorrect,
);
} else if from_pointee_ty == to_pointee_ty
@ -49,14 +47,14 @@ pub(super) fn check<'tcx>(
diag.span_suggestion_verbose(
e.span,
format!("use `pointer::{method}` instead"),
format!("{}.{method}()", arg_sugg.maybe_paren()),
format!("{}.{method}()", arg.maybe_paren()),
Applicability::MaybeIncorrect,
);
} else {
diag.span_suggestion_verbose(
e.span,
"use an `as` cast instead",
arg_sugg.as_ty(to_ty),
arg.as_ty(to_ty),
Applicability::MaybeIncorrect,
);
}

View file

@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
arg: sugg::Sugg<'_>,
path: &'tcx Path<'_>,
msrv: Msrv,
) -> bool {
@ -27,7 +27,6 @@ pub(super) fn check<'tcx>(
e.span,
format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
let (deref, cast) = match mutbl {
Mutability::Mut => ("&mut *", "*mut"),
Mutability::Not => ("&*", "*const"),

View file

@ -1,9 +1,9 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeResPath;
use clippy_utils::visitors::for_each_local_use_after_expr;
use clippy_utils::visitors::local_used_once;
use clippy_utils::{get_enclosing_block, is_from_proc_macro};
use itertools::Itertools;
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind, Node, PatKind};
@ -11,7 +11,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use std::iter::once;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
@ -86,7 +85,7 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
ExprKind::Path(_) => Some(elements.iter().collect()),
_ => None,
})
&& all_bindings_are_for_conv(cx, &[ty], expr, elements, &locals, ToType::Array)
&& all_bindings_are_for_conv(cx, &[ty], elements, &locals, ToType::Array)
&& !is_from_proc_macro(cx, expr)
{
span_lint_and_help(
@ -123,7 +122,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
ExprKind::Path(_) => Some(elements.iter().collect()),
_ => None,
})
&& all_bindings_are_for_conv(cx, tys, expr, elements, &locals, ToType::Tuple)
&& all_bindings_are_for_conv(cx, tys, elements, &locals, ToType::Tuple)
&& !is_from_proc_macro(cx, expr)
{
span_lint_and_help(
@ -148,7 +147,6 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
fn all_bindings_are_for_conv<'tcx>(
cx: &LateContext<'tcx>,
final_tys: &[Ty<'tcx>],
expr: &Expr<'_>,
elements: &[Expr<'_>],
locals: &[&Expr<'_>],
kind: ToType,
@ -166,13 +164,30 @@ fn all_bindings_are_for_conv<'tcx>(
_ => None,
})
.all_equal()
// Fix #11124, very convenient utils function! ❤️
&& locals
.iter()
.all(|&l| for_each_local_use_after_expr(cx, l, expr.hir_id, |_| ControlFlow::Break::<()>(())).is_continue())
&& locals.iter().zip(local_parents.iter()).all(|(&l, &parent)| {
if let Node::LetStmt(_) = parent {
return true;
}
let Some(b) = get_enclosing_block(cx, l) else {
return true;
};
local_used_once(cx, b, l).is_some()
})
&& local_parents.first().is_some_and(|node| {
let Some(ty) = match node {
Node::Pat(pat) => Some(pat.hir_id),
Node::Pat(pat)
if let PatKind::Tuple(pats, _) | PatKind::Slice(pats, None, []) = &pat.kind
&& pats.iter().zip(locals.iter()).all(|(p, l)| {
if let PatKind::Binding(_, id, _, _) = p.kind {
id == *l
} else {
true
}
}) =>
{
Some(pat.hir_id)
},
Node::LetStmt(l) => Some(l.hir_id),
_ => None,
}
@ -186,7 +201,9 @@ fn all_bindings_are_for_conv<'tcx>(
tys.len() == elements.len() && tys.iter().chain(final_tys.iter().copied()).all_equal()
},
(ToType::Tuple, ty::Array(ty, len)) => {
let Some(len) = len.try_to_target_usize(cx.tcx) else { return false };
let Some(len) = len.try_to_target_usize(cx.tcx) else {
return false;
};
len as usize == elements.len() && final_tys.iter().chain(once(ty)).all_equal()
},
_ => false,

View file

@ -180,14 +180,19 @@ impl Local {
field_indices,
..
} => {
let field_projections = place
.projections
.iter()
.filter(|proj| matches!(proj.kind, ProjectionKind::Field(_, _)))
.collect::<Vec<_>>();
is_potentially_local_place(*local_id, place)
// If there were projections other than field projections, err on the side of caution and say that they
// _might_ be mutating something.
//
// The reason we use `<=` and not `==` is that a mutation of `struct` or `struct.field1` should count as
// mutation of the child fields such as `struct.field1.field2`
&& place.projections.len() <= field_indices.len()
&& iter::zip(&place.projections, field_indices.iter().copied().rev()).all(|(proj, field_idx)| {
&& field_projections.len() <= field_indices.len()
&& iter::zip(&field_projections, field_indices.iter().copied().rev()).all(|(proj, field_idx)| {
match proj.kind {
ProjectionKind::Field(f_idx, _) => f_idx == field_idx,
// If this is a projection we don't expect, it _might_ be mutating something

View file

@ -354,7 +354,10 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
return;
}
let sugg = snippet(cx, recv.span, "<expr>").into_owned();
let mut applicability = Applicability::MachineApplicable;
let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "<expr>", &mut applicability)
.0
.into_owned();
span_lint_and_sugg(
cx,
USELESS_CONVERSION,

View file

@ -4,10 +4,11 @@ use clippy_utils::source::{snippet, snippet_indent};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{ConstArgKind, ExprKind, Node};
use rustc_hir::{ConstArgKind, Expr, ExprKind, LetStmt, LocalSource, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::IsSuggestable;
use rustc_middle::ty::{IsSuggestable, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -44,7 +45,7 @@ declare_clippy_lint! {
declare_lint_pass!(ZeroRepeatSideEffects => [ZERO_REPEAT_SIDE_EFFECTS]);
impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &rustc_hir::Expr<'_>) {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(args) = VecArgs::hir(cx, expr)
&& let VecArgs::Repeat(inner_expr, len) = args
&& let ExprKind::Lit(l) = len.kind
@ -69,7 +70,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects {
}
}
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
fn inner_check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, inner_expr: &'_ Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if inner_expr.can_have_side_effects() {
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
@ -81,19 +82,22 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr:
let vec = if is_vec { "vec!" } else { "" };
let (span, sugg) = match parent_hir_node {
Node::LetStmt(l) => (
l.span,
format!(
"{inner_expr};\n{indent}let {var_name}: {return_type} = {vec}[];",
var_name = snippet(cx, l.pat.span.source_callsite(), "..")
),
),
Node::LetStmt(l)
if matches!(l.source, LocalSource::AssignDesugar)
&& let mut parent_iter = cx.tcx.hir_parent_iter(l.hir_id)
&& let Some((_, Node::Stmt(_))) = parent_iter.next()
&& let Some((_, Node::Block(_))) = parent_iter.next()
&& let Some((_, Node::Expr(x))) = parent_iter.next() =>
{
(
x.span,
assign_expr_suggestion(cx, x, l.pat.span, &inner_expr, return_type, vec),
)
},
Node::LetStmt(l) => (l.span, let_stmt_suggestion(cx, l, &inner_expr, return_type, vec)),
Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => (
x.span,
format!(
"{inner_expr};\n{indent}{var_name} = {vec}[] as {return_type}",
var_name = snippet(cx, l.span.source_callsite(), "..")
),
assign_expr_suggestion(cx, x, l.span, &inner_expr, return_type, vec),
),
// NOTE: don't use the stmt span to avoid touching the trailing semicolon
Node::Stmt(_) => (expr.span, format!("{inner_expr};\n{indent}{vec}[] as {return_type}")),
@ -131,3 +135,41 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr:
);
}
}
fn let_stmt_suggestion(
cx: &LateContext<'_>,
let_stmt: &LetStmt<'_>,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let indent = snippet_indent(cx, let_stmt.span).unwrap_or_default();
format!(
"{inner_expr};\n{}let {var_name}: {return_type} = {vec_str}[];",
indent,
var_name = snippet(cx, let_stmt.pat.span.source_callsite(), "..")
)
}
fn assign_expr_suggestion(
cx: &LateContext<'_>,
outer_expr: &Expr<'_>,
assign_expr_span: Span,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let mut parent_hir_node = cx.tcx.parent_hir_node(outer_expr.hir_id);
if let Node::Stmt(stmt) = parent_hir_node {
parent_hir_node = cx.tcx.parent_hir_node(stmt.hir_id);
}
let needs_curly = !matches!(parent_hir_node, Node::Block(_));
let indent = snippet_indent(cx, outer_expr.span).unwrap_or_default();
let var_name = snippet(cx, assign_expr_span.source_callsite(), "..");
if needs_curly {
format!("{{\n {indent}{inner_expr};\n {indent}{var_name} = {vec_str}[] as {return_type}\n{indent}}}",)
} else {
format!("{inner_expr};\n{indent}{var_name} = {vec_str}[] as {return_type}")
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
description = "Helpful tools for writing lints, provided as they are used in Clippy"
repository = "https://github.com/rust-lang/rust-clippy"
@ -16,6 +16,10 @@ itertools = "0.12"
rustc_apfloat = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(bootstrap)']
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]
rustc_private = true

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-11-28
nightly-2025-12-11
```
<!-- end autogenerated nightly -->

View file

@ -40,6 +40,7 @@ msrv_aliases! {
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,67,0 { ILOG2 }
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }

View file

@ -180,6 +180,7 @@ generate! {
has_significant_drop,
hidden_glob_reexports,
hygiene,
ilog,
insert,
insert_str,
inspect,
@ -207,6 +208,7 @@ generate! {
join,
kw,
lazy_static,
leading_zeros,
lint_vec,
ln,
lock,

View file

@ -1,6 +1,6 @@
[package]
name = "declare_clippy_lint"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
repository = "https://github.com/rust-lang/rust-clippy"
license = "MIT OR Apache-2.0"

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-11-28"
channel = "nightly-2025-12-11"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -0,0 +1 @@
stack-size-threshold = 0

View file

@ -0,0 +1,42 @@
//@ignore-target: i686
//@normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR"
//@normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR"
#![warn(clippy::large_stack_frames)]
extern crate serde;
use serde::{Deserialize, Serialize};
struct ArrayDefault<const N: usize>([u8; N]);
macro_rules! mac {
($name:ident) => {
fn foo() {
let $name = 1;
println!("macro_name called");
}
fn bar() {
let $name = ArrayDefault([0; 1000]);
}
};
}
mac!(something);
//~^ large_stack_frames
//~| large_stack_frames
#[derive(Deserialize, Serialize)]
//~^ large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
struct S {
a: [u128; 31],
}
fn main() {}

View file

@ -0,0 +1,89 @@
error: function `foo` generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:25:1
|
LL | fn foo() {
| --- this function has a stack frame size of 20 bytes
...
LL | mac!(something);
| ^^^^^^^^^^^^^^^
|
= note: 20 bytes is larger than Clippy's configured `stack-size-threshold` of 0
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
error: function `bar` generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:25:1
|
LL | fn bar() {
| --- this function has a stack frame size of 2000 bytes
LL | let $name = ArrayDefault([0; 1000]);
| --------- this is the largest part, at 1000 bytes for type `[u8; 1000]`
...
LL | mac!(something);
| ^^^^^^^^^^^^^^^
|
= note: 2000 bytes is larger than Clippy's configured `stack-size-threshold` of 0
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:23
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^
error: aborting due to 10 previous errors

View file

@ -0,0 +1,2 @@
stack-size-threshold = 0
allow-large-stack-frames-in-tests = false

View file

@ -0,0 +1,13 @@
// This test checks if `clippy::large_stack_frames` is working correctly when encountering functions
// generated by special compiling targets like `--test`.
//@compile-flags: --test
//@check-pass
#![warn(clippy::large_stack_frames)]
#[cfg(test)]
#[expect(clippy::large_stack_frames)]
mod test {
#[test]
fn main_test() {}
}

View file

@ -1,6 +1,7 @@
//@aux-build:proc_macro_derive.rs
#![warn(clippy::nonstandard_macro_braces)]
#![allow(clippy::println_empty_string)]
extern crate proc_macro_derive;
extern crate quote;
@ -75,3 +76,16 @@ fn issue9913() {
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
fn issue15594() {
println!();
println!("");
println!();
//~^ nonstandard_macro_braces
println!("");
//~^ nonstandard_macro_braces
println!();
//~^ nonstandard_macro_braces
println!("");
//~^ nonstandard_macro_braces
}

View file

@ -1,6 +1,7 @@
//@aux-build:proc_macro_derive.rs
#![warn(clippy::nonstandard_macro_braces)]
#![allow(clippy::println_empty_string)]
extern crate proc_macro_derive;
extern crate quote;
@ -75,3 +76,16 @@ fn issue9913() {
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
fn issue15594() {
println!();
println!("");
println![];
//~^ nonstandard_macro_braces
println![""];
//~^ nonstandard_macro_braces
println! {};
//~^ nonstandard_macro_braces
println! {""};
//~^ nonstandard_macro_braces
}

View file

@ -1,5 +1,5 @@
error: use of irregular braces for `vec!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:44:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:45:13
|
LL | let _ = vec! {1, 2, 3};
| ^^^^^^^^^^^^^^ help: consider writing: `vec![1, 2, 3]`
@ -8,31 +8,31 @@ LL | let _ = vec! {1, 2, 3};
= help: to override `-D warnings` add `#[allow(clippy::nonstandard_macro_braces)]`
error: use of irregular braces for `format!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:46:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:47:13
|
LL | let _ = format!["ugh {} stop being such a good compiler", "hello"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `format!("ugh {} stop being such a good compiler", "hello")`
error: use of irregular braces for `matches!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:48:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:49:13
|
LL | let _ = matches!{{}, ()};
| ^^^^^^^^^^^^^^^^ help: consider writing: `matches!({}, ())`
error: use of irregular braces for `quote!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:50:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:51:13
|
LL | let _ = quote!(let x = 1;);
| ^^^^^^^^^^^^^^^^^^ help: consider writing: `quote!{let x = 1;}`
error: use of irregular braces for `quote::quote!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:52:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:53:13
|
LL | let _ = quote::quote!(match match match);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `quote::quote!{match match match}`
error: use of irregular braces for `vec!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:18:9
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:19:9
|
LL | vec!{0, 0, 0}
| ^^^^^^^^^^^^^ help: consider writing: `vec![0, 0, 0]`
@ -43,22 +43,46 @@ LL | let _ = test!(); // trigger when macro def is inside our own crate
= note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)
error: use of irregular braces for `type_pos!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:62:12
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:63:12
|
LL | let _: type_pos!(usize) = vec![];
| ^^^^^^^^^^^^^^^^ help: consider writing: `type_pos![usize]`
error: use of irregular braces for `eprint!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:65:5
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:66:5
|
LL | eprint!("test if user config overrides defaults");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:74:5
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:75:5
|
LL | println! {"hello world"}
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `println!("hello world");`
error: aborting due to 9 previous errors
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:83:5
|
LL | println![];
| ^^^^^^^^^^ help: consider writing: `println!()`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:85:5
|
LL | println![""];
| ^^^^^^^^^^^^ help: consider writing: `println!("")`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:87:5
|
LL | println! {};
| ^^^^^^^^^^^ help: consider writing: `println!()`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:89:5
|
LL | println! {""};
| ^^^^^^^^^^^^^ help: consider writing: `println!("")`
error: aborting due to 13 previous errors

View file

@ -9,6 +9,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests
@ -107,6 +108,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests
@ -205,6 +207,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests

View file

@ -1,6 +1,6 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
#![allow(clippy::useless_vec, clippy::ptr_offset_by_literal)]
extern crate proc_macros;

View file

@ -1,6 +1,6 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
#![allow(clippy::useless_vec, clippy::ptr_offset_by_literal)]
extern crate proc_macros;

View file

@ -472,3 +472,22 @@ fn issue15321() {
//~^ unnecessary_unwrap
}
}
mod issue16188 {
struct Foo {
value: Option<i32>,
}
impl Foo {
pub fn bar(&mut self) {
let print_value = |v: i32| {
println!("{}", v);
};
if self.value.is_none() {
self.value = Some(10);
print_value(self.value.unwrap());
}
}
}
}

View file

@ -1,6 +1,6 @@
//@ check-pass
#![allow(clippy::single_match)]
#![allow(clippy::single_match, clippy::ptr_offset_by_literal)]
use std::ptr;

View file

@ -0,0 +1,133 @@
#![allow(
clippy::erasing_op,
clippy::no_effect,
clippy::unnecessary_operation,
clippy::unnecessary_cast,
clippy::op_ref
)]
#![warn(clippy::decimal_bitwise_operands)]
macro_rules! bitwise_op {
($x:expr, $y:expr) => {
$x & $y;
};
}
pub const SOME_CONST: i32 = 12345;
fn main() {
let mut x = 0;
// BAD: Bitwise operation, decimal literal, one literal
x & 9_8765_4321; //~ decimal_bitwise_operands
x & 100_i32; //~ decimal_bitwise_operands
x | (/* comment */99); //~ decimal_bitwise_operands
x ^ (99); //~ decimal_bitwise_operands
x &= 99; //~ decimal_bitwise_operands
x |= { 99 }; //~ decimal_bitwise_operands
x |= { { 99 } }; //~ decimal_bitwise_operands
x |= {
0b1000;
99 //~ decimal_bitwise_operands
};
x ^= (99); //~ decimal_bitwise_operands
// BAD: Bitwise operation, decimal literal, two literals
0b1010 & 99; //~ decimal_bitwise_operands
0b1010 | (99); //~ decimal_bitwise_operands
0b1010 ^ (/* comment */99); //~ decimal_bitwise_operands
99 & 0b1010; //~ decimal_bitwise_operands
(99) | 0b1010; //~ decimal_bitwise_operands
(/* comment */99) ^ 0b1010; //~ decimal_bitwise_operands
0xD | { 99 }; //~ decimal_bitwise_operands
88 & 99;
//~^ decimal_bitwise_operands
//~| decimal_bitwise_operands
37 & 38 & 39;
//~^ decimal_bitwise_operands
//~| decimal_bitwise_operands
//~| decimal_bitwise_operands
// GOOD: Bitwise operation, binary/hex/octal literal, one literal
x & 0b1010;
x | 0b1010;
x ^ 0b1010;
x &= 0b1010;
x |= 0b1010;
x ^= 0b1010;
x & 0xD;
x & 0o77;
x | 0o123;
x ^ 0o377;
x &= 0o777;
x |= 0o7;
x ^= 0o70;
// GOOD: Bitwise operation, binary/hex/octal literal, two literals
0b1010 & 0b1101;
0xD ^ 0xF;
0o377 ^ 0o77;
0b1101 ^ 0xFF;
// GOOD: Numeric operation, any literal
x += 99;
x -= 0b1010;
x *= 0xD;
99 + 99;
0b1010 - 0b1101;
0xD * 0xD;
// BAD: Unary, cast and reference, decimal literal
x & !100; //~ decimal_bitwise_operands
x & -100; //~ decimal_bitwise_operands
x & (100 as i32); //~ decimal_bitwise_operands
x & &100; //~ decimal_bitwise_operands
// GOOD: Unary, cast and reference, non-decimal literal
x & !0b1101;
x & -0xD;
x & (0o333 as i32);
x & &0b1010;
// GOOD: Bitwise operation, variables only
let y = 0;
x & y;
x &= y;
x + y;
x += y;
// GOOD: Macro expansion (should be ignored)
bitwise_op!(x, 123);
bitwise_op!(0b1010, 123);
// GOOD: Using const (should be ignored)
x & SOME_CONST;
x |= SOME_CONST;
// GOOD: Parenthesized binary/hex literal (should not trigger lint)
x & (0b1111);
x |= (0b1010);
x ^ (/* comment */0b1100);
(0xFF) & x;
// GOOD: Power of two and power of two minus one
x & 16; // 2^4
x | (31); // 2^5 - 1
x ^ 0x40; // 2^6 (hex)
x ^= 7; // 2^3 - 1
// GOOD: Bitwise operation, single digit decimal literal
5 & 9;
x ^ 6;
x ^= 7;
// GOOD: More complex expressions
(x + 1) & 0xFF;
(x * 2) | (y & 0xF);
(x ^ y) & 0b11110000;
x | (1 << 9);
// GOOD: Special cases
x & 0; // All bits off
x | !0; // All bits on
x ^ 1; // Toggle LSB
}

View file

@ -0,0 +1,204 @@
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:21:9
|
LL | x & 9_8765_4321;
| ^^^^^^^^^^^
|
= help: use binary (0b11_1010_1101_1110_0110_1000_1011_0001), hex (0x3ade_68b1), or octal (0o7_267_464_261) notation for better readability
= note: `-D clippy::decimal-bitwise-operands` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::decimal_bitwise_operands)]`
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:22:9
|
LL | x & 100_i32;
| ^^^^^^^
|
= help: use binary (0b110_0100_i32), hex (0x0064_i32), or octal (0o144_i32) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:23:23
|
LL | x | (/* comment */99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:24:10
|
LL | x ^ (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:25:10
|
LL | x &= 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:26:12
|
LL | x |= { 99 };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:27:14
|
LL | x |= { { 99 } };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:30:9
|
LL | 99
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:32:11
|
LL | x ^= (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:35:14
|
LL | 0b1010 & 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:36:15
|
LL | 0b1010 | (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:37:28
|
LL | 0b1010 ^ (/* comment */99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:38:5
|
LL | 99 & 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:39:6
|
LL | (99) | 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:40:19
|
LL | (/* comment */99) ^ 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:41:13
|
LL | 0xD | { 99 };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:42:5
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b101_1000), hex (0x0058), or octal (0o130) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:42:10
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:15
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0111), hex (0x0027), or octal (0o47) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:5
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0101), hex (0x0025), or octal (0o45) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:10
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0110), hex (0x0026), or octal (0o46) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:80:10
|
LL | x & !100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:81:10
|
LL | x & -100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:82:10
|
LL | x & (100 as i32);
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:83:10
|
LL | x & &100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: aborting due to 25 previous errors

View file

@ -61,7 +61,7 @@ error: lint `clippy::should_assert_eq` has been removed: `assert!(a == b)` can n
LL | #![warn(clippy::should_assert_eq)]
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::string_to_string` has been removed: `clippy:implicit_clone` covers those cases
error: lint `clippy::string_to_string` has been removed: `clippy::implicit_clone` covers those cases
--> tests/ui/deprecated.rs:15:9
|
LL | #![warn(clippy::string_to_string)]

View file

@ -248,4 +248,28 @@ mod issue14449 {
}
}
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
mod issue_16173 {
use std::collections::HashMap;
use std::sync::Mutex;
async fn f() {}
async fn foo() {
let mu_map = Mutex::new(HashMap::new());
if !mu_map.lock().unwrap().contains_key(&0) {
f().await;
mu_map.lock().unwrap().insert(0, 0);
}
if mu_map.lock().unwrap().contains_key(&1) {
todo!();
} else {
mu_map.lock().unwrap().insert(1, 42);
todo!();
f().await;
}
}
}
fn main() {}

View file

@ -254,4 +254,28 @@ mod issue14449 {
}
}
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
mod issue_16173 {
use std::collections::HashMap;
use std::sync::Mutex;
async fn f() {}
async fn foo() {
let mu_map = Mutex::new(HashMap::new());
if !mu_map.lock().unwrap().contains_key(&0) {
f().await;
mu_map.lock().unwrap().insert(0, 0);
}
if mu_map.lock().unwrap().contains_key(&1) {
todo!();
} else {
mu_map.lock().unwrap().insert(1, 42);
todo!();
f().await;
}
}
}
fn main() {}

View file

@ -473,4 +473,16 @@ impl Alias2 {
}
}
// Issue #16190
pub struct RefMutLenButRefIsEmpty;
impl RefMutLenButRefIsEmpty {
pub fn len(&mut self) -> usize {
todo!()
}
pub fn is_empty(&self) -> bool {
todo!()
}
}
fn main() {}

View file

@ -0,0 +1,32 @@
//@aux-build:proc_macros.rs
#![warn(clippy::manual_ilog2)]
#![allow(clippy::unnecessary_operation)]
use proc_macros::{external, with_span};
fn foo(a: u32, b: u64) {
a.ilog2(); //~ manual_ilog2
a.ilog2(); //~ manual_ilog2
b.ilog2(); //~ manual_ilog2
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
// don't lint when macros are involved
macro_rules! two {
() => {
2
};
};
macro_rules! thirty_one {
() => {
31
};
};
a.ilog(two!());
thirty_one!() - a.leading_zeros();
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}

View file

@ -0,0 +1,32 @@
//@aux-build:proc_macros.rs
#![warn(clippy::manual_ilog2)]
#![allow(clippy::unnecessary_operation)]
use proc_macros::{external, with_span};
fn foo(a: u32, b: u64) {
31 - a.leading_zeros(); //~ manual_ilog2
a.ilog(2); //~ manual_ilog2
63 - b.leading_zeros(); //~ manual_ilog2
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
// don't lint when macros are involved
macro_rules! two {
() => {
2
};
};
macro_rules! thirty_one {
() => {
31
};
};
a.ilog(two!());
thirty_one!() - a.leading_zeros();
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}

View file

@ -0,0 +1,23 @@
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:8:5
|
LL | 31 - a.leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.ilog2()`
|
= note: `-D clippy::manual-ilog2` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_ilog2)]`
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:9:5
|
LL | a.ilog(2);
| ^^^^^^^^^ help: try: `a.ilog2()`
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:11:5
|
LL | 63 - b.leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()`
error: aborting due to 3 previous errors

View file

@ -58,3 +58,13 @@ fn main() {
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
}
fn issue15655() {
let _ = 5u32.saturating_sub(1u32); //~ manual_saturating_arithmetic
let _ = 5u32.checked_add(1u32).unwrap_or_default(); // ok
let _ = 5u32.checked_mul(1u32).unwrap_or_default(); // ok
let _ = 5i32.checked_sub(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_add(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_mul(1i32).unwrap_or_default(); // ok
}

Some files were not shown because too many files have changed in this diff Show more