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:
commit
3391c01336
203 changed files with 4948 additions and 1726 deletions
1
.mailmap
1
.mailmap
|
|
@ -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>
|
||||
|
|
|
|||
10
Cargo.lock
10
Cargo.lock
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.93"
|
||||
version = "0.1.94"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
289
src/tools/clippy/clippy_lints/src/casts/needless_type_cast.rs
Normal file
289
src/tools/clippy/clippy_lints/src/casts/needless_type_cast.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
342
src/tools/clippy/clippy_lints/src/len_without_is_empty.rs
Normal file
342
src/tools/clippy/clippy_lints/src/len_without_is_empty.rs
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
115
src/tools/clippy/clippy_lints/src/manual_ilog2.rs
Normal file
115
src/tools/clippy/clippy_lints/src/manual_ilog2.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
stack-size-threshold = 0
|
||||
|
|
@ -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() {}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
stack-size-threshold = 0
|
||||
allow-large-stack-frames-in-tests = false
|
||||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//@ check-pass
|
||||
|
||||
#![allow(clippy::single_match)]
|
||||
#![allow(clippy::single_match, clippy::ptr_offset_by_literal)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
|
|
|
|||
133
src/tools/clippy/tests/ui/decimal_bitwise_operands.rs
Normal file
133
src/tools/clippy/tests/ui/decimal_bitwise_operands.rs
Normal 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
|
||||
}
|
||||
204
src/tools/clippy/tests/ui/decimal_bitwise_operands.stderr
Normal file
204
src/tools/clippy/tests/ui/decimal_bitwise_operands.stderr
Normal 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
|
||||
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
32
src/tools/clippy/tests/ui/manual_ilog2.fixed
Normal file
32
src/tools/clippy/tests/ui/manual_ilog2.fixed
Normal 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));
|
||||
}
|
||||
32
src/tools/clippy/tests/ui/manual_ilog2.rs
Normal file
32
src/tools/clippy/tests/ui/manual_ilog2.rs
Normal 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));
|
||||
}
|
||||
23
src/tools/clippy/tests/ui/manual_ilog2.stderr
Normal file
23
src/tools/clippy/tests/ui/manual_ilog2.stderr
Normal 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
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue