Auto merge of #82517 - Dylan-DPC:rollup-a1958gb, r=Dylan-DPC

Rollup of 16 pull requests

Successful merges:

 - #75807 (Convert core/num/mod.rs to intra-doc links)
 - #80534 (Use #[doc = include_str!()] in std)
 - #80553 (Add an impl of Error on `Arc<impl Error>`.)
 - #81167 (Make ptr::write const)
 - #81575 (rustdoc: Name fields of `ResolutionFailure::WrongNamespace`)
 - #81713 (Account for associated consts in the "unstable assoc item name colission" lint)
 - #82078 (Make char and u8 methods const)
 - #82087 (Fix ICE caused by suggestion with no code substitutions)
 - #82090 (Do not consider using a semicolon inside of a different-crate macro)
 - #82213 (Slices for vecs)
 - #82214 (Remove redundant to_string calls)
 - #82220 (fix the false 'defined here' messages)
 - #82313 (Update normalize.css to 8.0.1)
 - #82321 (AST: Remove some unnecessary boxes)
 - #82364 (Improve error msgs when found type is deref of expected)
 - #82514 (Update Clippy)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2021-02-25 15:15:59 +00:00
commit 0ab7c1d56f
172 changed files with 7163 additions and 3460 deletions

View File

@ -565,14 +565,14 @@ dependencies = [
"cargo_metadata 0.12.0",
"clippy-mini-macro-test",
"clippy_lints",
"compiletest_rs",
"compiletest_rs 0.6.0",
"derive-new",
"rustc-workspace-hack",
"rustc_tools_util 0.2.0",
"semver 0.11.0",
"serde",
"tempfile",
"tester",
"tester 0.9.0",
]
[[package]]
@ -584,6 +584,7 @@ name = "clippy_lints"
version = "0.1.52"
dependencies = [
"cargo_metadata 0.12.0",
"clippy_utils",
"if_chain",
"itertools 0.9.0",
"pulldown-cmark 0.8.0",
@ -600,6 +601,20 @@ dependencies = [
"url 2.1.1",
]
[[package]]
name = "clippy_utils"
version = "0.1.52"
dependencies = [
"if_chain",
"itertools 0.9.0",
"regex-syntax",
"rustc-semver",
"serde",
"smallvec 1.6.1",
"toml",
"unicode-normalization",
]
[[package]]
name = "cloudabi"
version = "0.1.0"
@ -695,7 +710,30 @@ dependencies = [
"serde_derive",
"serde_json",
"tempfile",
"tester",
"tester 0.7.0",
"winapi 0.3.9",
]
[[package]]
name = "compiletest_rs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0086d6ad78cf409c3061618cd98e2789d5c9ce598fc9651611cf62eae0a599cb"
dependencies = [
"diff",
"filetime",
"getopts",
"lazy_static",
"libc",
"log",
"miow 0.3.6",
"regex",
"rustfix",
"serde",
"serde_derive",
"serde_json",
"tempfile",
"tester 0.9.0",
"winapi 0.3.9",
]
@ -984,6 +1022,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if 1.0.0",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.5"
@ -991,7 +1039,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
dependencies = [
"libc",
"redox_users",
"redox_users 0.3.4",
"winapi 0.3.9",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users 0.4.0",
"winapi 0.3.9",
]
@ -1115,7 +1174,7 @@ checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
@ -2226,7 +2285,7 @@ name = "miri"
version = "0.1.0"
dependencies = [
"colored",
"compiletest_rs",
"compiletest_rs 0.5.0",
"env_logger 0.7.1",
"getrandom 0.2.0",
"hex 0.4.2",
@ -2458,7 +2517,7 @@ dependencies = [
"cloudabi",
"instant",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"smallvec 1.6.1",
"winapi 0.3.9",
]
@ -2899,6 +2958,15 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.3.4"
@ -2906,10 +2974,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
dependencies = [
"getrandom 0.1.14",
"redox_syscall",
"redox_syscall 0.1.57",
"rust-argon2",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom 0.2.0",
"redox_syscall 0.2.5",
]
[[package]]
name = "regex"
version = "1.4.3"
@ -4535,6 +4613,12 @@ dependencies = [
"unicode_categories",
]
[[package]]
name = "rustversion"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
[[package]]
name = "ryu"
version = "1.0.5"
@ -4812,7 +4896,7 @@ checksum = "7fd8b795c389288baa5f355489c65e71fd48a02104600d15c4cfbc561e9e429d"
dependencies = [
"cfg-if 0.1.10",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
@ -4973,7 +5057,7 @@ checksum = "c8a4c1d0bee3230179544336c15eefb563cf0302955d962e456542323e8c2e8a"
dependencies = [
"filetime",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"xattr",
]
@ -4986,7 +5070,7 @@ dependencies = [
"cfg-if 0.1.10",
"libc",
"rand",
"redox_syscall",
"redox_syscall 0.1.57",
"remove_dir_all",
"winapi 0.3.9",
]
@ -5020,6 +5104,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi 0.3.9",
]
[[package]]
name = "termcolor"
version = "1.1.0"
@ -5065,6 +5160,19 @@ dependencies = [
"term 0.6.1",
]
[[package]]
name = "tester"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0639d10d8f4615f223a57275cf40f9bdb7cfbb806bcb7f7cc56e3beb55a576eb"
dependencies = [
"cfg-if 1.0.0",
"getopts",
"libc",
"num_cpus",
"term 0.7.0",
]
[[package]]
name = "textwrap"
version = "0.11.0"

View File

@ -2695,7 +2695,7 @@ pub enum ItemKind {
/// A use declaration item (`use`).
///
/// E.g., `use foo;`, `use foo::bar;` or `use foo::bar as FooBar;`.
Use(P<UseTree>),
Use(UseTree),
/// A static item (`static`).
///
/// E.g., `static FOO: i32 = 42;` or `static FOO: &'static str = "bar";`.
@ -2719,7 +2719,7 @@ pub enum ItemKind {
/// E.g., `extern {}` or `extern "C" {}`.
ForeignMod(ForeignMod),
/// Module-level inline assembly (from `global_asm!()`).
GlobalAsm(P<GlobalAsm>),
GlobalAsm(GlobalAsm),
/// A type alias (`type`).
///
/// E.g., `type Foo = Bar<u8>;`.

View File

@ -28,7 +28,7 @@ pub fn expand_global_asm<'cx>(
ident: Ident::invalid(),
attrs: Vec::new(),
id: ast::DUMMY_NODE_ID,
kind: ast::ItemKind::GlobalAsm(P(global_asm)),
kind: ast::ItemKind::GlobalAsm(global_asm),
vis: ast::Visibility {
span: sp.shrink_to_lo(),
kind: ast::VisibilityKind::Inherited,

View File

@ -1,5 +1,4 @@
use rustc_ast as ast;
use rustc_ast::ptr::P;
use rustc_expand::base::{ExtCtxt, ResolverExpand};
use rustc_expand::expand::ExpansionConfig;
use rustc_session::Session;
@ -72,11 +71,11 @@ pub fn inject(
span,
Ident::invalid(),
vec![cx.attribute(cx.meta_word(span, sym::prelude_import))],
ast::ItemKind::Use(P(ast::UseTree {
ast::ItemKind::Use(ast::UseTree {
prefix: cx.path(span, import_path),
kind: ast::UseTreeKind::Glob,
span,
})),
}),
);
krate.items.insert(0, use_item);

View File

@ -93,7 +93,7 @@ pub(crate) unsafe fn codegen(
let args = [usize, usize]; // size, align
let ty = llvm::LLVMFunctionType(void, args.as_ptr(), args.len() as c_uint, False);
let name = "__rust_alloc_error_handler".to_string();
let name = "__rust_alloc_error_handler";
let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_ptr().cast(), name.len(), ty);
// -> ! DIFlagNoReturn
llvm::Attribute::NoReturn.apply_llfn(llvm::AttributePlace::Function, llfn);

View File

@ -61,9 +61,9 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
// Default per-arch clobbers
// Basically what clang does
let arch_clobbers = match &self.sess().target.arch[..] {
"x86" | "x86_64" => vec!["~{dirflag}", "~{fpsr}", "~{flags}"],
"mips" | "mips64" => vec!["~{$1}"],
_ => Vec::new(),
"x86" | "x86_64" => &["~{dirflag}", "~{fpsr}", "~{flags}"][..],
"mips" | "mips64" => &["~{$1}"],
_ => &[],
};
let all_constraints = ia

View File

@ -181,16 +181,16 @@ fn get_linker(
let original_path = tool.path();
if let Some(ref root_lib_path) = original_path.ancestors().nth(4) {
let arch = match t.arch.as_str() {
"x86_64" => Some("x64".to_string()),
"x86" => Some("x86".to_string()),
"aarch64" => Some("arm64".to_string()),
"arm" => Some("arm".to_string()),
"x86_64" => Some("x64"),
"x86" => Some("x86"),
"aarch64" => Some("arm64"),
"arm" => Some("arm"),
_ => None,
};
if let Some(ref a) = arch {
// FIXME: Move this to `fn linker_with_args`.
let mut arg = OsString::from("/LIBPATH:");
arg.push(format!("{}\\lib\\{}\\store", root_lib_path.display(), a.to_string()));
arg.push(format!("{}\\lib\\{}\\store", root_lib_path.display(), a));
cmd.arg(&arg);
} else {
warn!("arch is not supported");

View File

@ -295,6 +295,7 @@ impl Diagnostic {
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
assert!(!suggestion.is_empty());
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion
@ -318,6 +319,10 @@ impl Diagnostic {
suggestions: Vec<Vec<(Span, String)>>,
applicability: Applicability,
) -> &mut Self {
assert!(!suggestions.is_empty());
for s in &suggestions {
assert!(!s.is_empty());
}
self.suggestions.push(CodeSuggestion {
substitutions: suggestions
.into_iter()
@ -348,6 +353,7 @@ impl Diagnostic {
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
assert!(!suggestion.is_empty());
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion

View File

@ -1577,6 +1577,14 @@ impl Expr<'_> {
expr
}
pub fn peel_blocks(&self) -> &Self {
let mut expr = self;
while let ExprKind::Block(Block { expr: Some(inner), .. }, _) = &expr.kind {
expr = inner;
}
expr
}
pub fn can_have_side_effects(&self) -> bool {
match self.peel_drop_temps().kind {
ExprKind::Path(_) | ExprKind::Lit(_) => false,

View File

@ -353,12 +353,12 @@ pub fn struct_lint_level<'s, 'd>(
it will become a hard error";
let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
"once this method is added to the standard library, \
the ambiguity may cause an error or change in behavior!"
"once this associated item is added to the standard library, the ambiguity may \
cause an error or change in behavior!"
.to_owned()
} else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
"this borrowing pattern was not meant to be accepted, \
and may become a hard error in the future"
"this borrowing pattern was not meant to be accepted, and may become a hard error \
in the future"
.to_owned()
} else if let Some(edition) = future_incompatible.edition {
format!("{} in the {} edition!", STANDARD_MESSAGE, edition)

View File

@ -225,7 +225,7 @@ impl<'a> Parser<'a> {
return Err(e);
}
(Ident::invalid(), ItemKind::Use(P(tree)))
(Ident::invalid(), ItemKind::Use(tree))
} else if self.check_fn_front_matter() {
// FUNCTION ITEM
let (ident, sig, generics, body) = self.parse_fn(attrs, req_name, lo)?;

View File

@ -319,9 +319,13 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
.collect::<Vec<_>>();
let crate_def_id = DefId::local(CRATE_DEF_INDEX);
if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) {
let enum_candidates =
self.r.lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant);
let mut enum_candidates: Vec<_> = self
.r
.lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant)
.into_iter()
.map(|suggestion| import_candidate_to_enum_paths(&suggestion))
.filter(|(_, enum_ty_path)| enum_ty_path != "std::prelude::v1")
.collect();
if !enum_candidates.is_empty() {
if let (PathSource::Type, Some(span)) =
(source, self.diagnostic_metadata.current_type_ascription.last())
@ -340,10 +344,6 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
}
}
let mut enum_candidates = enum_candidates
.iter()
.map(|suggestion| import_candidate_to_enum_paths(&suggestion))
.collect::<Vec<_>>();
enum_candidates.sort();
// Contextualize for E0412 "cannot find type", but don't belabor the point
@ -363,19 +363,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
err.span_suggestions(
span,
&msg,
enum_candidates
.into_iter()
.map(|(_variant_path, enum_ty_path)| enum_ty_path)
// Variants re-exported in prelude doesn't mean `prelude::v1` is the
// type name!
// FIXME: is there a more principled way to do this that
// would work for other re-exports?
.filter(|enum_ty_path| enum_ty_path != "std::prelude::v1")
// Also write `Option` rather than `std::prelude::v1::Option`.
.map(|enum_ty_path| {
// FIXME #56861: DRY-er prelude filtering.
enum_ty_path.trim_start_matches("std::prelude::v1::").to_owned()
}),
enum_candidates.into_iter().map(|(_variant_path, enum_ty_path)| enum_ty_path),
Applicability::MachineApplicable,
);
}

View File

@ -4,7 +4,7 @@ use crate::type_error_struct;
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::{infer, traits};
@ -374,7 +374,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|p| format!("`{}` defined here returns `{}`", p, callee_ty),
)
}
_ => Some(format!("`{}` defined here", callee_ty)),
_ => {
match def {
// Emit a different diagnostic for local variables, as they are not
// type definitions themselves, but rather variables *of* that type.
Res::Local(hir_id) => Some(format!(
"`{}` has type `{}`",
self.tcx.hir().name(hir_id),
callee_ty
)),
Res::Def(kind, def_id)
if kind.ns() == Some(Namespace::ValueNS) =>
{
Some(format!(
"`{}` defined here",
self.tcx.def_path_str(def_id),
))
}
_ => Some(format!("`{}` defined here", callee_ty)),
}
}
};
if let Some(label) = label {
err.span_label(span, label);

View File

@ -42,6 +42,7 @@ use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::{Coercion, InferOk, InferResult};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast,
};
@ -1448,7 +1449,12 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
expected.is_unit(),
pointing_at_return_type,
) {
if cond_expr.span.desugaring_kind().is_none() {
// If the block is from an external macro, then do not suggest
// adding a semicolon, because there's nowhere to put it.
// See issue #81943.
if cond_expr.span.desugaring_kind().is_none()
&& !in_external_macro(fcx.tcx.sess, cond_expr.span)
{
err.span_label(cond_expr.span, "expected this to be `()`");
if expr.can_have_side_effects() {
fcx.suggest_semicolon_at_end(cond_expr.span, &mut err);

View File

@ -616,10 +616,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
_ if sp == expr.span && !is_macro => {
if let Some(steps) = self.deref_steps(checked_ty, expected) {
let expr = expr.peel_blocks();
if steps == 1 {
// For a suggestion to make sense, the type would need to be `Copy`.
if self.infcx.type_is_copy_modulo_regions(self.param_env, expected, sp) {
if let Ok(code) = sm.span_to_snippet(sp) {
if let hir::ExprKind::AddrOf(_, mutbl, inner) = expr.kind {
// If the expression has `&`, removing it would fix the error
let prefix_span = expr.span.with_hi(inner.span.lo());
let message = match mutbl {
hir::Mutability::Not => "consider removing the `&`",
hir::Mutability::Mut => "consider removing the `&mut`",
};
let suggestion = String::new();
return Some((
prefix_span,
message,
suggestion,
Applicability::MachineApplicable,
));
} else if self.infcx.type_is_copy_modulo_regions(
self.param_env,
expected,
sp,
) {
// For this suggestion to make sense, the type would need to be `Copy`.
if let Ok(code) = sm.span_to_snippet(expr.span) {
let message = if checked_ty.is_region_ptr() {
"consider dereferencing the borrow"
} else {
@ -631,7 +651,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
format!("*{}", code)
};
return Some((
sp,
expr.span,
message,
suggestion,
Applicability::MachineApplicable,

View File

@ -10,6 +10,7 @@ use rustc_hir::def::{CtorOf, DefKind};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, ItemKind, Node};
use rustc_infer::infer;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_span::symbol::kw;
@ -44,7 +45,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
blk_id: hir::HirId,
) -> bool {
let expr = expr.peel_drop_temps();
if expr.can_have_side_effects() {
// If the expression is from an external macro, then do not suggest
// adding a semicolon, because there's nowhere to put it.
// See issue #81943.
if expr.can_have_side_effects() && !in_external_macro(self.tcx.sess, cause_span) {
self.suggest_missing_semicolon(err, expr, expected, cause_span);
}
let mut pointing_at_return_type = false;

View File

@ -10,6 +10,7 @@ use crate::hir::def_id::DefId;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Namespace;
use rustc_infer::infer::canonical::OriginalQueryValues;
@ -1167,7 +1168,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
//
// We suppress warning if we're picking the method only because it is a
// suggestion.
self.emit_unstable_name_collision_hint(p, &unstable_candidates);
self.emit_unstable_name_collision_hint(p, &unstable_candidates, self_ty);
}
}
return Some(pick);
@ -1246,24 +1247,46 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
&self,
stable_pick: &Pick<'_>,
unstable_candidates: &[(&Candidate<'tcx>, Symbol)],
self_ty: Ty<'tcx>,
) {
self.tcx.struct_span_lint_hir(
lint::builtin::UNSTABLE_NAME_COLLISIONS,
self.fcx.body_id,
self.span,
|lint| {
let mut diag = lint.build(
"a method with this name may be added to the standard library in the future",
);
// FIXME: This should be a `span_suggestion` instead of `help`
// However `self.span` only
// highlights the method name, so we can't use it. Also consider reusing the code from
// `report_method_error()`.
diag.help(&format!(
"call with fully qualified syntax `{}(...)` to keep using the current method",
self.tcx.def_path_str(stable_pick.item.def_id),
let def_kind = stable_pick.item.kind.as_def_kind();
let mut diag = lint.build(&format!(
"{} {} with this name may be added to the standard library in the future",
def_kind.article(),
def_kind.descr(stable_pick.item.def_id),
));
match (stable_pick.item.kind, stable_pick.item.container) {
(ty::AssocKind::Fn, _) => {
// FIXME: This should be a `span_suggestion` instead of `help`
// However `self.span` only
// highlights the method name, so we can't use it. Also consider reusing
// the code from `report_method_error()`.
diag.help(&format!(
"call with fully qualified syntax `{}(...)` to keep using the current \
method",
self.tcx.def_path_str(stable_pick.item.def_id),
));
}
(ty::AssocKind::Const, ty::AssocItemContainer::TraitContainer(def_id)) => {
diag.span_suggestion(
self.span,
"use the fully qualified path to the associated const",
format!(
"<{} as {}>::{}",
self_ty,
self.tcx.def_path_str(def_id),
stable_pick.item.ident
),
Applicability::MachineApplicable,
);
}
_ => {}
}
if self.tcx.sess.is_nightly_build() {
for (candidate, feature) in unstable_candidates {
diag.help(&format!(

View File

@ -569,8 +569,9 @@ impl char {
/// assert_eq!(len, tokyo.len());
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_char_len_utf", since = "1.52.0")]
#[inline]
pub fn len_utf8(self) -> usize {
pub const fn len_utf8(self) -> usize {
len_utf8(self as u32)
}
@ -594,8 +595,9 @@ impl char {
/// assert_eq!(len, 2);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_char_len_utf", since = "1.52.0")]
#[inline]
pub fn len_utf16(self) -> usize {
pub const fn len_utf16(self) -> usize {
let ch = self as u32;
if (ch & 0xFFFF) == ch { 1 } else { 2 }
}
@ -1086,8 +1088,9 @@ impl char {
/// [`make_ascii_uppercase()`]: #method.make_ascii_uppercase
/// [`to_uppercase()`]: #method.to_uppercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn to_ascii_uppercase(&self) -> char {
pub const fn to_ascii_uppercase(&self) -> char {
if self.is_ascii_lowercase() {
(*self as u8).ascii_change_case_unchecked() as char
} else {
@ -1118,8 +1121,9 @@ impl char {
/// [`make_ascii_lowercase()`]: #method.make_ascii_lowercase
/// [`to_lowercase()`]: #method.to_lowercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn to_ascii_lowercase(&self) -> char {
pub const fn to_ascii_lowercase(&self) -> char {
if self.is_ascii_uppercase() {
(*self as u8).ascii_change_case_unchecked() as char
} else {
@ -1143,8 +1147,9 @@ impl char {
/// assert!(!upper_a.eq_ignore_ascii_case(&lower_z));
/// ```
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn eq_ignore_ascii_case(&self, other: &char) -> bool {
pub const fn eq_ignore_ascii_case(&self, other: &char) -> bool {
self.to_ascii_lowercase() == other.to_ascii_lowercase()
}
@ -1561,7 +1566,7 @@ impl char {
}
#[inline]
fn len_utf8(code: u32) -> usize {
const fn len_utf8(code: u32) -> usize {
if code < MAX_ONE_B {
1
} else if code < MAX_TWO_B {

View File

@ -833,6 +833,7 @@ extern "rust-intrinsic" {
///
/// This exists solely for [`mem::forget_unsized`]; normal `forget` uses
/// `ManuallyDrop` instead.
#[rustc_const_unstable(feature = "const_intrinsic_forget", issue = "none")]
pub fn forget<T: ?Sized>(_: T);
/// Reinterprets the bits of a value of one type as another type.

View File

@ -73,10 +73,12 @@
#![feature(const_discriminant)]
#![feature(const_cell_into_inner)]
#![feature(const_intrinsic_copy)]
#![feature(const_intrinsic_forget)]
#![feature(const_float_classify)]
#![feature(const_float_bits_conv)]
#![feature(const_int_unchecked_arith)]
#![feature(const_mut_refs)]
#![feature(const_refs_to_cell)]
#![feature(const_cttz)]
#![feature(const_panic)]
#![feature(const_pin)]
@ -90,6 +92,7 @@
#![feature(const_ptr_offset)]
#![feature(const_ptr_offset_from)]
#![feature(const_ptr_read)]
#![feature(const_ptr_write)]
#![feature(const_raw_ptr_comparison)]
#![feature(const_raw_ptr_deref)]
#![feature(const_slice_from_raw_parts)]

View File

@ -1,4 +1,4 @@
#[doc(include = "panic.md")]
#[doc = include_str!("panic.md")]
#[macro_export]
#[rustc_builtin_macro = "core_panic"]
#[allow_internal_unstable(edition_panic)]

View File

@ -54,10 +54,7 @@ impl From<!> for TryFromIntError {
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
/// Using the [`str::trim()`] method ensures that no whitespace remains before parsing.
///
/// # Example
///

View File

@ -18,7 +18,7 @@ use crate::mem;
use crate::num::FpCategory;
/// The radix or base of the internal representation of `f32`.
/// Use [`f32::RADIX`](../../std/primitive.f32.html#associatedconstant.RADIX) instead.
/// Use [`f32::RADIX`] instead.
///
/// # Examples
///
@ -832,8 +832,8 @@ impl f32 {
/// As the target platform's native endianness is used, portable code
/// should use [`to_be_bytes`] or [`to_le_bytes`], as appropriate, instead.
///
/// [`to_be_bytes`]: #method.to_be_bytes
/// [`to_le_bytes`]: #method.to_le_bytes
/// [`to_be_bytes`]: f32::to_be_bytes
/// [`to_le_bytes`]: f32::to_le_bytes
///
/// # Examples
///
@ -860,7 +860,7 @@ impl f32 {
///
/// [`to_ne_bytes`] should be preferred over this whenever possible.
///
/// [`to_ne_bytes`]: #method.to_ne_bytes
/// [`to_ne_bytes`]: f32::to_ne_bytes
///
/// # Examples
///
@ -920,8 +920,8 @@ impl f32 {
/// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as
/// appropriate instead.
///
/// [`from_be_bytes`]: #method.from_be_bytes
/// [`from_le_bytes`]: #method.from_le_bytes
/// [`from_be_bytes`]: f32::from_be_bytes
/// [`from_le_bytes`]: f32::from_le_bytes
///
/// # Examples
///

View File

@ -846,8 +846,8 @@ impl f64 {
/// As the target platform's native endianness is used, portable code
/// should use [`to_be_bytes`] or [`to_le_bytes`], as appropriate, instead.
///
/// [`to_be_bytes`]: #method.to_be_bytes
/// [`to_le_bytes`]: #method.to_le_bytes
/// [`to_be_bytes`]: f64::to_be_bytes
/// [`to_le_bytes`]: f64::to_le_bytes
///
/// # Examples
///
@ -874,7 +874,7 @@ impl f64 {
///
/// [`to_ne_bytes`] should be preferred over this whenever possible.
///
/// [`to_ne_bytes`]: #method.to_ne_bytes
/// [`to_ne_bytes`]: f64::to_ne_bytes
///
/// # Examples
///
@ -934,8 +934,8 @@ impl f64 {
/// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as
/// appropriate instead.
///
/// [`from_be_bytes`]: #method.from_be_bytes
/// [`from_le_bytes`]: #method.from_le_bytes
/// [`from_be_bytes`]: f64::from_be_bytes
/// [`from_le_bytes`]: f64::from_le_bytes
///
/// # Examples
///

View File

@ -1067,7 +1067,7 @@ macro_rules! int_impl {
///
/// Note that this is *not* the same as a rotate-left; the RHS of a wrapping shift-left is restricted to
/// the range of the type, rather than the bits shifted out of the LHS being returned to the other end.
/// The primitive integer types all implement a [`rotate_left`](#method.rotate_left) function,
/// The primitive integer types all implement a [`rotate_left`](Self::rotate_left) function,
/// which may be what you want instead.
///
/// # Examples
@ -1096,7 +1096,7 @@ macro_rules! int_impl {
///
/// Note that this is *not* the same as a rotate-right; the RHS of a wrapping shift-right is restricted
/// to the range of the type, rather than the bits shifted out of the LHS being returned to the other
/// end. The primitive integer types all implement a [`rotate_right`](#method.rotate_right) function,
/// end. The primitive integer types all implement a [`rotate_right`](Self::rotate_right) function,
/// which may be what you want instead.
///
/// # Examples
@ -1812,8 +1812,8 @@ macro_rules! int_impl {
///
#[doc = $to_xe_bytes_doc]
///
/// [`to_be_bytes`]: #method.to_be_bytes
/// [`to_le_bytes`]: #method.to_le_bytes
/// [`to_be_bytes`]: Self::to_be_bytes
/// [`to_le_bytes`]: Self::to_le_bytes
///
/// # Examples
///
@ -1845,7 +1845,7 @@ macro_rules! int_impl {
///
/// [`to_ne_bytes`] should be preferred over this whenever possible.
///
/// [`to_ne_bytes`]: #method.to_ne_bytes
/// [`to_ne_bytes`]: Self::to_ne_bytes
///
/// # Examples
///
@ -1937,8 +1937,8 @@ macro_rules! int_impl {
/// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as
/// appropriate instead.
///
/// [`from_be_bytes`]: #method.from_be_bytes
/// [`from_le_bytes`]: #method.from_le_bytes
/// [`from_be_bytes`]: Self::from_be_bytes
/// [`from_le_bytes`]: Self::from_le_bytes
///
#[doc = $to_xe_bytes_doc]
///
@ -1976,7 +1976,7 @@ macro_rules! int_impl {
}
/// New code should prefer to use
#[doc = concat!("[`", stringify!($SelfT), "::MIN", "`](#associatedconstant.MIN).")]
#[doc = concat!("[`", stringify!($SelfT), "::MIN", "`] instead.")]
///
/// Returns the smallest value that can be represented by this integer type.
#[stable(feature = "rust1", since = "1.0.0")]
@ -1989,7 +1989,7 @@ macro_rules! int_impl {
}
/// New code should prefer to use
#[doc = concat!("[`", stringify!($SelfT), "::MAX", "`](#associatedconstant.MAX).")]
#[doc = concat!("[`", stringify!($SelfT), "::MAX", "`] instead.")]
///
/// Returns the largest value that can be represented by this integer type.
#[stable(feature = "rust1", since = "1.0.0")]

View File

@ -193,10 +193,11 @@ impl u8 {
/// assert_eq!(65, lowercase_a.to_ascii_uppercase());
/// ```
///
/// [`make_ascii_uppercase`]: #method.make_ascii_uppercase
/// [`make_ascii_uppercase`]: Self::make_ascii_uppercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn to_ascii_uppercase(&self) -> u8 {
pub const fn to_ascii_uppercase(&self) -> u8 {
// Unset the fifth bit if this is a lowercase letter
*self & !((self.is_ascii_lowercase() as u8) * ASCII_CASE_MASK)
}
@ -216,17 +217,18 @@ impl u8 {
/// assert_eq!(97, uppercase_a.to_ascii_lowercase());
/// ```
///
/// [`make_ascii_lowercase`]: #method.make_ascii_lowercase
/// [`make_ascii_lowercase`]: Self::make_ascii_lowercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn to_ascii_lowercase(&self) -> u8 {
pub const fn to_ascii_lowercase(&self) -> u8 {
// Set the fifth bit if this is an uppercase letter
*self | (self.is_ascii_uppercase() as u8 * ASCII_CASE_MASK)
}
/// Assumes self is ascii
#[inline]
pub(crate) fn ascii_change_case_unchecked(&self) -> u8 {
pub(crate) const fn ascii_change_case_unchecked(&self) -> u8 {
*self ^ ASCII_CASE_MASK
}
@ -243,8 +245,9 @@ impl u8 {
/// assert!(lowercase_a.eq_ignore_ascii_case(&uppercase_a));
/// ```
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
#[inline]
pub fn eq_ignore_ascii_case(&self, other: &u8) -> bool {
pub const fn eq_ignore_ascii_case(&self, other: &u8) -> bool {
self.to_ascii_lowercase() == other.to_ascii_lowercase()
}
@ -266,7 +269,7 @@ impl u8 {
/// assert_eq!(b'A', byte);
/// ```
///
/// [`to_ascii_uppercase`]: #method.to_ascii_uppercase
/// [`to_ascii_uppercase`]: Self::to_ascii_uppercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[inline]
pub fn make_ascii_uppercase(&mut self) {
@ -291,7 +294,7 @@ impl u8 {
/// assert_eq!(b'a', byte);
/// ```
///
/// [`to_ascii_lowercase`]: #method.to_ascii_lowercase
/// [`to_ascii_lowercase`]: Self::to_ascii_lowercase
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
#[inline]
pub fn make_ascii_lowercase(&mut self) {
@ -723,9 +726,6 @@ impl usize {
/// This `enum` is used as the return type for [`f32::classify`] and [`f64::classify`]. See
/// their documentation for more.
///
/// [`f32::classify`]: ../../std/primitive.f32.html#method.classify
/// [`f64::classify`]: ../../std/primitive.f64.html#method.classify
///
/// # Examples
///
/// ```

View File

@ -994,7 +994,7 @@ macro_rules! uint_impl {
/// RHS of a wrapping shift-left is restricted to the range
/// of the type, rather than the bits shifted out of the LHS
/// being returned to the other end. The primitive integer
/// types all implement a [`rotate_left`](#method.rotate_left) function,
/// types all implement a [`rotate_left`](Self::rotate_left) function,
/// which may be what you want instead.
///
/// # Examples
@ -1026,7 +1026,7 @@ macro_rules! uint_impl {
/// RHS of a wrapping shift-right is restricted to the range
/// of the type, rather than the bits shifted out of the LHS
/// being returned to the other end. The primitive integer
/// types all implement a [`rotate_right`](#method.rotate_right) function,
/// types all implement a [`rotate_right`](Self::rotate_right) function,
/// which may be what you want instead.
///
/// # Examples
@ -1642,8 +1642,8 @@ macro_rules! uint_impl {
///
#[doc = $to_xe_bytes_doc]
///
/// [`to_be_bytes`]: #method.to_be_bytes
/// [`to_le_bytes`]: #method.to_le_bytes
/// [`to_be_bytes`]: Self::to_be_bytes
/// [`to_le_bytes`]: Self::to_le_bytes
///
/// # Examples
///
@ -1675,7 +1675,7 @@ macro_rules! uint_impl {
///
/// [`to_ne_bytes`] should be preferred over this whenever possible.
///
/// [`to_ne_bytes`]: #method.to_ne_bytes
/// [`to_ne_bytes`]: Self::to_ne_bytes
///
/// # Examples
///
@ -1767,8 +1767,8 @@ macro_rules! uint_impl {
/// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as
/// appropriate instead.
///
/// [`from_be_bytes`]: #method.from_be_bytes
/// [`from_le_bytes`]: #method.from_le_bytes
/// [`from_be_bytes`]: Self::from_be_bytes
/// [`from_le_bytes`]: Self::from_le_bytes
///
#[doc = $from_xe_bytes_doc]
///
@ -1806,8 +1806,7 @@ macro_rules! uint_impl {
}
/// New code should prefer to use
#[doc = concat!("[`", stringify!($SelfT), "::MIN", "`](#associatedconstant.MIN).")]
/// instead.
#[doc = concat!("[`", stringify!($SelfT), "::MIN", "`] instead.")]
///
/// Returns the smallest value that can be represented by this integer type.
#[stable(feature = "rust1", since = "1.0.0")]
@ -1818,8 +1817,7 @@ macro_rules! uint_impl {
pub const fn min_value() -> Self { Self::MIN }
/// New code should prefer to use
#[doc = concat!("[`", stringify!($SelfT), "::MAX", "`](#associatedconstant.MAX).")]
/// instead.
#[doc = concat!("[`", stringify!($SelfT), "::MAX", "`] instead.")]
///
/// Returns the largest value that can be represented by this integer type.
#[stable(feature = "rust1", since = "1.0.0")]

View File

@ -902,7 +902,8 @@ pub const unsafe fn read_unaligned<T>(src: *const T) -> T {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub unsafe fn write<T>(dst: *mut T, src: T) {
#[rustc_const_unstable(feature = "const_ptr_write", issue = "none")]
pub const unsafe fn write<T>(dst: *mut T, src: T) {
// SAFETY: the caller must guarantee that `dst` is valid for writes.
// `dst` cannot overlap `src` because the caller has mutable access
// to `dst` while `src` is owned by this function.
@ -998,14 +999,16 @@ pub unsafe fn write<T>(dst: *mut T, src: T) {
/// ```
#[inline]
#[stable(feature = "ptr_unaligned", since = "1.17.0")]
pub unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
#[rustc_const_unstable(feature = "const_ptr_write", issue = "none")]
pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
// SAFETY: the caller must guarantee that `dst` is valid for writes.
// `dst` cannot overlap `src` because the caller has mutable access
// to `dst` while `src` is owned by this function.
unsafe {
copy_nonoverlapping(&src as *const T as *const u8, dst as *mut u8, mem::size_of::<T>());
// We are calling the intrinsic directly to avoid function calls in the generated code.
intrinsics::forget(src);
}
mem::forget(src);
}
/// Performs a volatile read of the value from `src` without moving it. This

View File

@ -1003,8 +1003,9 @@ impl<T: ?Sized> *mut T {
///
/// [`ptr::write`]: crate::ptr::write()
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[rustc_const_unstable(feature = "const_ptr_write", issue = "none")]
#[inline]
pub unsafe fn write(self, val: T)
pub const unsafe fn write(self, val: T)
where
T: Sized,
{
@ -1057,8 +1058,9 @@ impl<T: ?Sized> *mut T {
///
/// [`ptr::write_unaligned`]: crate::ptr::write_unaligned()
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[rustc_const_unstable(feature = "const_ptr_write", issue = "none")]
#[inline]
pub unsafe fn write_unaligned(self, val: T)
pub const unsafe fn write_unaligned(self, val: T)
where
T: Sized,
{

View File

@ -49,3 +49,53 @@ fn mut_ptr_read() {
const UNALIGNED: u16 = unsafe { UNALIGNED_PTR.read_unaligned() };
assert_eq!(UNALIGNED, u16::from_ne_bytes([0x23, 0x45]));
}
#[test]
fn write() {
use core::ptr;
const fn write_aligned() -> i32 {
let mut res = 0;
unsafe {
ptr::write(&mut res as *mut _, 42);
}
res
}
const ALIGNED: i32 = write_aligned();
assert_eq!(ALIGNED, 42);
const fn write_unaligned() -> [u16; 2] {
let mut two_aligned = [0u16; 2];
unsafe {
let unaligned_ptr = (two_aligned.as_mut_ptr() as *mut u8).add(1) as *mut u16;
ptr::write_unaligned(unaligned_ptr, u16::from_ne_bytes([0x23, 0x45]));
}
two_aligned
}
const UNALIGNED: [u16; 2] = write_unaligned();
assert_eq!(UNALIGNED, [u16::from_ne_bytes([0x00, 0x23]), u16::from_ne_bytes([0x45, 0x00])]);
}
#[test]
fn mut_ptr_write() {
const fn aligned() -> i32 {
let mut res = 0;
unsafe {
(&mut res as *mut i32).write(42);
}
res
}
const ALIGNED: i32 = aligned();
assert_eq!(ALIGNED, 42);
const fn write_unaligned() -> [u16; 2] {
let mut two_aligned = [0u16; 2];
unsafe {
let unaligned_ptr = (two_aligned.as_mut_ptr() as *mut u8).add(1) as *mut u16;
unaligned_ptr.write_unaligned(u16::from_ne_bytes([0x23, 0x45]));
}
two_aligned
}
const UNALIGNED: [u16; 2] = write_unaligned();
assert_eq!(UNALIGNED, [u16::from_ne_bytes([0x00, 0x23]), u16::from_ne_bytes([0x45, 0x00])]);
}

View File

@ -14,6 +14,7 @@
#![feature(const_cell_into_inner)]
#![feature(const_maybe_uninit_assume_init)]
#![feature(const_ptr_read)]
#![feature(const_ptr_write)]
#![feature(const_ptr_offset)]
#![feature(control_flow_enum)]
#![feature(core_intrinsics)]

View File

@ -30,6 +30,7 @@ use crate::mem::transmute;
use crate::num;
use crate::str;
use crate::string;
use crate::sync::Arc;
/// `Error` is a trait representing the basic expectations for error values,
/// i.e., values of type `E` in [`Result<T, E>`]. Errors must describe
@ -507,6 +508,27 @@ impl<'a, T: Error + ?Sized> Error for &'a T {
}
}
#[stable(feature = "arc_error", since = "1.52.0")]
impl<T: Error + ?Sized> Error for Arc<T> {
#[allow(deprecated, deprecated_in_future)]
fn description(&self) -> &str {
Error::description(&**self)
}
#[allow(deprecated)]
fn cause(&self) -> Option<&dyn Error> {
Error::cause(&**self)
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
Error::source(&**self)
}
fn backtrace(&self) -> Option<&Backtrace> {
Error::backtrace(&**self)
}
}
#[stable(feature = "fmt_error", since = "1.11.0")]
impl Error for fmt::Error {
#[allow(deprecated)]

View File

@ -264,7 +264,6 @@
#![feature(exhaustive_patterns)]
#![feature(extend_one)]
#![feature(extended_key_value_attributes)]
#![feature(external_doc)]
#![feature(fn_traits)]
#![feature(format_args_nl)]
#![feature(gen_future)]

View File

@ -4,7 +4,7 @@
//! library. Each macro is available for use when linking against the standard
//! library.
#[doc(include = "../../core/src/macros/panic.md")]
#[doc = include_str!("../../core/src/macros/panic.md")]
#[macro_export]
#[rustc_builtin_macro = "std_panic"]
#[stable(feature = "rust1", since = "1.0.0")]

View File

@ -18,7 +18,7 @@ macro_rules! type_alias_no_nz {
$Docfile:tt, $Alias:ident = $Real:ty;
$( $Cfg:tt )*
} => {
#[doc(include = $Docfile)]
#[doc = include_str!($Docfile)]
$( $Cfg )*
#[stable(feature = "raw_os", since = "1.1.0")]
pub type $Alias = $Real;

View File

@ -1,2 +1,2 @@
/* ignore-tidy-linelength */
/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}

View File

@ -134,15 +134,20 @@ impl TryFrom<ResolveRes> for Res {
}
}
#[derive(Debug)]
/// A link failed to resolve.
#[derive(Debug)]
enum ResolutionFailure<'a> {
/// This resolved, but with the wrong namespace.
///
/// `Namespace` is the namespace specified with a disambiguator
/// (as opposed to the actual namespace of the `Res`).
WrongNamespace(Res, /* disambiguated */ Namespace),
/// The link failed to resolve. `resolution_failure` should look to see if there's
WrongNamespace {
/// What the link resolved to.
res: Res,
/// The expected namespace for the resolution, determined from the link's disambiguator.
///
/// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`],
/// even though `Result`'s actual namespace is [`Namespace::TypeNS`].
expected_ns: Namespace,
},
/// The link failed to resolve. [`resolution_failure`] should look to see if there's
/// a more helpful error that can be given.
NotResolved {
/// The scope the link was resolved in.
@ -157,12 +162,11 @@ enum ResolutionFailure<'a> {
unresolved: Cow<'a, str>,
},
/// This happens when rustdoc can't determine the parent scope for an item.
///
/// It is always a bug in rustdoc.
NoParentItem,
/// This link has malformed generic parameters; e.g., the angle brackets are unbalanced.
MalformedGenerics(MalformedGenerics),
/// Used to communicate that this should be ignored, but shouldn't be reported to the user
/// Used to communicate that this should be ignored, but shouldn't be reported to the user.
///
/// This happens when there is no disambiguator and one of the namespaces
/// failed to resolve.
@ -216,7 +220,7 @@ impl ResolutionFailure<'a> {
/// Returns the full resolution of the link, if present.
fn full_res(&self) -> Option<Res> {
match self {
Self::WrongNamespace(res, _) => Some(*res),
Self::WrongNamespace { res, expected_ns: _ } => Some(*res),
_ => None,
}
}
@ -1308,20 +1312,20 @@ impl LinkCollector<'_, '_> {
let extra_fragment = &key.extra_fragment;
match disambiguator.map(Disambiguator::ns) {
Some(ns @ (ValueNS | TypeNS)) => {
match self.resolve(path_str, ns, base_node, extra_fragment) {
Some(expected_ns @ (ValueNS | TypeNS)) => {
match self.resolve(path_str, expected_ns, base_node, extra_fragment) {
Ok(res) => Some(res),
Err(ErrorKind::Resolve(box mut kind)) => {
// We only looked in one namespace. Try to give a better error if possible.
if kind.full_res().is_none() {
let other_ns = if ns == ValueNS { TypeNS } else { ValueNS };
let other_ns = if expected_ns == ValueNS { TypeNS } else { ValueNS };
// FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`
// See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach
for &new_ns in &[other_ns, MacroNS] {
if let Some(res) =
self.check_full_res(new_ns, path_str, base_node, extra_fragment)
{
kind = ResolutionFailure::WrongNamespace(res, ns);
kind = ResolutionFailure::WrongNamespace { res, expected_ns };
break;
}
}
@ -1396,7 +1400,7 @@ impl LinkCollector<'_, '_> {
// Constructors are picked up in the type namespace.
match res {
Res::Def(DefKind::Ctor(..), _) => {
Err(ResolutionFailure::WrongNamespace(res, TypeNS))
Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS })
}
_ => {
match (fragment, extra_fragment.clone()) {
@ -1457,7 +1461,8 @@ impl LinkCollector<'_, '_> {
if let Some(res) =
self.check_full_res(ns, path_str, base_node, extra_fragment)
{
kind = ResolutionFailure::WrongNamespace(res, MacroNS);
kind =
ResolutionFailure::WrongNamespace { res, expected_ns: MacroNS };
break;
}
}
@ -1889,7 +1894,7 @@ fn resolution_failure(
let note = match failure {
ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
ResolutionFailure::Dummy => continue,
ResolutionFailure::WrongNamespace(res, expected_ns) => {
ResolutionFailure::WrongNamespace { res, expected_ns } => {
if let Res::Def(kind, _) = res {
let disambiguator = Disambiguator::Kind(kind);
suggest_disambiguator(
@ -1910,7 +1915,7 @@ fn resolution_failure(
}
ResolutionFailure::NoParentItem => {
diag.level = rustc_errors::Level::Bug;
"all intra doc links should have a parent item".to_owned()
"all intra-doc links should have a parent item".to_owned()
}
ResolutionFailure::MalformedGenerics(variant) => match variant {
MalformedGenerics::UnbalancedAngleBrackets => {

View File

@ -0,0 +1,5 @@
const FOO: usize = 0;
fn main() {
FOO(); //~ ERROR expected function, found `usize`
}

View File

@ -0,0 +1,14 @@
error[E0618]: expected function, found `usize`
--> $DIR/const-as-fn.rs:4:5
|
LL | const FOO: usize = 0;
| --------------------- `FOO` defined here
...
LL | FOO();
| ^^^--
| |
| call expression requires function
error: aborting due to previous error
For more information about this error, try `rustc --explain E0618`.

View File

@ -18,7 +18,7 @@ error[E0618]: expected function, found `i32`
--> $DIR/E0618.rs:9:5
|
LL | let x = 0i32;
| - `i32` defined here
| - `x` has type `i32`
LL | x();
| ^--
| |

View File

@ -8,7 +8,11 @@ pub trait IpuIterator {
fn ipu_flatten(&self) -> u32 {
0
}
#[unstable(feature = "assoc_const_ipu_iter", issue = "99999")]
const C: i32;
}
#[stable(feature = "ipu_iterator", since = "1.0.0")]
impl IpuIterator for char {}
impl IpuIterator for char {
const C: i32 = 42;
}

View File

@ -2,6 +2,10 @@ pub trait IpuItertools {
fn ipu_flatten(&self) -> u32 {
1
}
const C: i32;
}
impl IpuItertools for char {}
impl IpuItertools for char {
const C: i32 = 1;
}

View File

@ -14,6 +14,9 @@ use inference_unstable_itertools::IpuItertools;
fn main() {
assert_eq!('x'.ipu_flatten(), 1);
//~^ WARN a method with this name may be added to the standard library in the future
//~^^ WARN once this method is added to the standard library, the ambiguity may cause an error
//~^ WARN an associated function with this name may be added to the standard library in the future
//~| WARN once this associated item is added to the standard library, the ambiguity may cause an
assert_eq!(char::C, 1);
//~^ WARN an associated constant with this name may be added to the standard library in the future
//~| WARN once this associated item is added to the standard library, the ambiguity may cause an
}

View File

@ -1,14 +1,24 @@
warning: a method with this name may be added to the standard library in the future
warning: an associated function with this name may be added to the standard library in the future
--> $DIR/inference_unstable.rs:16:20
|
LL | assert_eq!('x'.ipu_flatten(), 1);
| ^^^^^^^^^^^
|
= note: `#[warn(unstable_name_collisions)]` on by default
= warning: once this method is added to the standard library, the ambiguity may cause an error or change in behavior!
= warning: once this associated item is added to the standard library, the ambiguity may cause an error or change in behavior!
= note: for more information, see issue #48919 <https://github.com/rust-lang/rust/issues/48919>
= help: call with fully qualified syntax `inference_unstable_itertools::IpuItertools::ipu_flatten(...)` to keep using the current method
= help: add `#![feature(ipu_flatten)]` to the crate attributes to enable `inference_unstable_iterator::IpuIterator::ipu_flatten`
warning: 1 warning emitted
warning: an associated constant with this name may be added to the standard library in the future
--> $DIR/inference_unstable.rs:19:16
|
LL | assert_eq!(char::C, 1);
| ^^^^^^^ help: use the fully qualified path to the associated const: `<char as IpuItertools>::C`
|
= warning: once this associated item is added to the standard library, the ambiguity may cause an error or change in behavior!
= note: for more information, see issue #48919 <https://github.com/rust-lang/rust/issues/48919>
= help: add `#![feature(assoc_const_ipu_iter)]` to the crate attributes to enable `inference_unstable_iterator::IpuIterator::C`
warning: 2 warnings emitted

View File

@ -2,7 +2,7 @@ error[E0618]: expected function, found `i32`
--> $DIR/issue-10969.rs:2:5
|
LL | fn func(i: i32) {
| - `i32` defined here
| - `i` has type `i32`
LL | i();
| ^--
| |
@ -12,7 +12,7 @@ error[E0618]: expected function, found `i32`
--> $DIR/issue-10969.rs:6:5
|
LL | let i = 0i32;
| - `i32` defined here
| - `i` has type `i32`
LL | i();
| ^--
| |

View File

@ -2,7 +2,7 @@ error[E0618]: expected function, found `U`
--> $DIR/issue-21701.rs:2:13
|
LL | fn foo<U>(t: U) {
| - `U` defined here
| - `t` has type `U`
LL | let y = t();
| ^--
| |

View File

@ -2,7 +2,7 @@ error[E0618]: expected function, found `&str`
--> $DIR/issue-22468.rs:3:13
|
LL | let foo = "bar";
| --- `&str` defined here
| --- `foo` has type `&str`
LL | let x = foo("baz");
| ^^^-------
| |

View File

@ -5,7 +5,7 @@ LL | $not_a_function($some_argument)
| ------------------------------- call expression requires function
...
LL | let mut value_a = 0;
| ----------- `{integer}` defined here
| ----------- `value_a` has type `{integer}`
LL | let mut value_b = 0;
LL | macro_panic!(value_a, value_b);
| ^^^^^^^

View File

@ -14,7 +14,7 @@ error[E0618]: expected function, found `{integer}`
--> $DIR/parse-error-correct.rs:7:13
|
LL | let y = 42;
| - `{integer}` defined here
| - `y` has type `{integer}`
LL | let x = y.;
LL | let x = y.();
| ^---

View File

@ -0,0 +1,7 @@
struct S;
fn repro_ref(thing: S) {
thing(); //~ ERROR expected function, found `S`
}
fn main() {}

View File

@ -0,0 +1,13 @@
error[E0618]: expected function, found `S`
--> $DIR/80853.rs:4:5
|
LL | fn repro_ref(thing: S) {
| ----- `thing` has type `S`
LL | thing();
| ^^^^^--
| |
| call expression requires function
error: aborting due to previous error
For more information about this error, try `rustc --explain E0618`.

View File

@ -0,0 +1,5 @@
use std::result;
impl result { //~ ERROR expected type, found module `result`
fn into_future() -> Err {} //~ ERROR expected type, found variant `Err`
}
fn main() {}

View File

@ -0,0 +1,20 @@
error[E0573]: expected type, found module `result`
--> $DIR/do-not-attempt-to-add-suggestions-with-no-changes.rs:2:6
|
LL | impl result {
| ^^^^^^ help: an enum with a similar name exists: `Result`
|
::: $SRC_DIR/core/src/result.rs:LL:COL
|
LL | pub enum Result<T, E> {
| --------------------- similarly named enum `Result` defined here
error[E0573]: expected type, found variant `Err`
--> $DIR/do-not-attempt-to-add-suggestions-with-no-changes.rs:3:25
|
LL | fn into_future() -> Err {}
| ^^^ not a type
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0573`.

View File

@ -0,0 +1,24 @@
// run-rustfix
fn main() {
let a: usize = 123;
let b: &usize = &a;
if true {
a
} else {
*b //~ ERROR `if` and `else` have incompatible types [E0308]
};
if true {
1
} else {
1 //~ ERROR `if` and `else` have incompatible types [E0308]
};
if true {
1
} else {
1 //~ ERROR `if` and `else` have incompatible types [E0308]
};
}

View File

@ -0,0 +1,24 @@
// run-rustfix
fn main() {
let a: usize = 123;
let b: &usize = &a;
if true {
a
} else {
b //~ ERROR `if` and `else` have incompatible types [E0308]
};
if true {
1
} else {
&1 //~ ERROR `if` and `else` have incompatible types [E0308]
};
if true {
1
} else {
&mut 1 //~ ERROR `if` and `else` have incompatible types [E0308]
};
}

View File

@ -0,0 +1,48 @@
error[E0308]: `if` and `else` have incompatible types
--> $DIR/issue-82361.rs:10:9
|
LL | / if true {
LL | | a
| | - expected because of this
LL | | } else {
LL | | b
| | ^
| | |
| | expected `usize`, found `&usize`
| | help: consider dereferencing the borrow: `*b`
LL | | };
| |_____- `if` and `else` have incompatible types
error[E0308]: `if` and `else` have incompatible types
--> $DIR/issue-82361.rs:16:9
|
LL | / if true {
LL | | 1
| | - expected because of this
LL | | } else {
LL | | &1
| | -^
| | |
| | expected integer, found `&{integer}`
| | help: consider removing the `&`
LL | | };
| |_____- `if` and `else` have incompatible types
error[E0308]: `if` and `else` have incompatible types
--> $DIR/issue-82361.rs:22:9
|
LL | / if true {
LL | | 1
| | - expected because of this
LL | | } else {
LL | | &mut 1
| | -----^
| | |
| | expected integer, found `&mut {integer}`
| | help: consider removing the `&mut`
LL | | };
| |_____- `if` and `else` have incompatible types
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0308`.

View File

@ -0,0 +1,7 @@
pub fn g(t: i32) -> i32 { t }
// This function imitates `dbg!` so that future changes
// to its macro definition won't make this test a dud.
#[macro_export]
macro_rules! d {
($e:expr) => { match $e { x => { $crate::g(x) } } }
}

View File

@ -0,0 +1,13 @@
// aux-build:issue-81943-lib.rs
extern crate issue_81943_lib as lib;
fn f<F: Fn(i32)>(f: F) { f(0); }
fn g(t: i32) -> i32 { t }
fn main() {
f(|x| lib::d!(x)); //~ERROR
f(|x| match x { tmp => { g(tmp) } }); //~ERROR
macro_rules! d {
($e:expr) => { match $e { x => { g(x) } } } //~ERROR
}
f(|x| d!(x));
}

View File

@ -0,0 +1,51 @@
error[E0308]: mismatched types
--> $DIR/issue-81943.rs:7:9
|
LL | f(|x| lib::d!(x));
| ^^^^^^^^^^ expected `()`, found `i32`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> $DIR/issue-81943.rs:8:28
|
LL | f(|x| match x { tmp => { g(tmp) } });
| -------------------^^^^^^----
| | |
| | expected `()`, found `i32`
| expected this to be `()`
|
help: consider using a semicolon here
|
LL | f(|x| match x { tmp => { g(tmp); } });
| ^
help: consider using a semicolon here
|
LL | f(|x| match x { tmp => { g(tmp) } };);
| ^
error[E0308]: mismatched types
--> $DIR/issue-81943.rs:10:38
|
LL | ($e:expr) => { match $e { x => { g(x) } } }
| ------------------^^^^----
| | |
| | expected `()`, found `i32`
| expected this to be `()`
LL | }
LL | f(|x| d!(x));
| ----- in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using a semicolon here
|
LL | ($e:expr) => { match $e { x => { g(x); } } }
| ^
help: consider using a semicolon here
|
LL | ($e:expr) => { match $e { x => { g(x) } }; }
| ^
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0308`.

View File

@ -1,7 +1,7 @@
---
name: Bug Report
about: Create a bug report for Clippy
labels: L-bug
labels: C-bug
---
<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,

View File

@ -1,7 +1,7 @@
---
name: Bug Report (False Negative)
about: Create a bug report about missing warnings from a lint
labels: L-bug, L-false-negative
labels: C-bug, I-false-negative
---
<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,

View File

@ -1,7 +1,7 @@
---
name: Bug Report (False Positive)
about: Create a bug report about a wrongly emitted lint warning
labels: L-bug, L-false-positive
labels: C-bug, I-false-positive
---
<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,

View File

@ -1,7 +1,7 @@
---
name: Internal Compiler Error
about: Create a report for an internal compiler error in Clippy.
labels: L-bug, L-crash
labels: C-bug, I-ICE
---
<!--
Thank you for finding an Internal Compiler Error! 🧊 If possible, try to provide

View File

@ -1,7 +1,7 @@
---
name: New lint suggestion
about: Suggest a new Clippy lint.
labels: L-lint
labels: A-lint
---
### What it does

View File

@ -53,16 +53,8 @@ jobs:
- name: Test "--fix -Zunstable-options"
run: cargo run --features deny-warnings,internal-lints --bin cargo-clippy -- clippy --fix -Zunstable-options
- name: Test
run: cargo test --features deny-warnings,internal-lints
- name: Test clippy_lints
run: cargo test --features deny-warnings,internal-lints
working-directory: clippy_lints
- name: Test rustc_tools_util
run: cargo test --features deny-warnings
working-directory: rustc_tools_util
- name: Test Workspace
run: cargo test --all --features deny-warnings,internal-lints
- name: Test clippy_dev
run: cargo test --features deny-warnings

View File

@ -112,16 +112,8 @@ jobs:
- name: Build
run: cargo build --features deny-warnings,internal-lints
- name: Test
run: cargo test --features deny-warnings,internal-lints
- name: Test clippy_lints
run: cargo test --features deny-warnings,internal-lints
working-directory: clippy_lints
- name: Test rustc_tools_util
run: cargo test --features deny-warnings
working-directory: rustc_tools_util
- name: Test Workspace
run: cargo test --all --features deny-warnings,internal-lints
- name: Test clippy_dev
run: cargo test --features deny-warnings

View File

@ -18,6 +18,7 @@ out
*Cargo.lock
/target
/clippy_lints/target
/clippy_utils/target
/clippy_workspace_tests/target
/clippy_dev/target
/rustc_tools_util/target

View File

@ -6,13 +6,128 @@ document.
## Unreleased / In Rust Nightly
[4911ab1...master](https://github.com/rust-lang/rust-clippy/compare/4911ab1...master)
[3e41797...master](https://github.com/rust-lang/rust-clippy/compare/3e41797...master)
## Rust 1.51
Current beta, release 2021-03-25
[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
### New Lints
* [`upper_case_acronyms`]
[#6475](https://github.com/rust-lang/rust-clippy/pull/6475)
* [`from_over_into`] [#6476](https://github.com/rust-lang/rust-clippy/pull/6476)
* [`case_sensitive_file_extension_comparisons`]
[#6500](https://github.com/rust-lang/rust-clippy/pull/6500)
* [`needless_question_mark`]
[#6507](https://github.com/rust-lang/rust-clippy/pull/6507)
* [`missing_panics_doc`]
[#6523](https://github.com/rust-lang/rust-clippy/pull/6523)
* [`redundant_slicing`]
[#6528](https://github.com/rust-lang/rust-clippy/pull/6528)
* [`vec_init_then_push`]
[#6538](https://github.com/rust-lang/rust-clippy/pull/6538)
* [`ptr_as_ptr`] [#6542](https://github.com/rust-lang/rust-clippy/pull/6542)
* [`collapsible_else_if`] (split out from `collapsible_if`)
[#6544](https://github.com/rust-lang/rust-clippy/pull/6544)
* [`inspect_for_each`] [#6577](https://github.com/rust-lang/rust-clippy/pull/6577)
* [`manual_filter_map`]
[#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
* [`exhaustive_enums`]
[#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
* [`exhaustive_structs`]
[#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
### Moves and Deprecations
* Replace [`find_map`] with [`manual_find_map`]
[#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
* [`unknown_clippy_lints`] Now integrated in the `unknown_lints` rustc lint
[#6653](https://github.com/rust-lang/rust-clippy/pull/6653)
### Enhancements
* [`ptr_arg`] Now also suggests to use `&Path` instead of `&PathBuf`
[#6506](https://github.com/rust-lang/rust-clippy/pull/6506)
* [`cast_ptr_alignment`] Also lint when the `pointer::cast` method is used
[#6557](https://github.com/rust-lang/rust-clippy/pull/6557)
* [`collapsible_match`] Now also deals with `&` and `*` operators in the `match`
scrutinee [#6619](https://github.com/rust-lang/rust-clippy/pull/6619)
### False Positive Fixes
* [`similar_names`] Ignore underscore prefixed names
[#6403](https://github.com/rust-lang/rust-clippy/pull/6403)
* [`print_literal`] and [`write_literal`] No longer lint numeric literals
[#6408](https://github.com/rust-lang/rust-clippy/pull/6408)
* [`large_enum_variant`] No longer lints in external macros
[#6485](https://github.com/rust-lang/rust-clippy/pull/6485)
* [`empty_enum`] Only lint if `never_type` feature is enabled
[#6513](https://github.com/rust-lang/rust-clippy/pull/6513)
* [`field_reassign_with_default`] No longer lints in macros
[#6553](https://github.com/rust-lang/rust-clippy/pull/6553)
* [`size_of_in_element_count`] No longer lints when dividing by element size
[#6578](https://github.com/rust-lang/rust-clippy/pull/6578)
* [`needless_return`] No longer lints in macros
[#6586](https://github.com/rust-lang/rust-clippy/pull/6586)
* [`match_overlapping_arm`] No longer lint when first arm is completely included
in second arm [#6603](https://github.com/rust-lang/rust-clippy/pull/6603)
* [`doc_markdown`] Add `WebGL` to the default configuration as an allowed
identifier [#6605](https://github.com/rust-lang/rust-clippy/pull/6605)
### Suggestion Fixes/Improvements
* [`field_reassign_with_default`] Don't expand macro in lint suggestion
[#6531](https://github.com/rust-lang/rust-clippy/pull/6531)
* [`match_like_matches_macro`] Strip references in suggestion
[#6532](https://github.com/rust-lang/rust-clippy/pull/6532)
* [`single_match`] Suggest `if` over `if let` when possible
[#6574](https://github.com/rust-lang/rust-clippy/pull/6574)
* [`ref_in_deref`] Use parentheses correctly in suggestion
[#6609](https://github.com/rust-lang/rust-clippy/pull/6609)
* [`stable_sort_primitive`] Clarify error message
[#6611](https://github.com/rust-lang/rust-clippy/pull/6611)
### ICE Fixes
* [`zero_sized_map_values`]
[#6582](https://github.com/rust-lang/rust-clippy/pull/6582)
### Documentation Improvements
* Improve search performance on the Clippy website and make it possible to
directly search for lints on the GitHub issue tracker
[#6483](https://github.com/rust-lang/rust-clippy/pull/6483)
* Clean up `README.md` by removing outdated paragraph
[#6488](https://github.com/rust-lang/rust-clippy/pull/6488)
* [`await_holding_refcell_ref`] and [`await_holding_lock`]
[#6585](https://github.com/rust-lang/rust-clippy/pull/6585)
* [`as_conversions`] [#6608](https://github.com/rust-lang/rust-clippy/pull/6608)
### Others
* Clippy now has a [Roadmap] for 2021. If you like to get involved in a bigger
project, take a look at the [Roadmap project page]. All issues listed there
are actively mentored
[#6462](https://github.com/rust-lang/rust-clippy/pull/6462)
* The Clippy version number now corresponds to the Rust version number
[#6526](https://github.com/rust-lang/rust-clippy/pull/6526)
* Fix oversight which caused Clippy to lint deps in some environments, where
`CLIPPY_TESTS=true` was set somewhere
[#6575](https://github.com/rust-lang/rust-clippy/pull/6575)
* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool
[#6469](https://github.com/rust-lang/rust-clippy/pull/6469)
[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md
[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3
## Rust 1.50
Current beta, release 2021-02-11
Current stable, released 2021-02-11
[b20d4c1...4911ab1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4911ab1)
[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
### New Lints
@ -90,6 +205,8 @@ Current beta, release 2021-02-11
* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]:
Both now ignore enums with frozen variants
[#6110](https://github.com/rust-lang/rust-clippy/pull/6110)
* [`field_reassign_with_default`] No longer lint for private fields
[#6537](https://github.com/rust-lang/rust-clippy/pull/6537)
### Suggestion Fixes/Improvements
@ -137,7 +254,7 @@ Current beta, release 2021-02-11
## Rust 1.49
Current stable, released 2020-12-31
Released 2020-12-31
[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1)
@ -1910,6 +2027,7 @@ Released 2018-09-13
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`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_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
@ -1975,6 +2093,7 @@ Released 2018-09-13
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
@ -1990,6 +2109,7 @@ Released 2018-09-13
[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
@ -2042,6 +2162,7 @@ Released 2018-09-13
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or

View File

@ -37,8 +37,8 @@ tempfile = { version = "3.1.0", optional = true }
[dev-dependencies]
cargo_metadata = "0.12"
compiletest_rs = { version = "0.5.0", features = ["tmp"] }
tester = "0.7"
compiletest_rs = { version = "0.6.0", features = ["tmp"] }
tester = "0.9"
clippy-mini-macro-test = { version = "0.2", path = "mini-macro" }
serde = { version = "1.0", features = ["derive"] }
derive-new = "0.5"

View File

@ -98,6 +98,23 @@ If you want to run Clippy **only** on the given crate, use the `--no-deps` optio
cargo clippy -p example -- --no-deps
```
### As a rustc replacement (`clippy-driver`)
Clippy can also be used in projects that do not use cargo. To do so, you will need to replace
your `rustc` compilation commands with `clippy-driver`. For example, if your project runs:
```terminal
rustc --edition 2018 -Cpanic=abort foo.rs
```
Then, to enable Clippy, you will need to call:
```terminal
clippy-driver --edition 2018 -Cpanic=abort foo.rs
```
Note that `rustc` will still run, i.e. it will still emit the output files it normally does.
### Travis CI
You can add Clippy to Travis CI in the same way you use it locally:

View File

@ -19,8 +19,9 @@ shell-escape = "0.1"
tar = { version = "0.4.30", optional = true }
toml = { version = "0.5", optional = true }
ureq = { version = "2.0.0-rc3", optional = true }
rayon = { version = "1.5.0", optional = true }
walkdir = "2"
[features]
lintcheck = ["flate2", "serde_json", "tar", "toml", "ureq", "serde", "fs_extra"]
lintcheck = ["flate2", "serde_json", "tar", "toml", "ureq", "serde", "fs_extra", "rayon"]
deny-warnings = []

View File

@ -1,28 +1,77 @@
# Clippy Dev Tool
The Clippy Dev Tool is a tool to ease Clippy development, similar to `rustc`s `x.py`.
The Clippy Dev Tool is a tool to ease Clippy development, similar to `rustc`s
`x.py`.
Functionalities (incomplete):
## `lintcheck`
Runs clippy on a fixed set of crates read from `clippy_dev/lintcheck_crates.toml`
and saves logs of the lint warnings into the repo.
We can then check the diff and spot new or disappearing warnings.
Runs clippy on a fixed set of crates read from
`clippy_dev/lintcheck_crates.toml` and saves logs of the lint warnings into the
repo. We can then check the diff and spot new or disappearing warnings.
From the repo root, run:
````
```
cargo run --target-dir clippy_dev/target --package clippy_dev \
--bin clippy_dev --manifest-path clippy_dev/Cargo.toml --features lintcheck -- lintcheck
````
```
or
````
```
cargo dev-lintcheck
````
```
By default the logs will be saved into `lintcheck-logs/lintcheck_crates_logs.txt`.
By default the logs will be saved into
`lintcheck-logs/lintcheck_crates_logs.txt`.
You can set a custom sources.toml by adding `--crates-toml custom.toml`
where `custom.toml` must be a relative path from the repo root.
You can set a custom sources.toml by adding `--crates-toml custom.toml` or using
`LINTCHECK_TOML="custom.toml"` where `custom.toml` must be a relative path from
the repo root.
The results will then be saved to `lintcheck-logs/custom_logs.toml`.
### Configuring the Crate Sources
The sources to check are saved in a `toml` file. There are three types of
sources.
1. Crates-io Source
```toml
bitflags = {name = "bitflags", versions = ['1.2.1']}
```
Requires a "name" and one or multiple "versions" to be checked.
2. `git` Source
````toml
puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
````
Requires a name, the url to the repo and unique identifier of a commit,
branch or tag which is checked out before linting. There is no way to always
check `HEAD` because that would lead to changing lint-results as the repo
would get updated. If `git_url` or `git_hash` is missing, an error will be
thrown.
3. Local Dependency
```toml
clippy = {name = "clippy", path = "/home/user/clippy"}
```
For when you want to add a repository that is not published yet.
#### Command Line Options (optional)
```toml
bitflags = {name = "bitflags", versions = ['1.2.1'], options = ['-Wclippy::pedantic', '-Wclippy::cargo']}
```
It is possible to specify command line options for each crate. This makes it
possible to only check a crate for certain lint groups. If no options are
specified, the lint groups `clippy::all`, `clippy::pedantic`, and
`clippy::cargo` are checked. If an empty array is specified only `clippy::all`
is checked.
**Note:** `-Wclippy::all` is always enabled by default, unless `-Aclippy::all`
is explicitly specified in the options.

View File

@ -42,9 +42,10 @@ pub fn bless(ignore_timestamp: bool) {
.for_each(|f| {
let test_name = f.path().strip_prefix(test_suite_dir).unwrap();
for &ext in &["stdout", "stderr", "fixed"] {
let test_name_ext = format!("stage-id.{}", ext);
update_reference_file(
f.path().with_extension(ext),
test_name.with_extension(ext),
test_name.with_extension(test_name_ext),
ignore_timestamp,
);
}

View File

@ -11,20 +11,22 @@ use crate::clippy_project_root;
use std::collections::HashMap;
use std::process::Command;
use std::{fmt, fs::write, path::PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{env, fmt, fs::write, path::PathBuf};
use clap::ArgMatches;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
// use this to store the crates when interacting with the crates.toml file
/// List of sources to check, loaded from a .toml file
#[derive(Debug, Serialize, Deserialize)]
struct CrateList {
struct SourceList {
crates: HashMap<String, TomlCrate>,
}
// crate data we stored in the toml, can have multiple versions per crate
// A single TomlCrate is laster mapped to several CrateSources in that case
/// A crate source stored inside the .toml
/// will be translated into on one of the `CrateSource` variants
#[derive(Debug, Serialize, Deserialize)]
struct TomlCrate {
name: String,
@ -32,27 +34,42 @@ struct TomlCrate {
git_url: Option<String>,
git_hash: Option<String>,
path: Option<String>,
options: Option<Vec<String>>,
}
// represents an archive we download from crates.io, or a git repo, or a local repo
#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)]
enum CrateSource {
CratesIo { name: String, version: String },
Git { name: String, url: String, commit: String },
Path { name: String, path: PathBuf },
CratesIo {
name: String,
version: String,
options: Option<Vec<String>>,
},
Git {
name: String,
url: String,
commit: String,
options: Option<Vec<String>>,
},
Path {
name: String,
path: PathBuf,
options: Option<Vec<String>>,
},
}
// represents the extracted sourcecode of a crate
// we actually don't need to special-case git repos here because it does not matter for clippy, yay!
// (clippy only needs a simple path)
/// Represents the actual source code of a crate that we ran "cargo clippy" on
#[derive(Debug)]
struct Crate {
version: String,
name: String,
// path to the extracted sources that clippy can check
path: PathBuf,
options: Option<Vec<String>>,
}
/// A single warning that clippy issued while checking a `Crate`
#[derive(Debug)]
struct ClippyWarning {
crate_name: String,
@ -62,7 +79,7 @@ struct ClippyWarning {
column: String,
linttype: String,
message: String,
ice: bool,
is_ice: bool,
}
impl std::fmt::Display for ClippyWarning {
@ -76,9 +93,12 @@ impl std::fmt::Display for ClippyWarning {
}
impl CrateSource {
/// Makes the sources available on the disk for clippy to check.
/// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
/// copies a local folder
fn download_and_extract(&self) -> Crate {
match self {
CrateSource::CratesIo { name, version } => {
CrateSource::CratesIo { name, version, options } => {
let extract_dir = PathBuf::from("target/lintcheck/crates");
let krate_download_dir = PathBuf::from("target/lintcheck/downloads");
@ -110,9 +130,15 @@ impl CrateSource {
version: version.clone(),
name: name.clone(),
path: extract_dir.join(format!("{}-{}/", name, version)),
options: options.clone(),
}
},
CrateSource::Git { name, url, commit } => {
CrateSource::Git {
name,
url,
commit,
options,
} => {
let repo_path = {
let mut repo_path = PathBuf::from("target/lintcheck/crates");
// add a -git suffix in case we have the same crate from crates.io and a git repo
@ -122,27 +148,37 @@ impl CrateSource {
// clone the repo if we have not done so
if !repo_path.is_dir() {
println!("Cloning {} and checking out {}", url, commit);
Command::new("git")
if !Command::new("git")
.arg("clone")
.arg(url)
.arg(&repo_path)
.output()
.expect("Failed to clone git repo!");
.status()
.expect("Failed to clone git repo!")
.success()
{
eprintln!("Failed to clone {} into {}", url, repo_path.display())
}
}
// check out the commit/branch/whatever
Command::new("git")
if !Command::new("git")
.arg("checkout")
.arg(commit)
.output()
.expect("Failed to check out commit");
.current_dir(&repo_path)
.status()
.expect("Failed to check out commit")
.success()
{
eprintln!("Failed to checkout {} of repo at {}", commit, repo_path.display())
}
Crate {
version: commit.clone(),
name: name.clone(),
path: repo_path,
options: options.clone(),
}
},
CrateSource::Path { name, path } => {
CrateSource::Path { name, path, options } => {
use fs_extra::dir;
// simply copy the entire directory into our target dir
@ -171,6 +207,7 @@ impl CrateSource {
version: String::from("local"),
name: name.clone(),
path: crate_root,
options: options.clone(),
}
},
}
@ -178,24 +215,56 @@ impl CrateSource {
}
impl Crate {
fn run_clippy_lints(&self, cargo_clippy_path: &PathBuf) -> Vec<ClippyWarning> {
println!("Linting {} {}...", &self.name, &self.version);
/// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
/// issued
fn run_clippy_lints(
&self,
cargo_clippy_path: &PathBuf,
target_dir_index: &AtomicUsize,
thread_limit: usize,
total_crates_to_lint: usize,
) -> Vec<ClippyWarning> {
// advance the atomic index by one
let index = target_dir_index.fetch_add(1, Ordering::SeqCst);
// "loop" the index within 0..thread_limit
let target_dir_index = index % thread_limit;
let perc = ((index * 100) as f32 / total_crates_to_lint as f32) as u8;
if thread_limit == 1 {
println!(
"{}/{} {}% Linting {} {}",
index, total_crates_to_lint, perc, &self.name, &self.version
);
} else {
println!(
"{}/{} {}% Linting {} {} in target dir {:?}",
index, total_crates_to_lint, perc, &self.name, &self.version, target_dir_index
);
}
let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir/");
let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir");
let mut args = vec!["--", "--message-format=json", "--", "--cap-lints=warn"];
if let Some(options) = &self.options {
for opt in options {
args.push(opt);
}
} else {
args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
}
let all_output = std::process::Command::new(&cargo_clippy_path)
.env("CARGO_TARGET_DIR", shared_target_dir)
// use the looping index to create individual target dirs
.env(
"CARGO_TARGET_DIR",
shared_target_dir.join(format!("_{:?}", target_dir_index)),
)
// lint warnings will look like this:
// src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter`
.args(&[
"--",
"--message-format=json",
"--",
"--cap-lints=warn",
"-Wclippy::pedantic",
"-Wclippy::cargo",
])
.args(&args)
.current_dir(&self.path)
.output()
.unwrap_or_else(|error| {
@ -211,28 +280,69 @@ impl Crate {
let warnings: Vec<ClippyWarning> = output_lines
.into_iter()
// get all clippy warnings and ICEs
.filter(|line| line.contains("clippy::") || line.contains("internal compiler error: "))
.filter(|line| filter_clippy_warnings(&line))
.map(|json_msg| parse_json_message(json_msg, &self))
.collect();
warnings
}
}
fn build_clippy() {
Command::new("cargo")
.arg("build")
.output()
.expect("Failed to build clippy!");
/// takes a single json-formatted clippy warnings and returns true (we are interested in that line)
/// or false (we aren't)
fn filter_clippy_warnings(line: &str) -> bool {
// we want to collect ICEs because clippy might have crashed.
// these are summarized later
if line.contains("internal compiler error: ") {
return true;
}
// in general, we want all clippy warnings
// however due to some kind of bug, sometimes there are absolute paths
// to libcore files inside the message
// or we end up with cargo-metadata output (https://github.com/rust-lang/rust-clippy/issues/6508)
// filter out these message to avoid unnecessary noise in the logs
if line.contains("clippy::")
&& !(line.contains("could not read cargo metadata")
|| (line.contains(".rustup") && line.contains("toolchains")))
{
return true;
}
false
}
// get a list of CrateSources we want to check from a "lintcheck_crates.toml" file.
/// get the path to lintchecks crate sources .toml file, check LINTCHECK_TOML first but if it's
/// empty use the default path
fn lintcheck_config_toml(toml_path: Option<&str>) -> PathBuf {
PathBuf::from(
env::var("LINTCHECK_TOML").unwrap_or(
toml_path
.clone()
.unwrap_or("clippy_dev/lintcheck_crates.toml")
.to_string(),
),
)
}
/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
fn build_clippy() {
let status = Command::new("cargo")
.arg("build")
.status()
.expect("Failed to build clippy!");
if !status.success() {
eprintln!("Error: Failed to compile Clippy!");
std::process::exit(1);
}
}
/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
let toml_path = PathBuf::from(toml_path.unwrap_or("clippy_dev/lintcheck_crates.toml"));
let toml_path = lintcheck_config_toml(toml_path);
// save it so that we can use the name of the sources.toml as name for the logfile later.
let toml_filename = toml_path.file_stem().unwrap().to_str().unwrap().to_string();
let toml_content: String =
std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
let crate_list: CrateList =
let crate_list: SourceList =
toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{}", toml_path.display(), e));
// parse the hashmap of the toml file into a list of crates
let tomlcrates: Vec<TomlCrate> = crate_list
@ -249,6 +359,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
crate_sources.push(CrateSource::Path {
name: tk.name.clone(),
path: PathBuf::from(path),
options: tk.options.clone(),
});
}
@ -258,6 +369,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
crate_sources.push(CrateSource::CratesIo {
name: tk.name.clone(),
version: ver.to_string(),
options: tk.options.clone(),
});
})
}
@ -267,6 +379,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
name: tk.name.clone(),
url: tk.git_url.clone().unwrap(),
commit: tk.git_hash.clone().unwrap(),
options: tk.options.clone(),
});
}
// if we have a version as well as a git data OR only one git data, something is funky
@ -283,10 +396,13 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
unreachable!("Failed to translate TomlCrate into CrateSource!");
}
});
// sort the crates
crate_sources.sort();
(toml_filename, crate_sources)
}
// extract interesting data from a json lint message
/// Parse the json output of clippy and return a `ClippyWarning`
fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
let jmsg: Value = serde_json::from_str(&json_message).unwrap_or_else(|e| panic!("Failed to parse json:\n{:?}", e));
@ -307,18 +423,84 @@ fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
.into(),
linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(),
message: jmsg["message"]["message"].to_string().trim_matches('"').into(),
ice: json_message.contains("internal compiler error: "),
is_ice: json_message.contains("internal compiler error: "),
}
}
// the main fn
pub fn run(clap_config: &ArgMatches) {
let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy");
/// Generate a short list of occuring lints-types and their count
fn gather_stats(clippy_warnings: &[ClippyWarning]) -> String {
// count lint type occurrences
let mut counter: HashMap<&String, usize> = HashMap::new();
clippy_warnings
.iter()
.for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
// collect into a tupled list for sorting
let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
// sort by "000{count} {clippy::lintname}"
// to not have a lint with 200 and 2 warnings take the same spot
stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
stats
.iter()
.map(|(lint, count)| format!("{} {}\n", lint, count))
.collect::<String>()
}
/// check if the latest modification of the logfile is older than the modification date of the
/// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck
fn lintcheck_needs_rerun(toml_path: Option<&str>) -> bool {
let clippy_modified: std::time::SystemTime = {
let mut times = ["target/debug/clippy-driver", "target/debug/cargo-clippy"]
.iter()
.map(|p| {
std::fs::metadata(p)
.expect("failed to get metadata of file")
.modified()
.expect("failed to get modification date")
});
// the lates modification of either of the binaries
std::cmp::max(times.next().unwrap(), times.next().unwrap())
};
let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_config_toml(toml_path))
.expect("failed to get metadata of file")
.modified()
.expect("failed to get modification date");
// if clippys modification time is bigger (older) than the logs mod time, we need to rerun lintcheck
clippy_modified > logs_modified
}
/// lintchecks `main()` function
pub fn run(clap_config: &ArgMatches) {
println!("Compiling clippy...");
build_clippy();
println!("Done compiling");
let clap_toml_path = clap_config.value_of("crates-toml");
// if the clippy bin is newer than our logs, throw away target dirs to force clippy to
// refresh the logs
if lintcheck_needs_rerun(clap_toml_path) {
let shared_target_dir = "target/lintcheck/shared_target_dir";
match std::fs::metadata(&shared_target_dir) {
Ok(metadata) => {
if metadata.is_dir() {
println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir...");
std::fs::remove_dir_all(&shared_target_dir)
.expect("failed to remove target/lintcheck/shared_target_dir");
}
},
Err(_) => { // dir probably does not exist, don't remove anything
},
}
}
let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy")
.canonicalize()
.expect("failed to canonicalize path to clippy binary");
// assert that clippy is found
assert!(
cargo_clippy_path.is_file(),
@ -335,7 +517,7 @@ pub fn run(clap_config: &ArgMatches) {
// download and extract the crates, then run clippy on them and collect clippys warnings
// flatten into one big list of warnings
let (filename, crates) = read_crates(clap_config.value_of("crates-toml"));
let (filename, crates) = read_crates(clap_toml_path);
let clippy_warnings: Vec<ClippyWarning> = if let Some(only_one_crate) = clap_config.value_of("only") {
// if we don't have the specified crate in the .toml, throw an error
@ -359,45 +541,60 @@ pub fn run(clap_config: &ArgMatches) {
.into_iter()
.map(|krate| krate.download_and_extract())
.filter(|krate| krate.name == only_one_crate)
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path))
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1))
.flatten()
.collect()
} else {
let counter = std::sync::atomic::AtomicUsize::new(0);
// Ask rayon for thread count. Assume that half of that is the number of physical cores
// Use one target dir for each core so that we can run N clippys in parallel.
// We need to use different target dirs because cargo would lock them for a single build otherwise,
// killing the parallelism. However this also means that deps will only be reused half/a
// quarter of the time which might result in a longer wall clock runtime
// This helps when we check many small crates with dep-trees that don't have a lot of branches in
// order to achive some kind of parallelism
// by default, use a single thread
let num_cpus = match clap_config.value_of("threads") {
Some(threads) => {
let threads: usize = threads
.parse()
.expect(&format!("Failed to parse '{}' to a digit", threads));
if threads == 0 {
// automatic choice
// Rayon seems to return thread count so half that for core count
(rayon::current_num_threads() / 2) as usize
} else {
threads
}
},
// no -j passed, use a single thread
None => 1,
};
let num_crates = crates.len();
// check all crates (default)
crates
.into_iter()
.into_par_iter()
.map(|krate| krate.download_and_extract())
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path))
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates))
.flatten()
.collect()
};
// generate some stats:
// generate some stats
let stats_formatted = gather_stats(&clippy_warnings);
// grab crashes/ICEs, save the crate name and the ice message
let ices: Vec<(&String, &String)> = clippy_warnings
.iter()
.filter(|warning| warning.ice)
.filter(|warning| warning.is_ice)
.map(|w| (&w.crate_name, &w.message))
.collect();
// count lint type occurrences
let mut counter: HashMap<&String, usize> = HashMap::new();
clippy_warnings
.iter()
.for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
// collect into a tupled list for sorting
let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
// sort by "000{count} {clippy::lintname}"
// to not have a lint with 200 and 2 warnings take the same spot
stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
let stats_formatted: String = stats
.iter()
.map(|(lint, count)| format!("{} {}\n", lint, count))
.collect::<String>();
let mut all_msgs: Vec<String> = clippy_warnings.iter().map(|warning| warning.to_string()).collect();
all_msgs.sort();
all_msgs.push("\n\n\n\nStats\n\n".into());
@ -411,5 +608,6 @@ pub fn run(clap_config: &ArgMatches) {
.for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg)));
let file = format!("lintcheck-logs/{}_logs.txt", filename);
println!("Writing logs to {}", file);
write(file, text).unwrap();
}

View File

@ -69,6 +69,14 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.value_name("CRATES-SOURCES-TOML-PATH")
.long("crates-toml")
.help("set the path for a crates.toml where lintcheck should read the sources from"),
)
.arg(
Arg::with_name("threads")
.takes_value(true)
.value_name("N")
.short("j")
.long("jobs")
.help("number of threads to use, 0 automatic choice"),
);
let app = App::new("Clippy developer tooling")

View File

@ -18,6 +18,7 @@ edition = "2018"
[dependencies]
cargo_metadata = "0.12"
clippy_utils = { path = "../clippy_utils" }
if_chain = "1.0.0"
itertools = "0.9"
pulldown-cmark = { version = "0.8", default-features = false }
@ -38,4 +39,4 @@ syn = { version = "1", features = ["full"] }
[features]
deny-warnings = []
# build clippy with internal lints enabled, off by default
internal-lints = []
internal-lints = ["clippy_utils/internal-lints"]

View File

@ -1,4 +1,8 @@
use crate::utils::{differing_macro_contexts, snippet_block_with_applicability, span_lint, span_lint_and_sugg};
use crate::utils::{
differing_macro_contexts, get_parent_expr, get_trait_def_id, implements_trait, paths,
snippet_block_with_applicability, span_lint, span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
@ -52,6 +56,18 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Closure(_, _, eid, _, _) = expr.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(self.cx, expr);
if let ExprKind::MethodCall(_, _, args, _) = parent.kind;
let caller = self.cx.typeck_results().expr_ty(&args[0]);
if let Some(iter_id) = get_trait_def_id(self.cx, &paths::ITERATOR);
if implements_trait(self.cx, caller, iter_id, &[]);
then {
return;
}
}
let body = self.cx.tcx.hir().body(eid);
let ex = &body.value;
if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() {

View File

@ -122,6 +122,7 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
if let ast::ExprKind::Block(ref block, _) = else_.kind;
if !block_starts_with_comment(cx, block);
if let Some(else_) = expr_block(block);
if else_.attrs.is_empty();
if !else_.span.from_expansion();
if let ast::ExprKind::If(..) = else_.kind;
then {
@ -143,16 +144,12 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
if_chain! {
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if inner.attrs.is_empty();
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
// Prevent triggering on `if c { if let a = b { .. } }`.
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
if expr.span.ctxt() == inner.span.ctxt();
then {
if let ast::ExprKind::Let(..) = check_inner.kind {
// Prevent triggering on `if c { if let a = b { .. } }`.
return;
}
if expr.span.ctxt() != inner.span.ctxt() {
return;
}
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
let lhs = Sugg::ast(cx, check, "..");
let rhs = Sugg::ast(cx, check_inner, "..");

View File

@ -96,12 +96,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
cx,
COLLAPSIBLE_MATCH,
expr.span,
"Unnecessary nested match",
"unnecessary nested match",
|diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]);
help_span.push_span_label(binding_span, "Replace this binding".into());
help_span.push_span_label(binding_span, "replace this binding".into());
help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into());
diag.span_help(help_span, "The outer pattern can be modified to include the inner pattern.");
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
}

View File

@ -1,574 +1 @@
#![allow(clippy::float_cmp)]
use crate::utils::{clip, sext, unsext};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_data_structures::sync::Lrc;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::ty::subst::{Subst, SubstsRef};
use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_span::symbol::Symbol;
use std::cmp::Ordering::{self, Equal};
use std::convert::TryInto;
use std::hash::{Hash, Hasher};
/// A `LitKind`-like enum to fold constant `Expr`s into.
#[derive(Debug, Clone)]
pub enum Constant {
/// A `String` (e.g., "abc").
Str(String),
/// A binary string (e.g., `b"abc"`).
Binary(Lrc<[u8]>),
/// A single `char` (e.g., `'a'`).
Char(char),
/// An integer's bit representation.
Int(u128),
/// An `f32`.
F32(f32),
/// An `f64`.
F64(f64),
/// `true` or `false`.
Bool(bool),
/// An array of constants.
Vec(Vec<Constant>),
/// Also an array, but with only one constant, repeated N times.
Repeat(Box<Constant>, u64),
/// A tuple of constants.
Tuple(Vec<Constant>),
/// A raw pointer.
RawPtr(u128),
/// A reference
Ref(Box<Constant>),
/// A literal with syntax error.
Err(Symbol),
}
impl PartialEq for Constant {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs,
(&Self::Binary(ref l), &Self::Binary(ref r)) => l == r,
(&Self::Char(l), &Self::Char(r)) => l == r,
(&Self::Int(l), &Self::Int(r)) => l == r,
(&Self::F64(l), &Self::F64(r)) => {
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
// `Fw32 == Fw64`, so dont compare them.
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
l.to_bits() == r.to_bits()
},
(&Self::F32(l), &Self::F32(r)) => {
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
// `Fw32 == Fw64`, so dont compare them.
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
f64::from(l).to_bits() == f64::from(r).to_bits()
},
(&Self::Bool(l), &Self::Bool(r)) => l == r,
(&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r,
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv,
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb,
// TODO: are there inter-type equalities?
_ => false,
}
}
}
impl Hash for Constant {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
std::mem::discriminant(self).hash(state);
match *self {
Self::Str(ref s) => {
s.hash(state);
},
Self::Binary(ref b) => {
b.hash(state);
},
Self::Char(c) => {
c.hash(state);
},
Self::Int(i) => {
i.hash(state);
},
Self::F32(f) => {
f64::from(f).to_bits().hash(state);
},
Self::F64(f) => {
f.to_bits().hash(state);
},
Self::Bool(b) => {
b.hash(state);
},
Self::Vec(ref v) | Self::Tuple(ref v) => {
v.hash(state);
},
Self::Repeat(ref c, l) => {
c.hash(state);
l.hash(state);
},
Self::RawPtr(u) => {
u.hash(state);
},
Self::Ref(ref r) => {
r.hash(state);
},
Self::Err(ref s) => {
s.hash(state);
},
}
}
}
impl Constant {
pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option<Ordering> {
match (left, right) {
(&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)),
(&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)),
(&Self::Int(l), &Self::Int(r)) => {
if let ty::Int(int_ty) = *cmp_type.kind() {
Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty)))
} else {
Some(l.cmp(&r))
}
},
(&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r),
(&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r),
(&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)),
(&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l
.iter()
.zip(r.iter())
.map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
.find(|r| r.map_or(true, |o| o != Ordering::Equal))
.unwrap_or_else(|| Some(l.len().cmp(&r.len()))),
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => {
match Self::partial_cmp(tcx, cmp_type, lv, rv) {
Some(Equal) => Some(ls.cmp(rs)),
x => x,
}
},
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb),
// TODO: are there any useful inter-type orderings?
_ => None,
}
}
}
/// Parses a `LitKind` to a `Constant`.
pub fn lit_to_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
match *lit {
LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
LitKind::Byte(b) => Constant::Int(u128::from(b)),
LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)),
LitKind::Char(c) => Constant::Char(c),
LitKind::Int(n, _) => Constant::Int(n),
LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty {
ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()),
ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()),
},
LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() {
ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()),
ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()),
_ => bug!(),
},
LitKind::Bool(b) => Constant::Bool(b),
LitKind::Err(s) => Constant::Err(s),
}
}
pub fn constant<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
e: &Expr<'_>,
) -> Option<(Constant, bool)> {
let mut cx = ConstEvalLateContext {
lcx,
typeck_results,
param_env: lcx.param_env,
needed_resolution: false,
substs: lcx.tcx.intern_substs(&[]),
};
cx.expr(e).map(|cst| (cst, cx.needed_resolution))
}
pub fn constant_simple<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
e: &Expr<'_>,
) -> Option<Constant> {
constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) })
}
/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`.
pub fn constant_context<'a, 'tcx>(
lcx: &'a LateContext<'tcx>,
typeck_results: &'a ty::TypeckResults<'tcx>,
) -> ConstEvalLateContext<'a, 'tcx> {
ConstEvalLateContext {
lcx,
typeck_results,
param_env: lcx.param_env,
needed_resolution: false,
substs: lcx.tcx.intern_substs(&[]),
}
}
pub struct ConstEvalLateContext<'a, 'tcx> {
lcx: &'a LateContext<'tcx>,
typeck_results: &'a ty::TypeckResults<'tcx>,
param_env: ty::ParamEnv<'tcx>,
needed_resolution: bool,
substs: SubstsRef<'tcx>,
}
impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
/// Simple constant folding: Insert an expression, get a constant or none.
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> {
match e.kind {
ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)),
ExprKind::Block(ref block, _) => self.block(block),
ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))),
ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec),
ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple),
ExprKind::Repeat(ref value, _) => {
let n = match self.typeck_results.expr_ty(e).kind() {
ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?,
_ => span_bug!(e.span, "typeck error"),
};
self.expr(value).map(|v| Constant::Repeat(Box::new(v), n))
},
ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op {
UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)),
UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)),
UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }),
}),
ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right),
ExprKind::Call(ref callee, ref args) => {
// We only handle a few const functions for now.
if_chain! {
if args.is_empty();
if let ExprKind::Path(qpath) = &callee.kind;
let res = self.typeck_results.qpath_res(qpath, callee.hir_id);
if let Some(def_id) = res.opt_def_id();
let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect();
let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect();
if let ["core", "num", int_impl, "max_value"] = *def_path;
then {
let value = match int_impl {
"<impl i8>" => i8::MAX as u128,
"<impl i16>" => i16::MAX as u128,
"<impl i32>" => i32::MAX as u128,
"<impl i64>" => i64::MAX as u128,
"<impl i128>" => i128::MAX as u128,
_ => return None,
};
Some(Constant::Int(value))
}
else {
None
}
}
},
ExprKind::Index(ref arr, ref index) => self.index(arr, index),
ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
// TODO: add other expressions.
_ => None,
}
}
#[allow(clippy::cast_possible_wrap)]
fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
use self::Constant::{Bool, Int};
match *o {
Bool(b) => Some(Bool(!b)),
Int(value) => {
let value = !value;
match *ty.kind() {
ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))),
ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))),
_ => None,
}
},
_ => None,
}
}
fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
use self::Constant::{Int, F32, F64};
match *o {
Int(value) => {
let ity = match *ty.kind() {
ty::Int(ity) => ity,
_ => return None,
};
// sign extend
let value = sext(self.lcx.tcx, value, ity);
let value = value.checked_neg()?;
// clear unused bits
Some(Int(unsext(self.lcx.tcx, value, ity)))
},
F32(f) => Some(F32(-f)),
F64(f) => Some(F64(-f)),
_ => None,
}
}
/// Create `Some(Vec![..])` of all constants, unless there is any
/// non-constant part.
fn multi(&mut self, vec: &[Expr<'_>]) -> Option<Vec<Constant>> {
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
}
/// Lookup a possibly constant expression from a `ExprKind::Path`.
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant> {
let res = self.typeck_results.qpath_res(qpath, id);
match res {
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
let substs = self.typeck_results.node_substs(id);
let substs = if self.substs.is_empty() {
substs
} else {
substs.subst(self.lcx.tcx, self.substs)
};
let result = self
.lcx
.tcx
.const_eval_resolve(
self.param_env,
ty::WithOptConstParam::unknown(def_id),
substs,
None,
None,
)
.ok()
.map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?;
let result = miri_to_const(&result);
if result.is_some() {
self.needed_resolution = true;
}
result
},
// FIXME: cover all usable cases.
_ => None,
}
}
fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> {
let lhs = self.expr(lhs);
let index = self.expr(index);
match (lhs, index) {
(Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) {
Some(Constant::F32(x)) => Some(Constant::F32(*x)),
Some(Constant::F64(x)) => Some(Constant::F64(*x)),
_ => None,
},
(Some(Constant::Vec(vec)), _) => {
if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
match vec.get(0) {
Some(Constant::F32(x)) => Some(Constant::F32(*x)),
Some(Constant::F64(x)) => Some(Constant::F64(*x)),
_ => None,
}
} else {
None
}
},
_ => None,
}
}
/// A block can only yield a constant if it only has one constant expression.
fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
if block.stmts.is_empty() {
block.expr.as_ref().and_then(|b| self.expr(b))
} else {
None
}
}
fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant> {
if let Some(Constant::Bool(b)) = self.expr(cond) {
if b {
self.expr(&*then)
} else {
otherwise.as_ref().and_then(|expr| self.expr(expr))
}
} else {
None
}
}
fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant> {
let l = self.expr(left)?;
let r = self.expr(right);
match (l, r) {
(Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() {
ty::Int(ity) => {
let l = sext(self.lcx.tcx, l, ity);
let r = sext(self.lcx.tcx, r, ity);
let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity));
match op.node {
BinOpKind::Add => l.checked_add(r).map(zext),
BinOpKind::Sub => l.checked_sub(r).map(zext),
BinOpKind::Mul => l.checked_mul(r).map(zext),
BinOpKind::Div if r != 0 => l.checked_div(r).map(zext),
BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext),
BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext),
BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext),
BinOpKind::BitXor => Some(zext(l ^ r)),
BinOpKind::BitOr => Some(zext(l | r)),
BinOpKind::BitAnd => Some(zext(l & r)),
BinOpKind::Eq => Some(Constant::Bool(l == r)),
BinOpKind::Ne => Some(Constant::Bool(l != r)),
BinOpKind::Lt => Some(Constant::Bool(l < r)),
BinOpKind::Le => Some(Constant::Bool(l <= r)),
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
BinOpKind::Gt => Some(Constant::Bool(l > r)),
_ => None,
}
},
ty::Uint(_) => match op.node {
BinOpKind::Add => l.checked_add(r).map(Constant::Int),
BinOpKind::Sub => l.checked_sub(r).map(Constant::Int),
BinOpKind::Mul => l.checked_mul(r).map(Constant::Int),
BinOpKind::Div => l.checked_div(r).map(Constant::Int),
BinOpKind::Rem => l.checked_rem(r).map(Constant::Int),
BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int),
BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int),
BinOpKind::BitXor => Some(Constant::Int(l ^ r)),
BinOpKind::BitOr => Some(Constant::Int(l | r)),
BinOpKind::BitAnd => Some(Constant::Int(l & r)),
BinOpKind::Eq => Some(Constant::Bool(l == r)),
BinOpKind::Ne => Some(Constant::Bool(l != r)),
BinOpKind::Lt => Some(Constant::Bool(l < r)),
BinOpKind::Le => Some(Constant::Bool(l <= r)),
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
BinOpKind::Gt => Some(Constant::Bool(l > r)),
_ => None,
},
_ => None,
},
(Constant::F32(l), Some(Constant::F32(r))) => match op.node {
BinOpKind::Add => Some(Constant::F32(l + r)),
BinOpKind::Sub => Some(Constant::F32(l - r)),
BinOpKind::Mul => Some(Constant::F32(l * r)),
BinOpKind::Div => Some(Constant::F32(l / r)),
BinOpKind::Rem => Some(Constant::F32(l % r)),
BinOpKind::Eq => Some(Constant::Bool(l == r)),
BinOpKind::Ne => Some(Constant::Bool(l != r)),
BinOpKind::Lt => Some(Constant::Bool(l < r)),
BinOpKind::Le => Some(Constant::Bool(l <= r)),
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
BinOpKind::Gt => Some(Constant::Bool(l > r)),
_ => None,
},
(Constant::F64(l), Some(Constant::F64(r))) => match op.node {
BinOpKind::Add => Some(Constant::F64(l + r)),
BinOpKind::Sub => Some(Constant::F64(l - r)),
BinOpKind::Mul => Some(Constant::F64(l * r)),
BinOpKind::Div => Some(Constant::F64(l / r)),
BinOpKind::Rem => Some(Constant::F64(l % r)),
BinOpKind::Eq => Some(Constant::Bool(l == r)),
BinOpKind::Ne => Some(Constant::Bool(l != r)),
BinOpKind::Lt => Some(Constant::Bool(l < r)),
BinOpKind::Le => Some(Constant::Bool(l <= r)),
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
BinOpKind::Gt => Some(Constant::Bool(l > r)),
_ => None,
},
(l, r) => match (op.node, l, r) {
(BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)),
(BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)),
(BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => {
Some(r)
},
(BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)),
(BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)),
(BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)),
_ => None,
},
}
}
}
pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
use rustc_middle::mir::interpret::ConstValue;
match result.val {
ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => {
match result.ty.kind() {
ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)),
ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))),
ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits(
int.try_into().expect("invalid f32 bit representation"),
))),
ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(
int.try_into().expect("invalid f64 bit representation"),
))),
ty::RawPtr(type_and_mut) => {
if let ty::Uint(_) = type_and_mut.ty.kind() {
return Some(Constant::RawPtr(int.assert_bits(int.size())));
}
None
},
// FIXME: implement other conversions.
_ => None,
}
},
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() {
ty::Ref(_, tam, _) => match tam.kind() {
ty::Str => String::from_utf8(
data.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
.to_owned(),
)
.ok()
.map(Constant::Str),
_ => None,
},
_ => None,
},
ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() {
ty::Array(sub_type, len) => match sub_type.kind() {
ty::Float(FloatTy::F32) => match miri_to_const(len) {
Some(Constant::Int(len)) => alloc
.inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
.to_owned()
.chunks(4)
.map(|chunk| {
Some(Constant::F32(f32::from_le_bytes(
chunk.try_into().expect("this shouldn't happen"),
)))
})
.collect::<Option<Vec<Constant>>>()
.map(Constant::Vec),
_ => None,
},
ty::Float(FloatTy::F64) => match miri_to_const(len) {
Some(Constant::Int(len)) => alloc
.inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
.to_owned()
.chunks(8)
.map(|chunk| {
Some(Constant::F64(f64::from_le_bytes(
chunk.try_into().expect("this shouldn't happen"),
)))
})
.collect::<Option<Vec<Constant>>>()
.map(Constant::Vec),
_ => None,
},
// FIXME: implement other array type conversions.
_ => None,
},
_ => None,
},
// FIXME: implement other conversions.
_ => None,
}
}
pub use clippy_utils::consts::*;

View File

@ -0,0 +1,237 @@
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{
intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor},
Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::{
hir::map::Map,
ty::{self, FloatTy, IntTy, PolyFnSig, Ty},
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use if_chain::if_chain;
use crate::utils::{snippet, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type
/// inference.
///
/// Default numeric fallback means that if numeric types have not yet been bound to concrete
/// types at the end of type inference, then integer type is bound to `i32`, and similarly
/// floating type is bound to `f64`.
///
/// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback.
///
/// **Why is this bad?** For those who are very careful about types, default numeric fallback
/// can be a pitfall that cause unexpected runtime behavior.
///
/// **Known problems:** This lint can only be allowed at the function level or above.
///
/// **Example:**
/// ```rust
/// let i = 10;
/// let f = 1.23;
/// ```
///
/// Use instead:
/// ```rust
/// let i = 10i32;
/// let f = 1.23f64;
/// ```
pub DEFAULT_NUMERIC_FALLBACK,
restriction,
"usage of unconstrained numeric literals which may cause default numeric fallback."
}
declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
impl LateLintPass<'_> for DefaultNumericFallback {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
let mut visitor = NumericFallbackVisitor::new(cx);
visitor.visit_body(body);
}
}
struct NumericFallbackVisitor<'a, 'tcx> {
/// Stack manages type bound of exprs. The top element holds current expr type.
ty_bounds: Vec<TyBound<'tcx>>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
ty_bounds: vec![TyBound::Nothing],
cx,
}
}
/// Check whether a passed literal has potential to cause fallback or not.
fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) {
if_chain! {
if let Some(ty_bound) = self.ty_bounds.last();
if matches!(lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
if !ty_bound.is_integral();
then {
let suffix = match lit_ty.kind() {
ty::Int(IntTy::I32) => "i32",
ty::Float(FloatTy::F64) => "f64",
// Default numeric fallback never results in other types.
_ => return,
};
let sugg = format!("{}_{}", snippet(self.cx, lit.span, ""), suffix);
span_lint_and_sugg(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
lit.span,
"default numeric fallback might occur",
"consider adding suffix",
sugg,
Applicability::MaybeIncorrect,
);
}
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
#[allow(clippy::too_many_lines)]
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
match &expr.kind {
ExprKind::Call(func, args) => {
if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
for (expr, bound) in args.iter().zip(fn_sig.skip_binder().inputs().iter()) {
// Push found arg type, then visit arg.
self.ty_bounds.push(TyBound::Ty(bound));
self.visit_expr(expr);
self.ty_bounds.pop();
}
return;
}
},
ExprKind::MethodCall(_, _, args, _) => {
if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) {
self.ty_bounds.push(TyBound::Ty(bound));
self.visit_expr(expr);
self.ty_bounds.pop();
}
return;
}
},
ExprKind::Struct(qpath, fields, base) => {
if_chain! {
if let Some(def_id) = self.cx.qpath_res(qpath, expr.hir_id).opt_def_id();
let ty = self.cx.tcx.type_of(def_id);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next();
then {
let fields_def = &variant.fields;
// Push field type then visit each field expr.
for field in fields.iter() {
let bound =
fields_def
.iter()
.find_map(|f_def| {
if f_def.ident == field.ident
{ Some(self.cx.tcx.type_of(f_def.did)) }
else { None }
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(TyBound::Nothing);
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
}
}
},
ExprKind::Lit(lit) => {
let ty = self.cx.typeck_results().expr_ty(expr);
self.check_lit(lit, ty);
return;
},
_ => {},
}
walk_expr(self, expr);
}
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
match stmt.kind {
StmtKind::Local(local) => {
if local.ty.is_some() {
self.ty_bounds.push(TyBound::Any)
} else {
self.ty_bounds.push(TyBound::Nothing)
}
},
_ => self.ty_bounds.push(TyBound::Nothing),
}
walk_stmt(self, stmt);
self.ty_bounds.pop();
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> {
let node_ty = cx.typeck_results().node_type_opt(hir_id)?;
// We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs.
match node_ty.kind() {
ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)),
ty::FnPtr(fn_sig) => Some(*fn_sig),
_ => None,
}
}
#[derive(Debug, Clone, Copy)]
enum TyBound<'tcx> {
Any,
Ty(Ty<'tcx>),
Nothing,
}
impl<'tcx> TyBound<'tcx> {
fn is_integral(self) -> bool {
match self {
TyBound::Any => true,
TyBound::Ty(t) => t.is_integral(),
TyBound::Nothing => false,
}
}
}
impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> {
fn from(v: Option<Ty<'tcx>>) -> Self {
match v {
Some(t) => TyBound::Ty(t),
None => TyBound::Nothing,
}
}
}

View File

@ -1,6 +1,6 @@
use crate::utils::{
implements_trait, is_entrypoint_fn, is_type_diagnostic_item, match_panic_def_id, method_chain_args, return_ty,
span_lint, span_lint_and_note,
implements_trait, is_entrypoint_fn, is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args,
return_ty, span_lint, span_lint_and_note,
};
use if_chain::if_chain;
use itertools::Itertools;
@ -216,9 +216,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
match item.kind {
hir::ItemKind::Fn(ref sig, _, body_id) => {
if !(is_entrypoint_fn(cx, item.def_id.to_def_id())
|| in_external_macro(cx.tcx.sess, item.span))
{
if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
cx,
@ -226,7 +224,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
panic_span: None,
};
fpu.visit_expr(&body.value);
lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span);
lint_for_missing_headers(
cx,
item.hir_id(),
item.span,
sig,
headers,
Some(body_id),
fpu.panic_span,
);
}
},
hir::ItemKind::Impl(ref impl_) => {
@ -264,7 +270,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
panic_span: None,
};
fpu.visit_expr(&body.value);
lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span);
lint_for_missing_headers(
cx,
item.hir_id(),
item.span,
sig,
headers,
Some(body_id),
fpu.panic_span,
);
}
}
}
@ -561,9 +575,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
| ItemKind::ExternCrate(..)
| ItemKind::ForeignMod(..) => return false,
// We found a main function ...
ItemKind::Fn(box FnKind(_, sig, _, Some(block)))
if item.ident.name == sym::main =>
{
ItemKind::Fn(box FnKind(_, sig, _, Some(block))) if item.ident.name == sym::main => {
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
let returns_nothing = match &sig.decl.output {
FnRetTy::Default(..) => true,
@ -699,6 +711,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.cx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none();
then {
self.panic_span = Some(expr.span);
}

View File

@ -0,0 +1,101 @@
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
use crate::utils::is_type_diagnostic_item;
use crate::utils::span_lint_and_sugg;
use crate::utils::sugg::Sugg;
declare_clippy_lint! {
/// **What it does:**
/// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
///
/// **Why is this bad?**
/// This specific common use case can be rewritten as `s.parse::<primitive>()`
/// (and in most cases, the turbofish can be removed), which reduces code length
/// and complexity.
///
/// **Known problems:**
/// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
/// in some cases, which is correct but adds unnecessary complexity to the code.
///
/// **Example:**
///
/// ```ignore
/// let input: &str = get_input();
/// let num = u16::from_str_radix(input, 10)?;
/// ```
/// Use instead:
/// ```ignore
/// let input: &str = get_input();
/// let num: u16 = input.parse()?;
/// ```
pub FROM_STR_RADIX_10,
style,
"from_str_radix with radix 10"
}
declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if_chain! {
if let ExprKind::Call(maybe_path, arguments) = &exp.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
// check if the first part of the path is some integer primitive
if let TyKind::Path(ty_qpath) = &ty.kind;
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
if let def::Res::PrimTy(prim_ty) = ty_res;
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
// check if the second part of the path indeed calls the associated
// function `from_str_radix`
if pathseg.ident.name.as_str() == "from_str_radix";
// check if the second argument is a primitive `10`
if arguments.len() == 2;
if let ExprKind::Lit(lit) = &arguments[1].kind;
if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
then {
let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) {
expr
} else {
&arguments[0]
}
} else {
&arguments[0]
};
let sugg = Sugg::hir_with_applicability(
cx,
expr,
"<string>",
&mut Applicability::MachineApplicable
).maybe_par();
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
Applicability::MaybeIncorrect
);
}
}
}
}
/// Checks if a Ty is `String` or `&str`
fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str)
}

View File

@ -1,7 +1,7 @@
use crate::utils::{
attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item, iter_input_pats,
last_path_segment, match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint,
span_lint_and_help, span_lint_and_then, trait_ref_of_method, type_is_unsafe_function,
match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help,
span_lint_and_then, trait_ref_of_method, type_is_unsafe_function,
};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
@ -470,12 +470,11 @@ fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span
if_chain! {
if !in_external_macro(cx.sess(), item_span);
if let hir::FnRetTy::Return(ref ty) = decl.output;
if let hir::TyKind::Path(ref qpath) = ty.kind;
if is_type_diagnostic_item(cx, hir_ty_to_ty(cx.tcx, ty), sym::result_type);
if let Some(ref args) = last_path_segment(qpath).args;
if let [_, hir::GenericArg::Type(ref err_ty)] = args.args;
if let hir::TyKind::Tup(t) = err_ty.kind;
if t.is_empty();
let ty = hir_ty_to_ty(cx.tcx, ty);
if is_type_diagnostic_item(cx, ty, sym::result_type);
if let ty::Adt(_, substs) = ty.kind();
let err_ty = substs.type_at(1);
if err_ty.is_unit();
then {
span_lint_and_help(
cx,

View File

@ -0,0 +1,134 @@
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use if_chain::if_chain;
use crate::utils::{snippet, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for struct constructors where the order of the field init
/// shorthand in the constructor is inconsistent with the order in the struct definition.
///
/// **Why is this bad?** Since the order of fields in a constructor doesn't affect the
/// resulted instance as the below example indicates,
///
/// ```rust
/// #[derive(Debug, PartialEq, Eq)]
/// struct Foo {
/// x: i32,
/// y: i32,
/// }
/// let x = 1;
/// let y = 2;
///
/// // This assertion never fails.
/// assert_eq!(Foo { x, y }, Foo { y, x });
/// ```
///
/// inconsistent order means nothing and just decreases readability and consistency.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// struct Foo {
/// x: i32,
/// y: i32,
/// }
/// let x = 1;
/// let y = 2;
/// Foo { y, x };
/// ```
///
/// Use instead:
/// ```rust
/// # struct Foo {
/// # x: i32,
/// # y: i32,
/// # }
/// # let x = 1;
/// # let y = 2;
/// Foo { x, y };
/// ```
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
style,
"the order of the field init shorthand is inconsistent with the order in the struct definition"
}
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
impl LateLintPass<'_> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
let ty = cx.tcx.type_of(def_id);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next();
if fields.iter().all(|f| f.is_shorthand);
then {
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.ident.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
fields_snippet.push_str(&format!("{}, ", ident));
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!("{} {{ {}{} }}",
snippet(cx, qpath.span(), ".."),
fields_snippet,
base_snippet,
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"inconsistent struct constructor",
"try",
sugg,
Applicability::MachineApplicable,
)
}
}
}
}
// Check whether the order of the fields in the constructor is consistent with the order in the
// definition.
fn is_consistent_order<'tcx>(fields: &'tcx [hir::Field<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
let mut cur_idx = usize::MIN;
for f in fields {
let next_idx = def_order_map[&f.ident.name];
if cur_idx > next_idx {
return false;
}
cur_idx = next_idx;
}
true
}

View File

@ -106,6 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
let decl = &signature.decl;
if decl.implicit_self.has_implicit_self();
if decl.inputs.len() == 1;
if impl_item.generics.params.is_empty();
// Check if return type is String
if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::string_type);

View File

@ -1,10 +1,7 @@
// error-pattern:cargo-clippy
#![feature(bindings_after_at)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(concat_idents)]
#![feature(crate_visibility_modifier)]
#![feature(drain_filter)]
#![feature(in_band_lifetimes)]
#![feature(once_cell)]
@ -149,6 +146,20 @@ macro_rules! declare_clippy_lint {
};
}
#[macro_export]
macro_rules! sym {
( $($x:tt)* ) => { clippy_utils::sym!($($x)*) }
}
#[macro_export]
macro_rules! unwrap_cargo_metadata {
( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) }
}
macro_rules! extract_msrv_attr {
( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); }
}
mod consts;
#[macro_use]
mod utils;
@ -181,6 +192,7 @@ mod copy_iterator;
mod create_dir;
mod dbg_macro;
mod default;
mod default_numeric_fallback;
mod dereference;
mod derive;
mod disallowed_method;
@ -210,6 +222,7 @@ mod floating_point_arithmetic;
mod format;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
mod functions;
mod future_not_send;
mod get_last_with_len;
@ -219,6 +232,7 @@ mod if_let_some_result;
mod if_not_else;
mod implicit_return;
mod implicit_saturating_sub;
mod inconsistent_struct_constructor;
mod indexing_slicing;
mod infinite_iter;
mod inherent_impl;
@ -239,6 +253,7 @@ mod loops;
mod macro_use;
mod main_recursion;
mod manual_async_fn;
mod manual_map;
mod manual_non_exhaustive;
mod manual_ok_or;
mod manual_strip;
@ -585,6 +600,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&dbg_macro::DBG_MACRO,
&default::DEFAULT_TRAIT_ACCESS,
&default::FIELD_REASSIGN_WITH_DEFAULT,
&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
&dereference::EXPLICIT_DEREF_METHODS,
&derive::DERIVE_HASH_XOR_EQ,
&derive::DERIVE_ORD_XOR_PARTIAL_ORD,
@ -637,6 +653,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&formatting::SUSPICIOUS_ELSE_FORMATTING,
&formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
&from_over_into::FROM_OVER_INTO,
&from_str_radix_10::FROM_STR_RADIX_10,
&functions::DOUBLE_MUST_USE,
&functions::MUST_USE_CANDIDATE,
&functions::MUST_USE_UNIT,
@ -652,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&if_not_else::IF_NOT_ELSE,
&implicit_return::IMPLICIT_RETURN,
&implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
&indexing_slicing::INDEXING_SLICING,
&indexing_slicing::OUT_OF_BOUNDS_INDEXING,
&infinite_iter::INFINITE_ITER,
@ -702,6 +720,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&macro_use::MACRO_USE_IMPORTS,
&main_recursion::MAIN_RECURSION,
&manual_async_fn::MANUAL_ASYNC_FN,
&manual_map::MANUAL_MAP,
&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
&manual_ok_or::MANUAL_OK_OR,
&manual_strip::MANUAL_STRIP,
@ -1031,6 +1050,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box strings::StringAdd);
store.register_late_pass(|| box implicit_return::ImplicitReturn);
store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback);
store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor);
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
@ -1195,7 +1216,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold));
store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments);
store.register_early_pass(|| box upper_case_acronyms::UpperCaseAcronyms);
let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
store.register_early_pass(move || box upper_case_acronyms::UpperCaseAcronyms::new(upper_case_acronyms_aggressive));
store.register_late_pass(|| box default::Default::default());
store.register_late_pass(|| box unused_self::UnusedSelf);
store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall);
@ -1256,6 +1278,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || box types::PtrAsPtr::new(msrv));
store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons);
store.register_late_pass(|| box redundant_slicing::RedundantSlicing);
store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
store.register_late_pass(|| box manual_map::ManualMap);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@ -1265,6 +1289,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(&create_dir::CREATE_DIR),
LintId::of(&dbg_macro::DBG_MACRO),
LintId::of(&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS),
LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS),
@ -1389,6 +1414,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::PTR_AS_PTR),
LintId::of(&unicode::NON_ASCII_LITERAL),
LintId::of(&unicode::UNICODE_NOT_NFC),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(&unused_self::UNUSED_SELF),
LintId::of(&wildcard_imports::ENUM_GLOB_USE),
@ -1468,6 +1494,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
LintId::of(&from_over_into::FROM_OVER_INTO),
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
LintId::of(&functions::DOUBLE_MUST_USE),
LintId::of(&functions::MUST_USE_UNIT),
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
@ -1477,6 +1504,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&identity_op::IDENTITY_OP),
LintId::of(&if_let_mutex::IF_LET_MUTEX),
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
LintId::of(&infinite_iter::INFINITE_ITER),
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
@ -1512,6 +1540,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_map::MANUAL_MAP),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&manual_strip::MANUAL_STRIP),
LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR),
@ -1682,7 +1711,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS),
LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT),
LintId::of(&unused_unit::UNUSED_UNIT),
@ -1724,10 +1752,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
LintId::of(&from_over_into::FROM_OVER_INTO),
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
LintId::of(&functions::DOUBLE_MUST_USE),
LintId::of(&functions::MUST_USE_UNIT),
LintId::of(&functions::RESULT_UNIT_ERR),
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
LintId::of(&len_zero::COMPARISON_TO_EMPTY),
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
@ -1741,6 +1771,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_map::MANUAL_MAP),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&map_clone::MAP_CLONE),
LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
@ -1899,7 +1930,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::UNNECESSARY_CAST),
LintId::of(&types::VEC_BOX),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unwrap::UNNECESSARY_UNWRAP),
LintId::of(&useless_conversion::USELESS_CONVERSION),
LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO),

View File

@ -0,0 +1,274 @@
use crate::{
map_unit_fn::OPTION_MAP_UNIT_FN,
matches::MATCH_AS_REF,
utils::{
is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs,
peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg,
},
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::{sym, Ident};
declare_clippy_lint! {
/// **What it does:** Checks for usages of `match` which could be implemented using `map`
///
/// **Why is this bad?** Using the `map` method is clearer and more concise.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// match Some(0) {
/// Some(x) => Some(x + 1),
/// None => None,
/// };
/// ```
/// Use instead:
/// ```rust
/// Some(0).map(|x| x + 1);
/// ```
pub MANUAL_MAP,
style,
"reimplementation of `map`"
}
declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl LateLintPass<'_> for ManualMap {
#[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Match(scrutinee, [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], _) =
expr.kind
{
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
|| !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)
{
return;
}
let (some_expr, some_pat, pat_ref_count, is_wild_none) =
match (try_parse_pattern(cx, arm1.pat), try_parse_pattern(cx, arm2.pat)) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count }))
if is_none_expr(cx, arm1.body) =>
{
(arm2.body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count }))
if is_none_expr(cx, arm1.body) =>
{
(arm2.body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild))
if is_none_expr(cx, arm2.body) =>
{
(arm1.body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None))
if is_none_expr(cx, arm2.body) =>
{
(arm1.body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr) {
Some(expr) => expr,
None => return,
};
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
&& !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. `as_ref` will be called,
// the type is copyable, or the option is being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let scrutinee_str = snippet_with_applicability(cx, scrutinee.span, "_", &mut app);
let scrutinee_str = if expr.precedence().order() < PREC_POSTFIX {
// Parens are needed to chain method calls.
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind {
if let Some(func) = can_pass_as_func(cx, some_binding, some_expr) {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if match_var(some_expr, some_binding.name)
&& !is_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_applicability(cx, some_expr.span, "..", &mut app)
)
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_applicability(cx, some_pat.span, "..", &mut app),
snippet_with_applicability(cx, some_expr.span, "..", &mut app)
)
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str),
app,
);
}
}
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
{
Some(func)
},
_ => None,
}
}
enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<OptionPat<'tcx>> {
fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1),
PatKind::Path(QPath::Resolved(None, path))
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) =>
{
Some(OptionPat::None)
},
PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _)
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat, 0)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(QPath::Resolved(None, path)),
..
},
[arg],
) => {
if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) {
Some(arg)
} else {
None
}
},
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
..
},
_,
) => get_some_expr(cx, expr),
_ => None,
}
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match expr.kind {
ExprKind::Path(QPath::Resolved(None, path)) => path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)),
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
..
},
_,
) => is_none_expr(cx, expr),
_ => false,
}
}

View File

@ -3,19 +3,19 @@ use crate::utils::sugg::Sugg;
use crate::utils::visitors::LocalUsedVisitor;
use crate::utils::{
expr_block, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local_id,
peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt,
snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
strip_pat_refs,
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local,
path_to_local_id, peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block,
snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
span_lint_and_then, strip_pat_refs,
};
use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::def::CtorKind;
use rustc_hir::{
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, Local, MatchSource, Mutability, Node, Pat,
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat,
PatKind, QPath, RangeEnd,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -24,7 +24,7 @@ use rustc_middle::ty::{self, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::{Span, Spanned};
use rustc_span::{sym, Symbol};
use rustc_span::sym;
use std::cmp::Ordering;
use std::collections::hash_map::Entry;
use std::collections::Bound;
@ -1873,13 +1873,6 @@ fn test_overlapping() {
/// Implementation of `MATCH_SAME_ARMS`.
fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool {
lhs.len() == rhs.len()
&& lhs
.iter()
.all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty)))
}
if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind {
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
let mut h = SpanlessHash::new(cx);
@ -1891,12 +1884,38 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
let min_index = usize::min(lindex, rindex);
let max_index = usize::max(lindex, rindex);
let mut local_map: FxHashMap<HirId, HirId> = FxHashMap::default();
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
if_chain! {
if let Some(a_id) = path_to_local(a);
if let Some(b_id) = path_to_local(b);
let entry = match local_map.entry(a_id) {
Entry::Vacant(entry) => entry,
// check if using the same bindings as before
Entry::Occupied(entry) => return *entry.get() == b_id,
};
// the names technically don't have to match; this makes the lint more conservative
if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
if pat_contains_local(lhs.pat, a_id);
if pat_contains_local(rhs.pat, b_id);
then {
entry.insert(b_id);
true
} else {
false
}
}
};
// Arms with a guard are ignored, those cant always be merged together
// This is also the case for arms in-between each there is an arm with a guard
(min_index..=max_index).all(|index| arms[index].guard.is_none()) &&
SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
// all patterns should have the same bindings
same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat))
(min_index..=max_index).all(|index| arms[index].guard.is_none())
&& SpanlessEq::new(cx)
.expr_fallback(eq_fallback)
.eq_expr(&lhs.body, &rhs.body)
// these checks could be removed to allow unused bindings
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
};
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
@ -1939,50 +1958,18 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
}
}
/// Returns the list of bindings in a pattern.
fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> {
fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) {
match pat.kind {
PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map),
PatKind::TupleStruct(_, pats, _) => {
for pat in pats {
bindings_impl(cx, pat, map);
}
},
PatKind::Binding(.., ident, ref as_pat) => {
if let Entry::Vacant(v) = map.entry(ident.name) {
v.insert(cx.typeck_results().pat_ty(pat));
}
if let Some(ref as_pat) = *as_pat {
bindings_impl(cx, as_pat, map);
}
},
PatKind::Or(fields) | PatKind::Tuple(fields, _) => {
for pat in fields {
bindings_impl(cx, pat, map);
}
},
PatKind::Struct(_, fields, _) => {
for pat in fields {
bindings_impl(cx, &pat.pat, map);
}
},
PatKind::Slice(lhs, ref mid, rhs) => {
for pat in lhs {
bindings_impl(cx, pat, map);
}
if let Some(ref mid) = *mid {
bindings_impl(cx, mid, map);
}
for pat in rhs {
bindings_impl(cx, pat, map);
}
},
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (),
}
}
let mut result = FxHashMap::default();
bindings_impl(cx, pat, &mut result);
fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
let mut result = false;
pat.walk_short(|p| {
result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
!result
});
result
}
/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet<HirId>) -> bool {
let mut result = true;
pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
result && ids.is_empty()
}

View File

@ -212,10 +212,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
if !expr_borrows(cx, left_expr) {
return Some(LintTrigger::SortByKey(SortByKeyDetection {
vec_name,
unstable,
closure_arg,
closure_body,
reverse
reverse,
unstable,
}));
}
}

View File

@ -1,5 +1,5 @@
use crate::utils::{
contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then,
contains_return, in_macro, match_qpath, paths, return_ty, snippet, span_lint_and_then,
visitors::find_all_ret_expressions,
};
use if_chain::if_chain;
@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
use rustc_span::Span;
@ -17,8 +17,8 @@ declare_clippy_lint! {
///
/// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned.
///
/// **Known problems:** Since this lint changes function type signature, you may need to
/// adjust some code at callee side.
/// **Known problems:** There can be false positives if the function signature is designed to
/// fit some external requirement.
///
/// **Example:**
///
@ -48,7 +48,7 @@ declare_clippy_lint! {
/// }
/// ```
pub UNNECESSARY_WRAPS,
complexity,
pedantic,
"functions that only return `Ok` or `Some`"
}
@ -64,6 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
span: Span,
hir_id: HirId,
) {
// Abort if public function/method or closure.
match fn_kind {
FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => {
if visibility.node.is_pub() {
@ -74,6 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
_ => (),
}
// Abort if the method is implementing a trait or of it a trait method.
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
if matches!(
item.kind,
@ -83,25 +85,44 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
}
}
let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::option_type) {
("Option", &paths::OPTION_SOME)
} else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) {
("Result", &paths::RESULT_OK)
// Get the wrapper and inner types, if can't, abort.
let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) {
("Option", &paths::OPTION_SOME, subst.type_at(0))
} else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) {
("Result", &paths::RESULT_OK, subst.type_at(0))
} else {
return;
}
} else {
return;
};
// Check if all return expression respect the following condition and collect them.
let mut suggs = Vec::new();
let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
if_chain! {
if !in_macro(ret_expr.span);
// Check if a function call.
if let ExprKind::Call(ref func, ref args) = ret_expr.kind;
// Get the Path of the function call.
if let ExprKind::Path(ref qpath) = func.kind;
// Check if OPTION_SOME or RESULT_OK, depending on return type.
if match_qpath(qpath, path);
if args.len() == 1;
// Make sure the function argument does not contain a return expression.
if !contains_return(&args[0]);
then {
suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string()));
suggs.push(
(
ret_expr.span,
if inner_type.is_unit() {
"".to_string()
} else {
snippet(cx, args[0].span.source_callsite(), "..").to_string()
}
)
);
true
} else {
false
@ -110,39 +131,34 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
});
if can_sugg && !suggs.is_empty() {
span_lint_and_then(
cx,
UNNECESSARY_WRAPS,
span,
format!(
"this function's return value is unnecessarily wrapped by `{}`",
return_type
let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
(
"this function's return value is unnecessary".to_string(),
"remove the return type...".to_string(),
snippet(cx, fn_decl.output.span(), "..").to_string(),
"...and then remove returned values",
)
.as_str(),
|diag| {
let inner_ty = return_ty(cx, hir_id)
.walk()
.skip(1) // skip `std::option::Option` or `std::result::Result`
.take(1) // take the first outermost inner type
.filter_map(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()),
_ => None,
});
inner_ty.for_each(|inner_ty| {
diag.span_suggestion(
fn_decl.output.span(),
format!("remove `{}` from the return type...", return_type).as_str(),
inner_ty,
Applicability::MaybeIncorrect,
);
});
diag.multipart_suggestion(
"...and change the returning expressions",
suggs,
Applicability::MaybeIncorrect,
);
},
);
} else {
(
format!(
"this function's return value is unnecessarily wrapped by `{}`",
return_type_label
),
format!("remove `{}` from the return type...", return_type_label),
inner_type.to_string(),
"...and then change returning expressions",
)
};
span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
diag.span_suggestion(
fn_decl.output.span(),
return_type_sugg_msg.as_str(),
return_type_sugg,
Applicability::MaybeIncorrect,
);
diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);
});
}
}
}

View File

@ -5,16 +5,20 @@ use rustc_ast::ast::{Item, ItemKind, Variant};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Ident;
declare_clippy_lint! {
/// **What it does:** Checks for camel case name containing a capitalized acronym.
/// **What it does:** Checks for fully capitalized names and optionally names containing a capitalized acronym.
///
/// **Why is this bad?** In CamelCase, acronyms count as one word.
/// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
/// for more.
///
/// By default, the lint only triggers on fully-capitalized names.
/// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
/// on all camel case names
///
/// **Known problems:** When two acronyms are contiguous, the lint can't tell where
/// the first acronym ends and the second starts, so it suggests to lowercase all of
/// the letters in the second acronym.
@ -33,7 +37,20 @@ declare_clippy_lint! {
"capitalized acronyms are against the naming convention"
}
declare_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
#[derive(Default)]
pub struct UpperCaseAcronyms {
upper_case_acronyms_aggressive: bool,
}
impl UpperCaseAcronyms {
pub fn new(aggressive: bool) -> Self {
Self {
upper_case_acronyms_aggressive: aggressive,
}
}
}
impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
fn correct_ident(ident: &str) -> String {
let ident = ident.chars().rev().collect::<String>();
@ -56,11 +73,18 @@ fn correct_ident(ident: &str) -> String {
ident
}
fn check_ident(cx: &EarlyContext<'_>, ident: &Ident) {
fn check_ident(cx: &EarlyContext<'_>, ident: &Ident, be_aggressive: bool) {
let span = ident.span;
let ident = &ident.as_str();
let corrected = correct_ident(ident);
if ident != &corrected {
// warn if we have pure-uppercase idents
// assume that two-letter words are some kind of valid abbreviation like FP for false positive
// (and don't warn)
if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
// otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
// upper-case-acronyms-aggressive config option enabled
|| (be_aggressive && ident != &corrected)
{
span_lint_and_sugg(
cx,
UPPER_CASE_ACRONYMS,
@ -82,12 +106,12 @@ impl EarlyLintPass for UpperCaseAcronyms {
ItemKind::TyAlias(..) | ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Trait(..)
);
then {
check_ident(cx, &it.ident);
check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
}
}
}
fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &Variant) {
check_ident(cx, &v.ident);
check_ident(cx, &v.ident, self.upper_case_acronyms_aggressive);
}
}

View File

@ -1,24 +1,24 @@
use crate::utils::{in_macro, meets_msrv, snippet_opt, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_item, walk_path, walk_ty, NestedVisitorMap, Visitor};
use rustc_hir::def::DefKind;
use rustc_hir::{
def, FnDecl, FnRetTy, FnSig, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Path, PathSegment, QPath,
TyKind,
def,
def_id::LocalDefId,
intravisit::{walk_ty, NestedVisitorMap, Visitor},
Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Node, Path, PathSegment,
QPath, TyKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_middle::ty::{DefIdTree, Ty};
use rustc_middle::ty::{AssocKind, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::kw;
use rustc_span::{BytePos, Span};
use rustc_typeck::hir_ty_to_ty;
use crate::utils::{differing_macro_contexts, meets_msrv, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for unnecessary repetition of structure name when a
/// replacement with `Self` is applicable.
@ -28,10 +28,11 @@ declare_clippy_lint! {
/// feels inconsistent.
///
/// **Known problems:**
/// - False positive when using associated types ([#2843](https://github.com/rust-lang/rust-clippy/issues/2843))
/// - False positives in some situations when using generics ([#3410](https://github.com/rust-lang/rust-clippy/issues/3410))
/// - Unaddressed false negative in fn bodies of trait implementations
/// - False positive with assotiated types in traits (#4140)
///
/// **Example:**
///
/// ```rust
/// struct Foo {}
/// impl Foo {
@ -54,52 +55,326 @@ declare_clippy_lint! {
"unnecessary structure name repetition whereas `Self` is applicable"
}
#[derive(Default)]
pub struct UseSelf {
msrv: Option<RustcVersion>,
stack: Vec<StackItem>,
}
const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
impl UseSelf {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self {
msrv,
..Self::default()
}
}
}
#[derive(Debug)]
enum StackItem {
Check {
hir_id: HirId,
impl_trait_ref_def_id: Option<LocalDefId>,
types_to_skip: Vec<HirId>,
types_to_lint: Vec<HirId>,
},
NoCheck,
}
impl_lint_pass!(UseSelf => [USE_SELF]);
const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
fn span_use_self_lint(cx: &LateContext<'_>, path: &Path<'_>, last_segment: Option<&PathSegment<'_>>) {
let last_segment = last_segment.unwrap_or_else(|| path.segments.last().expect(SEGMENTS_MSG));
// Path segments only include actual path, no methods or fields.
let last_path_span = last_segment.ident.span;
if differing_macro_contexts(path.span, last_path_span) {
return;
impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
// We push the self types of `impl`s on a stack here. Only the top type on the stack is
// relevant for linting, since this is the self type of the `impl` we're currently in. To
// avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
// we're in an `impl` or nested item, that we don't want to lint
//
// NB: If you push something on the stack in this method, remember to also pop it in the
// `check_item_post` method.
match &item.kind {
ItemKind::Impl(Impl {
self_ty: hir_self_ty,
of_trait,
..
}) => {
let should_check = if let TyKind::Path(QPath::Resolved(_, ref item_path)) = hir_self_ty.kind {
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
parameters.as_ref().map_or(true, |params| {
!params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
})
} else {
false
};
let impl_trait_ref_def_id = of_trait.as_ref().map(|_| cx.tcx.hir().local_def_id(item.hir_id()));
if should_check {
self.stack.push(StackItem::Check {
hir_id: hir_self_ty.hir_id,
impl_trait_ref_def_id,
types_to_lint: Vec::new(),
types_to_skip: Vec::new(),
});
} else {
self.stack.push(StackItem::NoCheck);
}
},
ItemKind::Static(..)
| ItemKind::Const(..)
| ItemKind::Fn(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..)
| ItemKind::Trait(..) => {
self.stack.push(StackItem::NoCheck);
},
_ => (),
}
}
// Only take path up to the end of last_path_span.
let span = path.span.with_hi(last_path_span.hi());
fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
use ItemKind::{Const, Enum, Fn, Impl, Static, Struct, Trait, Union};
match item.kind {
Impl { .. } | Static(..) | Const(..) | Fn(..) | Enum(..) | Struct(..) | Union(..) | Trait(..) => {
self.stack.pop();
},
_ => (),
}
}
span_lint_and_sugg(
cx,
USE_SELF,
span,
"unnecessary structure name repetition",
"use the applicable keyword",
"Self".to_owned(),
Applicability::MachineApplicable,
);
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
// We want to skip types in trait `impl`s that aren't declared as `Self` in the trait
// declaration. The collection of those types is all this method implementation does.
if_chain! {
if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind;
if let Some(&mut StackItem::Check {
impl_trait_ref_def_id: Some(def_id),
ref mut types_to_skip,
..
}) = self.stack.last_mut();
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(def_id);
then {
// `self_ty` is the semantic self type of `impl <trait> for <type>`. This cannot be
// `Self`.
let self_ty = impl_trait_ref.self_ty();
// `trait_method_sig` is the signature of the function, how it is declared in the
// trait, not in the impl of the trait.
let trait_method = cx
.tcx
.associated_items(impl_trait_ref.def_id)
.find_by_name_and_kind(cx.tcx, impl_item.ident, AssocKind::Fn, impl_trait_ref.def_id)
.expect("impl method matches a trait method");
let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
// `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the
// implementation of the trait.
let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output {
Some(&**ty)
} else {
None
};
let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty);
// `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
//
// `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the
// trait declaration. This is used to check if `Self` was used in the trait
// declaration.
//
// If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed
// to `Self`), we want to skip linting that type and all subtypes of it. This
// avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`, in an `impl Trait
// for u8`, when the trait always uses `Vec<u8>`.
//
// See also https://github.com/rust-lang/rust-clippy/issues/2894.
for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) {
if trait_sem_ty.walk().any(|inner| inner == self_ty.into()) {
let mut visitor = SkipTyCollector::default();
visitor.visit_ty(&impl_hir_ty);
types_to_skip.extend(visitor.types_to_skip);
}
}
}
}
}
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) {
// `hir_ty_to_ty` cannot be called in `Body`s or it will panic (sometimes). But in bodies
// we can use `cx.typeck_results.node_type(..)` to get the `ty::Ty` from a `hir::Ty`.
// However the `node_type()` method can *only* be called in bodies.
//
// This method implementation determines which types should get linted in a `Body` and
// which shouldn't, with a visitor. We could directly lint in the visitor, but then we
// could only allow this lint on item scope. And we would have to check if those types are
// already dealt with in `check_ty` anyway.
if let Some(StackItem::Check {
hir_id,
types_to_lint,
types_to_skip,
..
}) = self.stack.last_mut()
{
let self_ty = ty_from_hir_id(cx, *hir_id);
let mut visitor = LintTyCollector {
cx,
self_ty,
types_to_lint: vec![],
types_to_skip: vec![],
};
visitor.visit_expr(&body.value);
types_to_lint.extend(visitor.types_to_lint);
types_to_skip.extend(visitor.types_to_skip);
}
}
fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
if in_macro(hir_ty.span) | in_impl(cx, hir_ty) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
let lint_dependend_on_expr_kind = if let Some(StackItem::Check {
hir_id,
types_to_lint,
types_to_skip,
..
}) = self.stack.last()
{
if types_to_skip.contains(&hir_ty.hir_id) {
false
} else if types_to_lint.contains(&hir_ty.hir_id) {
true
} else {
let self_ty = ty_from_hir_id(cx, *hir_id);
should_lint_ty(hir_ty, hir_ty_to_ty(cx.tcx, hir_ty), self_ty)
}
} else {
false
};
if lint_dependend_on_expr_kind {
// FIXME: this span manipulation should not be necessary
// @flip1995 found an ast lowering issue in
// https://github.com/rust-lang/rust/blob/master/src/librustc_ast_lowering/path.rs#l142-l162
match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_ty.hir_id)) {
Some(Node::Expr(Expr {
kind: ExprKind::Path(QPath::TypeRelative(_, segment)),
..
})) => span_lint_until_last_segment(cx, hir_ty.span, segment),
_ => span_lint(cx, hir_ty.span),
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
fn expr_ty_matches(cx: &LateContext<'_>, expr: &Expr<'_>, self_ty: Ty<'_>) -> bool {
let def_id = expr.hir_id.owner;
if cx.tcx.has_typeck_results(def_id) {
cx.tcx.typeck(def_id).expr_ty_opt(expr) == Some(self_ty)
} else {
false
}
}
if in_macro(expr.span) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
if let Some(StackItem::Check { hir_id, .. }) = self.stack.last() {
let self_ty = ty_from_hir_id(cx, *hir_id);
match &expr.kind {
ExprKind::Struct(QPath::Resolved(_, path), ..) => {
if expr_ty_matches(cx, expr, self_ty) {
match path.res {
def::Res::SelfTy(..) => (),
def::Res::Def(DefKind::Variant, _) => span_lint_on_path_until_last_segment(cx, path),
_ => {
span_lint(cx, path.span);
},
}
}
},
// tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`)
ExprKind::Call(fun, _) => {
if let Expr {
kind: ExprKind::Path(ref qpath),
..
} = fun
{
if expr_ty_matches(cx, expr, self_ty) {
let res = cx.qpath_res(qpath, fun.hir_id);
if let def::Res::Def(DefKind::Ctor(ctor_of, _), ..) = res {
match ctor_of {
def::CtorOf::Variant => {
span_lint_on_qpath_resolved(cx, qpath, true);
},
def::CtorOf::Struct => {
span_lint_on_qpath_resolved(cx, qpath, false);
},
}
}
}
}
},
// unit enum variants (`Enum::A`)
ExprKind::Path(qpath) => {
if expr_ty_matches(cx, expr, self_ty) {
span_lint_on_qpath_resolved(cx, &qpath, true);
}
},
_ => (),
}
}
}
extract_msrv_attr!(LateContext);
}
// FIXME: always use this (more correct) visitor, not just in method signatures.
struct SemanticUseSelfVisitor<'a, 'tcx> {
#[derive(Default)]
struct SkipTyCollector {
types_to_skip: Vec<HirId>,
}
impl<'tcx> Visitor<'tcx> for SkipTyCollector {
type Map = Map<'tcx>;
fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) {
self.types_to_skip.push(hir_ty.hir_id);
walk_ty(self, hir_ty)
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
struct LintTyCollector<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
self_ty: Ty<'tcx>,
types_to_lint: Vec<HirId>,
types_to_skip: Vec<HirId>,
}
impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for LintTyCollector<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) {
if let TyKind::Path(QPath::Resolved(_, path)) = &hir_ty.kind {
match path.res {
def::Res::SelfTy(..) => {},
_ => {
if hir_ty_to_ty(self.cx.tcx, hir_ty) == self.self_ty {
span_use_self_lint(self.cx, path, None);
}
},
if_chain! {
if let Some(ty) = self.cx.typeck_results().node_type_opt(hir_ty.hir_id);
if should_lint_ty(hir_ty, ty, self.self_ty);
then {
self.types_to_lint.push(hir_ty.hir_id);
} else {
self.types_to_skip.push(hir_ty.hir_id);
}
}
@ -111,177 +386,78 @@ impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
}
}
fn check_trait_method_impl_decl<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &ImplItem<'_>,
impl_decl: &'tcx FnDecl<'_>,
impl_trait_ref: ty::TraitRef<'tcx>,
) {
let trait_method = cx
.tcx
.associated_items(impl_trait_ref.def_id)
.find_by_name_and_kind(cx.tcx, impl_item.ident, ty::AssocKind::Fn, impl_trait_ref.def_id)
.expect("impl method matches a trait method");
fn span_lint(cx: &LateContext<'_>, span: Span) {
span_lint_and_sugg(
cx,
USE_SELF,
span,
"unnecessary structure name repetition",
"use the applicable keyword",
"Self".to_owned(),
Applicability::MachineApplicable,
);
}
let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
let output_hir_ty = if let FnRetTy::Return(ty) = &impl_decl.output {
Some(&**ty)
} else {
None
#[allow(clippy::cast_possible_truncation)]
fn span_lint_until_last_segment(cx: &LateContext<'_>, span: Span, segment: &PathSegment<'_>) {
let sp = span.with_hi(segment.ident.span.lo());
// remove the trailing ::
let span_without_last_segment = match snippet_opt(cx, sp) {
Some(snippet) => match snippet.rfind("::") {
Some(bidx) => sp.with_hi(sp.lo() + BytePos(bidx as u32)),
None => sp,
},
None => sp,
};
span_lint(cx, span_without_last_segment);
}
// `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
// `trait_ty` (of type `ty::Ty`) is the semantic type for the signature in the trait.
// We use `impl_hir_ty` to see if the type was written as `Self`,
// `hir_ty_to_ty(...)` to check semantic types of paths, and
// `trait_ty` to determine which parts of the signature in the trait, mention
// the type being implemented verbatim (as opposed to `Self`).
for (impl_hir_ty, trait_ty) in impl_decl
.inputs
.iter()
.chain(output_hir_ty)
.zip(trait_method_sig.inputs_and_output)
{
// Check if the input/output type in the trait method specifies the implemented
// type verbatim, and only suggest `Self` if that isn't the case.
// This avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`,
// in an `impl Trait for u8`, when the trait always uses `Vec<u8>`.
// See also https://github.com/rust-lang/rust-clippy/issues/2894.
let self_ty = impl_trait_ref.self_ty();
if !trait_ty.walk().any(|inner| inner == self_ty.into()) {
let mut visitor = SemanticUseSelfVisitor { cx, self_ty };
fn span_lint_on_path_until_last_segment(cx: &LateContext<'_>, path: &Path<'_>) {
if path.segments.len() > 1 {
span_lint_until_last_segment(cx, path.span, path.segments.last().unwrap());
}
}
visitor.visit_ty(&impl_hir_ty);
fn span_lint_on_qpath_resolved(cx: &LateContext<'_>, qpath: &QPath<'_>, until_last_segment: bool) {
if let QPath::Resolved(_, path) = qpath {
if until_last_segment {
span_lint_on_path_until_last_segment(cx, path);
} else {
span_lint(cx, path.span);
}
}
}
const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
pub struct UseSelf {
msrv: Option<RustcVersion>,
}
impl UseSelf {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
fn ty_from_hir_id<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Ty<'tcx> {
if let Some(Node::Ty(hir_ty)) = cx.tcx.hir().find(hir_id) {
hir_ty_to_ty(cx.tcx, hir_ty)
} else {
unreachable!("This function should only be called with `HirId`s that are for sure `Node::Ty`")
}
}
impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
if in_external_macro(cx.sess(), item.span) {
return;
}
if_chain! {
if let ItemKind::Impl(impl_) = &item.kind;
if let TyKind::Path(QPath::Resolved(_, ref item_path)) = impl_.self_ty.kind;
then {
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
let should_check = parameters.as_ref().map_or(
true,
|params| !params.parenthesized
&&!params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
);
if should_check {
let visitor = &mut UseSelfVisitor {
item_path,
cx,
};
let impl_trait_ref = cx.tcx.impl_trait_ref(item.def_id);
if let Some(impl_trait_ref) = impl_trait_ref {
for impl_item_ref in impl_.items {
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
if let ImplItemKind::Fn(FnSig{ decl: impl_decl, .. }, impl_body_id)
= &impl_item.kind {
check_trait_method_impl_decl(cx, impl_item, impl_decl, impl_trait_ref);
let body = cx.tcx.hir().body(*impl_body_id);
visitor.visit_body(body);
} else {
visitor.visit_impl_item(impl_item);
}
}
} else {
for impl_item_ref in impl_.items {
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
visitor.visit_impl_item(impl_item);
}
}
}
}
fn in_impl(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> bool {
let map = cx.tcx.hir();
let parent = map.get_parent_node(hir_ty.hir_id);
if_chain! {
if let Some(Node::Item(item)) = map.find(parent);
if let ItemKind::Impl { .. } = item.kind;
then {
true
} else {
false
}
}
extract_msrv_attr!(LateContext);
}
struct UseSelfVisitor<'a, 'tcx> {
item_path: &'a Path<'a>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for UseSelfVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
if !path.segments.iter().any(|p| p.ident.span.is_dummy()) {
if path.segments.len() >= 2 {
let last_but_one = &path.segments[path.segments.len() - 2];
if last_but_one.ident.name != kw::SelfUpper {
let enum_def_id = match path.res {
Res::Def(DefKind::Variant, variant_def_id) => self.cx.tcx.parent(variant_def_id),
Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), ctor_def_id) => {
let variant_def_id = self.cx.tcx.parent(ctor_def_id);
variant_def_id.and_then(|def_id| self.cx.tcx.parent(def_id))
},
_ => None,
};
if self.item_path.res.opt_def_id() == enum_def_id {
span_use_self_lint(self.cx, path, Some(last_but_one));
}
}
}
if path.segments.last().expect(SEGMENTS_MSG).ident.name != kw::SelfUpper {
if self.item_path.res == path.res {
span_use_self_lint(self.cx, path, None);
} else if let Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), ctor_def_id) = path.res {
if self.item_path.res.opt_def_id() == self.cx.tcx.parent(ctor_def_id) {
span_use_self_lint(self.cx, path, None);
}
}
}
}
walk_path(self, path);
}
fn visit_item(&mut self, item: &'tcx Item<'_>) {
match item.kind {
ItemKind::Use(..)
| ItemKind::Static(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..)
| ItemKind::Impl { .. }
| ItemKind::Fn(..) => {
// Don't check statements that shadow `Self` or where `Self` can't be used
},
_ => walk_item(self, item),
fn should_lint_ty(hir_ty: &hir::Ty<'_>, ty: Ty<'_>, self_ty: Ty<'_>) -> bool {
if_chain! {
if TyS::same_type(ty, self_ty);
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
then {
!matches!(path.res, def::Res::SelfTy(..))
} else {
false
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::All(self.cx.tcx.hir())
}
}

View File

@ -126,7 +126,7 @@ define_Conf! {
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
"WebGL",
"TensorFlow",
"TrueType",
@ -173,6 +173,8 @@ define_Conf! {
(disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
/// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
(unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true),
/// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
(upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false),
/// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
(cargo_ignore_publish, "cargo_ignore_publish": bool, false),
}

View File

@ -4,7 +4,7 @@ use crate::utils::{
span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq,
};
use if_chain::if_chain;
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId};
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId};
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
@ -301,17 +301,12 @@ declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) {
if let Some(utils) = krate
.module
.items
.iter()
.find(|item| item.ident.name.as_str() == "utils")
{
if let ItemKind::Mod(ref utils_mod) = utils.kind {
if let Some(paths) = utils_mod.items.iter().find(|item| item.ident.name.as_str() == "paths") {
if let ItemKind::Mod(ref paths_mod) = paths.kind {
if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
let mut last_name: Option<SymbolStr> = None;
for item in &*paths_mod.items {
for item in items {
let name = item.ident.as_str();
if let Some(ref last_name) = last_name {
if **last_name > *name {
@ -343,7 +338,7 @@ impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]);
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !run_lints(cx, &[DEFAULT_LINT], item.hir_id) {
if !run_lints(cx, &[DEFAULT_LINT], item.hir_id()) {
return;
}
@ -393,7 +388,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
.find(|iiref| iiref.ident.as_str() == "get_lints")
.expect("LintPass needs to implement get_lints")
.id
.hir_id,
.hir_id(),
);
collector.visit_expr(&cx.tcx.hir().body(body_id).value);
}
@ -861,7 +856,7 @@ declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let local_def_id = &cx.tcx.parent_module(item.hir_id);
let local_def_id = &cx.tcx.parent_module(item.hir_id());
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! {
if mod_name.as_str() == "paths";

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,14 @@
use crate::utils::{is_type_diagnostic_item, match_def_path, paths, snippet, span_lint_and_sugg};
use crate::utils::{
is_type_diagnostic_item, match_def_path, path_to_local, path_to_local_id, paths, snippet, span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span, Symbol};
use rustc_span::{symbol::sym, Span};
use std::convert::TryInto;
declare_clippy_lint! {
@ -45,8 +47,8 @@ enum VecInitKind {
WithCapacity(u64),
}
struct VecPushSearcher {
local_id: HirId,
init: VecInitKind,
name: Symbol,
lhs_is_local: bool,
lhs_span: Span,
err_span: Span,
@ -81,17 +83,20 @@ impl VecPushSearcher {
}
impl LateLintPass<'_> for VecInitThenPush {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
self.searcher = None;
}
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
if_chain! {
if !in_external_macro(cx.sess(), local.span);
if let Some(init) = local.init;
if let PatKind::Binding(BindingAnnotation::Mutable, _, ident, None) = local.pat.kind;
if let PatKind::Binding(BindingAnnotation::Mutable, id, _, None) = local.pat.kind;
if let Some(init_kind) = get_vec_init_kind(cx, init);
then {
self.searcher = Some(VecPushSearcher {
local_id: id,
init: init_kind,
name: ident.name,
lhs_is_local: true,
lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)),
err_span: local.span,
@ -106,13 +111,12 @@ impl LateLintPass<'_> for VecInitThenPush {
if_chain! {
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Assign(left, right, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = left.kind;
if let Some(name) = path.segments.get(0);
if let Some(id) = path_to_local(left);
if let Some(init_kind) = get_vec_init_kind(cx, right);
then {
self.searcher = Some(VecPushSearcher {
local_id: id,
init: init_kind,
name: name.ident.name,
lhs_is_local: false,
lhs_span: left.span,
err_span: expr.span,
@ -128,10 +132,8 @@ impl LateLintPass<'_> for VecInitThenPush {
if_chain! {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind;
if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind;
if path_to_local_id(self_arg, searcher.local_id);
if path.ident.name.as_str() == "push";
if let ExprKind::Path(QPath::Resolved(_, self_path)) = self_arg.kind;
if let [self_name] = self_path.segments;
if self_name.ident.name == searcher.name;
then {
self.searcher = Some(VecPushSearcher {
found: searcher.found + 1,

View File

@ -149,7 +149,6 @@ declare_clippy_lint! {
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
///
/// // Bad
/// writeln!(buf, "");
///
@ -176,7 +175,6 @@ declare_clippy_lint! {
/// # use std::fmt::Write;
/// # let mut buf = String::new();
/// # let name = "World";
///
/// // Bad
/// write!(buf, "Hello {}!\n", name);
///
@ -202,7 +200,6 @@ declare_clippy_lint! {
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
///
/// // Bad
/// writeln!(buf, "{}", "foo");
///

View File

@ -0,0 +1,19 @@
[package]
name = "clippy_utils"
version = "0.1.52"
authors = ["The Rust Clippy Developers"]
edition = "2018"
publish = false
[dependencies]
if_chain = "1.0.0"
itertools = "0.9"
regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] }
smallvec = { version = "1", features = ["union"] }
toml = "0.5.3"
unicode-normalization = "0.1"
rustc-semver="1.1.0"
[features]
internal-lints = []

View File

@ -4,7 +4,7 @@
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::utils::{both, over};
use crate::{both, over};
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
@ -229,23 +229,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(lt, lm, le), Static(rt, rm, re)) => {
lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Const(ld, lt, le), Const(rd, rt, re)) => {
eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& both(lb, rb, |l, r| eq_block(l, r))
}
(Mod(lu, lmk), Mod(ru, rmk)) => lu == ru && match (lmk, rmk) {
(ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) =>
linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind)),
(ModKind::Unloaded, ModKind::Unloaded) => true,
_ => false,
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(Mod(lu, lmk), Mod(ru, rmk)) => {
lu == ru
&& match (lmk, rmk) {
(ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
},
(ModKind::Unloaded, ModKind::Unloaded) => true,
_ => false,
}
},
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
@ -311,15 +308,10 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
use ForeignItemKind::*;
match (l, r) {
(Static(lt, lm, le), Static(rt, rm, re)) => {
lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& both(lb, rb, |l, r| eq_block(l, r))
}
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)

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