"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]]
version = "0.1.52"
dependencies = [
"cargo_metadata 0.12.0",
+ "clippy_utils",
"if_chain",
"itertools 0.9.0",
"pulldown-cmark 0.8.0",
"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"
"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",
]
"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"
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",
]
dependencies = [
"cfg-if 0.1.10",
"libc",
- "redox_syscall",
+ "redox_syscall 0.1.57",
"winapi 0.3.9",
]
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",
"cloudabi",
"instant",
"libc",
- "redox_syscall",
+ "redox_syscall 0.1.57",
"smallvec 1.6.1",
"winapi 0.3.9",
]
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"
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"
"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"
dependencies = [
"cfg-if 0.1.10",
"libc",
- "redox_syscall",
+ "redox_syscall 0.1.57",
"winapi 0.3.9",
]
dependencies = [
"filetime",
"libc",
- "redox_syscall",
+ "redox_syscall 0.1.57",
"xattr",
]
"cfg-if 0.1.10",
"libc",
"rand",
- "redox_syscall",
+ "redox_syscall 0.1.57",
"remove_dir_all",
"winapi 0.3.9",
]
"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"
"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"
/// 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";`.
/// 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>;`.
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def::Res;
+use rustc_hir::definitions::DefPathData;
use rustc_session::parse::feature_err;
+use rustc_span::hygiene::ExpnId;
use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned};
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{hygiene::ForLoopLoc, DUMMY_SP};
}
ExprKind::Tup(ref elts) => hir::ExprKind::Tup(self.lower_exprs(elts)),
ExprKind::Call(ref f, ref args) => {
- let f = self.lower_expr(f);
- hir::ExprKind::Call(f, self.lower_exprs(args))
+ if let Some(legacy_args) = self.resolver.legacy_const_generic_args(f) {
+ self.lower_legacy_const_generics((**f).clone(), args.clone(), &legacy_args)
+ } else {
+ let f = self.lower_expr(f);
+ hir::ExprKind::Call(f, self.lower_exprs(args))
+ }
}
ExprKind::MethodCall(ref seg, ref args, span) => {
let hir_seg = self.arena.alloc(self.lower_path_segment(
}
}
+ fn lower_legacy_const_generics(
+ &mut self,
+ mut f: Expr,
+ args: Vec<AstP<Expr>>,
+ legacy_args_idx: &[usize],
+ ) -> hir::ExprKind<'hir> {
+ let path = match f.kind {
+ ExprKind::Path(None, ref mut path) => path,
+ _ => unreachable!(),
+ };
+
+ // Split the arguments into const generics and normal arguments
+ let mut real_args = vec![];
+ let mut generic_args = vec![];
+ for (idx, arg) in args.into_iter().enumerate() {
+ if legacy_args_idx.contains(&idx) {
+ let parent_def_id = self.current_hir_id_owner.last().unwrap().0;
+ let node_id = self.resolver.next_node_id();
+
+ // Add a definition for the in-band const def.
+ self.resolver.create_def(
+ parent_def_id,
+ node_id,
+ DefPathData::AnonConst,
+ ExpnId::root(),
+ arg.span,
+ );
+
+ let anon_const = AnonConst { id: node_id, value: arg };
+ generic_args.push(AngleBracketedArg::Arg(GenericArg::Const(anon_const)));
+ } else {
+ real_args.push(arg);
+ }
+ }
+
+ // Add generic args to the last element of the path.
+ let last_segment = path.segments.last_mut().unwrap();
+ assert!(last_segment.args.is_none());
+ last_segment.args = Some(AstP(GenericArgs::AngleBracketed(AngleBracketedArgs {
+ span: DUMMY_SP,
+ args: generic_args,
+ })));
+
+ // Now lower everything as normal.
+ let f = self.lower_expr(&f);
+ hir::ExprKind::Call(f, self.lower_exprs(&real_args))
+ }
+
/// Emit an error and lower `ast::ExprKind::Let(pat, scrutinee)` into:
/// ```rust
/// match scrutinee { pats => true, _ => false }
fn item_generics_num_lifetimes(&self, def: DefId, sess: &Session) -> usize;
+ fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option<Vec<usize>>;
+
/// Obtains resolution for a `NodeId` with a single resolution.
fn get_partial_res(&mut self, id: NodeId) -> Option<PartialRes>;
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,
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;
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);
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);
// 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
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");
use rustc_middle::hir::map as hir_map;
use rustc_middle::ty::{self, TyCtxt};
use rustc_mir::util::{write_mir_graphviz, write_mir_pretty};
-use rustc_session::config::{Input, PpMode, PpSourceMode};
+use rustc_session::config::{Input, PpHirMode, PpMode, PpSourceMode};
use rustc_session::Session;
use rustc_span::symbol::Ident;
use rustc_span::FileName;
F: FnOnce(&dyn PrinterSupport) -> A,
{
match *ppmode {
- PpmNormal | PpmEveryBodyLoops | PpmExpanded => {
+ Normal | EveryBodyLoops | Expanded => {
let annotation = NoAnn { sess, tcx };
f(&annotation)
}
- PpmIdentified | PpmExpandedIdentified => {
+ Identified | ExpandedIdentified => {
let annotation = IdentifiedAnnotation { sess, tcx };
f(&annotation)
}
- PpmExpandedHygiene => {
+ ExpandedHygiene => {
let annotation = HygieneAnnotation { sess };
f(&annotation)
}
- _ => panic!("Should use call_with_pp_support_hir"),
}
}
-fn call_with_pp_support_hir<A, F>(ppmode: &PpSourceMode, tcx: TyCtxt<'_>, f: F) -> A
+fn call_with_pp_support_hir<A, F>(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> A
where
F: FnOnce(&dyn HirPrinterSupport<'_>, &hir::Crate<'_>) -> A,
{
match *ppmode {
- PpmNormal => {
+ PpHirMode::Normal => {
let annotation = NoAnn { sess: tcx.sess, tcx: Some(tcx) };
f(&annotation, tcx.hir().krate())
}
- PpmIdentified => {
+ PpHirMode::Identified => {
let annotation = IdentifiedAnnotation { sess: tcx.sess, tcx: Some(tcx) };
f(&annotation, tcx.hir().krate())
}
- PpmTyped => {
+ PpHirMode::Typed => {
abort_on_err(tcx.analysis(LOCAL_CRATE), tcx.sess);
let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) };
tcx.dep_graph.with_ignore(|| f(&annotation, tcx.hir().krate()))
}
- _ => panic!("Should use call_with_pp_support"),
}
}
) {
let (src, src_name) = get_source(input, sess);
- let mut out = String::new();
-
- if let PpmSource(s) = ppm {
+ let out = if let Source(s) = ppm {
// Silently ignores an identified node.
- let out = &mut out;
call_with_pp_support(&s, sess, None, move |annotation| {
debug!("pretty printing source code {:?}", s);
let sess = annotation.sess();
let parse = &sess.parse_sess;
- *out = pprust::print_crate(
+ pprust::print_crate(
sess.source_map(),
krate,
src_name,
)
})
} else {
- unreachable!();
+ unreachable!()
};
write_or_print(&out, ofile);
let (src, src_name) = get_source(input, tcx.sess);
- let mut out = String::new();
-
- match ppm {
- PpmSource(s) => {
+ let out = match ppm {
+ Source(s) => {
// Silently ignores an identified node.
- let out = &mut out;
call_with_pp_support(&s, tcx.sess, Some(tcx), move |annotation| {
debug!("pretty printing source code {:?}", s);
let sess = annotation.sess();
let parse = &sess.parse_sess;
- *out = pprust::print_crate(
+ pprust::print_crate(
sess.source_map(),
krate,
src_name,
})
}
- PpmHir(s) => {
- let out = &mut out;
- call_with_pp_support_hir(&s, tcx, move |annotation, krate| {
- debug!("pretty printing source code {:?}", s);
- let sess = annotation.sess();
- let sm = sess.source_map();
- *out = pprust_hir::print_crate(sm, krate, src_name, src, annotation.pp_ann())
- })
- }
+ Hir(s) => call_with_pp_support_hir(&s, tcx, move |annotation, krate| {
+ debug!("pretty printing HIR {:?}", s);
+ let sess = annotation.sess();
+ let sm = sess.source_map();
+ pprust_hir::print_crate(sm, krate, src_name, src, annotation.pp_ann())
+ }),
- PpmHirTree(s) => {
- let out = &mut out;
- call_with_pp_support_hir(&s, tcx, move |_annotation, krate| {
- debug!("pretty printing source code {:?}", s);
- *out = format!("{:#?}", krate);
- });
- }
+ HirTree => call_with_pp_support_hir(&PpHirMode::Normal, tcx, move |_annotation, krate| {
+ debug!("pretty printing HIR tree");
+ format!("{:#?}", krate)
+ }),
_ => unreachable!(),
- }
+ };
write_or_print(&out, ofile);
}
tcx.analysis(LOCAL_CRATE)?;
match ppm {
- PpmMir | PpmMirCFG => match ppm {
- PpmMir => write_mir_pretty(tcx, None, &mut out),
- PpmMirCFG => write_mir_graphviz(tcx, None, &mut out),
- _ => unreachable!(),
- },
+ Mir => write_mir_pretty(tcx, None, &mut out).unwrap(),
+ MirCFG => write_mir_graphviz(tcx, None, &mut out).unwrap(),
_ => unreachable!(),
}
- .unwrap();
let out = std::str::from_utf8(&out).unwrap();
write_or_print(out, ofile);
Erroneous code example:
-```ignore (multiple source files required for compile_fail)
+```ignore (Multiple source files are required for compile_fail.)
// file: ambiguous_module/mod.rs
fn foo() {}
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
+ assert!(!suggestion.is_empty());
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion
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()
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
+ assert!(!suggestion.is_empty());
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: suggestion
rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL),
rustc_attr!(rustc_args_required_const, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
+ rustc_attr!(rustc_legacy_const_generics, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
// ==========================================================================
// Internal attributes, Layout related:
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,
rustc_builtin_macros::test_harness::inject(&sess, &mut resolver, &mut krate)
});
- if let Some(PpMode::PpmSource(PpSourceMode::PpmEveryBodyLoops)) = sess.opts.pretty {
+ if let Some(PpMode::Source(PpSourceMode::EveryBodyLoops)) = sess.opts.pretty {
tracing::debug!("replacing bodies with loop {{}}");
util::ReplaceBodyWithLoop::new(&mut resolver).visit_crate(&mut krate);
}
pub fn num_def_ids(&self, cnum: CrateNum) -> usize {
self.get_crate_data(cnum).num_def_ids()
}
+
+ pub fn item_attrs(&self, def_id: DefId, sess: &Session) -> Vec<ast::Attribute> {
+ self.get_crate_data(def_id.krate).get_item_attrs(def_id.index, sess).collect()
+ }
}
impl CrateStore for CStore {
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)
/// error which will report the first range of bytes which is uninitialized.
fn check_init(&self, ptr: Pointer<Tag>, size: Size) -> InterpResult<'tcx> {
self.is_init(ptr, size).or_else(|idx_range| {
- throw_ub!(InvalidUninitBytes(Some(Box::new(UninitBytesAccess {
+ throw_ub!(InvalidUninitBytes(Some(UninitBytesAccess {
access_ptr: ptr.erase_tag(),
access_size: size,
uninit_ptr: Pointer::new(ptr.alloc_id, idx_range.start),
uninit_size: idx_range.end - idx_range.start, // `Size` subtraction
- }))))
+ })))
})
}
use rustc_session::CtfeBacktrace;
use rustc_span::def_id::DefId;
use rustc_target::abi::{Align, Size};
-use std::{any::Any, backtrace::Backtrace, fmt, mem};
+use std::{any::Any, backtrace::Backtrace, fmt};
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum ErrorHandled {
/// Using a string that is not valid UTF-8,
InvalidStr(std::str::Utf8Error),
/// Using uninitialized data where it is not allowed.
- InvalidUninitBytes(Option<Box<UninitBytesAccess>>),
+ InvalidUninitBytes(Option<UninitBytesAccess>),
/// Working with a local that is not currently live.
DeadLocal,
/// Data size is not equal to target size.
}
#[cfg(target_arch = "x86_64")]
-static_assert_size!(InterpError<'_>, 40);
+static_assert_size!(InterpError<'_>, 72);
pub enum InterpError<'tcx> {
/// The program caused undefined behavior.
}
impl InterpError<'_> {
- /// Some errors allocate to be created as they contain free-form strings.
- /// And sometimes we want to be sure that did not happen as it is a
- /// waste of resources.
- pub fn allocates(&self) -> bool {
+ /// Some errors to string formatting even if the error is never printed.
+ /// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors,
+ /// so this method lets us detect them and `bug!` on unexpected errors.
+ pub fn formatted_string(&self) -> bool {
match self {
- // Zero-sized boxes do not allocate.
- InterpError::MachineStop(b) => mem::size_of_val::<dyn MachineStopType>(&**b) > 0,
InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure(_))
- | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
- | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some(_))) => {
- true
- }
+ | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) => true,
_ => false,
}
}
desc { |tcx| "computing normalized predicates of `{}`", tcx.def_path_str(def_id) }
}
- /// Like `param_env`, but returns the `ParamEnv in `Reveal::All` mode.
+ /// Like `param_env`, but returns the `ParamEnv` in `Reveal::All` mode.
/// Prefer this over `tcx.param_env(def_id).with_reveal_all_normalized(tcx)`,
/// as this method is more efficient.
query param_env_reveal_all_normalized(def_id: DefId) -> ty::ParamEnv<'tcx> {
query normalize_opaque_types(key: &'tcx ty::List<ty::Predicate<'tcx>>) -> &'tcx ty::List<ty::Predicate<'tcx>> {
desc { "normalizing opaque types in {:?}", key }
}
+
+ /// Checks whether a type is definitely uninhabited. This is
+ /// conservative: for some types that are uninhabited we return `false`,
+ /// but we only return `true` for types that are definitely uninhabited.
+ /// `ty.conservative_is_privately_uninhabited` implies that any value of type `ty`
+ /// will be `Abi::Uninhabited`. (Note that uninhabited types may have nonzero
+ /// size, to account for partial initialisation. See #49298 for details.)
+ query conservative_is_privately_uninhabited(key: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
+ desc { "conservatively checking if {:?} is privately uninhabited", key }
+ }
}
let layout = cx.layout_raw_uncached(ty);
// Type-level uninhabitedness should always imply ABI uninhabitedness.
if let Ok(layout) = layout {
- if ty.conservative_is_privately_uninhabited(tcx) {
+ if tcx.conservative_is_privately_uninhabited(param_env.and(ty)) {
assert!(layout.abi.is_uninhabited());
}
}
let size =
element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow(ty))?;
- let abi = if count != 0 && ty.conservative_is_privately_uninhabited(tcx) {
- Abi::Uninhabited
- } else {
- Abi::Aggregate { sized: true }
- };
+ let abi =
+ if count != 0 && tcx.conservative_is_privately_uninhabited(param_env.and(ty)) {
+ Abi::Uninhabited
+ } else {
+ Abi::Aggregate { sized: true }
+ };
let largest_niche = if count != 0 { element.largest_niche.clone() } else { None };
matches!(self.kind(), Never)
}
- /// Checks whether a type is definitely uninhabited. This is
- /// conservative: for some types that are uninhabited we return `false`,
- /// but we only return `true` for types that are definitely uninhabited.
- /// `ty.conservative_is_privately_uninhabited` implies that any value of type `ty`
- /// will be `Abi::Uninhabited`. (Note that uninhabited types may have nonzero
- /// size, to account for partial initialisation. See #49298 for details.)
- pub fn conservative_is_privately_uninhabited(&self, tcx: TyCtxt<'tcx>) -> bool {
- // FIXME(varkor): we can make this less conversative by substituting concrete
- // type arguments.
- match self.kind() {
- ty::Never => true,
- ty::Adt(def, _) if def.is_union() => {
- // For now, `union`s are never considered uninhabited.
- false
- }
- ty::Adt(def, _) => {
- // Any ADT is uninhabited if either:
- // (a) It has no variants (i.e. an empty `enum`);
- // (b) Each of its variants (a single one in the case of a `struct`) has at least
- // one uninhabited field.
- def.variants.iter().all(|var| {
- var.fields.iter().any(|field| {
- tcx.type_of(field.did).conservative_is_privately_uninhabited(tcx)
- })
- })
- }
- ty::Tuple(..) => {
- self.tuple_fields().any(|ty| ty.conservative_is_privately_uninhabited(tcx))
- }
- ty::Array(ty, len) => {
- match len.try_eval_usize(tcx, ParamEnv::empty()) {
- Some(0) | None => false,
- // If the array is definitely non-empty, it's uninhabited if
- // the type of its elements is uninhabited.
- Some(1..) => ty.conservative_is_privately_uninhabited(tcx),
- }
- }
- ty::Ref(..) => {
- // References to uninitialised memory is valid for any type, including
- // uninhabited types, in unsafe code, so we treat all references as
- // inhabited.
- false
- }
- _ => false,
- }
- }
-
#[inline]
pub fn is_primitive(&self) -> bool {
self.kind().is_primitive()
}
}
None => {
- if !sig.output().conservative_is_privately_uninhabited(self.tcx()) {
+ if !self
+ .tcx()
+ .conservative_is_privately_uninhabited(self.param_env.and(sig.output()))
+ {
span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
}
}
//! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use
//! `visit_results`. The following example uses the `ResultsCursor` approach.
//!
-//! ```ignore(cross-crate-imports)
+//! ```ignore (cross-crate-imports)
//! use rustc_mir::dataflow::Analysis; // Makes `into_engine` available.
//!
//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
/// default impl and the one for all `A: GenKillAnalysis` will do the right thing.
/// Its purpose is to enable method chaining like so:
///
- /// ```ignore(cross-crate-imports)
+ /// ```ignore (cross-crate-imports)
/// let results = MyAnalysis::new(tcx, body)
/// .into_engine(tcx, body, def_id)
/// .iterate_to_fixpoint()
error
),
);
- // Some errors shouldn't come up because creating them causes
- // an allocation, which we should avoid. When that happens,
- // dedicated error variants should be introduced instead.
- assert!(
- !error.kind().allocates(),
- "interning encountered allocating error: {}",
- error
- );
}
}
}
// an allocation, which we should avoid. When that happens,
// dedicated error variants should be introduced instead.
assert!(
- !error.kind().allocates(),
- "const-prop encountered allocating error: {}",
+ !error.kind().formatted_string(),
+ "const-prop encountered formatting error: {}",
error
);
None
assert_block
}
-fn can_return<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
+fn can_return<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
// Returning from a function with an uninhabited return type is undefined behavior.
- if body.return_ty().conservative_is_privately_uninhabited(tcx) {
+ if tcx.conservative_is_privately_uninhabited(param_env.and(body.return_ty())) {
return false;
}
// `storage_liveness` tells us which locals have live storage at suspension points
let (remap, layout, storage_liveness) = compute_layout(liveness_info, body);
- let can_return = can_return(tcx, body);
+ let can_return = can_return(tcx, body, tcx.param_env(body.source.def_id()));
// Run the transformation which converts Places from Local to generator struct
// accesses for locals in `remap`.
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)?;
self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target)
} else if self.tcx.sess.check_name(attr, sym::naked) {
self.check_naked(hir_id, attr, span, target)
+ } else if self.tcx.sess.check_name(attr, sym::rustc_legacy_const_generics) {
+ self.check_rustc_legacy_const_generics(&attr, span, target, item)
} else {
// lint-only checks
if self.tcx.sess.check_name(attr, sym::cold) {
}
}
+ /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument.
+ fn check_rustc_legacy_const_generics(
+ &self,
+ attr: &Attribute,
+ span: &Span,
+ target: Target,
+ item: Option<ItemLike<'_>>,
+ ) -> bool {
+ let is_function = matches!(target, Target::Fn | Target::Method(..));
+ if !is_function {
+ self.tcx
+ .sess
+ .struct_span_err(attr.span, "attribute should be applied to a function")
+ .span_label(*span, "not a function")
+ .emit();
+ return false;
+ }
+
+ let list = match attr.meta_item_list() {
+ // The attribute form is validated on AST.
+ None => return false,
+ Some(it) => it,
+ };
+
+ let (decl, generics) = match item {
+ Some(ItemLike::Item(Item {
+ kind: ItemKind::Fn(FnSig { decl, .. }, generics, _),
+ ..
+ })) => (decl, generics),
+ _ => bug!("should be a function item"),
+ };
+
+ for param in generics.params {
+ match param.kind {
+ hir::GenericParamKind::Const { .. } => {}
+ _ => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ attr.span,
+ "#[rustc_legacy_const_generics] functions must \
+ only have const generics",
+ )
+ .span_label(param.span, "non-const generic parameter")
+ .emit();
+ return false;
+ }
+ }
+ }
+
+ if list.len() != generics.params.len() {
+ self.tcx
+ .sess
+ .struct_span_err(
+ attr.span,
+ "#[rustc_legacy_const_generics] must have one index for each generic parameter",
+ )
+ .span_label(generics.span, "generic parameters")
+ .emit();
+ return false;
+ }
+
+ let arg_count = decl.inputs.len() as u128 + generics.params.len() as u128;
+ let mut invalid_args = vec![];
+ for meta in list {
+ if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) {
+ if *val >= arg_count {
+ let span = meta.span();
+ self.tcx
+ .sess
+ .struct_span_err(span, "index exceeds number of arguments")
+ .span_label(
+ span,
+ format!(
+ "there {} only {} argument{}",
+ if arg_count != 1 { "are" } else { "is" },
+ arg_count,
+ pluralize!(arg_count)
+ ),
+ )
+ .emit();
+ return false;
+ }
+ } else {
+ invalid_args.push(meta.span());
+ }
+ }
+
+ if !invalid_args.is_empty() {
+ self.tcx
+ .sess
+ .struct_span_err(invalid_args, "arguments should be non-negative integers")
+ .emit();
+ false
+ } else {
+ true
+ }
+ }
+
/// Checks if `#[link_section]` is applied to a function or static.
fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
match target {
ExprKind::Call(ref callee, ref arguments) => {
self.resolve_expr(callee, Some(expr));
- for argument in arguments {
- self.resolve_expr(argument, None);
+ let const_args = self.r.legacy_const_generic_args(callee).unwrap_or(Vec::new());
+ for (idx, argument) in arguments.iter().enumerate() {
+ // Constant arguments need to be treated as AnonConst since
+ // that is how they will be later lowered to HIR.
+ if const_args.contains(&idx) {
+ self.with_constant_rib(
+ IsRepeatExpr::No,
+ argument.is_potential_trivial_const_param(),
+ None,
+ |this| {
+ this.resolve_expr(argument, None);
+ },
+ );
+ } else {
+ self.resolve_expr(argument, None);
+ }
}
}
ExprKind::Type(ref type_expr, ref ty) => {
.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())
}
}
- 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
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,
);
}
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{self as ast, NodeId};
use rustc_ast::{Crate, CRATE_NODE_ID};
+use rustc_ast::{Expr, ExprKind, LitKind};
use rustc_ast::{ItemKind, ModKind, Path};
use rustc_ast_lowering::ResolverAstLowering;
use rustc_ast_pretty::pprust;
/// Some way to know that we are in a *trait* impl in `visit_assoc_item`.
/// FIXME: Replace with a more general AST map (together with some other fields).
trait_impl_items: FxHashSet<LocalDefId>,
+
+ legacy_const_generic_args: FxHashMap<DefId, Option<Vec<usize>>>,
}
/// Nothing really interesting here; it just provides memory for the rest of the crate.
self.cstore().item_generics_num_lifetimes(def_id, sess)
}
+ fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option<Vec<usize>> {
+ self.legacy_const_generic_args(expr)
+ }
+
fn get_partial_res(&mut self, id: NodeId) -> Option<PartialRes> {
self.partial_res_map.get(&id).cloned()
}
invocation_parents,
next_disambiguator: Default::default(),
trait_impl_items: Default::default(),
+ legacy_const_generic_args: Default::default(),
};
let root_parent_scope = ParentScope::module(graph_root, &resolver);
pub fn opt_span(&self, def_id: DefId) -> Option<Span> {
if let Some(def_id) = def_id.as_local() { Some(self.def_id_to_span[def_id]) } else { None }
}
+
+ /// Checks if an expression refers to a function marked with
+ /// `#[rustc_legacy_const_generics]` and returns the argument index list
+ /// from the attribute.
+ pub fn legacy_const_generic_args(&mut self, expr: &Expr) -> Option<Vec<usize>> {
+ if let ExprKind::Path(None, path) = &expr.kind {
+ // Don't perform legacy const generics rewriting if the path already
+ // has generic arguments.
+ if path.segments.last().unwrap().args.is_some() {
+ return None;
+ }
+
+ let partial_res = self.partial_res_map.get(&expr.id)?;
+ if partial_res.unresolved_segments() != 0 {
+ return None;
+ }
+
+ if let Res::Def(def::DefKind::Fn, def_id) = partial_res.base_res() {
+ // We only support cross-crate argument rewriting. Uses
+ // within the same crate should be updated to use the new
+ // const generics style.
+ if def_id.is_local() {
+ return None;
+ }
+
+ if let Some(v) = self.legacy_const_generic_args.get(&def_id) {
+ return v.clone();
+ }
+
+ let parse_attrs = || {
+ let attrs = self.cstore().item_attrs(def_id, self.session);
+ let attr = attrs
+ .iter()
+ .find(|a| self.session.check_name(a, sym::rustc_legacy_const_generics))?;
+ let mut ret = vec![];
+ for meta in attr.meta_item_list()? {
+ match meta.literal()?.kind {
+ LitKind::Int(a, _) => {
+ ret.push(a as usize);
+ }
+ _ => panic!("invalid arg index"),
+ }
+ }
+ Some(ret)
+ };
+
+ // Cache the lookup to avoid parsing attributes for an iterm
+ // multiple times.
+ let ret = parse_attrs();
+ self.legacy_const_generic_args.insert(def_id, ret.clone());
+ return ret;
+ }
+ }
+ None
+ }
}
fn names_to_string(names: &[Symbol]) -> String {
debugging_opts: &DebuggingOptions,
efmt: ErrorOutputType,
) -> Option<PpMode> {
- let pretty = if debugging_opts.unstable_options {
- matches.opt_default("pretty", "normal").map(|a| {
- // stable pretty-print variants only
- parse_pretty_inner(efmt, &a, false)
- })
- } else {
- None
- };
-
- return if pretty.is_none() {
- debugging_opts.unpretty.as_ref().map(|a| {
- // extended with unstable pretty-print variants
- parse_pretty_inner(efmt, &a, true)
- })
- } else {
- pretty
- };
-
fn parse_pretty_inner(efmt: ErrorOutputType, name: &str, extended: bool) -> PpMode {
use PpMode::*;
- use PpSourceMode::*;
let first = match (name, extended) {
- ("normal", _) => PpmSource(PpmNormal),
- ("identified", _) => PpmSource(PpmIdentified),
- ("everybody_loops", true) => PpmSource(PpmEveryBodyLoops),
- ("expanded", _) => PpmSource(PpmExpanded),
- ("expanded,identified", _) => PpmSource(PpmExpandedIdentified),
- ("expanded,hygiene", _) => PpmSource(PpmExpandedHygiene),
- ("hir", true) => PpmHir(PpmNormal),
- ("hir,identified", true) => PpmHir(PpmIdentified),
- ("hir,typed", true) => PpmHir(PpmTyped),
- ("hir-tree", true) => PpmHirTree(PpmNormal),
- ("mir", true) => PpmMir,
- ("mir-cfg", true) => PpmMirCFG,
+ ("normal", _) => Source(PpSourceMode::Normal),
+ ("identified", _) => Source(PpSourceMode::Identified),
+ ("everybody_loops", true) => Source(PpSourceMode::EveryBodyLoops),
+ ("expanded", _) => Source(PpSourceMode::Expanded),
+ ("expanded,identified", _) => Source(PpSourceMode::ExpandedIdentified),
+ ("expanded,hygiene", _) => Source(PpSourceMode::ExpandedHygiene),
+ ("hir", true) => Hir(PpHirMode::Normal),
+ ("hir,identified", true) => Hir(PpHirMode::Identified),
+ ("hir,typed", true) => Hir(PpHirMode::Typed),
+ ("hir-tree", true) => HirTree,
+ ("mir", true) => Mir,
+ ("mir-cfg", true) => MirCFG,
_ => {
if extended {
early_error(
tracing::debug!("got unpretty option: {:?}", first);
first
}
+
+ if debugging_opts.unstable_options {
+ if let Some(a) = matches.opt_default("pretty", "normal") {
+ // stable pretty-print variants only
+ return Some(parse_pretty_inner(efmt, &a, false));
+ }
+ }
+
+ debugging_opts.unpretty.as_ref().map(|a| {
+ // extended with unstable pretty-print variants
+ parse_pretty_inner(efmt, &a, true)
+ })
}
pub fn make_crate_type_option() -> RustcOptGroup {
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PpSourceMode {
- PpmNormal,
- PpmEveryBodyLoops,
- PpmExpanded,
- PpmIdentified,
- PpmExpandedIdentified,
- PpmExpandedHygiene,
- PpmTyped,
+ /// `--pretty=normal`
+ Normal,
+ /// `-Zunpretty=everybody_loops`
+ EveryBodyLoops,
+ /// `--pretty=expanded`
+ Expanded,
+ /// `--pretty=identified`
+ Identified,
+ /// `--pretty=expanded,identified`
+ ExpandedIdentified,
+ /// `--pretty=expanded,hygiene`
+ ExpandedHygiene,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum PpHirMode {
+ /// `-Zunpretty=hir`
+ Normal,
+ /// `-Zunpretty=hir,identified`
+ Identified,
+ /// `-Zunpretty=hir,typed`
+ Typed,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PpMode {
- PpmSource(PpSourceMode),
- PpmHir(PpSourceMode),
- PpmHirTree(PpSourceMode),
- PpmMir,
- PpmMirCFG,
+ /// Options that print the source code, i.e.
+ /// `--pretty` and `-Zunpretty=everybody_loops`
+ Source(PpSourceMode),
+ /// Options that print the HIR, i.e. `-Zunpretty=hir`
+ Hir(PpHirMode),
+ /// `-Zunpretty=hir-tree`
+ HirTree,
+ /// `-Zunpretty=mir`
+ Mir,
+ /// `-Zunpretty=mir-cfg`
+ MirCFG,
}
impl PpMode {
use PpMode::*;
use PpSourceMode::*;
match *self {
- PpmSource(PpmNormal | PpmIdentified) => false,
+ Source(Normal | Identified) => false,
- PpmSource(
- PpmExpanded | PpmEveryBodyLoops | PpmExpandedIdentified | PpmExpandedHygiene,
- )
- | PpmHir(_)
- | PpmHirTree(_)
- | PpmMir
- | PpmMirCFG => true,
- PpmSource(PpmTyped) => panic!("invalid state"),
+ Source(Expanded | EveryBodyLoops | ExpandedIdentified | ExpandedHygiene)
+ | Hir(_)
+ | HirTree
+ | Mir
+ | MirCFG => true,
}
}
pub fn needs_analysis(&self) -> bool {
use PpMode::*;
- matches!(*self, PpmMir | PpmMirCFG)
+ matches!(*self, Mir | MirCFG)
}
}
rustc_layout,
rustc_layout_scalar_valid_range_end,
rustc_layout_scalar_valid_range_start,
+ rustc_legacy_const_generics,
rustc_macro_transparency,
rustc_mir,
rustc_nonnull_optimization_guaranteed,
//! This API is completely unstable and subject to change.
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(half_open_range_patterns)]
+#![feature(exclusive_range_pattern)]
#![feature(nll)]
#![recursion_limit = "256"]
fn_like.asyncness()
}
+/// Don't call this directly: use ``tcx.conservative_is_privately_uninhabited`` instead.
+#[instrument(level = "debug", skip(tcx))]
+pub fn conservative_is_privately_uninhabited_raw<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ param_env_and: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
+) -> bool {
+ let (param_env, ty) = param_env_and.into_parts();
+ match ty.kind() {
+ ty::Never => {
+ debug!("ty::Never =>");
+ true
+ }
+ ty::Adt(def, _) if def.is_union() => {
+ debug!("ty::Adt(def, _) if def.is_union() =>");
+ // For now, `union`s are never considered uninhabited.
+ false
+ }
+ ty::Adt(def, substs) => {
+ debug!("ty::Adt(def, _) if def.is_not_union() =>");
+ // Any ADT is uninhabited if either:
+ // (a) It has no variants (i.e. an empty `enum`);
+ // (b) Each of its variants (a single one in the case of a `struct`) has at least
+ // one uninhabited field.
+ def.variants.iter().all(|var| {
+ var.fields.iter().any(|field| {
+ let ty = tcx.type_of(field.did).subst(tcx, substs);
+ tcx.conservative_is_privately_uninhabited(param_env.and(ty))
+ })
+ })
+ }
+ ty::Tuple(..) => {
+ debug!("ty::Tuple(..) =>");
+ ty.tuple_fields().any(|ty| tcx.conservative_is_privately_uninhabited(param_env.and(ty)))
+ }
+ ty::Array(ty, len) => {
+ debug!("ty::Array(ty, len) =>");
+ match len.try_eval_usize(tcx, param_env) {
+ Some(0) | None => false,
+ // If the array is definitely non-empty, it's uninhabited if
+ // the type of its elements is uninhabited.
+ Some(1..) => tcx.conservative_is_privately_uninhabited(param_env.and(ty)),
+ }
+ }
+ ty::Ref(..) => {
+ debug!("ty::Ref(..) =>");
+ // References to uninitialised memory is valid for any type, including
+ // uninhabited types, in unsafe code, so we treat all references as
+ // inhabited.
+ false
+ }
+ _ => {
+ debug!("_ =>");
+ false
+ }
+ }
+}
+
pub fn provide(providers: &mut ty::query::Providers) {
*providers = ty::query::Providers {
asyncness,
instance_def_size_estimate,
issue33140_self_ty,
impl_defaultness,
+ conservative_is_privately_uninhabited: conservative_is_privately_uninhabited_raw,
..*providers
};
}
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};
|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);
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,
};
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);
}
_ 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 {
format!("*{}", code)
};
return Some((
- sp,
+ expr.span,
message,
suggestion,
Applicability::MachineApplicable,
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;
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;
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;
pub kind: PickKind<'tcx>,
pub import_ids: SmallVec<[LocalDefId; 1]>,
- // Indicates that the source expression should be autoderef'd N times
- //
- // A = expr | *expr | **expr | ...
+ /// Indicates that the source expression should be autoderef'd N times
+ ///
+ /// A = expr | *expr | **expr | ...
pub autoderefs: usize,
- // Indicates that an autoref is applied after the optional autoderefs
- //
- // B = A | &A | &mut A
+ /// Indicates that an autoref is applied after the optional autoderefs
+ ///
+ /// B = A | &A | &mut A
pub autoref: Option<hir::Mutability>,
- // Indicates that the source expression should be "unsized" to a
- // target type. This should probably eventually go away in favor
- // of just coercing method receivers.
- //
- // C = B | unsize(B)
+ /// Indicates that the source expression should be "unsized" to a
+ /// target type. This should probably eventually go away in favor
+ /// of just coercing method receivers.
+ ///
+ /// C = B | unsize(B)
pub unsize: Option<Ty<'tcx>>,
}
.next()
}
+ /// For each type `T` in the step list, this attempts to find a method where
+ /// the (transformed) self type is exactly `T`. We do however do one
+ /// transformation on the adjustment: if we are passing a region pointer in,
+ /// we will potentially *reborrow* it to a shorter lifetime. This allows us
+ /// to transparently pass `&mut` pointers, in particular, without consuming
+ /// them for their entire lifetime.
fn pick_by_value_method(
&mut self,
step: &CandidateStep<'tcx>,
self_ty: Ty<'tcx>,
) -> Option<PickResult<'tcx>> {
- //! For each type `T` in the step list, this attempts to find a
- //! method where the (transformed) self type is exactly `T`. We
- //! do however do one transformation on the adjustment: if we
- //! are passing a region pointer in, we will potentially
- //! *reborrow* it to a shorter lifetime. This allows us to
- //! transparently pass `&mut` pointers, in particular, without
- //! consuming them for their entire lifetime.
-
if step.unsize {
return None;
}
//
// 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);
&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!(
pub fn insert(self, value: V) -> &'a mut V {
let out_ptr = match self.handle.insert_recursing(self.key, value) {
(Fit(_), val_ptr) => {
- // Safety: We have consumed self.handle and the handle returned.
+ // SAFETY: We have consumed self.handle and the handle returned.
let map = unsafe { self.dormant_map.awaken() };
map.length += 1;
val_ptr
}
(Split(ins), val_ptr) => {
drop(ins.left);
- // Safety: We have consumed self.handle and the reference returned.
+ // SAFETY: We have consumed self.handle and the reference returned.
let map = unsafe { self.dormant_map.awaken() };
let root = map.root.as_mut().unwrap();
root.push_internal_level().push(ins.kv.0, ins.kv.1, ins.right);
pub fn split_at_spare_mut(&mut self) -> (&mut [T], &mut [MaybeUninit<T>]) {
let ptr = self.as_mut_ptr();
- // Safety:
+ // SAFETY:
// - `ptr` is guaranteed to be in bounds for `capacity` elements
// - `len` is guaranteed to less or equal to `capacity`
// - `MaybeUninit<T>` has the same layout as `T`
let spare_ptr = unsafe { ptr.cast::<MaybeUninit<T>>().add(self.len) };
- // Safety:
+ // SAFETY:
// - `ptr` is guaranteed to be valid for `len` elements
// - `spare_ptr` is offseted from `ptr` by `len`, so it doesn't overlap `initialized` slice
unsafe {
}
trait ExtendFromWithinSpec {
- /// Safety:
+ /// # Safety
+ ///
/// - `src` needs to be valid index
/// - `self.capacity() - self.len()` must be `>= src.len()`
unsafe fn spec_extend_from_within(&mut self, src: Range<usize>);
let initialized = {
let (this, spare) = self.split_at_spare_mut();
- // Safety:
+ // SAFETY:
// - caller guaratees that src is a valid index
let to_clone = unsafe { this.get_unchecked(src) };
to_clone.iter().cloned().zip(spare.iter_mut()).map(|(e, s)| s.write(e)).count()
};
- // Safety:
+ // SAFETY:
// - elements were just initialized
unsafe {
let new_len = self.len() + initialized;
{
let (init, spare) = self.split_at_spare_mut();
- // Safety:
+ // SAFETY:
// - caller guaratees that `src` is a valid index
let source = unsafe { init.get_unchecked(src) };
- // Safety:
+ // SAFETY:
// - Both pointers are created from unique slice references (`&mut [_]`)
// so they are valid and do not overlap.
// - Elements are :Copy so it's OK to to copy them, without doing
unsafe { ptr::copy_nonoverlapping(source.as_ptr(), spare.as_mut_ptr() as _, count) };
}
- // Safety:
+ // SAFETY:
// - The elements were just initialized by `copy_nonoverlapping`
self.len += count;
}
/// 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)
}
/// 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 }
}
/// [`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 {
/// [`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 {
/// 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()
}
}
#[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 {
///
/// 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.
}
#[inline]
- fn fold<Acc, Fold>(self, init: Acc, ref mut fold: Fold) -> Acc
+ fn fold<Acc, Fold>(self, mut init: Acc, mut fold: Fold) -> Acc
where
Fold: FnMut(Acc, Self::Item) -> Acc,
{
#[inline]
- fn flatten<U: Iterator, Acc>(
- fold: &mut impl FnMut(Acc, U::Item) -> Acc,
- ) -> impl FnMut(Acc, U) -> Acc + '_ {
- move |acc, iter| iter.fold(acc, &mut *fold)
+ fn flatten<T: IntoIterator, Acc>(
+ fold: &mut impl FnMut(Acc, T::Item) -> Acc,
+ ) -> impl FnMut(Acc, T) -> Acc + '_ {
+ move |acc, x| x.into_iter().fold(acc, &mut *fold)
}
- self.frontiter
- .into_iter()
- .chain(self.iter.map(IntoIterator::into_iter))
- .chain(self.backiter)
- .fold(init, flatten(fold))
+ if let Some(front) = self.frontiter {
+ init = front.fold(init, &mut fold);
+ }
+
+ init = self.iter.fold(init, flatten(&mut fold));
+
+ if let Some(back) = self.backiter {
+ init = back.fold(init, &mut fold);
+ }
+
+ init
}
}
}
#[inline]
- fn rfold<Acc, Fold>(self, init: Acc, ref mut fold: Fold) -> Acc
+ fn rfold<Acc, Fold>(self, mut init: Acc, mut fold: Fold) -> Acc
where
Fold: FnMut(Acc, Self::Item) -> Acc,
{
#[inline]
- fn flatten<U: DoubleEndedIterator, Acc>(
- fold: &mut impl FnMut(Acc, U::Item) -> Acc,
- ) -> impl FnMut(Acc, U) -> Acc + '_ {
- move |acc, iter| iter.rfold(acc, &mut *fold)
+ fn flatten<T: IntoIterator, Acc>(
+ fold: &mut impl FnMut(Acc, T::Item) -> Acc,
+ ) -> impl FnMut(Acc, T) -> Acc + '_
+ where
+ T::IntoIter: DoubleEndedIterator,
+ {
+ move |acc, x| x.into_iter().rfold(acc, &mut *fold)
+ }
+
+ if let Some(back) = self.backiter {
+ init = back.rfold(init, &mut fold);
+ }
+
+ init = self.iter.rfold(init, flatten(&mut fold));
+
+ if let Some(front) = self.frontiter {
+ init = front.rfold(init, &mut fold);
}
- self.frontiter
- .into_iter()
- .chain(self.iter.map(IntoIterator::into_iter))
- .chain(self.backiter)
- .rfold(init, flatten(fold))
+ init
}
}
#![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)]
#![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)]
-#[doc(include = "panic.md")]
+#[doc = include_str!("panic.md")]
#[macro_export]
#[rustc_builtin_macro = "core_panic"]
#[allow_internal_unstable(edition_panic)]
///
/// 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
///
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
///
/// 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
///
///
/// [`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
///
/// 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
///
/// 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
///
///
/// [`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
///
/// 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
///
///
/// 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
///
/// 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
///
#[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
///
///
/// [`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
///
/// 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]
///
}
/// 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")]
}
/// 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")]
/// 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)
}
/// 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
}
/// 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()
}
/// 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) {
/// 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) {
/// 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
///
/// ```
/// 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
/// 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
///
#[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
///
///
/// [`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
///
/// 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]
///
}
/// 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")]
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")]
/// assert_eq!(x.expect("fruits are healthy"), "value");
/// ```
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Option<&str> = None;
/// x.expect("fruits are healthy"); // panics with `fruits are healthy`
/// ```
/// assert_eq!(x.unwrap(), "air");
/// ```
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Option<&str> = None;
/// assert_eq!(x.unwrap(), "air"); // fails
/// ```
/// }
/// ```
///
- /// ```{.should_panic}
+ /// ```should_panic
/// #![feature(option_expect_none)]
///
/// use std::collections::HashMap;
/// }
/// ```
///
- /// ```{.should_panic}
+ /// ```should_panic
/// #![feature(option_unwrap_none)]
///
/// use std::collections::HashMap;
/// ```
#[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.
/// ```
#[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
///
/// [`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,
{
///
/// [`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,
{
//! assert success with [`expect`]. This will panic if the
//! write fails, providing a marginally useful message indicating why:
//!
-//! ```{.no_run}
+//! ```no_run
//! use std::fs::File;
//! use std::io::prelude::*;
//!
//!
//! You might also simply assert success:
//!
-//! ```{.no_run}
+//! ```no_run
//! # use std::fs::File;
//! # use std::io::prelude::*;
//! # let mut file = File::create("valuable_data.txt").unwrap();
///
/// Basic usage:
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Result<u32, &str> = Err("emergency failure");
/// x.expect("Testing expect"); // panics with `Testing expect: emergency failure`
/// ```
/// assert_eq!(x.unwrap(), 2);
/// ```
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Result<u32, &str> = Err("emergency failure");
/// x.unwrap(); // panics with `emergency failure`
/// ```
///
/// Basic usage:
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Result<u32, &str> = Ok(10);
/// x.expect_err("Testing expect_err"); // panics with `Testing expect_err: 10`
/// ```
///
/// # Examples
///
- /// ```{.should_panic}
+ /// ```should_panic
/// let x: Result<u32, &str> = Ok(2);
/// x.unwrap_err(); // panics with `2`
/// ```
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])]);
+}
#![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)]
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
}
}
+#[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)]
Ok(0) => return Ok(len), // EOF reached
Ok(bytes_read) => {
assert!(bytes_read <= spare_cap.len());
- // Safety: The initializer contract guarantees that either it or `read`
+ // SAFETY: The initializer contract guarantees that either it or `read`
// will have initialized these bytes. And we just checked that the number
// of bytes is within the buffer capacity.
unsafe { buf.set_len(buf.len() + bytes_read) };
res
}
- /// Safety: The value must be initialized
+ /// # Safety
+ ///
+ /// The value must be initialized
unsafe fn get_unchecked(&self) -> &T {
debug_assert!(self.is_initialized());
(&*self.value.get()).assume_init_ref()
}
- /// Safety: The value must be initialized
+ /// # Safety
+ ///
+ /// The value must be initialized
unsafe fn get_unchecked_mut(&mut self) -> &mut T {
debug_assert!(self.is_initialized());
(&mut *self.value.get()).assume_init_mut()
unsafe impl<#[may_dangle] T> Drop for SyncOnceCell<T> {
fn drop(&mut self) {
if self.is_initialized() {
- // Safety: The cell is initialized and being dropped, so it can't
+ // SAFETY: The cell is initialized and being dropped, so it can't
// be accessed again. We also don't touch the `T` other than
// dropping it, which validates our usage of #[may_dangle].
unsafe { (&mut *self.value.get()).assume_init_drop() };
#![feature(exhaustive_patterns)]
#![feature(extend_one)]
#![feature(extended_key_value_attributes)]
-#![feature(external_doc)]
#![feature(fn_traits)]
#![feature(format_args_nl)]
#![feature(gen_future)]
//! 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")]
$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;
}
fn exec(&mut self) -> io::Error {
+ // NOTE: This may *not* be safe to call after `libc::fork`, because it
+ // may allocate. That may be worth fixing at some point in the future.
self.as_inner_mut().exec(sys::process::Stdio::Inherit)
}
////////////////////////////////////////////////////////////////////////////////
pub struct Command {
- // Currently we try hard to ensure that the call to `.exec()` doesn't
- // actually allocate any memory. While many platforms try to ensure that
- // memory allocation works after a fork in a multithreaded process, it's
- // been observed to be buggy and somewhat unreliable, so we do our best to
- // just not do it at all!
- //
- // Along those lines, the `argv` and `envp` raw pointers here are exactly
- // what's gonna get passed to `execvp`. The `argv` array starts with the
- // `program` and ends with a NULL, and the `envp` pointer, if present, is
- // also null-terminated.
- //
- // Right now we don't support removing arguments, so there's no much fancy
- // support there, but we support adding and removing environment variables,
- // so a side table is used to track where in the `envp` array each key is
- // located. Whenever we add a key we update it in place if it's already
- // present, and whenever we remove a key we update the locations of all
- // other keys.
program: CString,
args: Vec<CString>,
+ /// Exactly what will be passed to `execvp`.
+ ///
+ /// First element is a pointer to `program`, followed by pointers to
+ /// `args`, followed by a `null`. Be careful when modifying `program` or
+ /// `args` to properly update this as well.
argv: Argv,
env: CommandEnv,
pub const MAIN_SEP_STR: &str = "\\";
pub const MAIN_SEP: char = '\\';
-// Safety: `bytes` must be a valid wtf8 encoded slice
+/// # Safety
+///
+/// `bytes` must be a valid wtf8 encoded slice
#[inline]
unsafe fn bytes_as_os_str(bytes: &[u8]) -> &OsStr {
// &OsStr is layout compatible with &Slice, which is compatible with &Wtf8,
// The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index.
let path = &path.bytes()[separator_end..];
- // Safety: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
+ // SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
// is encoded in a single byte, therefore `bytes[separator_start]` and
// `bytes[separator_end]` must be code point boundaries and thus
// `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices.
/// The lock is automatically unlocked when the returned guard is dropped.
#[inline]
pub fn read_with_guard(&'static self) -> RWLockReadGuard {
- // Safety: All methods require static references, therefore self
+ // SAFETY: All methods require static references, therefore self
// cannot be moved between invocations.
unsafe {
self.0.read();
/// The lock is automatically unlocked when the returned guard is dropped.
#[inline]
pub fn write_with_guard(&'static self) -> RWLockWriteGuard {
- // Safety: All methods require static references, therefore self
+ // SAFETY: All methods require static references, therefore self
// cannot be moved between invocations.
unsafe {
self.0.write();
Detecting the use of uninitialized memory. The `-Zbuild-std` flag rebuilds and
instruments the standard library, and is strictly necessary for the correct
-operation of the tool. The `-Zsanitizer-track-origins` enables tracking of the
-origins of uninitialized memory:
+operation of the tool. The `-Zsanitizer-memory-track-origins` enables tracking
+of the origins of uninitialized memory:
```rust
use std::mem::MaybeUninit;
marked with lang items; those specific four are `eq`, `ord`,
`deref`, and `add` respectively.
- stack unwinding and general failure; the `eh_personality`,
- `panic` and `panic_bounds_checks` lang items.
+ `panic` and `panic_bounds_check` lang items.
- the traits in `std::marker` used to indicate types of
various kinds; lang items `send`, `sync` and `copy`.
- the marker types and variance indicators found in
#!/usr/bin/env bash
#
# Call `tidy --bless` before each commit
-# Copy this scripts to .git/hooks to activate,
+# Copy this script to .git/hooks to activate,
# and remove it from .git/hooks to deactivate.
#
debug!("build_impl: impl {:?} for {:?}", trait_.def_id(), for_.def_id());
- let mut item = clean::Item::from_def_id_and_parts(
+ let attrs = box merge_attrs(cx, parent_module.into(), load_attrs(cx, did), attrs);
+ debug!("merged_attrs={:?}", attrs);
+
+ ret.push(clean::Item::from_def_id_and_attrs_and_parts(
did,
None,
clean::ImplItem(clean::Impl {
synthetic: false,
blanket_impl: None,
}),
+ attrs,
cx,
- );
- item.attrs = box merge_attrs(cx, parent_module.into(), load_attrs(cx, did), attrs);
- debug!("merged_attrs={:?}", item.attrs);
- ret.push(item);
+ ));
}
fn build_module(
name: Option<Symbol>,
kind: ItemKind,
cx: &mut DocContext<'_>,
+ ) -> Item {
+ Self::from_def_id_and_attrs_and_parts(
+ def_id,
+ name,
+ kind,
+ box cx.tcx.get_attrs(def_id).clean(cx),
+ cx,
+ )
+ }
+
+ pub fn from_def_id_and_attrs_and_parts(
+ def_id: DefId,
+ name: Option<Symbol>,
+ kind: ItemKind,
+ attrs: Box<Attributes>,
+ cx: &mut DocContext<'_>,
) -> Item {
debug!("name={:?}, def_id={:?}", name, def_id);
kind: box kind,
name,
source: source.clean(cx),
- attrs: box cx.tcx.get_attrs(def_id).clean(cx),
+ attrs,
visibility: cx.tcx.visibility(def_id).clean(cx),
}
}
Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
}
+ fn tokens(string: &str) -> impl Iterator<Item = &str> {
+ // Pandoc, which Rust once used for generating documentation,
+ // expects lang strings to be surrounded by `{}` and for each token
+ // to be proceeded by a `.`. Since some of these lang strings are still
+ // loose in the wild, we strip a pair of surrounding `{}` from the lang
+ // string and a leading `.` from each token.
+
+ let string = string.trim();
+
+ let first = string.chars().next();
+ let last = string.chars().last();
+
+ let string = if first == Some('{') && last == Some('}') {
+ &string[1..string.len() - 1]
+ } else {
+ string
+ };
+
+ string
+ .split(|c| c == ',' || c == ' ' || c == '\t')
+ .map(str::trim)
+ .map(|token| if token.chars().next() == Some('.') { &token[1..] } else { token })
+ .filter(|token| !token.is_empty())
+ }
+
fn parse(
string: &str,
allow_error_code_check: ErrorCodes,
let mut ignores = vec![];
data.original = string.to_owned();
- let tokens = string.split(|c: char| !(c == '_' || c == '-' || c.is_alphanumeric()));
+
+ let tokens = Self::tokens(string).collect::<Vec<&str>>();
for token in tokens {
- match token.trim() {
- "" => {}
+ match token {
"should_panic" => {
data.should_panic = true;
seen_rust_tags = !seen_other_tags;
_ => seen_other_tags = true,
}
}
+
// ignore-foo overrides ignore
if !ignores.is_empty() {
data.ignore = Ignore::Some(ignores);
t(Default::default());
t(LangString { original: "rust".into(), ..Default::default() });
+ t(LangString { original: ".rust".into(), ..Default::default() });
+ t(LangString { original: "{rust}".into(), ..Default::default() });
+ t(LangString { original: "{.rust}".into(), ..Default::default() });
t(LangString { original: "sh".into(), rust: false, ..Default::default() });
t(LangString { original: "ignore".into(), ignore: Ignore::All, ..Default::default() });
t(LangString {
..Default::default()
});
t(LangString { original: "allow_fail".into(), allow_fail: true, ..Default::default() });
- t(LangString { original: "{.no_run .example}".into(), no_run: true, ..Default::default() });
+ t(LangString { original: "no_run,example".into(), no_run: true, ..Default::default() });
t(LangString {
- original: "{.sh .should_panic}".into(),
+ original: "sh,should_panic".into(),
should_panic: true,
rust: false,
..Default::default()
});
- t(LangString { original: "{.example .rust}".into(), ..Default::default() });
+ t(LangString { original: "example,rust".into(), ..Default::default() });
t(LangString {
- original: "{.test_harness .rust}".into(),
+ original: "test_harness,.rust".into(),
test_harness: true,
..Default::default()
});
rust: false,
..Default::default()
});
+ t(LangString {
+ original: "text,no_run, ".into(),
+ no_run: true,
+ rust: false,
+ ..Default::default()
+ });
+ t(LangString {
+ original: "text,no_run,".into(),
+ no_run: true,
+ rust: false,
+ ..Default::default()
+ });
t(LangString {
original: "edition2015".into(),
edition: Some(Edition::Edition2015),
});
}
+#[test]
+fn test_lang_string_tokenizer() {
+ fn case(lang_string: &str, want: &[&str]) {
+ let have = LangString::tokens(lang_string).collect::<Vec<&str>>();
+ assert_eq!(have, want, "Unexpected lang string split for `{}`", lang_string);
+ }
+
+ case("", &[]);
+ case("foo", &["foo"]);
+ case("foo,bar", &["foo", "bar"]);
+ case(".foo,.bar", &["foo", "bar"]);
+ case("{.foo,.bar}", &["foo", "bar"]);
+ case(" {.foo,.bar} ", &["foo", "bar"]);
+ case("foo bar", &["foo", "bar"]);
+ case("foo\tbar", &["foo", "bar"]);
+ case("foo\t, bar", &["foo", "bar"]);
+ case(" foo , bar ", &["foo", "bar"]);
+ case(",,foo,,bar,,", &["foo", "bar"]);
+ case("foo=bar", &["foo=bar"]);
+ case("a-b-c", &["a-b-c"]);
+ case("a_b_c", &["a_b_c"]);
+}
+
#[test]
fn test_header() {
fn t(input: &str, expect: &str) {
</a>\
</span>
</span>
- <span class=\"in-band\">List of all items</span>\
</h1>",
);
// Note: print_entries does not escape the title, because we know the current set of titles
sorted.sort_by(|&l, r| compare_names(l, r));
assert_eq!(names, sorted);
}
+
+#[test]
+fn test_all_types_prints_header_once() {
+ // Regression test for #82477
+ let all_types = AllTypes::new();
+
+ let mut buffer = Buffer::new();
+ all_types.print(&mut buffer);
+
+ assert_eq!(1, buffer.into_inner().matches("List of all items").count());
+}
/* 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}
}
}
-#[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.
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.
/// 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,
}
}
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;
}
}
// 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()) {
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;
}
}
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(
}
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 => {
--- /dev/null
+#![feature(rustc_attrs)]
+
+#[rustc_legacy_const_generics(1)]
+pub fn foo<const Y: usize>(x: usize, z: usize) -> [usize; 3] {
+ [x, Y, z]
+}
--- /dev/null
+// run-pass
+#![feature(const_generics, const_evaluatable_checked)]
+#![allow(incomplete_features)]
+
+// This tests that the `conservative_is_privately_uninhabited` fn doesn't cause
+// ICEs by trying to evaluate `T::ASSOC` with an incorrect `ParamEnv`.
+
+trait Foo {
+ const ASSOC: usize = 1;
+}
+
+struct Iced<T: Foo>(T, [(); T::ASSOC])
+where
+ [(); T::ASSOC]: ;
+
+impl Foo for u32 {}
+
+fn foo<T: Foo>()
+where
+ [(); T::ASSOC]: ,
+{
+ let _iced: Iced<T> = return;
+}
+
+fn main() {
+ foo::<u32>();
+}
--- /dev/null
+// run-pass
+#![feature(const_generics, const_evaluatable_checked)]
+#![allow(incomplete_features)]
+
+// This tests that the `conservative_is_privately_uninhabited` fn doesn't cause
+// ICEs by trying to evaluate `T::ASSOC` with an incorrect `ParamEnv`.
+
+trait Foo {
+ const ASSOC: usize = 1;
+}
+
+struct Iced<T: Foo>(T, [(); T::ASSOC])
+where
+ [(); T::ASSOC]: ;
+
+impl Foo for u32 {}
+
+fn main() {
+ let _iced: Iced<u32> = return;
+}
--- /dev/null
+const FOO: usize = 0;
+
+fn main() {
+ FOO(); //~ ERROR expected function, found `usize`
+}
--- /dev/null
+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`.
--> $DIR/E0618.rs:9:5
|
LL | let x = 0i32;
- | - `i32` defined here
+ | - `x` has type `i32`
LL | x();
| ^--
| |
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;
+}
fn ipu_flatten(&self) -> u32 {
1
}
+
+ const C: i32;
}
-impl IpuItertools for char {}
+impl IpuItertools for char {
+ const C: i32 = 1;
+}
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
}
-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
+++ /dev/null
-#![feature(rustc_attrs)]
-
-#[rustc_args_required_const(0)] //~ ERROR index exceeds number of arguments
-fn foo1() {}
-
-#[rustc_args_required_const(1)] //~ ERROR index exceeds number of arguments
-fn foo2(_: u8) {}
-
-#[rustc_args_required_const(a)] //~ ERROR arguments should be non-negative integers
-fn foo4() {}
-
-#[rustc_args_required_const(1, a, 2, b)] //~ ERROR arguments should be non-negative integers
-fn foo5(_: u8, _: u8, _: u8) {}
-
-#[rustc_args_required_const(0)] //~ ERROR attribute should be applied to a function
-struct S;
-
-#[rustc_args_required_const(0usize)] //~ ERROR suffixed literals are not allowed in attributes
-fn foo6(_: u8) {}
-
-extern {
- #[rustc_args_required_const(1)] //~ ERROR index exceeds number of arguments
- fn foo7(_: u8);
-}
-
-#[rustc_args_required_const] //~ ERROR malformed `rustc_args_required_const` attribute
-fn bar1() {}
-
-#[rustc_args_required_const = 1] //~ ERROR malformed `rustc_args_required_const` attribute
-fn bar2() {}
-
-fn main() {}
+++ /dev/null
-error: suffixed literals are not allowed in attributes
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:18:29
- |
-LL | #[rustc_args_required_const(0usize)]
- | ^^^^^^
- |
- = help: instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
-
-error: malformed `rustc_args_required_const` attribute input
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:26:1
- |
-LL | #[rustc_args_required_const]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_args_required_const(N)]`
-
-error: malformed `rustc_args_required_const` attribute input
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:29:1
- |
-LL | #[rustc_args_required_const = 1]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_args_required_const(N)]`
-
-error: index exceeds number of arguments
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:3:29
- |
-LL | #[rustc_args_required_const(0)]
- | ^ there are only 0 arguments
-
-error: index exceeds number of arguments
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:6:29
- |
-LL | #[rustc_args_required_const(1)]
- | ^ there is only 1 argument
-
-error: arguments should be non-negative integers
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:9:29
- |
-LL | #[rustc_args_required_const(a)]
- | ^
-
-error: arguments should be non-negative integers
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:12:32
- |
-LL | #[rustc_args_required_const(1, a, 2, b)]
- | ^ ^
-
-error: attribute should be applied to a function
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:15:1
- |
-LL | #[rustc_args_required_const(0)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-LL | struct S;
- | --------- not a function
-
-error: index exceeds number of arguments
- --> $DIR/invalid-rustc_args_required_const-arguments.rs:22:33
- |
-LL | #[rustc_args_required_const(1)]
- | ^ there is only 1 argument
-
-error: aborting due to 9 previous errors
-
--- /dev/null
+#![feature(rustc_attrs)]
+
+#[rustc_args_required_const(0)] //~ ERROR index exceeds number of arguments
+fn foo1() {}
+
+#[rustc_args_required_const(1)] //~ ERROR index exceeds number of arguments
+fn foo2(_: u8) {}
+
+#[rustc_args_required_const(a)] //~ ERROR arguments should be non-negative integers
+fn foo4() {}
+
+#[rustc_args_required_const(1, a, 2, b)] //~ ERROR arguments should be non-negative integers
+fn foo5(_: u8, _: u8, _: u8) {}
+
+#[rustc_args_required_const(0)] //~ ERROR attribute should be applied to a function
+struct S;
+
+#[rustc_args_required_const(0usize)] //~ ERROR suffixed literals are not allowed in attributes
+fn foo6(_: u8) {}
+
+extern {
+ #[rustc_args_required_const(1)] //~ ERROR index exceeds number of arguments
+ fn foo7(_: u8);
+}
+
+#[rustc_args_required_const] //~ ERROR malformed `rustc_args_required_const` attribute
+fn bar1() {}
+
+#[rustc_args_required_const = 1] //~ ERROR malformed `rustc_args_required_const` attribute
+fn bar2() {}
+
+fn main() {}
--- /dev/null
+error: suffixed literals are not allowed in attributes
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:18:29
+ |
+LL | #[rustc_args_required_const(0usize)]
+ | ^^^^^^
+ |
+ = help: instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
+
+error: malformed `rustc_args_required_const` attribute input
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:26:1
+ |
+LL | #[rustc_args_required_const]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_args_required_const(N)]`
+
+error: malformed `rustc_args_required_const` attribute input
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:29:1
+ |
+LL | #[rustc_args_required_const = 1]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_args_required_const(N)]`
+
+error: index exceeds number of arguments
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:3:29
+ |
+LL | #[rustc_args_required_const(0)]
+ | ^ there are only 0 arguments
+
+error: index exceeds number of arguments
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:6:29
+ |
+LL | #[rustc_args_required_const(1)]
+ | ^ there is only 1 argument
+
+error: arguments should be non-negative integers
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:9:29
+ |
+LL | #[rustc_args_required_const(a)]
+ | ^
+
+error: arguments should be non-negative integers
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:12:32
+ |
+LL | #[rustc_args_required_const(1, a, 2, b)]
+ | ^ ^
+
+error: attribute should be applied to a function
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:15:1
+ |
+LL | #[rustc_args_required_const(0)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | struct S;
+ | --------- not a function
+
+error: index exceeds number of arguments
+ --> $DIR/invalid-rustc_args_required_const-arguments.rs:22:33
+ |
+LL | #[rustc_args_required_const(1)]
+ | ^ there is only 1 argument
+
+error: aborting due to 9 previous errors
+
--- /dev/null
+#![feature(rustc_attrs)]
+
+#[rustc_legacy_const_generics(0)] //~ ERROR #[rustc_legacy_const_generics] must have one index for
+fn foo1() {}
+
+#[rustc_legacy_const_generics(1)] //~ ERROR index exceeds number of arguments
+fn foo2<const X: usize>() {}
+
+#[rustc_legacy_const_generics(2)] //~ ERROR index exceeds number of arguments
+fn foo3<const X: usize>(_: u8) {}
+
+#[rustc_legacy_const_generics(a)] //~ ERROR arguments should be non-negative integers
+fn foo4<const X: usize>() {}
+
+#[rustc_legacy_const_generics(1, a, 2, b)] //~ ERROR arguments should be non-negative integers
+fn foo5<const X: usize, const Y: usize, const Z: usize, const W: usize>() {}
+
+#[rustc_legacy_const_generics(0)] //~ ERROR attribute should be applied to a function
+struct S;
+
+#[rustc_legacy_const_generics(0usize)] //~ ERROR suffixed literals are not allowed in attributes
+fn foo6<const X: usize>() {}
+
+extern {
+ #[rustc_legacy_const_generics(1)] //~ ERROR attribute should be applied to a function
+ fn foo7<const X: usize>(); //~ ERROR foreign items may not have const parameters
+}
+
+#[rustc_legacy_const_generics(0)] //~ ERROR #[rustc_legacy_const_generics] functions must only have
+fn foo8<X>() {}
+
+#[rustc_legacy_const_generics] //~ ERROR malformed `rustc_legacy_const_generics` attribute
+fn bar1() {}
+
+#[rustc_legacy_const_generics = 1] //~ ERROR malformed `rustc_legacy_const_generics` attribute
+fn bar2() {}
+
+fn main() {}
--- /dev/null
+error: suffixed literals are not allowed in attributes
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:21:31
+ |
+LL | #[rustc_legacy_const_generics(0usize)]
+ | ^^^^^^
+ |
+ = help: instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
+
+error: malformed `rustc_legacy_const_generics` attribute input
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:32:1
+ |
+LL | #[rustc_legacy_const_generics]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_legacy_const_generics(N)]`
+
+error: malformed `rustc_legacy_const_generics` attribute input
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:35:1
+ |
+LL | #[rustc_legacy_const_generics = 1]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_legacy_const_generics(N)]`
+
+error: #[rustc_legacy_const_generics] must have one index for each generic parameter
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:3:1
+ |
+LL | #[rustc_legacy_const_generics(0)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | fn foo1() {}
+ | - generic parameters
+
+error: index exceeds number of arguments
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:6:31
+ |
+LL | #[rustc_legacy_const_generics(1)]
+ | ^ there is only 1 argument
+
+error: index exceeds number of arguments
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:9:31
+ |
+LL | #[rustc_legacy_const_generics(2)]
+ | ^ there are only 2 arguments
+
+error: arguments should be non-negative integers
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:12:31
+ |
+LL | #[rustc_legacy_const_generics(a)]
+ | ^
+
+error: arguments should be non-negative integers
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:15:34
+ |
+LL | #[rustc_legacy_const_generics(1, a, 2, b)]
+ | ^ ^
+
+error: attribute should be applied to a function
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:18:1
+ |
+LL | #[rustc_legacy_const_generics(0)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | struct S;
+ | --------- not a function
+
+error: #[rustc_legacy_const_generics] functions must only have const generics
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:29:1
+ |
+LL | #[rustc_legacy_const_generics(0)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | fn foo8<X>() {}
+ | - non-const generic parameter
+
+error: attribute should be applied to a function
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:25:5
+ |
+LL | #[rustc_legacy_const_generics(1)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | fn foo7<const X: usize>();
+ | -------------------------- not a function
+
+error[E0044]: foreign items may not have const parameters
+ --> $DIR/invalid-rustc_legacy_const_generics-arguments.rs:26:5
+ |
+LL | fn foo7<const X: usize>();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ can't have const parameters
+ |
+ = help: replace the const parameters with concrete consts
+
+error: aborting due to 12 previous errors
+
+For more information about this error, try `rustc --explain E0044`.
--> $DIR/issue-10969.rs:2:5
|
LL | fn func(i: i32) {
- | - `i32` defined here
+ | - `i` has type `i32`
LL | i();
| ^--
| |
--> $DIR/issue-10969.rs:6:5
|
LL | let i = 0i32;
- | - `i32` defined here
+ | - `i` has type `i32`
LL | i();
| ^--
| |
--> $DIR/issue-21701.rs:2:13
|
LL | fn foo<U>(t: U) {
- | - `U` defined here
+ | - `t` has type `U`
LL | let y = t();
| ^--
| |
--> $DIR/issue-22468.rs:3:13
|
LL | let foo = "bar";
- | --- `&str` defined here
+ | --- `foo` has type `&str`
LL | let x = foo("baz");
| ^^^-------
| |
| ------------------------------- 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);
| ^^^^^^^
--- /dev/null
+// aux-build:legacy-const-generics.rs
+
+extern crate legacy_const_generics;
+
+fn foo<const N: usize>() {
+ let a = 1;
+ legacy_const_generics::foo(0, a, 2);
+ //~^ ERROR attempt to use a non-constant value in a constant
+
+ legacy_const_generics::foo(0, N, 2);
+
+ legacy_const_generics::foo(0, N + 1, 2);
+ //~^ ERROR generic parameters may not be used in const operations
+}
+
+fn main() {}
--- /dev/null
+error[E0435]: attempt to use a non-constant value in a constant
+ --> $DIR/legacy-const-generics-bad.rs:7:35
+ |
+LL | let a = 1;
+ | ----- help: consider using `const` instead of `let`: `const a`
+LL | legacy_const_generics::foo(0, a, 2);
+ | ^ non-constant value
+
+error: generic parameters may not be used in const operations
+ --> $DIR/legacy-const-generics-bad.rs:12:35
+ |
+LL | legacy_const_generics::foo(0, N + 1, 2);
+ | ^ cannot perform const operation using `N`
+ |
+ = help: const parameters may only be used as standalone arguments, i.e. `N`
+ = help: use `#![feature(const_generics)]` and `#![feature(const_evaluatable_checked)]` to allow generic const expressions
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0435`.
--- /dev/null
+// aux-build:legacy-const-generics.rs
+// run-pass
+
+#![feature(rustc_attrs)]
+
+extern crate legacy_const_generics;
+
+#[rustc_legacy_const_generics(1)]
+pub fn bar<const Y: usize>(x: usize, z: usize) -> [usize; 3] {
+ [x, Y, z]
+}
+
+fn main() {
+ assert_eq!(legacy_const_generics::foo(0 + 0, 1 + 1, 2 + 2), [0, 2, 4]);
+ assert_eq!(legacy_const_generics::foo::<{1 + 1}>(0 + 0, 2 + 2), [0, 2, 4]);
+ // FIXME: Only works cross-crate
+ //assert_eq!(bar(0, 1, 2), [0, 1, 2]);
+}
--> $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.();
| ^---
--- /dev/null
+struct S;
+
+fn repro_ref(thing: S) {
+ thing(); //~ ERROR expected function, found `S`
+}
+
+fn main() {}
--- /dev/null
+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`.
--- /dev/null
+use std::result;
+impl result { //~ ERROR expected type, found module `result`
+ fn into_future() -> Err {} //~ ERROR expected type, found variant `Err`
+}
+fn main() {}
--- /dev/null
+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`.
--- /dev/null
+// 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]
+ };
+}
--- /dev/null
+// 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]
+ };
+}
--- /dev/null
+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`.
// error-pattern: [trigger_delay_span_bug] trigger a delay span bug
// normalize-stderr-test "note: .*\n\n" -> ""
// normalize-stderr-test "thread 'rustc' panicked.*\n" -> ""
+// rustc-env:RUST_BACKTRACE=0
#![feature(rustc_attrs)]
error: internal compiler error: delayed span bug triggered by #[rustc_error(delay_span_bug_from_inside_query)]
- --> $DIR/delay_span_bug.rs:11:1
+ --> $DIR/delay_span_bug.rs:12:1
|
LL | fn main() {}
| ^^^^^^^^^
// error-pattern: [eval_to_allocation_raw] const-evaluating + checking `C`
// normalize-stderr-test "note: .*\n\n" -> ""
// normalize-stderr-test "thread 'rustc' panicked.*\n" -> ""
+// rustc-env:RUST_BACKTRACE=0
#![crate_type = "rlib"]
error[E0080]: could not evaluate static initializer
- --> $DIR/err.rs:10:21
+ --> $DIR/err.rs:11:21
|
LL | pub static C: u32 = 0 - 1;
| ^^^^^ attempt to compute `0_u32 - 1_u32`, which would overflow
--- /dev/null
+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) } } }
+}
--- /dev/null
+// 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));
+}
--- /dev/null
+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`.
---
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,
---
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,
---
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,
---
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
---
name: New lint suggestion
about: Suggest a new Clippy lint.
-labels: L-lint
+labels: A-lint
---
### What it does
- 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
- 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
*Cargo.lock
/target
/clippy_lints/target
+/clippy_utils/target
/clippy_workspace_tests/target
/clippy_dev/target
/rustc_tools_util/target
## 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
* [`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
## 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)
[`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
[`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
[`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
[`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
[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"
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:
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 = []
-# Clippy Dev Tool
+# 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.
.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,
);
}
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,
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,
column: String,
linttype: String,
message: String,
- ice: bool,
+ is_ice: bool,
}
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");
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
// 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
version: String::from("local"),
name: name.clone(),
path: crate_root,
+ options: options.clone(),
}
},
}
}
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| {
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
}
}
+/// 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 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() {
- Command::new("cargo")
+ let status = Command::new("cargo")
.arg("build")
- .output()
+ .status()
.expect("Failed to build clippy!");
+ if !status.success() {
+ eprintln!("Error: Failed to compile Clippy!");
+ std::process::exit(1);
+ }
}
-// get a list of CrateSources we want to check from a "lintcheck_crates.toml" file.
+/// 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
crate_sources.push(CrateSource::Path {
name: tk.name.clone(),
path: PathBuf::from(path),
+ options: tk.options.clone(),
});
}
crate_sources.push(CrateSource::CratesIo {
name: tk.name.clone(),
version: ver.to_string(),
+ options: tk.options.clone(),
});
})
}
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
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));
.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(),
// 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
.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());
.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();
}
.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")
[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 }
[features]
deny-warnings = []
# build clippy with internal lints enabled, off by default
-internal-lints = []
+internal-lints = ["clippy_utils/internal-lints"]
-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};
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() {
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 {
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, "..");
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");
},
);
}
-#![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 don’t 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 don’t 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::*;
--- /dev/null
+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,
+ }
+ }
+}
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;
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,
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_) => {
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,
+ );
}
}
}
| 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,
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);
}
--- /dev/null
+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)
+}
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;
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,
--- /dev/null
+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
+}
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);
// 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)]
};
}
+#[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;
mod create_dir;
mod dbg_macro;
mod default;
+mod default_numeric_fallback;
mod dereference;
mod derive;
mod disallowed_method;
mod format;
mod formatting;
mod from_over_into;
+mod from_str_radix_10;
mod functions;
mod future_not_send;
mod get_last_with_len;
mod if_not_else;
mod implicit_return;
mod implicit_saturating_sub;
+mod inconsistent_struct_constructor;
mod indexing_slicing;
mod infinite_iter;
mod inherent_impl;
mod macro_use;
mod main_recursion;
mod manual_async_fn;
+mod manual_map;
mod manual_non_exhaustive;
mod manual_ok_or;
mod manual_strip;
&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,
&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,
&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,
¯o_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,
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(|| {
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);
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),
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),
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),
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),
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),
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),
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),
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),
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),
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),
--- /dev/null
+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,
+ }
+}
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};
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;
/// 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);
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 can’t 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();
}
}
-/// 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()
+}
if !expr_borrows(cx, left_expr) {
return Some(LintTrigger::SortByKey(SortByKeyDetection {
vec_name,
- unstable,
closure_arg,
closure_body,
- reverse
+ reverse,
+ unstable,
}));
}
}
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;
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;
///
/// **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:**
///
/// }
/// ```
pub UNNECESSARY_WRAPS,
- complexity,
+ pedantic,
"functions that only return `Ok` or `Some`"
}
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() {
_ => (),
}
+ // 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,
}
}
- 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
});
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);
+ });
}
}
}
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.
"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>();
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,
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);
}
}
+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.
/// 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 {
"unnecessary structure name repetition whereas `Self` is applicable"
}
-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));
+#[derive(Default)]
+pub struct UseSelf {
+ msrv: Option<RustcVersion>,
+ stack: Vec<StackItem>,
+}
- // Path segments only include actual path, no methods or fields.
- let last_path_span = last_segment.ident.span;
+const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
- if differing_macro_contexts(path.span, last_path_span) {
- return;
+impl UseSelf {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Self::default()
+ }
}
-
- // Only take path up to the end of last_path_span.
- let span = path.span.with_hi(last_path_span.hi());
-
- span_lint_and_sugg(
- cx,
- USE_SELF,
- span,
- "unnecessary structure name repetition",
- "use the applicable keyword",
- "Self".to_owned(),
- Applicability::MachineApplicable,
- );
}
-// FIXME: always use this (more correct) visitor, not just in method signatures.
-struct SemanticUseSelfVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- self_ty: Ty<'tcx>,
+#[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<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
- type Map = Map<'tcx>;
+impl_lint_pass!(UseSelf => [USE_SELF]);
- 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);
- }
- },
- }
- }
+const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
- walk_ty(self, hir_ty)
+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);
+ },
+ _ => (),
+ }
}
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
+ 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();
+ },
+ _ => (),
+ }
}
-}
-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");
-
- 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
- };
-
- // `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 };
-
- visitor.visit_ty(&impl_hir_ty);
+ 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);
+ }
+ }
+ }
}
}
-}
-const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
+ 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);
+ }
+ }
-pub struct UseSelf {
- msrv: Option<RustcVersion>,
-}
+ 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;
+ }
-impl UseSelf {
- #[must_use]
- pub fn new(msrv: Option<RustcVersion>) -> Self {
- Self { msrv }
+ 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),
+ }
+ }
}
-}
-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;
+ 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_external_macro(cx.sess(), item.span) {
+ if in_macro(expr.span) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
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);
- }
+ 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);
+ },
}
- } 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);
+ }
+ },
+ // 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);
}
-struct UseSelfVisitor<'a, 'tcx> {
- item_path: &'a Path<'a>,
+#[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 UseSelfVisitor<'a, 'tcx> {
+impl<'a, 'tcx> Visitor<'tcx> for LintTyCollector<'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));
- }
- }
+ fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) {
+ 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);
}
+ }
- 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_ty(self, hir_ty)
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+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,
+ );
+}
+
+#[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);
+}
+
+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());
+ }
+}
+
+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);
}
+ }
+}
- walk_path(self, path);
+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`")
}
+}
- 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 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
}
}
+}
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::All(self.cx.tcx.hir())
+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
+ }
}
}
+++ /dev/null
-//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
-//!
-//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
-
-#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
-
-use crate::utils::{both, over};
-use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, *};
-use rustc_span::symbol::Ident;
-use std::mem;
-
-pub mod ident_iter;
-pub use ident_iter::IdentIter;
-
-pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
- use BinOpKind::*;
- matches!(
- kind,
- Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
- )
-}
-
-/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
-pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
- left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
-}
-
-pub fn eq_id(l: Ident, r: Ident) -> bool {
- l.name == r.name
-}
-
-pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
- use PatKind::*;
- match (&l.kind, &r.kind) {
- (Paren(l), _) => eq_pat(l, r),
- (_, Paren(r)) => eq_pat(l, r),
- (Wild, Wild) | (Rest, Rest) => true,
- (Lit(l), Lit(r)) => eq_expr(l, r),
- (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
- (Range(lf, lt, le), Range(rf, rt, re)) => {
- eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
- },
- (Box(l), Box(r))
- | (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
- | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
- (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
- (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
- (TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)),
- (Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => {
- lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf))
- },
- (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- _ => false,
- }
-}
-
-pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
- match (l, r) {
- (RangeEnd::Excluded, RangeEnd::Excluded) => true,
- (RangeEnd::Included(l), RangeEnd::Included(r)) => {
- matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
- },
- _ => false,
- }
-}
-
-pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool {
- l.is_placeholder == r.is_placeholder
- && eq_id(l.ident, r.ident)
- && eq_pat(&l.pat, &r.pat)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
-}
-
-pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
- l.position == r.position && eq_ty(&l.ty, &r.ty)
-}
-
-pub fn eq_path(l: &Path, r: &Path) -> bool {
- over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r))
-}
-
-pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
- eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
-}
-
-pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
- match (l, r) {
- (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => {
- over(&l.args, &r.args, |l, r| eq_angle_arg(l, r))
- },
- (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
- over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
- },
- _ => false,
- }
-}
-
-pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
- match (l, r) {
- (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
- (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
- _ => false,
- }
-}
-
-pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
- match (l, r) {
- (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
- (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
- (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
- _ => false,
- }
-}
-
-pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
- both(l, r, |l, r| eq_expr(l, r))
-}
-
-pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
- match (l, r) {
- (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
- (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
- _ => false,
- }
-}
-
-pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
- use ExprKind::*;
- if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) {
- return false;
- }
- match (&l.kind, &r.kind) {
- (Paren(l), _) => eq_expr(l, r),
- (_, Paren(r)) => eq_expr(l, r),
- (Err, Err) => true,
- (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
- (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
- (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
- (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
- (MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
- (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
- (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
- (Lit(l), Lit(r)) => l.kind == r.kind,
- (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
- (Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re),
- (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
- (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
- (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
- eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
- },
- (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
- (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
- (TryBlock(l), TryBlock(r)) => eq_block(l, r),
- (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
- (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
- (Continue(ll), Continue(rl)) => eq_label(ll, rl),
- (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
- (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
- (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
- (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)),
- (Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
- lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
- },
- (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
- (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
- (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
- (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- (Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => {
- eq_path(lp, rp) && eq_struct_rest(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
- },
- _ => false,
- }
-}
-
-pub fn eq_field(l: &Field, r: &Field) -> bool {
- l.is_placeholder == r.is_placeholder
- && eq_id(l.ident, r.ident)
- && eq_expr(&l.expr, &r.expr)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
-}
-
-pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
- l.is_placeholder == r.is_placeholder
- && eq_pat(&l.pat, &r.pat)
- && eq_expr(&l.body, &r.body)
- && eq_expr_opt(&l.guard, &r.guard)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
-}
-
-pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
- both(l, r, |l, r| eq_id(l.ident, r.ident))
-}
-
-pub fn eq_block(l: &Block, r: &Block) -> bool {
- l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
-}
-
-pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
- use StmtKind::*;
- match (&l.kind, &r.kind) {
- (Local(l), Local(r)) => {
- eq_pat(&l.pat, &r.pat)
- && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
- && eq_expr_opt(&l.init, &r.init)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- },
- (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
- (Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
- (Empty, Empty) => true,
- (MacCall(l), MacCall(r)) => {
- l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- },
- _ => false,
- }
-}
-
-pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
- eq_id(l.ident, r.ident)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- && eq_vis(&l.vis, &r.vis)
- && eq_kind(&l.kind, &r.kind)
-}
-
-pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
- use ItemKind::*;
- 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)
- }
- (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,
- },
- (ForeignMod(l), ForeignMod(r)) => {
- both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
- && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
- },
- (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
- eq_defaultness(*ld, *rd)
- && eq_generics(lg, rg)
- && over(lb, rb, |l, r| eq_generic_bound(l, r))
- && both(lt, rt, |l, r| eq_ty(l, r))
- },
- (Enum(le, lg), Enum(re, rg)) => {
- over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
- },
- (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
- eq_variant_data(lv, rv) && eq_generics(lg, rg)
- },
- (Trait(box TraitKind(la, lu, lg, lb, li)), Trait(box TraitKind(ra, ru, rg, rb, ri))) => {
- la == ra
- && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
- && eq_generics(lg, rg)
- && over(lb, rb, |l, r| eq_generic_bound(l, r))
- && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
- },
- (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
- (
- Impl(box ImplKind {
- unsafety: lu,
- polarity: lp,
- defaultness: ld,
- constness: lc,
- generics: lg,
- of_trait: lot,
- self_ty: lst,
- items: li,
- }),
- Impl(box ImplKind {
- unsafety: ru,
- polarity: rp,
- defaultness: rd,
- constness: rc,
- generics: rg,
- of_trait: rot,
- self_ty: rst,
- items: ri,
- }),
- ) => {
- matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
- && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
- && eq_defaultness(*ld, *rd)
- && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
- && eq_generics(lg, rg)
- && both(lot, rot, |l, r| eq_path(&l.path, &r.path))
- && eq_ty(lst, rst)
- && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
- },
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- (MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
- _ => false,
- }
-}
-
-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)
- }
- (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))
- }
- (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
- eq_defaultness(*ld, *rd)
- && eq_generics(lg, rg)
- && over(lb, rb, |l, r| eq_generic_bound(l, r))
- && both(lt, rt, |l, r| eq_ty(l, r))
- },
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- _ => false,
- }
-}
-
-pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
- use AssocItemKind::*;
- match (l, r) {
- (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))
- },
- (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
- eq_defaultness(*ld, *rd)
- && eq_generics(lg, rg)
- && over(lb, rb, |l, r| eq_generic_bound(l, r))
- && both(lt, rt, |l, r| eq_ty(l, r))
- },
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- _ => false,
- }
-}
-
-pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
- l.is_placeholder == r.is_placeholder
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- && eq_vis(&l.vis, &r.vis)
- && eq_id(l.ident, r.ident)
- && eq_variant_data(&l.data, &r.data)
- && both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
-}
-
-pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
- use VariantData::*;
- match (l, r) {
- (Unit(_), Unit(_)) => true,
- (Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
- _ => false,
- }
-}
-
-pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
- l.is_placeholder == r.is_placeholder
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- && eq_vis(&l.vis, &r.vis)
- && both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
- && eq_ty(&l.ty, &r.ty)
-}
-
-pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
- eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
-}
-
-pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
- matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
- && l.asyncness.is_async() == r.asyncness.is_async()
- && matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
- && eq_ext(&l.ext, &r.ext)
-}
-
-pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
- over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
- && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
- eq_where_predicate(l, r)
- })
-}
-
-pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
- use WherePredicate::*;
- match (l, r) {
- (BoundPredicate(l), BoundPredicate(r)) => {
- over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
- eq_generic_param(l, r)
- }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
- && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
- },
- (RegionPredicate(l), RegionPredicate(r)) => {
- eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
- },
- (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
- _ => false,
- }
-}
-
-pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
- eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
-}
-
-pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
- use UseTreeKind::*;
- match (l, r) {
- (Glob, Glob) => true,
- (Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
- (Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
- _ => false,
- }
-}
-
-pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
- eq_expr(&l.value, &r.value)
-}
-
-pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
- matches!(
- (l, r),
- (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
- )
-}
-
-pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
- use VisibilityKind::*;
- match (&l.kind, &r.kind) {
- (Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
- (Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
- _ => false,
- }
-}
-
-pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
- eq_fn_ret_ty(&l.output, &r.output)
- && over(&l.inputs, &r.inputs, |l, r| {
- l.is_placeholder == r.is_placeholder
- && eq_pat(&l.pat, &r.pat)
- && eq_ty(&l.ty, &r.ty)
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
- })
-}
-
-pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
- match (l, r) {
- (FnRetTy::Default(_), FnRetTy::Default(_)) => true,
- (FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
- _ => false,
- }
-}
-
-pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
- use TyKind::*;
- match (&l.kind, &r.kind) {
- (Paren(l), _) => eq_ty(l, r),
- (_, Paren(r)) => eq_ty(l, r),
- (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
- (Slice(l), Slice(r)) => eq_ty(l, r),
- (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
- (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
- (Rptr(ll, l), Rptr(rl, r)) => {
- both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
- },
- (BareFn(l), BareFn(r)) => {
- l.unsafety == r.unsafety
- && eq_ext(&l.ext, &r.ext)
- && over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
- && eq_fn_decl(&l.decl, &r.decl)
- },
- (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
- (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
- (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
- (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
- (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
- (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
- _ => false,
- }
-}
-
-pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
- use Extern::*;
- match (l, r) {
- (None, None) | (Implicit, Implicit) => true,
- (Explicit(l), Explicit(r)) => eq_str_lit(l, r),
- _ => false,
- }
-}
-
-pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
- l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
-}
-
-pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
- eq_path(&l.trait_ref.path, &r.trait_ref.path)
- && over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
- eq_generic_param(l, r)
- })
-}
-
-pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
- use GenericParamKind::*;
- l.is_placeholder == r.is_placeholder
- && eq_id(l.ident, r.ident)
- && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
- && match (&l.kind, &r.kind) {
- (Lifetime, Lifetime) => true,
- (Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
- (
- Const {
- ty: lt,
- kw_span: _,
- default: ld,
- },
- Const {
- ty: rt,
- kw_span: _,
- default: rd,
- },
- ) => eq_ty(lt, rt) && both(ld, rd, |ld, rd| eq_anon_const(ld, rd)),
- _ => false,
- }
- && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
-}
-
-pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
- use GenericBound::*;
- match (l, r) {
- (Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
- (Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
- _ => false,
- }
-}
-
-pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
- use AssocTyConstraintKind::*;
- eq_id(l.ident, r.ident)
- && match (&l.kind, &r.kind) {
- (Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
- (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
- _ => false,
- }
-}
-
-pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
- eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
-}
-
-pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
- use AttrKind::*;
- l.style == r.style
- && match (&l.kind, &r.kind) {
- (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
- (Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
- _ => false,
- }
-}
-
-pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
- use MacArgs::*;
- match (l, r) {
- (Empty, Empty) => true,
- (Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
- (Eq(_, lt), Eq(_, rt)) => lt.kind == rt.kind,
- _ => false,
- }
-}
+++ /dev/null
-use core::iter::FusedIterator;
-use rustc_ast::visit::{walk_attribute, walk_expr, Visitor};
-use rustc_ast::{Attribute, Expr};
-use rustc_span::symbol::Ident;
-
-pub struct IdentIter(std::vec::IntoIter<Ident>);
-
-impl Iterator for IdentIter {
- type Item = Ident;
-
- fn next(&mut self) -> Option<Self::Item> {
- self.0.next()
- }
-}
-
-impl FusedIterator for IdentIter {}
-
-impl From<&Expr> for IdentIter {
- fn from(expr: &Expr) -> Self {
- let mut visitor = IdentCollector::default();
-
- walk_expr(&mut visitor, expr);
-
- IdentIter(visitor.0.into_iter())
- }
-}
-
-impl From<&Attribute> for IdentIter {
- fn from(attr: &Attribute) -> Self {
- let mut visitor = IdentCollector::default();
-
- walk_attribute(&mut visitor, attr);
-
- IdentIter(visitor.0.into_iter())
- }
-}
-
-#[derive(Default)]
-struct IdentCollector(Vec<Ident>);
-
-impl Visitor<'_> for IdentCollector {
- fn visit_ident(&mut self, ident: Ident) {
- self.0.push(ident);
- }
-}
+++ /dev/null
-use rustc_ast::ast;
-use rustc_errors::Applicability;
-use rustc_session::Session;
-use rustc_span::sym;
-use std::str::FromStr;
-
-/// Deprecation status of attributes known by Clippy.
-#[allow(dead_code)]
-pub enum DeprecationStatus {
- /// Attribute is deprecated
- Deprecated,
- /// Attribute is deprecated and was replaced by the named attribute
- Replaced(&'static str),
- None,
-}
-
-pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
- ("author", DeprecationStatus::None),
- ("cognitive_complexity", DeprecationStatus::None),
- (
- "cyclomatic_complexity",
- DeprecationStatus::Replaced("cognitive_complexity"),
- ),
- ("dump", DeprecationStatus::None),
- ("msrv", DeprecationStatus::None),
-];
-
-pub struct LimitStack {
- stack: Vec<u64>,
-}
-
-impl Drop for LimitStack {
- fn drop(&mut self) {
- assert_eq!(self.stack.len(), 1);
- }
-}
-
-impl LimitStack {
- #[must_use]
- pub fn new(limit: u64) -> Self {
- Self { stack: vec![limit] }
- }
- pub fn limit(&self) -> u64 {
- *self.stack.last().expect("there should always be a value in the stack")
- }
- pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
- let stack = &mut self.stack;
- parse_attrs(sess, attrs, name, |val| stack.push(val));
- }
- pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
- let stack = &mut self.stack;
- parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
- }
-}
-
-pub fn get_attr<'a>(
- sess: &'a Session,
- attrs: &'a [ast::Attribute],
- name: &'static str,
-) -> impl Iterator<Item = &'a ast::Attribute> {
- attrs.iter().filter(move |attr| {
- let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
- attr
- } else {
- return false;
- };
- let attr_segments = &attr.path.segments;
- if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
- BUILTIN_ATTRIBUTES
- .iter()
- .find_map(|&(builtin_name, ref deprecation_status)| {
- if attr_segments[1].ident.name.as_str() == builtin_name {
- Some(deprecation_status)
- } else {
- None
- }
- })
- .map_or_else(
- || {
- sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
- false
- },
- |deprecation_status| {
- let mut diag =
- sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
- match *deprecation_status {
- DeprecationStatus::Deprecated => {
- diag.emit();
- false
- },
- DeprecationStatus::Replaced(new_name) => {
- diag.span_suggestion(
- attr_segments[1].ident.span,
- "consider using",
- new_name.to_string(),
- Applicability::MachineApplicable,
- );
- diag.emit();
- false
- },
- DeprecationStatus::None => {
- diag.cancel();
- attr_segments[1].ident.name.as_str() == name
- },
- }
- },
- )
- } else {
- false
- }
- })
-}
-
-fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
- for attr in get_attr(sess, attrs, name) {
- if let Some(ref value) = attr.value_str() {
- if let Ok(value) = FromStr::from_str(&value.as_str()) {
- f(value)
- } else {
- sess.span_err(attr.span, "not a number");
- }
- } else {
- sess.span_err(attr.span, "bad clippy attribute");
- }
- }
-}
-
-pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
- let mut unique_attr = None;
- for attr in get_attr(sess, attrs, name) {
- match attr.style {
- ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
- ast::AttrStyle::Inner => {
- sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
- .span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
- .emit();
- },
- ast::AttrStyle::Outer => {
- sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
- },
- }
- }
- unique_attr
-}
-
-/// Return true if the attributes contain any of `proc_macro`,
-/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
-pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
- attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
-}
+++ /dev/null
-/// Returns the index of the character after the first camel-case component of `s`.
-#[must_use]
-pub fn until(s: &str) -> usize {
- let mut iter = s.char_indices();
- if let Some((_, first)) = iter.next() {
- if !first.is_uppercase() {
- return 0;
- }
- } else {
- return 0;
- }
- let mut up = true;
- let mut last_i = 0;
- for (i, c) in iter {
- if up {
- if c.is_lowercase() {
- up = false;
- } else {
- return last_i;
- }
- } else if c.is_uppercase() {
- up = true;
- last_i = i;
- } else if !c.is_lowercase() {
- return i;
- }
- }
- if up {
- last_i
- } else {
- s.len()
- }
-}
-
-/// Returns index of the last camel-case component of `s`.
-#[must_use]
-pub fn from(s: &str) -> usize {
- let mut iter = s.char_indices().rev();
- if let Some((_, first)) = iter.next() {
- if !first.is_lowercase() {
- return s.len();
- }
- } else {
- return s.len();
- }
- let mut down = true;
- let mut last_i = s.len();
- for (i, c) in iter {
- if down {
- if c.is_uppercase() {
- down = false;
- last_i = i;
- } else if !c.is_lowercase() {
- return last_i;
- }
- } else if c.is_lowercase() {
- down = true;
- } else {
- return last_i;
- }
- }
- last_i
-}
-
-#[cfg(test)]
-mod test {
- use super::{from, until};
-
- #[test]
- fn from_full() {
- assert_eq!(from("AbcDef"), 0);
- assert_eq!(from("Abc"), 0);
- }
-
- #[test]
- fn from_partial() {
- assert_eq!(from("abcDef"), 3);
- assert_eq!(from("aDbc"), 1);
- }
-
- #[test]
- fn from_not() {
- assert_eq!(from("AbcDef_"), 7);
- assert_eq!(from("AbcDD"), 5);
- }
-
- #[test]
- fn from_caps() {
- assert_eq!(from("ABCD"), 4);
- }
-
- #[test]
- fn until_full() {
- assert_eq!(until("AbcDef"), 6);
- assert_eq!(until("Abc"), 3);
- }
-
- #[test]
- fn until_not() {
- assert_eq!(until("abcDef"), 0);
- assert_eq!(until("aDbc"), 0);
- }
-
- #[test]
- fn until_partial() {
- assert_eq!(until("AbcDef_"), 6);
- assert_eq!(until("CallTypeC"), 8);
- assert_eq!(until("AbcDD"), 3);
- }
-
- #[test]
- fn until_caps() {
- assert_eq!(until("ABCD"), 0);
- }
-}
+++ /dev/null
-//! Utility functions about comparison operators.
-
-#![deny(clippy::missing_docs_in_private_items)]
-
-use rustc_hir::{BinOpKind, Expr};
-
-#[derive(PartialEq, Eq, Debug, Copy, Clone)]
-/// Represent a normalized comparison operator.
-pub enum Rel {
- /// `<`
- Lt,
- /// `<=`
- Le,
- /// `==`
- Eq,
- /// `!=`
- Ne,
-}
-
-/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or
-/// `lhs != rhs`.
-pub fn normalize_comparison<'a>(
- op: BinOpKind,
- lhs: &'a Expr<'a>,
- rhs: &'a Expr<'a>,
-) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> {
- match op {
- BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)),
- BinOpKind::Le => Some((Rel::Le, lhs, rhs)),
- BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)),
- BinOpKind::Ge => Some((Rel::Le, rhs, lhs)),
- BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)),
- BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)),
- _ => None,
- }
-}
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",
- "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
+ "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
"WebGL",
"TensorFlow",
"TrueType",
(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),
}
+++ /dev/null
-//! Clippy wrappers around rustc's diagnostic functions.
-
-use rustc_errors::{Applicability, DiagnosticBuilder};
-use rustc_hir::HirId;
-use rustc_lint::{LateContext, Lint, LintContext};
-use rustc_span::source_map::{MultiSpan, Span};
-use std::env;
-
-fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
- if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
- diag.help(&format!(
- "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
- &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
- // extract just major + minor version and ignore patch versions
- format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
- }),
- lint.name_lower().replacen("clippy::", "", 1)
- ));
- }
-}
-
-/// Emit a basic lint message with a `msg` and a `span`.
-///
-/// This is the most primitive of our lint emission methods and can
-/// be a good way to get a new lint started.
-///
-/// Usually it's nicer to provide more context for lint messages.
-/// Be sure the output is understandable when you use this method.
-///
-/// # Example
-///
-/// ```ignore
-/// error: usage of mem::forget on Drop type
-/// --> $DIR/mem_forget.rs:17:5
-/// |
-/// 17 | std::mem::forget(seven);
-/// | ^^^^^^^^^^^^^^^^^^^^^^^
-/// ```
-pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
- cx.struct_span_lint(lint, sp, |diag| {
- let mut diag = diag.build(msg);
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-/// Same as `span_lint` but with an extra `help` message.
-///
-/// Use this if you want to provide some general help but
-/// can't provide a specific machine applicable suggestion.
-///
-/// The `help` message can be optionally attached to a `Span`.
-///
-/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
-///
-/// # Example
-///
-/// ```ignore
-/// error: constant division of 0.0 with 0.0 will always result in NaN
-/// --> $DIR/zero_div_zero.rs:6:25
-/// |
-/// 6 | let other_f64_nan = 0.0f64 / 0.0;
-/// | ^^^^^^^^^^^^
-/// |
-/// = help: Consider using `f64::NAN` if you would like a constant representing NaN
-/// ```
-pub fn span_lint_and_help<'a, T: LintContext>(
- cx: &'a T,
- lint: &'static Lint,
- span: Span,
- msg: &str,
- help_span: Option<Span>,
- help: &str,
-) {
- cx.struct_span_lint(lint, span, |diag| {
- let mut diag = diag.build(msg);
- if let Some(help_span) = help_span {
- diag.span_help(help_span, help);
- } else {
- diag.help(help);
- }
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-/// Like `span_lint` but with a `note` section instead of a `help` message.
-///
-/// The `note` message is presented separately from the main lint message
-/// and is attached to a specific span:
-///
-/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
-///
-/// # Example
-///
-/// ```ignore
-/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
-/// --> $DIR/drop_forget_ref.rs:10:5
-/// |
-/// 10 | forget(&SomeStruct);
-/// | ^^^^^^^^^^^^^^^^^^^
-/// |
-/// = note: `-D clippy::forget-ref` implied by `-D warnings`
-/// note: argument has type &SomeStruct
-/// --> $DIR/drop_forget_ref.rs:10:12
-/// |
-/// 10 | forget(&SomeStruct);
-/// | ^^^^^^^^^^^
-/// ```
-pub fn span_lint_and_note<'a, T: LintContext>(
- cx: &'a T,
- lint: &'static Lint,
- span: impl Into<MultiSpan>,
- msg: &str,
- note_span: Option<Span>,
- note: &str,
-) {
- cx.struct_span_lint(lint, span, |diag| {
- let mut diag = diag.build(msg);
- if let Some(note_span) = note_span {
- diag.span_note(note_span, note);
- } else {
- diag.note(note);
- }
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
-///
-/// If you need to customize your lint output a lot, use this function.
-/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
-pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
-where
- F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
-{
- cx.struct_span_lint(lint, sp, |diag| {
- let mut diag = diag.build(msg);
- f(&mut diag);
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
- cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
- let mut diag = diag.build(msg);
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-pub fn span_lint_hir_and_then(
- cx: &LateContext<'_>,
- lint: &'static Lint,
- hir_id: HirId,
- sp: Span,
- msg: &str,
- f: impl FnOnce(&mut DiagnosticBuilder<'_>),
-) {
- cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
- let mut diag = diag.build(msg);
- f(&mut diag);
- docs_link(&mut diag, lint);
- diag.emit();
- });
-}
-
-/// Add a span lint with a suggestion on how to fix it.
-///
-/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
-/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
-/// 2)"`.
-///
-/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
-///
-/// # Example
-///
-/// ```ignore
-/// error: This `.fold` can be more succinctly expressed as `.any`
-/// --> $DIR/methods.rs:390:13
-/// |
-/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
-/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
-/// |
-/// = note: `-D fold-any` implied by `-D warnings`
-/// ```
-#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
-pub fn span_lint_and_sugg<'a, T: LintContext>(
- cx: &'a T,
- lint: &'static Lint,
- sp: Span,
- msg: &str,
- help: &str,
- sugg: String,
- applicability: Applicability,
-) {
- span_lint_and_then(cx, lint, sp, msg, |diag| {
- diag.span_suggestion(sp, help, sugg, applicability);
- });
-}
-
-/// Create a suggestion made from several `span → replacement`.
-///
-/// Note: in the JSON format (used by `compiletest_rs`), the help message will
-/// appear once per
-/// replacement. In human-readable format though, it only appears once before
-/// the whole suggestion.
-pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
-where
- I: IntoIterator<Item = (Span, String)>,
-{
- multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg)
-}
-
-pub fn multispan_sugg_with_applicability<I>(
- diag: &mut DiagnosticBuilder<'_>,
- help_msg: &str,
- applicability: Applicability,
- sugg: I,
-) where
- I: IntoIterator<Item = (Span, String)>,
-{
- diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
-}
+++ /dev/null
-//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
-//!
-//! Things to consider:
-//! - has the expression side-effects?
-//! - is the expression computationally expensive?
-//!
-//! See lints:
-//! - unnecessary-lazy-evaluations
-//! - or-fun-call
-//! - option-if-let-else
-
-use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
-use rustc_hir::def::{DefKind, Res};
-
-use rustc_hir::intravisit;
-use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
-
-use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
-use rustc_lint::LateContext;
-use rustc_middle::hir::map::Map;
-
-/// Is the expr pure (is it free from side-effects)?
-/// This function is named so to stress that it isn't exhaustive and returns FNs.
-fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
- match expr.kind {
- ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
- ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
- ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)),
- ExprKind::Struct(_, fields, expr) => {
- fields.iter().all(|f| identify_some_pure_patterns(f.expr))
- && expr.map_or(true, |e| identify_some_pure_patterns(e))
- },
- ExprKind::Call(
- &Expr {
- kind:
- ExprKind::Path(QPath::Resolved(
- _,
- Path {
- res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
- ..
- },
- )),
- ..
- },
- args,
- ) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
- ExprKind::Block(
- &Block {
- stmts,
- expr: Some(expr),
- ..
- },
- _,
- ) => stmts.is_empty() && identify_some_pure_patterns(expr),
- ExprKind::Box(..)
- | ExprKind::Array(..)
- | ExprKind::Call(..)
- | ExprKind::MethodCall(..)
- | ExprKind::Binary(..)
- | ExprKind::Unary(..)
- | ExprKind::Cast(..)
- | ExprKind::Type(..)
- | ExprKind::DropTemps(..)
- | ExprKind::Loop(..)
- | ExprKind::If(..)
- | ExprKind::Match(..)
- | ExprKind::Closure(..)
- | ExprKind::Block(..)
- | ExprKind::Assign(..)
- | ExprKind::AssignOp(..)
- | ExprKind::Index(..)
- | ExprKind::Break(..)
- | ExprKind::Continue(..)
- | ExprKind::Ret(..)
- | ExprKind::InlineAsm(..)
- | ExprKind::LlvmInlineAsm(..)
- | ExprKind::Repeat(..)
- | ExprKind::Yield(..)
- | ExprKind::Err => false,
- }
-}
-
-/// Identify some potentially computationally expensive patterns.
-/// This function is named so to stress that its implementation is non-exhaustive.
-/// It returns FNs and FPs.
-fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- // Searches an expression for method calls or function calls that aren't ctors
- struct FunCallFinder<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- found: bool,
- }
-
- impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- let call_found = match &expr.kind {
- // ignore enum and struct constructors
- ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
- ExprKind::Index(obj, _) => {
- let ty = self.cx.typeck_results().expr_ty(obj);
- is_type_diagnostic_item(self.cx, ty, sym!(hashmap_type))
- || match_type(self.cx, ty, &paths::BTREEMAP)
- },
- ExprKind::MethodCall(..) => true,
- _ => false,
- };
-
- if call_found {
- self.found |= true;
- }
-
- if !self.found {
- intravisit::walk_expr(self, expr);
- }
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
- }
-
- let mut finder = FunCallFinder { cx, found: false };
- finder.visit_expr(expr);
- finder.found
-}
-
-pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
-}
-
-pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- identify_some_potentially_expensive_patterns(cx, expr)
-}
+++ /dev/null
-//! This module contains functions for retrieve the original AST from lowered
-//! `hir`.
-
-#![deny(clippy::missing_docs_in_private_items)]
-
-use crate::utils::{is_expn_of, match_def_path, paths};
-use if_chain::if_chain;
-use rustc_ast::ast;
-use rustc_hir as hir;
-use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
-use rustc_lint::LateContext;
-use rustc_span::source_map::Span;
-
-/// Converts a hir binary operator to the corresponding `ast` type.
-#[must_use]
-pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
- match op {
- hir::BinOpKind::Eq => ast::BinOpKind::Eq,
- hir::BinOpKind::Ge => ast::BinOpKind::Ge,
- hir::BinOpKind::Gt => ast::BinOpKind::Gt,
- hir::BinOpKind::Le => ast::BinOpKind::Le,
- hir::BinOpKind::Lt => ast::BinOpKind::Lt,
- hir::BinOpKind::Ne => ast::BinOpKind::Ne,
- hir::BinOpKind::Or => ast::BinOpKind::Or,
- hir::BinOpKind::Add => ast::BinOpKind::Add,
- hir::BinOpKind::And => ast::BinOpKind::And,
- hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
- hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
- hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
- hir::BinOpKind::Div => ast::BinOpKind::Div,
- hir::BinOpKind::Mul => ast::BinOpKind::Mul,
- hir::BinOpKind::Rem => ast::BinOpKind::Rem,
- hir::BinOpKind::Shl => ast::BinOpKind::Shl,
- hir::BinOpKind::Shr => ast::BinOpKind::Shr,
- hir::BinOpKind::Sub => ast::BinOpKind::Sub,
- }
-}
-
-/// Represent a range akin to `ast::ExprKind::Range`.
-#[derive(Debug, Copy, Clone)]
-pub struct Range<'a> {
- /// The lower bound of the range, or `None` for ranges such as `..X`.
- pub start: Option<&'a hir::Expr<'a>>,
- /// The upper bound of the range, or `None` for ranges such as `X..`.
- pub end: Option<&'a hir::Expr<'a>>,
- /// Whether the interval is open or closed.
- pub limits: ast::RangeLimits,
-}
-
-/// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
-pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
- /// Finds the field named `name` in the field. Always return `Some` for
- /// convenience.
- fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> {
- let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
-
- Some(expr)
- }
-
- match expr.kind {
- hir::ExprKind::Call(ref path, ref args)
- if matches!(
- path.kind,
- hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
- ) =>
- {
- Some(Range {
- start: Some(&args[0]),
- end: Some(&args[1]),
- limits: ast::RangeLimits::Closed,
- })
- },
- hir::ExprKind::Struct(ref path, ref fields, None) => match path {
- hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
- start: None,
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::Closed,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- _ => None,
- },
- _ => None,
- }
-}
-
-/// Checks if a `let` statement is from a `for` loop desugaring.
-pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
- // This will detect plain for-loops without an actual variable binding:
- //
- // ```
- // for x in some_vec {
- // // do stuff
- // }
- // ```
- if_chain! {
- if let Some(ref expr) = local.init;
- if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
- then {
- return true;
- }
- }
-
- // This detects a variable binding in for loop to avoid `let_unit_value`
- // lint (see issue #1964).
- //
- // ```
- // for _ in vec![()] {
- // // anything
- // }
- // ```
- if let hir::LocalSource::ForLoopDesugar = local.source {
- return true;
- }
-
- false
-}
-
-/// Recover the essential nodes of a desugared for loop as well as the entire span:
-/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
-pub fn for_loop<'tcx>(
- expr: &'tcx hir::Expr<'tcx>,
-) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
- if_chain! {
- if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
- if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
- if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
- if let hir::ExprKind::Loop(ref block, ..) = arms[0].body.kind;
- if block.expr.is_none();
- if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
- if let hir::StmtKind::Local(ref local) = let_stmt.kind;
- if let hir::StmtKind::Expr(ref expr) = body.kind;
- then {
- return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
- }
- }
- None
-}
-
-/// Recover the essential nodes of a desugared while loop:
-/// `while cond { body }` becomes `(cond, body)`.
-pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
- if_chain! {
- if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
- if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
- if let hir::ExprKind::DropTemps(cond) = &cond.kind;
- if let [hir::Arm { body, .. }, ..] = &arms[..];
- then {
- return Some((cond, body));
- }
- }
- None
-}
-
-/// Represent the pre-expansion arguments of a `vec!` invocation.
-pub enum VecArgs<'a> {
- /// `vec![elem; len]`
- Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
- /// `vec![a, b, c]`
- Vec(&'a [hir::Expr<'a>]),
-}
-
-/// Returns the arguments of the `vec!` macro if this expression was expanded
-/// from `vec!`.
-pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
- if_chain! {
- if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
- if let hir::ExprKind::Path(ref qpath) = fun.kind;
- if is_expn_of(fun.span, "vec").is_some();
- if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
- then {
- return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
- // `vec![elem; size]` case
- Some(VecArgs::Repeat(&args[0], &args[1]))
- }
- else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
- // `vec![a, b, c]` case
- if_chain! {
- if let hir::ExprKind::Box(ref boxed) = args[0].kind;
- if let hir::ExprKind::Array(ref args) = boxed.kind;
- then {
- return Some(VecArgs::Vec(&*args));
- }
- }
-
- None
- }
- else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
- Some(VecArgs::Vec(&[]))
- }
- else {
- None
- };
- }
- }
-
- None
-}
-
-/// Extract args from an assert-like macro.
-/// Currently working with:
-/// - `assert!`, `assert_eq!` and `assert_ne!`
-/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
-/// For example:
-/// `assert!(expr)` will return Some([expr])
-/// `debug_assert_eq!(a, b)` will return Some([a, b])
-pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
- /// Try to match the AST for a pattern that contains a match, for example when two args are
- /// compared
- fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
- if_chain! {
- if let ExprKind::Match(ref headerexpr, _, _) = &matchblock_expr.kind;
- if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
- if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
- if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
- then {
- return Some(vec![lhs, rhs]);
- }
- }
- None
- }
-
- if let ExprKind::Block(ref block, _) = e.kind {
- if block.stmts.len() == 1 {
- if let StmtKind::Semi(ref matchexpr) = block.stmts.get(0)?.kind {
- // macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
- if_chain! {
- if let ExprKind::If(ref clause, _, _) = matchexpr.kind;
- if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
- then {
- return Some(vec![condition]);
- }
- }
-
- // debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
- if_chain! {
- if let ExprKind::Block(ref matchblock,_) = matchexpr.kind;
- if let Some(ref matchblock_expr) = matchblock.expr;
- then {
- return ast_matchblock(matchblock_expr);
- }
- }
- }
- } else if let Some(matchblock_expr) = block.expr {
- // macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
- return ast_matchblock(&matchblock_expr);
- }
- }
- None
-}
+++ /dev/null
-use crate::consts::{constant_context, constant_simple};
-use crate::utils::differing_macro_contexts;
-use rustc_ast::ast::InlineAsmTemplatePiece;
-use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
-use rustc_hir::{
- BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat, FnRetTy,
- GenericArg, GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
- PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
-};
-use rustc_lint::LateContext;
-use rustc_middle::ich::StableHashingContextProvider;
-use rustc_middle::ty::TypeckResults;
-use rustc_span::Symbol;
-use std::hash::Hash;
-
-/// Type used to check whether two ast are the same. This is different from the
-/// operator
-/// `==` on ast types as this operator would compare true equality with ID and
-/// span.
-///
-/// Note that some expressions kinds are not considered but could be added.
-pub struct SpanlessEq<'a, 'tcx> {
- /// Context used to evaluate constant expressions.
- cx: &'a LateContext<'tcx>,
- maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
- allow_side_effects: bool,
- expr_fallback: Option<Box<dyn Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
-}
-
-impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
- pub fn new(cx: &'a LateContext<'tcx>) -> Self {
- Self {
- cx,
- maybe_typeck_results: cx.maybe_typeck_results(),
- allow_side_effects: true,
- expr_fallback: None,
- }
- }
-
- /// Consider expressions containing potential side effects as not equal.
- pub fn deny_side_effects(self) -> Self {
- Self {
- allow_side_effects: false,
- ..self
- }
- }
-
- pub fn expr_fallback(self, expr_fallback: impl Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
- Self {
- expr_fallback: Some(Box::new(expr_fallback)),
- ..self
- }
- }
-
- /// Checks whether two statements are the same.
- pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
- match (&left.kind, &right.kind) {
- (&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
- self.eq_pat(&l.pat, &r.pat)
- && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
- && both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
- },
- (&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
- self.eq_expr(l, r)
- },
- _ => false,
- }
- }
-
- /// Checks whether two blocks are the same.
- pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
- over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
- && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
- }
-
- #[allow(clippy::similar_names)]
- pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
- if !self.allow_side_effects && differing_macro_contexts(left.span, right.span) {
- return false;
- }
-
- if let Some(typeck_results) = self.maybe_typeck_results {
- if let (Some(l), Some(r)) = (
- constant_simple(self.cx, typeck_results, left),
- constant_simple(self.cx, typeck_results, right),
- ) {
- if l == r {
- return true;
- }
- }
- }
-
- let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
- (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
- lb == rb && l_mut == r_mut && self.eq_expr(le, re)
- },
- (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
- both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
- },
- (&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
- self.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
- },
- (&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
- self.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
- },
- (&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
- (&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
- l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
- || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
- l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
- })
- },
- (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
- both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
- && both(le, re, |l, r| self.eq_expr(l, r))
- },
- (&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
- (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
- self.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
- },
- (&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
- | (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
- self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
- },
- (&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
- l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
- },
- (&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
- self.eq_expr(la, ra) && self.eq_expr(li, ri)
- },
- (&ExprKind::If(ref lc, ref lt, ref le), &ExprKind::If(ref rc, ref rt, ref re)) => {
- self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
- },
- (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
- (&ExprKind::Loop(ref lb, ref ll, ref lls, _), &ExprKind::Loop(ref rb, ref rl, ref rls, _)) => {
- lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
- },
- (&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
- ls == rs
- && self.eq_expr(le, re)
- && over(la, ra, |l, r| {
- self.eq_expr(&l.body, &r.body)
- && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
- && self.eq_pat(&l.pat, &r.pat)
- })
- },
- (&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
- self.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
- },
- (&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
- let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(ll_id.body));
- let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
- let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(rl_id.body));
- let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
-
- self.eq_expr(le, re) && ll == rl
- },
- (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
- (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
- (&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
- self.eq_qpath(l_path, r_path)
- && both(lo, ro, |l, r| self.eq_expr(l, r))
- && over(lf, rf, |l, r| self.eq_field(l, r))
- },
- (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
- (&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
- (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
- (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
- _ => false,
- };
- is_eq || self.expr_fallback.as_ref().map_or(false, |f| f(left, right))
- }
-
- fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
- over(left, right, |l, r| self.eq_expr(l, r))
- }
-
- fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool {
- left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
- }
-
- fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
- match (left, right) {
- (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
- (Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
- _ => false,
- }
- }
-
- fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
- match (left, right) {
- (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
- (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
- _ => false,
- }
- }
-
- fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
- left.name == right.name
- }
-
- pub fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
- let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
- li.name == ri.name && self.eq_pat(lp, rp)
- }
-
- /// Checks whether two patterns are the same.
- pub fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
- match (&left.kind, &right.kind) {
- (&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
- (&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
- self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_fieldpat(l, r))
- },
- (&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
- self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
- },
- (&PatKind::Binding(ref lb, .., ref li, ref lp), &PatKind::Binding(ref rb, .., ref ri, ref rp)) => {
- lb == rb && li.name == ri.name && both(lp, rp, |l, r| self.eq_pat(l, r))
- },
- (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
- (&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
- (&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
- ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
- },
- (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
- both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
- },
- (&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
- (&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
- over(ls, rs, |l, r| self.eq_pat(l, r))
- && over(le, re, |l, r| self.eq_pat(l, r))
- && both(li, ri, |l, r| self.eq_pat(l, r))
- },
- (&PatKind::Wild, &PatKind::Wild) => true,
- _ => false,
- }
- }
-
- #[allow(clippy::similar_names)]
- fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
- match (left, right) {
- (&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
- both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
- },
- (&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
- self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
- },
- (&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
- _ => false,
- }
- }
-
- fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
- left.is_global() == right.is_global()
- && over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r))
- }
-
- fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
- if !(left.parenthesized || right.parenthesized) {
- over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
- && over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
- } else if left.parenthesized && right.parenthesized {
- over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
- && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
- self.eq_ty(l, r)
- })
- } else {
- false
- }
- }
-
- pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
- left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
- }
-
- pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
- // The == of idents doesn't work with different contexts,
- // we have to be explicit about hygiene
- left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
- }
-
- pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
- self.eq_ty_kind(&left.kind, &right.kind)
- }
-
- #[allow(clippy::similar_names)]
- pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
- match (left, right) {
- (&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
- (&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
- let old_maybe_typeck_results = self.maybe_typeck_results;
-
- let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(ll_id.body));
- self.maybe_typeck_results = Some(self.cx.tcx.typeck_body(ll_id.body));
- let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
-
- let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(rl_id.body));
- self.maybe_typeck_results = Some(self.cx.tcx.typeck_body(rl_id.body));
- let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
-
- let eq_ty = self.eq_ty(lt, rt);
- self.maybe_typeck_results = old_maybe_typeck_results;
- eq_ty && ll == rl
- },
- (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
- l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
- },
- (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
- l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
- },
- (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
- (&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
- (&TyKind::Infer, &TyKind::Infer) => true,
- _ => false,
- }
- }
-
- fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
- left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
- }
-}
-
-/// Some simple reductions like `{ return }` => `return`
-fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
- if let ExprKind::Block(block, _) = kind {
- match (block.stmts, block.expr) {
- // `{}` => `()`
- ([], None) => &ExprKind::Tup(&[]),
- ([], Some(expr)) => match expr.kind {
- // `{ return .. }` => `return ..`
- ExprKind::Ret(..) => &expr.kind,
- _ => kind,
- },
- ([stmt], None) => match stmt.kind {
- StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
- // `{ return ..; }` => `return ..`
- ExprKind::Ret(..) => &expr.kind,
- _ => kind,
- },
- _ => kind,
- },
- _ => kind,
- }
- } else {
- kind
- }
-}
-
-fn swap_binop<'a>(
- binop: BinOpKind,
- lhs: &'a Expr<'a>,
- rhs: &'a Expr<'a>,
-) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
- match binop {
- BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
- Some((binop, rhs, lhs))
- },
- BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
- BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
- BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
- BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
- BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
- | BinOpKind::Shl
- | BinOpKind::Shr
- | BinOpKind::Rem
- | BinOpKind::Sub
- | BinOpKind::Div
- | BinOpKind::And
- | BinOpKind::Or => None,
- }
-}
-
-/// Checks if the two `Option`s are both `None` or some equal values as per
-/// `eq_fn`.
-pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
- l.as_ref()
- .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
-}
-
-/// Checks if two slices are equal as per `eq_fn`.
-pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
- left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
-}
-
-/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
-pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
- SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
-}
-
-/// Type used to hash an ast element. This is different from the `Hash` trait
-/// on ast types as this
-/// trait would consider IDs and spans.
-///
-/// All expressions kind are hashed, but some might have a weaker hash.
-pub struct SpanlessHash<'a, 'tcx> {
- /// Context used to evaluate constant expressions.
- cx: &'a LateContext<'tcx>,
- maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
- s: StableHasher,
-}
-
-impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
- pub fn new(cx: &'a LateContext<'tcx>) -> Self {
- Self {
- cx,
- maybe_typeck_results: cx.maybe_typeck_results(),
- s: StableHasher::new(),
- }
- }
-
- pub fn finish(self) -> u64 {
- self.s.finish()
- }
-
- pub fn hash_block(&mut self, b: &Block<'_>) {
- for s in b.stmts {
- self.hash_stmt(s);
- }
-
- if let Some(ref e) = b.expr {
- self.hash_expr(e);
- }
-
- match b.rules {
- BlockCheckMode::DefaultBlock => 0,
- BlockCheckMode::UnsafeBlock(_) => 1,
- BlockCheckMode::PushUnsafeBlock(_) => 2,
- BlockCheckMode::PopUnsafeBlock(_) => 3,
- }
- .hash(&mut self.s);
- }
-
- #[allow(clippy::many_single_char_names, clippy::too_many_lines)]
- pub fn hash_expr(&mut self, e: &Expr<'_>) {
- let simple_const = self
- .maybe_typeck_results
- .and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
-
- // const hashing may result in the same hash as some unrelated node, so add a sort of
- // discriminant depending on which path we're choosing next
- simple_const.is_some().hash(&mut self.s);
-
- if let Some(e) = simple_const {
- return e.hash(&mut self.s);
- }
-
- std::mem::discriminant(&e.kind).hash(&mut self.s);
-
- match e.kind {
- ExprKind::AddrOf(kind, m, ref e) => {
- match kind {
- BorrowKind::Ref => 0,
- BorrowKind::Raw => 1,
- }
- .hash(&mut self.s);
- m.hash(&mut self.s);
- self.hash_expr(e);
- },
- ExprKind::Continue(i) => {
- if let Some(i) = i.label {
- self.hash_name(i.ident.name);
- }
- },
- ExprKind::Assign(ref l, ref r, _) => {
- self.hash_expr(l);
- self.hash_expr(r);
- },
- ExprKind::AssignOp(ref o, ref l, ref r) => {
- o.node
- .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
- self.hash_expr(l);
- self.hash_expr(r);
- },
- ExprKind::Block(ref b, _) => {
- self.hash_block(b);
- },
- ExprKind::Binary(op, ref l, ref r) => {
- op.node
- .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
- self.hash_expr(l);
- self.hash_expr(r);
- },
- ExprKind::Break(i, ref j) => {
- if let Some(i) = i.label {
- self.hash_name(i.ident.name);
- }
- if let Some(ref j) = *j {
- self.hash_expr(&*j);
- }
- },
- ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
- self.hash_expr(e);
- },
- ExprKind::Call(ref fun, args) => {
- self.hash_expr(fun);
- self.hash_exprs(args);
- },
- ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
- self.hash_expr(e);
- self.hash_ty(ty);
- },
- ExprKind::Closure(cap, _, eid, _, _) => {
- match cap {
- CaptureBy::Value => 0,
- CaptureBy::Ref => 1,
- }
- .hash(&mut self.s);
- // closures inherit TypeckResults
- self.hash_expr(&self.cx.tcx.hir().body(eid).value);
- },
- ExprKind::Field(ref e, ref f) => {
- self.hash_expr(e);
- self.hash_name(f.name);
- },
- ExprKind::Index(ref a, ref i) => {
- self.hash_expr(a);
- self.hash_expr(i);
- },
- ExprKind::InlineAsm(ref asm) => {
- for piece in asm.template {
- match piece {
- InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
- InlineAsmTemplatePiece::Placeholder {
- operand_idx,
- modifier,
- span: _,
- } => {
- operand_idx.hash(&mut self.s);
- modifier.hash(&mut self.s);
- },
- }
- }
- asm.options.hash(&mut self.s);
- for (op, _op_sp) in asm.operands {
- match op {
- InlineAsmOperand::In { reg, expr } => {
- reg.hash(&mut self.s);
- self.hash_expr(expr);
- },
- InlineAsmOperand::Out { reg, late, expr } => {
- reg.hash(&mut self.s);
- late.hash(&mut self.s);
- if let Some(expr) = expr {
- self.hash_expr(expr);
- }
- },
- InlineAsmOperand::InOut { reg, late, expr } => {
- reg.hash(&mut self.s);
- late.hash(&mut self.s);
- self.hash_expr(expr);
- },
- InlineAsmOperand::SplitInOut {
- reg,
- late,
- in_expr,
- out_expr,
- } => {
- reg.hash(&mut self.s);
- late.hash(&mut self.s);
- self.hash_expr(in_expr);
- if let Some(out_expr) = out_expr {
- self.hash_expr(out_expr);
- }
- },
- InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
- }
- }
- },
- ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
- ExprKind::Lit(ref l) => {
- l.node.hash(&mut self.s);
- },
- ExprKind::Loop(ref b, ref i, ..) => {
- self.hash_block(b);
- if let Some(i) = *i {
- self.hash_name(i.ident.name);
- }
- },
- ExprKind::If(ref cond, ref then, ref else_opt) => {
- let c: fn(_, _, _) -> _ = ExprKind::If;
- c.hash(&mut self.s);
- self.hash_expr(cond);
- self.hash_expr(&**then);
- if let Some(ref e) = *else_opt {
- self.hash_expr(e);
- }
- },
- ExprKind::Match(ref e, arms, ref s) => {
- self.hash_expr(e);
-
- for arm in arms {
- // TODO: arm.pat?
- if let Some(ref e) = arm.guard {
- self.hash_guard(e);
- }
- self.hash_expr(&arm.body);
- }
-
- s.hash(&mut self.s);
- },
- ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
- self.hash_name(path.ident.name);
- self.hash_exprs(args);
- },
- ExprKind::ConstBlock(ref l_id) => {
- self.hash_body(l_id.body);
- },
- ExprKind::Repeat(ref e, ref l_id) => {
- self.hash_expr(e);
- self.hash_body(l_id.body);
- },
- ExprKind::Ret(ref e) => {
- if let Some(ref e) = *e {
- self.hash_expr(e);
- }
- },
- ExprKind::Path(ref qpath) => {
- self.hash_qpath(qpath);
- },
- ExprKind::Struct(ref path, fields, ref expr) => {
- self.hash_qpath(path);
-
- for f in fields {
- self.hash_name(f.ident.name);
- self.hash_expr(&f.expr);
- }
-
- if let Some(ref e) = *expr {
- self.hash_expr(e);
- }
- },
- ExprKind::Tup(tup) => {
- self.hash_exprs(tup);
- },
- ExprKind::Array(v) => {
- self.hash_exprs(v);
- },
- ExprKind::Unary(lop, ref le) => {
- lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
- self.hash_expr(le);
- },
- }
- }
-
- pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
- for e in e {
- self.hash_expr(e);
- }
- }
-
- pub fn hash_name(&mut self, n: Symbol) {
- n.as_str().hash(&mut self.s);
- }
-
- pub fn hash_qpath(&mut self, p: &QPath<'_>) {
- match *p {
- QPath::Resolved(_, ref path) => {
- self.hash_path(path);
- },
- QPath::TypeRelative(_, ref path) => {
- self.hash_name(path.ident.name);
- },
- QPath::LangItem(lang_item, ..) => {
- lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
- },
- }
- // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
- }
-
- pub fn hash_path(&mut self, p: &Path<'_>) {
- p.is_global().hash(&mut self.s);
- for p in p.segments {
- self.hash_name(p.ident.name);
- }
- }
-
- pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
- std::mem::discriminant(&b.kind).hash(&mut self.s);
-
- match &b.kind {
- StmtKind::Local(local) => {
- if let Some(ref init) = local.init {
- self.hash_expr(init);
- }
- },
- StmtKind::Item(..) => {},
- StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
- self.hash_expr(expr);
- },
- }
- }
-
- pub fn hash_guard(&mut self, g: &Guard<'_>) {
- match g {
- Guard::If(ref expr) | Guard::IfLet(_, ref expr) => {
- self.hash_expr(expr);
- },
- }
- }
-
- pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
- std::mem::discriminant(&lifetime.name).hash(&mut self.s);
- if let LifetimeName::Param(ref name) = lifetime.name {
- std::mem::discriminant(name).hash(&mut self.s);
- match name {
- ParamName::Plain(ref ident) => {
- ident.name.hash(&mut self.s);
- },
- ParamName::Fresh(ref size) => {
- size.hash(&mut self.s);
- },
- ParamName::Error => {},
- }
- }
- }
-
- pub fn hash_ty(&mut self, ty: &Ty<'_>) {
- self.hash_tykind(&ty.kind);
- }
-
- pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
- std::mem::discriminant(ty).hash(&mut self.s);
- match ty {
- TyKind::Slice(ty) => {
- self.hash_ty(ty);
- },
- TyKind::Array(ty, anon_const) => {
- self.hash_ty(ty);
- self.hash_body(anon_const.body);
- },
- TyKind::Ptr(mut_ty) => {
- self.hash_ty(&mut_ty.ty);
- mut_ty.mutbl.hash(&mut self.s);
- },
- TyKind::Rptr(lifetime, mut_ty) => {
- self.hash_lifetime(lifetime);
- self.hash_ty(&mut_ty.ty);
- mut_ty.mutbl.hash(&mut self.s);
- },
- TyKind::BareFn(bfn) => {
- bfn.unsafety.hash(&mut self.s);
- bfn.abi.hash(&mut self.s);
- for arg in bfn.decl.inputs {
- self.hash_ty(&arg);
- }
- match bfn.decl.output {
- FnRetTy::DefaultReturn(_) => {
- ().hash(&mut self.s);
- },
- FnRetTy::Return(ref ty) => {
- self.hash_ty(ty);
- },
- }
- bfn.decl.c_variadic.hash(&mut self.s);
- },
- TyKind::Tup(ty_list) => {
- for ty in *ty_list {
- self.hash_ty(ty);
- }
- },
- TyKind::Path(qpath) => match qpath {
- QPath::Resolved(ref maybe_ty, ref path) => {
- if let Some(ref ty) = maybe_ty {
- self.hash_ty(ty);
- }
- for segment in path.segments {
- segment.ident.name.hash(&mut self.s);
- self.hash_generic_args(segment.args().args);
- }
- },
- QPath::TypeRelative(ref ty, ref segment) => {
- self.hash_ty(ty);
- segment.ident.name.hash(&mut self.s);
- },
- QPath::LangItem(lang_item, ..) => {
- lang_item.hash(&mut self.s);
- },
- },
- TyKind::OpaqueDef(_, arg_list) => {
- self.hash_generic_args(arg_list);
- },
- TyKind::TraitObject(_, lifetime) => {
- self.hash_lifetime(lifetime);
- },
- TyKind::Typeof(anon_const) => {
- self.hash_body(anon_const.body);
- },
- TyKind::Err | TyKind::Infer | TyKind::Never => {},
- }
- }
-
- pub fn hash_body(&mut self, body_id: BodyId) {
- // swap out TypeckResults when hashing a body
- let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
- self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
- self.maybe_typeck_results = old_maybe_typeck_results;
- }
-
- fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
- for arg in arg_list {
- match arg {
- GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
- GenericArg::Type(ref ty) => self.hash_ty(&ty),
- GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
- }
- }
- }
-}
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;
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 {
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;
}
.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);
}
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";
-#[macro_use]
-pub mod sym_helper;
-
-#[allow(clippy::module_name_repetitions)]
-pub mod ast_utils;
-pub mod attrs;
pub mod author;
-pub mod camel_case;
-pub mod comparisons;
pub mod conf;
-mod diagnostics;
-pub mod eager_or_lazy;
-pub mod higher;
-mod hir_utils;
pub mod inspector;
#[cfg(feature = "internal-lints")]
pub mod internal_lints;
-pub mod numeric_literal;
-pub mod paths;
-pub mod ptr;
-pub mod qualify_min_const_fn;
-pub mod sugg;
-pub mod usage;
-pub mod visitors;
-
-pub use self::attrs::*;
-pub use self::diagnostics::*;
-pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash};
-
-use std::borrow::Cow;
-use std::collections::hash_map::Entry;
-use std::hash::BuildHasherDefault;
-
-use if_chain::if_chain;
-use rustc_ast::ast::{self, Attribute, LitKind};
-use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::Applicability;
-use rustc_hir as hir;
-use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
-use rustc_hir::def_id::{DefId, LOCAL_CRATE};
-use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
-use rustc_hir::Node;
-use rustc_hir::{
- def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind,
- MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety,
-};
-use rustc_infer::infer::TyCtxtInferExt;
-use rustc_lint::{LateContext, Level, Lint, LintContext};
-use rustc_middle::hir::exports::Export;
-use rustc_middle::hir::map::Map;
-use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
-use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
-use rustc_semver::RustcVersion;
-use rustc_session::Session;
-use rustc_span::hygiene::{ExpnKind, MacroKind};
-use rustc_span::source_map::original_sp;
-use rustc_span::sym;
-use rustc_span::symbol::{kw, Symbol};
-use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
-use rustc_target::abi::Integer;
-use rustc_trait_selection::traits::query::normalize::AtExt;
-use smallvec::SmallVec;
-
-use crate::consts::{constant, Constant};
-
-pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
- if let Ok(version) = RustcVersion::parse(msrv) {
- return Some(version);
- } else if let Some(sess) = sess {
- if let Some(span) = span {
- sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
- }
- }
- None
-}
-
-pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
- msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
-}
-
-macro_rules! extract_msrv_attr {
- (LateContext) => {
- extract_msrv_attr!(@LateContext, ());
- };
- (EarlyContext) => {
- extract_msrv_attr!(@EarlyContext);
- };
- (@$context:ident$(, $call:tt)?) => {
- fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
- use $crate::utils::get_unique_inner_attr;
- match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
- Some(msrv_attr) => {
- if let Some(msrv) = msrv_attr.value_str() {
- self.msrv = $crate::utils::parse_msrv(
- &msrv.to_string(),
- Some(cx.sess$($call)?),
- Some(msrv_attr.span),
- );
- } else {
- cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
- }
- },
- _ => (),
- }
- }
- };
-}
-
-/// Returns `true` if the two spans come from differing expansions (i.e., one is
-/// from a macro and one isn't).
-#[must_use]
-pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
- rhs.ctxt() != lhs.ctxt()
-}
-
-/// Returns `true` if the given `NodeId` is inside a constant context
-///
-/// # Example
-///
-/// ```rust,ignore
-/// if in_constant(cx, expr.hir_id) {
-/// // Do something
-/// }
-/// ```
-pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
- let parent_id = cx.tcx.hir().get_parent_item(id);
- match cx.tcx.hir().get(parent_id) {
- Node::Item(&Item {
- kind: ItemKind::Const(..) | ItemKind::Static(..),
- ..
- })
- | Node::TraitItem(&TraitItem {
- kind: TraitItemKind::Const(..),
- ..
- })
- | Node::ImplItem(&ImplItem {
- kind: ImplItemKind::Const(..),
- ..
- })
- | Node::AnonConst(_) => true,
- Node::Item(&Item {
- kind: ItemKind::Fn(ref sig, ..),
- ..
- })
- | Node::ImplItem(&ImplItem {
- kind: ImplItemKind::Fn(ref sig, _),
- ..
- }) => sig.header.constness == Constness::Const,
- _ => false,
- }
-}
-
-/// Returns `true` if this `span` was expanded by any macro.
-#[must_use]
-pub fn in_macro(span: Span) -> bool {
- if span.from_expansion() {
- !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
- } else {
- false
- }
-}
-
-// If the snippet is empty, it's an attribute that was inserted during macro
-// expansion and we want to ignore those, because they could come from external
-// sources that the user has no control over.
-// For some reason these attributes don't have any expansion info on them, so
-// we have to check it this way until there is a better way.
-pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
- if let Some(snippet) = snippet_opt(cx, span) {
- if snippet.is_empty() {
- return false;
- }
- }
- true
-}
-
-/// Checks if given pattern is a wildcard (`_`)
-pub fn is_wild<'tcx>(pat: &impl std::ops::Deref<Target = Pat<'tcx>>) -> bool {
- matches!(pat.kind, PatKind::Wild)
-}
-
-/// Checks if type is struct, enum or union type with the given def path.
-///
-/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
-/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
-pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
- match ty.kind() {
- ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
- _ => false,
- }
-}
-
-/// Checks if the type is equal to a diagnostic item
-///
-/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
-pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
- match ty.kind() {
- ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
- _ => false,
- }
-}
-
-/// Checks if the type is equal to a lang item
-pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
- match ty.kind() {
- ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).unwrap() == adt.did,
- _ => false,
- }
-}
-
-/// Checks if the method call given in `expr` belongs to the given trait.
-pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
- let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
- let trt_id = cx.tcx.trait_of_item(def_id);
- trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
-}
-
-/// Checks if an expression references a variable of the given name.
-pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool {
- if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
- if let [p] = path.segments {
- return p.ident.name == var;
- }
- }
- false
-}
-
-pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
- match *path {
- QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"),
- QPath::TypeRelative(_, ref seg) => seg,
- QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
- }
-}
-
-pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
- match *path {
- QPath::Resolved(_, ref path) => path.segments.get(0),
- QPath::TypeRelative(_, ref seg) => Some(seg),
- QPath::LangItem(..) => None,
- }
-}
-
-/// Matches a `QPath` against a slice of segment string literals.
-///
-/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
-/// `rustc_hir::QPath`.
-///
-/// # Examples
-/// ```rust,ignore
-/// match_qpath(path, &["std", "rt", "begin_unwind"])
-/// ```
-pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
- match *path {
- QPath::Resolved(_, ref path) => match_path(path, segments),
- QPath::TypeRelative(ref ty, ref segment) => match ty.kind {
- TyKind::Path(ref inner_path) => {
- if let [prefix @ .., end] = segments {
- if match_qpath(inner_path, prefix) {
- return segment.ident.name.as_str() == *end;
- }
- }
- false
- },
- _ => false,
- },
- QPath::LangItem(..) => false,
- }
-}
-
-/// Matches a `Path` against a slice of segment string literals.
-///
-/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
-/// `rustc_hir::Path`.
-///
-/// # Examples
-///
-/// ```rust,ignore
-/// if match_path(&trait_ref.path, &paths::HASH) {
-/// // This is the `std::hash::Hash` trait.
-/// }
-///
-/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
-/// // This is a `rustc_middle::lint::Lint`.
-/// }
-/// ```
-pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
- path.segments
- .iter()
- .rev()
- .zip(segments.iter().rev())
- .all(|(a, b)| a.ident.name.as_str() == *b)
-}
-
-/// Matches a `Path` against a slice of segment string literals, e.g.
-///
-/// # Examples
-/// ```rust,ignore
-/// match_path_ast(path, &["std", "rt", "begin_unwind"])
-/// ```
-pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
- path.segments
- .iter()
- .rev()
- .zip(segments.iter().rev())
- .all(|(a, b)| a.ident.name.as_str() == *b)
-}
-
-/// If the expression is a path to a local, returns the canonical `HirId` of the local.
-pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
- if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
- if let Res::Local(id) = path.res {
- return Some(id);
- }
- }
- None
-}
-
-/// Returns true if the expression is a path to a local with the specified `HirId`.
-/// Use this function to see if an expression matches a function argument or a match binding.
-pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
- path_to_local(expr) == Some(id)
-}
-
-/// Gets the definition associated to a path.
-#[allow(clippy::shadow_unrelated)] // false positive #6563
-pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
- macro_rules! try_res {
- ($e:expr) => {
- match $e {
- Some(e) => e,
- None => return Res::Err,
- }
- };
- }
- fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export<HirId>> {
- tcx.item_children(def_id)
- .iter()
- .find(|item| item.ident.name.as_str() == name)
- }
-
- let (krate, first, path) = match *path {
- [krate, first, ref path @ ..] => (krate, first, path),
- _ => return Res::Err,
- };
- let tcx = cx.tcx;
- let crates = tcx.crates();
- let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate));
- let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first));
- let last = path
- .iter()
- .copied()
- // `get_def_path` seems to generate these empty segments for extern blocks.
- // We can just ignore them.
- .filter(|segment| !segment.is_empty())
- // for each segment, find the child item
- .try_fold(first, |item, segment| {
- let def_id = item.res.def_id();
- if let Some(item) = item_child_by_name(tcx, def_id, segment) {
- Some(item)
- } else if matches!(item.res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
- // it is not a child item so check inherent impl items
- tcx.inherent_impls(def_id)
- .iter()
- .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment))
- } else {
- None
- }
- });
- try_res!(last).res
-}
-
-/// Convenience function to get the `DefId` of a trait by path.
-/// It could be a trait or trait alias.
-pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
- match path_to_res(cx, path) {
- Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
- _ => None,
- }
-}
-
-/// Checks whether a type implements a trait.
-/// See also `get_trait_def_id`.
-pub fn implements_trait<'tcx>(
- cx: &LateContext<'tcx>,
- ty: Ty<'tcx>,
- trait_id: DefId,
- ty_params: &[GenericArg<'tcx>],
-) -> bool {
- // Do not check on infer_types to avoid panic in evaluate_obligation.
- if ty.has_infer_types() {
- return false;
- }
- let ty = cx.tcx.erase_regions(ty);
- if ty.has_escaping_bound_vars() {
- return false;
- }
- let ty_params = cx.tcx.mk_substs(ty_params.iter());
- cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
-}
-
-/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
-///
-/// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
-///
-/// ```rust
-/// struct Point(isize, isize);
-///
-/// impl std::ops::Add for Point {
-/// type Output = Self;
-///
-/// fn add(self, other: Self) -> Self {
-/// Point(0, 0)
-/// }
-/// }
-/// ```
-pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> {
- // Get the implemented trait for the current function
- let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
- if_chain! {
- if parent_impl != hir::CRATE_HIR_ID;
- if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl);
- if let hir::ItemKind::Impl(impl_) = &item.kind;
- then { return impl_.of_trait.as_ref(); }
- }
- None
-}
-
-/// Checks whether this type implements `Drop`.
-pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- match ty.ty_adt_def() {
- Some(def) => def.has_dtor(cx.tcx),
- None => false,
- }
-}
-
-/// Returns the method names and argument list of nested method call expressions that make up
-/// `expr`. method/span lists are sorted with the most recent call first.
-pub fn method_calls<'tcx>(
- expr: &'tcx Expr<'tcx>,
- max_depth: usize,
-) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
- let mut method_names = Vec::with_capacity(max_depth);
- let mut arg_lists = Vec::with_capacity(max_depth);
- let mut spans = Vec::with_capacity(max_depth);
-
- let mut current = expr;
- for _ in 0..max_depth {
- if let ExprKind::MethodCall(path, span, args, _) = ¤t.kind {
- if args.iter().any(|e| e.span.from_expansion()) {
- break;
- }
- method_names.push(path.ident.name);
- arg_lists.push(&**args);
- spans.push(*span);
- current = &args[0];
- } else {
- break;
- }
- }
-
- (method_names, arg_lists, spans)
-}
-
-/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
-///
-/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
-/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
-/// containing the `Expr`s for
-/// `.bar()` and `.baz()`
-pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {
- let mut current = expr;
- let mut matched = Vec::with_capacity(methods.len());
- for method_name in methods.iter().rev() {
- // method chains are stored last -> first
- if let ExprKind::MethodCall(ref path, _, ref args, _) = current.kind {
- if path.ident.name.as_str() == *method_name {
- if args.iter().any(|e| e.span.from_expansion()) {
- return None;
- }
- matched.push(&**args); // build up `matched` backwards
- current = &args[0] // go to parent expression
- } else {
- return None;
- }
- } else {
- return None;
- }
- }
- // Reverse `matched` so that it is in the same order as `methods`.
- matched.reverse();
- Some(matched)
-}
-
-/// Returns `true` if the provided `def_id` is an entrypoint to a program.
-pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
- cx.tcx
- .entry_fn(LOCAL_CRATE)
- .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id())
-}
-
-/// Returns `true` if the expression is in the program's `#[panic_handler]`.
-pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
- let parent = cx.tcx.hir().get_parent_item(e.hir_id);
- let def_id = cx.tcx.hir().local_def_id(parent).to_def_id();
- Some(def_id) == cx.tcx.lang_items().panic_impl()
-}
-
-/// Gets the name of the item the expression is in, if available.
-pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
- let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
- match cx.tcx.hir().find(parent_id) {
- Some(
- Node::Item(Item { ident, .. })
- | Node::TraitItem(TraitItem { ident, .. })
- | Node::ImplItem(ImplItem { ident, .. }),
- ) => Some(ident.name),
- _ => None,
- }
-}
-
-/// Gets the name of a `Pat`, if any.
-pub fn get_pat_name(pat: &Pat<'_>) -> Option<Symbol> {
- match pat.kind {
- PatKind::Binding(.., ref spname, _) => Some(spname.name),
- PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
- PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
- _ => None,
- }
-}
-
-struct ContainsName {
- name: Symbol,
- result: bool,
-}
-
-impl<'tcx> Visitor<'tcx> for ContainsName {
- type Map = Map<'tcx>;
-
- fn visit_name(&mut self, _: Span, name: Symbol) {
- if self.name == name {
- self.result = true;
- }
- }
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-}
-
-/// Checks if an `Expr` contains a certain name.
-pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
- let mut cn = ContainsName { name, result: false };
- cn.visit_expr(expr);
- cn.result
-}
-
-/// Returns `true` if `expr` contains a return expression
-pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
- struct RetCallFinder {
- found: bool,
- }
-
- impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- if self.found {
- return;
- }
- if let hir::ExprKind::Ret(..) = &expr.kind {
- self.found = true;
- } else {
- hir::intravisit::walk_expr(self, expr);
- }
- }
-
- fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
- hir::intravisit::NestedVisitorMap::None
- }
- }
-
- let mut visitor = RetCallFinder { found: false };
- visitor.visit_expr(expr);
- visitor.found
-}
-
-struct FindMacroCalls<'a, 'b> {
- names: &'a [&'b str],
- result: Vec<Span>,
-}
-
-impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
- self.result.push(expr.span);
- }
- // and check sub-expressions
- intravisit::walk_expr(self, expr);
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-}
-
-/// Finds calls of the specified macros in a function body.
-pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
- let mut fmc = FindMacroCalls {
- names,
- result: Vec::new(),
- };
- fmc.visit_expr(&body.value);
- fmc.result
-}
-
-/// Converts a span to a code snippet if available, otherwise use default.
-///
-/// This is useful if you want to provide suggestions for your lint or more generally, if you want
-/// to convert a given `Span` to a `str`.
-///
-/// # Example
-/// ```rust,ignore
-/// snippet(cx, expr.span, "..")
-/// ```
-pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
- snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
-}
-
-/// Same as `snippet`, but it adapts the applicability level by following rules:
-///
-/// - Applicability level `Unspecified` will never be changed.
-/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
-/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
-/// `HasPlaceholders`
-pub fn snippet_with_applicability<'a, T: LintContext>(
- cx: &T,
- span: Span,
- default: &'a str,
- applicability: &mut Applicability,
-) -> Cow<'a, str> {
- if *applicability != Applicability::Unspecified && span.from_expansion() {
- *applicability = Applicability::MaybeIncorrect;
- }
- snippet_opt(cx, span).map_or_else(
- || {
- if *applicability == Applicability::MachineApplicable {
- *applicability = Applicability::HasPlaceholders;
- }
- Cow::Borrowed(default)
- },
- From::from,
- )
-}
-
-/// Same as `snippet`, but should only be used when it's clear that the input span is
-/// not a macro argument.
-pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
- snippet(cx, span.source_callsite(), default)
-}
-
-/// Converts a span to a code snippet. Returns `None` if not available.
-pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
- cx.sess().source_map().span_to_snippet(span).ok()
-}
-
-/// Converts a span (from a block) to a code snippet if available, otherwise use default.
-///
-/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
-/// things which need to be printed as such.
-///
-/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
-/// resulting snippet of the given span.
-///
-/// # Example
-///
-/// ```rust,ignore
-/// snippet_block(cx, block.span, "..", None)
-/// // where, `block` is the block of the if expr
-/// if x {
-/// y;
-/// }
-/// // will return the snippet
-/// {
-/// y;
-/// }
-/// ```
-///
-/// ```rust,ignore
-/// snippet_block(cx, block.span, "..", Some(if_expr.span))
-/// // where, `block` is the block of the if expr
-/// if x {
-/// y;
-/// }
-/// // will return the snippet
-/// {
-/// y;
-/// } // aligned with `if`
-/// ```
-/// Note that the first line of the snippet always has 0 indentation.
-pub fn snippet_block<'a, T: LintContext>(
- cx: &T,
- span: Span,
- default: &'a str,
- indent_relative_to: Option<Span>,
-) -> Cow<'a, str> {
- let snip = snippet(cx, span, default);
- let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
- reindent_multiline(snip, true, indent)
-}
-
-/// Same as `snippet_block`, but adapts the applicability level by the rules of
-/// `snippet_with_applicability`.
-pub fn snippet_block_with_applicability<'a, T: LintContext>(
- cx: &T,
- span: Span,
- default: &'a str,
- indent_relative_to: Option<Span>,
- applicability: &mut Applicability,
-) -> Cow<'a, str> {
- let snip = snippet_with_applicability(cx, span, default, applicability);
- let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
- reindent_multiline(snip, true, indent)
-}
-
-/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
-/// line.
-///
-/// ```rust,ignore
-/// let x = ();
-/// // ^^
-/// // will be converted to
-/// let x = ();
-/// // ^^^^^^^^^^
-/// ```
-pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
- first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
-}
-
-fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
- let line_span = line_span(cx, span);
- snippet_opt(cx, line_span).and_then(|snip| {
- snip.find(|c: char| !c.is_whitespace())
- .map(|pos| line_span.lo() + BytePos::from_usize(pos))
- })
-}
-
-/// Returns the indentation of the line of a span
-///
-/// ```rust,ignore
-/// let x = ();
-/// // ^^ -- will return 0
-/// let x = ();
-/// // ^^ -- will return 4
-/// ```
-pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
- snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
-}
-
-/// Returns the positon just before rarrow
-///
-/// ```rust,ignore
-/// fn into(self) -> () {}
-/// ^
-/// // in case of unformatted code
-/// fn into2(self)-> () {}
-/// ^
-/// fn into3(self) -> () {}
-/// ^
-/// ```
-pub fn position_before_rarrow(s: &str) -> Option<usize> {
- s.rfind("->").map(|rpos| {
- let mut rpos = rpos;
- let chars: Vec<char> = s.chars().collect();
- while rpos > 1 {
- if let Some(c) = chars.get(rpos - 1) {
- if c.is_whitespace() {
- rpos -= 1;
- continue;
- }
- }
- break;
- }
- rpos
- })
-}
-
-/// Extends the span to the beginning of the spans line, incl. whitespaces.
-///
-/// ```rust,ignore
-/// let x = ();
-/// // ^^
-/// // will be converted to
-/// let x = ();
-/// // ^^^^^^^^^^^^^^
-/// ```
-fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
- let span = original_sp(span, DUMMY_SP);
- let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
- let line_no = source_map_and_line.line;
- let line_start = source_map_and_line.sf.lines[line_no];
- Span::new(line_start, span.hi(), span.ctxt())
-}
-
-/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
-/// Also takes an `Option<String>` which can be put inside the braces.
-pub fn expr_block<'a, T: LintContext>(
- cx: &T,
- expr: &Expr<'_>,
- option: Option<String>,
- default: &'a str,
- indent_relative_to: Option<Span>,
-) -> Cow<'a, str> {
- let code = snippet_block(cx, expr.span, default, indent_relative_to);
- let string = option.unwrap_or_default();
- if expr.span.from_expansion() {
- Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
- } else if let ExprKind::Block(_, _) = expr.kind {
- Cow::Owned(format!("{}{}", code, string))
- } else if string.is_empty() {
- Cow::Owned(format!("{{ {} }}", code))
- } else {
- Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
- }
-}
-
-/// Reindent a multiline string with possibility of ignoring the first line.
-#[allow(clippy::needless_pass_by_value)]
-pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
- let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
- let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
- reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
-}
-
-fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
- let x = s
- .lines()
- .skip(ignore_first as usize)
- .filter_map(|l| {
- if l.is_empty() {
- None
- } else {
- // ignore empty lines
- Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
- }
- })
- .min()
- .unwrap_or(0);
- let indent = indent.unwrap_or(0);
- s.lines()
- .enumerate()
- .map(|(i, l)| {
- if (ignore_first && i == 0) || l.is_empty() {
- l.to_owned()
- } else if x > indent {
- l.split_at(x - indent).1.to_owned()
- } else {
- " ".repeat(indent - x) + l
- }
- })
- .collect::<Vec<String>>()
- .join("\n")
-}
-
-/// Gets the parent expression, if any –- this is useful to constrain a lint.
-pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
- let map = &cx.tcx.hir();
- let hir_id = e.hir_id;
- let parent_id = map.get_parent_node(hir_id);
- if hir_id == parent_id {
- return None;
- }
- map.find(parent_id).and_then(|node| {
- if let Node::Expr(parent) = node {
- Some(parent)
- } else {
- None
- }
- })
-}
-
-pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
- let map = &cx.tcx.hir();
- let enclosing_node = map
- .get_enclosing_scope(hir_id)
- .and_then(|enclosing_id| map.find(enclosing_id));
- enclosing_node.and_then(|node| match node {
- Node::Block(block) => Some(block),
- Node::Item(&Item {
- kind: ItemKind::Fn(_, _, eid),
- ..
- })
- | Node::ImplItem(&ImplItem {
- kind: ImplItemKind::Fn(_, eid),
- ..
- }) => match cx.tcx.hir().body(eid).value.kind {
- ExprKind::Block(ref block, _) => Some(block),
- _ => None,
- },
- _ => None,
- })
-}
-
-/// Returns the base type for HIR references and pointers.
-pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
- match ty.kind {
- TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty),
- _ => ty,
- }
-}
-
-/// Returns the base type for references and raw pointers, and count reference
-/// depth.
-pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
- fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
- match ty.kind() {
- ty::Ref(_, ty, _) => inner(ty, depth + 1),
- _ => (ty, depth),
- }
- }
- inner(ty, 0)
-}
-
-/// Checks whether the given expression is a constant integer of the given value.
-/// unlike `is_integer_literal`, this version does const folding
-pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
- if is_integer_literal(e, value) {
- return true;
- }
- let map = cx.tcx.hir();
- let parent_item = map.get_parent_item(e.hir_id);
- if let Some((Constant::Int(v), _)) = map
- .maybe_body_owned_by(parent_item)
- .and_then(|body_id| constant(cx, cx.tcx.typeck_body(body_id), e))
- {
- value == v
- } else {
- false
- }
-}
-
-/// Checks whether the given expression is a constant literal of the given value.
-pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
- // FIXME: use constant folding
- if let ExprKind::Lit(ref spanned) = expr.kind {
- if let LitKind::Int(v, _) = spanned.node {
- return v == value;
- }
- }
- false
-}
-
-/// Returns `true` if the given `Expr` has been coerced before.
-///
-/// Examples of coercions can be found in the Nomicon at
-/// <https://doc.rust-lang.org/nomicon/coercions.html>.
-///
-/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more
-/// information on adjustments and coercions.
-pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
- cx.typeck_results().adjustments().get(e.hir_id).is_some()
-}
-
-/// Returns the pre-expansion span if is this comes from an expansion of the
-/// macro `name`.
-/// See also `is_direct_expn_of`.
-#[must_use]
-pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
- loop {
- if span.from_expansion() {
- let data = span.ctxt().outer_expn_data();
- let new_span = data.call_site;
-
- if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
- if mac_name.as_str() == name {
- return Some(new_span);
- }
- }
-
- span = new_span;
- } else {
- return None;
- }
- }
-}
-
-/// Returns the pre-expansion span if the span directly comes from an expansion
-/// of the macro `name`.
-/// The difference with `is_expn_of` is that in
-/// ```rust,ignore
-/// foo!(bar!(42));
-/// ```
-/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
-/// `bar!` by
-/// `is_direct_expn_of`.
-#[must_use]
-pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
- if span.from_expansion() {
- let data = span.ctxt().outer_expn_data();
- let new_span = data.call_site;
-
- if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
- if mac_name.as_str() == name {
- return Some(new_span);
- }
- }
- }
-
- None
-}
-
-/// Convenience function to get the return type of a function.
-pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
- let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
- let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
- cx.tcx.erase_late_bound_regions(ret_ty)
-}
-
-/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
-pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool {
- ty.walk().any(|inner| match inner.unpack() {
- GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty),
- GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
- })
-}
-
-/// Returns `true` if the given type is an `unsafe` function.
-pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- match ty.kind() {
- ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe,
- _ => false,
- }
-}
-
-pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env)
-}
-
-/// Checks if an expression is constructing a tuple-like enum variant or struct
-pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
- if let ExprKind::Call(ref fun, _) = expr.kind {
- if let ExprKind::Path(ref qp) = fun.kind {
- let res = cx.qpath_res(qp, fun.hir_id);
- return match res {
- def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
- def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
- _ => false,
- };
- }
- }
- false
-}
-
-/// Returns `true` if a pattern is refutable.
-// TODO: should be implemented using rustc/mir_build/thir machinery
-pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
- fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
- matches!(
- cx.qpath_res(qpath, id),
- def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
- )
- }
-
- fn are_refutable<'a, I: Iterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, mut i: I) -> bool {
- i.any(|pat| is_refutable(cx, pat))
- }
-
- match pat.kind {
- PatKind::Wild => false,
- PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
- PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat),
- PatKind::Lit(..) | PatKind::Range(..) => true,
- PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
- PatKind::Or(ref pats) => {
- // TODO: should be the honest check, that pats is exhaustive set
- are_refutable(cx, pats.iter().map(|pat| &**pat))
- },
- PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
- PatKind::Struct(ref qpath, ref fields, _) => {
- is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
- },
- PatKind::TupleStruct(ref qpath, ref pats, _) => {
- is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat))
- },
- PatKind::Slice(ref head, ref middle, ref tail) => {
- match &cx.typeck_results().node_type(pat.hir_id).kind() {
- ty::Slice(..) => {
- // [..] is the only irrefutable slice pattern.
- !head.is_empty() || middle.is_none() || !tail.is_empty()
- },
- ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)),
- _ => {
- // unreachable!()
- true
- },
- }
- },
- }
-}
-
-/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
-/// implementations have.
-pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
- attrs.iter().any(|attr| attr.has_name(sym::automatically_derived))
-}
-
-/// Remove blocks around an expression.
-///
-/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return
-/// themselves.
-pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
- while let ExprKind::Block(ref block, ..) = expr.kind {
- match (block.stmts.is_empty(), block.expr.as_ref()) {
- (true, Some(e)) => expr = e,
- _ => break,
- }
- }
- expr
-}
-
-pub fn is_self(slf: &Param<'_>) -> bool {
- if let PatKind::Binding(.., name, _) = slf.pat.kind {
- name.name == kw::SelfLower
- } else {
- false
- }
-}
-
-pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
- if_chain! {
- if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind;
- if let Res::SelfTy(..) = path.res;
- then {
- return true
- }
- }
- false
-}
-
-pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
- (0..decl.inputs.len()).map(move |i| &body.params[i])
-}
-
-/// Checks if a given expression is a match expression expanded from the `?`
-/// operator or the `try` macro.
-pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
- fn is_ok(arm: &Arm<'_>) -> bool {
- if_chain! {
- if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind;
- if match_qpath(path, &paths::RESULT_OK[1..]);
- if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
- if path_to_local_id(arm.body, hir_id);
- then {
- return true;
- }
- }
- false
- }
-
- fn is_err(arm: &Arm<'_>) -> bool {
- if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
- match_qpath(path, &paths::RESULT_ERR[1..])
- } else {
- false
- }
- }
-
- if let ExprKind::Match(_, ref arms, ref source) = expr.kind {
- // desugared from a `?` operator
- if let MatchSource::TryDesugar = *source {
- return Some(expr);
- }
-
- if_chain! {
- if arms.len() == 2;
- if arms[0].guard.is_none();
- if arms[1].guard.is_none();
- if (is_ok(&arms[0]) && is_err(&arms[1])) ||
- (is_ok(&arms[1]) && is_err(&arms[0]));
- then {
- return Some(expr);
- }
- }
- }
-
- None
-}
-
-/// Returns `true` if the lint is allowed in the current context
-///
-/// Useful for skipping long running code when it's unnecessary
-pub fn is_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
- cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
-}
-
-pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
- while let PatKind::Ref(subpat, _) = pat.kind {
- pat = subpat;
- }
- pat
-}
-
-pub fn int_bits(tcx: TyCtxt<'_>, ity: ty::IntTy) -> u64 {
- Integer::from_int_ty(&tcx, ity).size().bits()
-}
-
-#[allow(clippy::cast_possible_wrap)]
-/// Turn a constant int byte representation into an i128
-pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ty::IntTy) -> i128 {
- let amt = 128 - int_bits(tcx, ity);
- ((u as i128) << amt) >> amt
-}
-
-#[allow(clippy::cast_sign_loss)]
-/// clip unused bytes
-pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ty::IntTy) -> u128 {
- let amt = 128 - int_bits(tcx, ity);
- ((u as u128) << amt) >> amt
-}
-
-/// clip unused bytes
-pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: ty::UintTy) -> u128 {
- let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
- let amt = 128 - bits;
- (u << amt) >> amt
-}
-
-/// Removes block comments from the given `Vec` of lines.
-///
-/// # Examples
-///
-/// ```rust,ignore
-/// without_block_comments(vec!["/*", "foo", "*/"]);
-/// // => vec![]
-///
-/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
-/// // => vec!["bar"]
-/// ```
-pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
- let mut without = vec![];
-
- let mut nest_level = 0;
-
- for line in lines {
- if line.contains("/*") {
- nest_level += 1;
- continue;
- } else if line.contains("*/") {
- nest_level -= 1;
- continue;
- }
-
- if nest_level == 0 {
- without.push(line);
- }
- }
-
- without
-}
-
-pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
- let map = &tcx.hir();
- let mut prev_enclosing_node = None;
- let mut enclosing_node = node;
- while Some(enclosing_node) != prev_enclosing_node {
- if is_automatically_derived(map.attrs(enclosing_node)) {
- return true;
- }
- prev_enclosing_node = Some(enclosing_node);
- enclosing_node = map.get_parent_item(enclosing_node);
- }
- false
-}
-
-/// Returns true if ty has `iter` or `iter_mut` methods
-pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> {
- // FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
- // exists and has the desired signature. Unfortunately FnCtxt is not exported
- // so we can't use its `lookup_method` method.
- let into_iter_collections: [&[&str]; 13] = [
- &paths::VEC,
- &paths::OPTION,
- &paths::RESULT,
- &paths::BTREESET,
- &paths::BTREEMAP,
- &paths::VEC_DEQUE,
- &paths::LINKED_LIST,
- &paths::BINARY_HEAP,
- &paths::HASHSET,
- &paths::HASHMAP,
- &paths::PATH_BUF,
- &paths::PATH,
- &paths::RECEIVER,
- ];
-
- let ty_to_check = match probably_ref_ty.kind() {
- ty::Ref(_, ty_to_check, _) => ty_to_check,
- _ => probably_ref_ty,
- };
-
- let def_id = match ty_to_check.kind() {
- ty::Array(..) => return Some("array"),
- ty::Slice(..) => return Some("slice"),
- ty::Adt(adt, _) => adt.did,
- _ => return None,
- };
-
- for path in &into_iter_collections {
- if match_def_path(cx, def_id, path) {
- return Some(*path.last().unwrap());
- }
- }
- None
-}
-
-/// Matches a function call with the given path and returns the arguments.
-///
-/// Usage:
-///
-/// ```rust,ignore
-/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
-/// ```
-pub fn match_function_call<'tcx>(
- cx: &LateContext<'tcx>,
- expr: &'tcx Expr<'_>,
- path: &[&str],
-) -> Option<&'tcx [Expr<'tcx>]> {
- if_chain! {
- if let ExprKind::Call(ref fun, ref args) = expr.kind;
- if let ExprKind::Path(ref qpath) = fun.kind;
- if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
- if match_def_path(cx, fun_def_id, path);
- then {
- return Some(&args)
- }
- };
- None
-}
-
-/// Checks if `Ty` is normalizable. This function is useful
-/// to avoid crashes on `layout_of`.
-pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
- cx.tcx.infer_ctxt().enter(|infcx| {
- let cause = rustc_middle::traits::ObligationCause::dummy();
- infcx.at(&cause, param_env).normalize(ty).is_ok()
- })
-}
-
-pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
- // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path`
- // accepts only that. We should probably move to Symbols in Clippy as well.
- let syms = syms.iter().map(|p| Symbol::intern(p)).collect::<Vec<Symbol>>();
- cx.match_def_path(did, &syms)
-}
-
-pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> {
- match_function_call(cx, expr, &paths::BEGIN_PANIC)
- .or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT))
- .or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY))
- .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC))
- .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT))
- .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR))
-}
-
-pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
- match_def_path(cx, did, &paths::BEGIN_PANIC)
- || match_def_path(cx, did, &paths::BEGIN_PANIC_FMT)
- || match_def_path(cx, did, &paths::PANIC_ANY)
- || match_def_path(cx, did, &paths::PANICKING_PANIC)
- || match_def_path(cx, did, &paths::PANICKING_PANIC_FMT)
- || match_def_path(cx, did, &paths::PANICKING_PANIC_STR)
-}
-
-/// Returns the list of condition expressions and the list of blocks in a
-/// sequence of `if/else`.
-/// E.g., this returns `([a, b], [c, d, e])` for the expression
-/// `if a { c } else if b { d } else { e }`.
-pub fn if_sequence<'tcx>(
- mut expr: &'tcx Expr<'tcx>,
-) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) {
- let mut conds = SmallVec::new();
- let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new();
-
- while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind {
- conds.push(&**cond);
- if let ExprKind::Block(ref block, _) = then_expr.kind {
- blocks.push(block);
- } else {
- panic!("ExprKind::If node is not an ExprKind::Block");
- }
-
- if let Some(ref else_expr) = *else_expr {
- expr = else_expr;
- } else {
- break;
- }
- }
-
- // final `else {..}`
- if !blocks.is_empty() {
- if let ExprKind::Block(ref block, _) = expr.kind {
- blocks.push(&**block);
- }
- }
-
- (conds, blocks)
-}
-
-pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
- let map = cx.tcx.hir();
- let parent_id = map.get_parent_node(expr.hir_id);
- let parent_node = map.get(parent_id);
- matches!(
- parent_node,
- Node::Expr(Expr {
- kind: ExprKind::If(_, _, _),
- ..
- })
- )
-}
-
-// Finds the attribute with the given name, if any
-pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> {
- attrs
- .iter()
- .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name))
-}
-
-// Finds the `#[must_use]` attribute, if any
-pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
- attr_by_name(attrs, "must_use")
-}
-
-// Returns whether the type has #[must_use] attribute
-pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- match ty.kind() {
- ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(),
- ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(),
- ty::Slice(ref ty)
- | ty::Array(ref ty, _)
- | ty::RawPtr(ty::TypeAndMut { ref ty, .. })
- | ty::Ref(_, ref ty, _) => {
- // for the Array case we don't need to care for the len == 0 case
- // because we don't want to lint functions returning empty arrays
- is_must_use_ty(cx, *ty)
- },
- ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)),
- ty::Opaque(ref def_id, _) => {
- for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
- if let ty::PredicateKind::Trait(trait_predicate, _) = predicate.kind().skip_binder() {
- if must_use_attr(&cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() {
- return true;
- }
- }
- }
- false
- },
- ty::Dynamic(binder, _) => {
- for predicate in binder.iter() {
- if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
- if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
- return true;
- }
- }
- }
- false
- },
- _ => false,
- }
-}
-
-// check if expr is calling method or function with #[must_use] attribute
-pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
- let did = match expr.kind {
- ExprKind::Call(ref path, _) => if_chain! {
- if let ExprKind::Path(ref qpath) = path.kind;
- if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id);
- then {
- Some(did)
- } else {
- None
- }
- },
- ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
- _ => None,
- };
-
- did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
-}
-
-pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
- krate.item.attrs.iter().any(|attr| {
- if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
- attr.path == sym::no_std
- } else {
- false
- }
- })
-}
-
-/// Check if parent of a hir node is a trait implementation block.
-/// For example, `f` in
-/// ```rust,ignore
-/// impl Trait for S {
-/// fn f() {}
-/// }
-/// ```
-pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
- if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
- matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
- } else {
- false
- }
-}
-
-/// Check if it's even possible to satisfy the `where` clause for the item.
-///
-/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example:
-///
-/// ```ignore
-/// fn foo() where i32: Iterator {
-/// for _ in 2i32 {}
-/// }
-/// ```
-pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
- use rustc_trait_selection::traits;
- let predicates =
- cx.tcx
- .predicates_of(did)
- .predicates
- .iter()
- .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
- traits::impossible_predicates(
- cx.tcx,
- traits::elaborate_predicates(cx.tcx, predicates)
- .map(|o| o.predicate)
- .collect::<Vec<_>>(),
- )
-}
-
-/// Returns the `DefId` of the callee if the given expression is a function or method call.
-pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
- match &expr.kind {
- ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
- ExprKind::Call(
- Expr {
- kind: ExprKind::Path(qpath),
- hir_id: path_hir_id,
- ..
- },
- ..,
- ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(),
- _ => None,
- }
-}
-
-pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool {
- lints.iter().any(|lint| {
- matches!(
- cx.tcx.lint_level_at_node(lint, id),
- (Level::Forbid | Level::Deny | Level::Warn, _)
- )
- })
-}
-
-/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
-/// number type, a str, or an array, slice, or tuple of those types).
-pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
- match ty.kind() {
- ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
- ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true,
- ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
- ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type),
- _ => false,
- }
-}
-
-/// Returns Option<String> where String is a textual representation of the type encapsulated in the
-/// slice iff the given expression is a slice of primitives (as defined in the
-/// `is_recursively_primitive_type` function) and None otherwise.
-pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
- let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
- let expr_kind = expr_type.kind();
- let is_primitive = match expr_kind {
- ty::Slice(element_type) => is_recursively_primitive_type(element_type),
- ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &ty::Slice(_)) => {
- if let ty::Slice(element_type) = inner_ty.kind() {
- is_recursively_primitive_type(element_type)
- } else {
- unreachable!()
- }
- },
- _ => false,
- };
-
- if is_primitive {
- // if we have wrappers like Array, Slice or Tuple, print these
- // and get the type enclosed in the slice ref
- match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
- ty::Slice(..) => return Some("slice".into()),
- ty::Array(..) => return Some("array".into()),
- ty::Tuple(..) => return Some("tuple".into()),
- _ => {
- // is_recursively_primitive_type() should have taken care
- // of the rest and we can rely on the type that is found
- let refs_peeled = expr_type.peel_refs();
- return Some(refs_peeled.walk().last().unwrap().to_string());
- },
- }
- }
- None
-}
-
-/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
-/// `hash` must be comformed with `eq`
-pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
-where
- Hash: Fn(&T) -> u64,
- Eq: Fn(&T, &T) -> bool,
-{
- if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) {
- return vec![(&exprs[0], &exprs[1])];
- }
-
- let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
-
- let mut map: FxHashMap<_, Vec<&_>> =
- FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
-
- for expr in exprs {
- match map.entry(hash(expr)) {
- Entry::Occupied(mut o) => {
- for o in o.get() {
- if eq(o, expr) {
- match_expr_list.push((o, expr));
- }
- }
- o.get_mut().push(expr);
- },
- Entry::Vacant(v) => {
- v.insert(vec![expr]);
- },
- }
- }
-
- match_expr_list
-}
-
-/// Peels off all references on the pattern. Returns the underlying pattern and the number of
-/// references removed.
-pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
- fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
- if let PatKind::Ref(pat, _) = pat.kind {
- peel(pat, count + 1)
- } else {
- (pat, count)
- }
- }
- peel(pat, 0)
-}
-
-/// Peels off up to the given number of references on the expression. Returns the underlying
-/// expression and the number of references removed.
-pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
- fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
- match expr.kind {
- ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
- _ => (expr, count),
- }
- }
- f(expr, 0, count)
-}
-
-/// Peels off all references on the type. Returns the underlying type and the number of references
-/// removed.
-pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
- fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
- if let ty::Ref(_, ty, _) = ty.kind() {
- peel(ty, count + 1)
- } else {
- (ty, count)
- }
- }
- peel(ty, 0)
-}
-
-#[macro_export]
-macro_rules! unwrap_cargo_metadata {
- ($cx: ident, $lint: ident, $deps: expr) => {{
- let mut command = cargo_metadata::MetadataCommand::new();
- if !$deps {
- command.no_deps();
- }
-
- match command.exec() {
- Ok(metadata) => metadata,
- Err(err) => {
- span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err));
- return;
- },
- }
- }};
-}
-
-pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
- if_chain! {
- if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
- if let Res::Def(_, def_id) = path.res;
- then {
- cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr)
- } else {
- false
- }
- }
-}
-
-/// Check if the resolution of a given path is an `Ok` variant of `Result`.
-pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
- if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
- if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
- if let Some(variant_id) = cx.tcx.parent(id) {
- return variant_id == ok_id;
- }
- }
- }
- false
-}
-
-/// Check if the resolution of a given path is a `Some` variant of `Option`.
-pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
- if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
- if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
- if let Some(variant_id) = cx.tcx.parent(id) {
- return variant_id == some_id;
- }
- }
- }
- false
-}
-
-#[cfg(test)]
-mod test {
- use super::{reindent_multiline, without_block_comments};
-
- #[test]
- fn test_reindent_multiline_single_line() {
- assert_eq!("", reindent_multiline("".into(), false, None));
- assert_eq!("...", reindent_multiline("...".into(), false, None));
- assert_eq!("...", reindent_multiline(" ...".into(), false, None));
- assert_eq!("...", reindent_multiline("\t...".into(), false, None));
- assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
- }
-
- #[test]
- #[rustfmt::skip]
- fn test_reindent_multiline_block() {
- assert_eq!("\
- if x {
- y
- } else {
- z
- }", reindent_multiline(" if x {
- y
- } else {
- z
- }".into(), false, None));
- assert_eq!("\
- if x {
- \ty
- } else {
- \tz
- }", reindent_multiline(" if x {
- \ty
- } else {
- \tz
- }".into(), false, None));
- }
-
- #[test]
- #[rustfmt::skip]
- fn test_reindent_multiline_empty_line() {
- assert_eq!("\
- if x {
- y
-
- } else {
- z
- }", reindent_multiline(" if x {
- y
-
- } else {
- z
- }".into(), false, None));
- }
-
- #[test]
- #[rustfmt::skip]
- fn test_reindent_multiline_lines_deeper() {
- assert_eq!("\
- if x {
- y
- } else {
- z
- }", reindent_multiline("\
- if x {
- y
- } else {
- z
- }".into(), true, Some(8)));
- }
-
- #[test]
- fn test_without_block_comments_lines_without_block_comments() {
- let result = without_block_comments(vec!["/*", "", "*/"]);
- println!("result: {:?}", result);
- assert!(result.is_empty());
-
- let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
- assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
-
- let result = without_block_comments(vec!["/* rust", "", "*/"]);
- assert!(result.is_empty());
-
- let result = without_block_comments(vec!["/* one-line comment */"]);
- assert!(result.is_empty());
-
- let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
- assert!(result.is_empty());
-
- let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
- assert!(result.is_empty());
- let result = without_block_comments(vec!["foo", "bar", "baz"]);
- assert_eq!(result, vec!["foo", "bar", "baz"]);
- }
-}
+pub use clippy_utils::*;
+++ /dev/null
-use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
-
-#[derive(Debug, PartialEq, Copy, Clone)]
-pub enum Radix {
- Binary,
- Octal,
- Decimal,
- Hexadecimal,
-}
-
-impl Radix {
- /// Returns a reasonable digit group size for this radix.
- #[must_use]
- fn suggest_grouping(self) -> usize {
- match self {
- Self::Binary | Self::Hexadecimal => 4,
- Self::Octal | Self::Decimal => 3,
- }
- }
-}
-
-/// A helper method to format numeric literals with digit grouping.
-/// `lit` must be a valid numeric literal without suffix.
-pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
- NumericLiteral::new(lit, type_suffix, float).format()
-}
-
-#[derive(Debug)]
-pub struct NumericLiteral<'a> {
- /// Which radix the literal was represented in.
- pub radix: Radix,
- /// The radix prefix, if present.
- pub prefix: Option<&'a str>,
-
- /// The integer part of the number.
- pub integer: &'a str,
- /// The fraction part of the number.
- pub fraction: Option<&'a str>,
- /// The exponent separator (b'e' or b'E') including preceding underscore if present
- /// and the exponent part.
- pub exponent: Option<(&'a str, &'a str)>,
-
- /// The type suffix, including preceding underscore if present.
- pub suffix: Option<&'a str>,
-}
-
-impl<'a> NumericLiteral<'a> {
- pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
- NumericLiteral::from_lit_kind(src, &lit.kind)
- }
-
- pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
- if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
- let (unsuffixed, suffix) = split_suffix(&src, lit_kind);
- let float = matches!(lit_kind, LitKind::Float(..));
- Some(NumericLiteral::new(unsuffixed, suffix, float))
- } else {
- None
- }
- }
-
- #[must_use]
- pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
- // Determine delimiter for radix prefix, if present, and radix.
- let radix = if lit.starts_with("0x") {
- Radix::Hexadecimal
- } else if lit.starts_with("0b") {
- Radix::Binary
- } else if lit.starts_with("0o") {
- Radix::Octal
- } else {
- Radix::Decimal
- };
-
- // Grab part of the literal after prefix, if present.
- let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
- (None, lit)
- } else {
- let (p, s) = lit.split_at(2);
- (Some(p), s)
- };
-
- if suffix.is_some() && sans_prefix.ends_with('_') {
- // The '_' before the suffix isn't part of the digits
- sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
- }
-
- let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
-
- Self {
- radix,
- prefix,
- integer,
- fraction,
- exponent,
- suffix,
- }
- }
-
- pub fn is_decimal(&self) -> bool {
- self.radix == Radix::Decimal
- }
-
- pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) {
- let mut integer = digits;
- let mut fraction = None;
- let mut exponent = None;
-
- if float {
- for (i, c) in digits.char_indices() {
- match c {
- '.' => {
- integer = &digits[..i];
- fraction = Some(&digits[i + 1..]);
- },
- 'e' | 'E' => {
- let exp_start = if digits[..i].ends_with('_') { i - 1 } else { i };
-
- if integer.len() > exp_start {
- integer = &digits[..exp_start];
- } else {
- fraction = Some(&digits[integer.len() + 1..exp_start]);
- };
- exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
- break;
- },
- _ => {},
- }
- }
- }
-
- (integer, fraction, exponent)
- }
-
- /// Returns literal formatted in a sensible way.
- pub fn format(&self) -> String {
- let mut output = String::new();
-
- if let Some(prefix) = self.prefix {
- output.push_str(prefix);
- }
-
- let group_size = self.radix.suggest_grouping();
-
- Self::group_digits(
- &mut output,
- self.integer,
- group_size,
- true,
- self.radix == Radix::Hexadecimal,
- );
-
- if let Some(fraction) = self.fraction {
- output.push('.');
- Self::group_digits(&mut output, fraction, group_size, false, false);
- }
-
- if let Some((separator, exponent)) = self.exponent {
- output.push_str(separator);
- Self::group_digits(&mut output, exponent, group_size, true, false);
- }
-
- if let Some(suffix) = self.suffix {
- output.push('_');
- output.push_str(suffix);
- }
-
- output
- }
-
- pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
- debug_assert!(group_size > 0);
-
- let mut digits = input.chars().filter(|&c| c != '_');
-
- let first_group_size;
-
- if partial_group_first {
- first_group_size = (digits.clone().count() - 1) % group_size + 1;
- if pad {
- for _ in 0..group_size - first_group_size {
- output.push('0');
- }
- }
- } else {
- first_group_size = group_size;
- }
-
- for _ in 0..first_group_size {
- if let Some(digit) = digits.next() {
- output.push(digit);
- }
- }
-
- for (c, i) in digits.zip((0..group_size).cycle()) {
- if i == 0 {
- output.push('_');
- }
- output.push(c);
- }
- }
-}
-
-fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
- debug_assert!(lit_kind.is_numeric());
- lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
- let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
- (unsuffixed, Some(suffix))
- })
-}
-
-fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
- debug_assert!(lit_kind.is_numeric());
- let suffix = match lit_kind {
- LitKind::Int(_, int_lit_kind) => match int_lit_kind {
- LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
- LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
- LitIntType::Unsuffixed => None,
- },
- LitKind::Float(_, float_lit_kind) => match float_lit_kind {
- LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
- LitFloatType::Unsuffixed => None,
- },
- _ => None,
- };
-
- suffix.map(str::len)
-}
+++ /dev/null
-//! This module contains paths to types and functions Clippy needs to know
-//! about.
-//!
-//! Whenever possible, please consider diagnostic items over hardcoded paths.
-//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
-
-pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
-pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
-pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
-pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
-pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
-pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
-pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
-pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
-pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"];
-pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
-pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
-pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
-pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
-pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
-pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
-pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
-pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
-pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
-pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
-pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
-pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
-pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
-pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
-pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
-pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
-pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
-pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
-pub const DROP: [&str; 3] = ["core", "mem", "drop"];
-pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
-#[cfg(feature = "internal-lints")]
-pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
-pub const EXIT: [&str; 3] = ["std", "process", "exit"];
-pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
-pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
-pub const FILE: [&str; 3] = ["std", "fs", "File"];
-pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
-pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
-pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
-pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
-pub const FN: [&str; 3] = ["core", "ops", "Fn"];
-pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
-pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
-pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
-pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
-pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
-pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
-pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
-pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
-pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
-#[cfg(feature = "internal-lints")]
-pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
-#[cfg(feature = "internal-lints")]
-pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
-pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
-pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
-pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
-pub const INTO: [&str; 3] = ["core", "convert", "Into"];
-pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
-pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
-pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
-pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
-pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
-pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
-#[cfg(feature = "internal-lints")]
-pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
-#[cfg(feature = "internal-lints")]
-pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
-pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
-#[cfg(feature = "internal-lints")]
-pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
-pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
-pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
-pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
-pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
-pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
-pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
-pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
-pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
-pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
-pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
-pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
-pub const OPTION: [&str; 3] = ["core", "option", "Option"];
-pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
-pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
-pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
-pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
-pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
-pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
-pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
-pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
-pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
-pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
-pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
-pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
-pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
-pub const PATH: [&str; 3] = ["std", "path", "Path"];
-pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
-pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
-pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
-pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
-pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
-pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
-pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
-pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
-pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
-pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
-pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
-pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
-pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
-pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
-pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
-pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
-pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"];
-pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
-pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
-pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
-pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
-pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
-pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
-pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
-pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
-pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
-pub const RESULT: [&str; 3] = ["core", "result", "Result"];
-pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
-pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
-pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
-pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
-pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
-pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
-pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
-pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
-pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
-pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
-pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
-pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
-pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
-pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
-pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
-pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
-pub const STRING: [&str; 3] = ["alloc", "string", "String"];
-pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
-pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
-pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
-pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
-pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
-pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
-#[cfg(feature = "internal-lints")]
-pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
-#[cfg(feature = "internal-lints")]
-pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
-#[cfg(feature = "internal-lints")]
-pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
-#[cfg(feature = "internal-lints")]
-pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
-#[cfg(feature = "internal-lints")]
-pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
-#[cfg(feature = "internal-lints")]
-pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
-pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
-pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
-pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
-pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
-pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
-pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
-pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
-pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
-pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
-pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
-pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
-pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
-pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
-pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
-pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
-pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
-pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
+++ /dev/null
-use crate::utils::{get_pat_name, match_var, snippet};
-use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
-use rustc_hir::{Body, BodyId, Expr, ExprKind, Param};
-use rustc_lint::LateContext;
-use rustc_middle::hir::map::Map;
-use rustc_span::{Span, Symbol};
-use std::borrow::Cow;
-
-pub fn get_spans(
- cx: &LateContext<'_>,
- opt_body_id: Option<BodyId>,
- idx: usize,
- replacements: &[(&'static str, &'static str)],
-) -> Option<Vec<(Span, Cow<'static, str>)>> {
- if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
- get_binding_name(&body.params[idx]).map_or_else(
- || Some(vec![]),
- |name| extract_clone_suggestions(cx, name, replacements, body),
- )
- } else {
- Some(vec![])
- }
-}
-
-fn extract_clone_suggestions<'tcx>(
- cx: &LateContext<'tcx>,
- name: Symbol,
- replace: &[(&'static str, &'static str)],
- body: &'tcx Body<'_>,
-) -> Option<Vec<(Span, Cow<'static, str>)>> {
- let mut visitor = PtrCloneVisitor {
- cx,
- name,
- replace,
- spans: vec![],
- abort: false,
- };
- visitor.visit_body(body);
- if visitor.abort {
- None
- } else {
- Some(visitor.spans)
- }
-}
-
-struct PtrCloneVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- name: Symbol,
- replace: &'a [(&'static str, &'static str)],
- spans: Vec<(Span, Cow<'static, str>)>,
- abort: bool,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.abort {
- return;
- }
- if let ExprKind::MethodCall(ref seg, _, ref args, _) = expr.kind {
- if args.len() == 1 && match_var(&args[0], self.name) {
- if seg.ident.name.as_str() == "capacity" {
- self.abort = true;
- return;
- }
- for &(fn_name, suffix) in self.replace {
- if seg.ident.name.as_str() == fn_name {
- self.spans
- .push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
- return;
- }
- }
- }
- }
- walk_expr(self, expr);
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-}
-
-fn get_binding_name(arg: &Param<'_>) -> Option<Symbol> {
- get_pat_name(&arg.pat)
-}
+++ /dev/null
-use rustc_hir as hir;
-use rustc_hir::def_id::DefId;
-use rustc_middle::mir::{
- Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
- TerminatorKind,
-};
-use rustc_middle::ty::subst::GenericArgKind;
-use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
-use rustc_span::symbol::sym;
-use rustc_span::Span;
-use rustc_target::spec::abi::Abi::RustIntrinsic;
-use std::borrow::Cow;
-
-type McfResult = Result<(), (Span, Cow<'static, str>)>;
-
-pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
- let def_id = body.source.def_id();
- let mut current = def_id;
- loop {
- let predicates = tcx.predicates_of(current);
- for (predicate, _) in predicates.predicates {
- match predicate.kind().skip_binder() {
- ty::PredicateKind::RegionOutlives(_)
- | ty::PredicateKind::TypeOutlives(_)
- | ty::PredicateKind::WellFormed(_)
- | ty::PredicateKind::Projection(_)
- | ty::PredicateKind::ConstEvaluatable(..)
- | ty::PredicateKind::ConstEquate(..)
- | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
- ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
- ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
- ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
- ty::PredicateKind::Trait(pred, _) => {
- if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
- continue;
- }
- match pred.self_ty().kind() {
- ty::Param(ref p) => {
- let generics = tcx.generics_of(current);
- let def = generics.type_param(p, tcx);
- let span = tcx.def_span(def.def_id);
- return Err((
- span,
- "trait bounds other than `Sized` \
- on const fn parameters are unstable"
- .into(),
- ));
- },
- // other kinds of bounds are either tautologies
- // or cause errors in other passes
- _ => continue,
- }
- },
- }
- }
- match predicates.parent {
- Some(parent) => current = parent,
- None => break,
- }
- }
-
- for local in &body.local_decls {
- check_ty(tcx, local.ty, local.source_info.span)?;
- }
- // impl trait is gone in MIR, so check the return type manually
- check_ty(
- tcx,
- tcx.fn_sig(def_id).output().skip_binder(),
- body.local_decls.iter().next().unwrap().source_info.span,
- )?;
-
- for bb in body.basic_blocks() {
- check_terminator(tcx, body, bb.terminator())?;
- for stmt in &bb.statements {
- check_statement(tcx, body, def_id, stmt)?;
- }
- }
- Ok(())
-}
-
-fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
- for arg in ty.walk() {
- let ty = match arg.unpack() {
- GenericArgKind::Type(ty) => ty,
-
- // No constraints on lifetimes or constants, except potentially
- // constants' types, but `walk` will get to them as well.
- GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
- };
-
- match ty.kind() {
- ty::Ref(_, _, hir::Mutability::Mut) => {
- return Err((span, "mutable references in const fn are unstable".into()));
- },
- ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
- ty::FnPtr(..) => {
- return Err((span, "function pointers in const fn are unstable".into()));
- },
- ty::Dynamic(preds, _) => {
- for pred in preds.iter() {
- match pred.skip_binder() {
- ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
- return Err((
- span,
- "trait bounds other than `Sized` \
- on const fn parameters are unstable"
- .into(),
- ));
- },
- ty::ExistentialPredicate::Trait(trait_ref) => {
- if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
- return Err((
- span,
- "trait bounds other than `Sized` \
- on const fn parameters are unstable"
- .into(),
- ));
- }
- },
- }
- }
- },
- _ => {},
- }
- }
- Ok(())
-}
-
-fn check_rvalue(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span) -> McfResult {
- match rvalue {
- Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
- Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => check_operand(tcx, operand, span, body),
- Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
- check_place(tcx, *place, span, body)
- },
- Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
- use rustc_middle::ty::cast::CastTy;
- let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
- let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
- match (cast_in, cast_out) {
- (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
- Err((span, "casting pointers to ints is unstable in const fn".into()))
- },
- _ => check_operand(tcx, operand, span, body),
- }
- },
- Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _) => {
- check_operand(tcx, operand, span, body)
- },
- Rvalue::Cast(
- CastKind::Pointer(
- PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
- ),
- _,
- _,
- ) => Err((span, "function pointer casts are not allowed in const fn".into())),
- Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
- let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
- deref_ty.ty
- } else {
- // We cannot allow this for now.
- return Err((span, "unsizing casts are only allowed for references right now".into()));
- };
- let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
- if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
- check_operand(tcx, op, span, body)?;
- // Casting/coercing things to slices is fine.
- Ok(())
- } else {
- // We just can't allow trait objects until we have figured out trait method calls.
- Err((span, "unsizing casts are not allowed in const fn".into()))
- }
- },
- // binops are fine on integers
- Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
- check_operand(tcx, lhs, span, body)?;
- check_operand(tcx, rhs, span, body)?;
- let ty = lhs.ty(body, tcx);
- if ty.is_integral() || ty.is_bool() || ty.is_char() {
- Ok(())
- } else {
- Err((
- span,
- "only int, `bool` and `char` operations are stable in const fn".into(),
- ))
- }
- },
- Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
- Rvalue::NullaryOp(NullOp::Box, _) => Err((span, "heap allocations are not allowed in const fn".into())),
- Rvalue::UnaryOp(_, operand) => {
- let ty = operand.ty(body, tcx);
- if ty.is_integral() || ty.is_bool() {
- check_operand(tcx, operand, span, body)
- } else {
- Err((span, "only int and `bool` operations are stable in const fn".into()))
- }
- },
- Rvalue::Aggregate(_, operands) => {
- for operand in operands {
- check_operand(tcx, operand, span, body)?;
- }
- Ok(())
- },
- }
-}
-
-fn check_statement(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>) -> McfResult {
- let span = statement.source_info.span;
- match &statement.kind {
- StatementKind::Assign(box (place, rval)) => {
- check_place(tcx, *place, span, body)?;
- check_rvalue(tcx, body, def_id, rval, span)
- },
-
- StatementKind::FakeRead(_, place) |
- // just an assignment
- StatementKind::SetDiscriminant { place, .. } => check_place(tcx, **place, span, body),
-
- StatementKind::LlvmInlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
-
- // These are all NOPs
- StatementKind::StorageLive(_)
- | StatementKind::StorageDead(_)
- | StatementKind::Retag { .. }
- | StatementKind::AscribeUserType(..)
- | StatementKind::Coverage(..)
- | StatementKind::Nop => Ok(()),
- }
-}
-
-fn check_operand(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
- match operand {
- Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
- Operand::Constant(c) => match c.check_static_ptr(tcx) {
- Some(_) => Err((span, "cannot access `static` items in const fn".into())),
- None => Ok(()),
- },
- }
-}
-
-fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
- let mut cursor = place.projection.as_ref();
- while let [ref proj_base @ .., elem] = *cursor {
- cursor = proj_base;
- match elem {
- ProjectionElem::Field(..) => {
- let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
- if let Some(def) = base_ty.ty_adt_def() {
- // No union field accesses in `const fn`
- if def.is_union() {
- return Err((span, "accessing union fields is unstable".into()));
- }
- }
- },
- ProjectionElem::ConstantIndex { .. }
- | ProjectionElem::Downcast(..)
- | ProjectionElem::Subslice { .. }
- | ProjectionElem::Deref
- | ProjectionElem::Index(_) => {},
- }
- }
-
- Ok(())
-}
-
-fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult {
- let span = terminator.source_info.span;
- match &terminator.kind {
- TerminatorKind::FalseEdge { .. }
- | TerminatorKind::FalseUnwind { .. }
- | TerminatorKind::Goto { .. }
- | TerminatorKind::Return
- | TerminatorKind::Resume
- | TerminatorKind::Unreachable => Ok(()),
-
- TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
- TerminatorKind::DropAndReplace { place, value, .. } => {
- check_place(tcx, *place, span, body)?;
- check_operand(tcx, value, span, body)
- },
-
- TerminatorKind::SwitchInt {
- discr,
- switch_ty: _,
- targets: _,
- } => check_operand(tcx, discr, span, body),
-
- TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
- TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
- Err((span, "const fn generators are unstable".into()))
- },
-
- TerminatorKind::Call {
- func,
- args,
- from_hir_call: _,
- destination: _,
- cleanup: _,
- fn_span: _,
- } => {
- let fn_ty = func.ty(body, tcx);
- if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
- if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) {
- return Err((
- span,
- format!(
- "can only call other `const fn` within a `const fn`, \
- but `{:?}` is not stable as `const fn`",
- func,
- )
- .into(),
- ));
- }
-
- // HACK: This is to "unstabilize" the `transmute` intrinsic
- // within const fns. `transmute` is allowed in all other const contexts.
- // This won't really scale to more intrinsics or functions. Let's allow const
- // transmutes in const fn before we add more hacks to this.
- if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute {
- return Err((
- span,
- "can only call `transmute` from const items, not `const fn`".into(),
- ));
- }
-
- check_operand(tcx, func, span, body)?;
-
- for arg in args {
- check_operand(tcx, arg, span, body)?;
- }
- Ok(())
- } else {
- Err((span, "can only call other const fns within const fn".into()))
- }
- },
-
- TerminatorKind::Assert {
- cond,
- expected: _,
- msg: _,
- target: _,
- cleanup: _,
- } => check_operand(tcx, cond, span, body),
-
- TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
- }
-}
+++ /dev/null
-//! Contains utility functions to generate suggestions.
-#![deny(clippy::missing_docs_in_private_items)]
-
-use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
-use rustc_ast::util::parser::AssocOp;
-use rustc_ast::{ast, token};
-use rustc_ast_pretty::pprust::token_kind_to_string;
-use rustc_errors::Applicability;
-use rustc_hir as hir;
-use rustc_lint::{EarlyContext, LateContext, LintContext};
-use rustc_span::source_map::{CharPos, Span};
-use rustc_span::{BytePos, Pos};
-use std::borrow::Cow;
-use std::convert::TryInto;
-use std::fmt::Display;
-use std::ops::{Add, Neg, Not, Sub};
-
-/// A helper type to build suggestion correctly handling parenthesis.
-#[derive(Clone, PartialEq)]
-pub enum Sugg<'a> {
- /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
- NonParen(Cow<'a, str>),
- /// An expression that does not fit in other variants.
- MaybeParen(Cow<'a, str>),
- /// A binary operator expression, including `as`-casts and explicit type
- /// coercion.
- BinOp(AssocOp, Cow<'a, str>),
-}
-
-/// Literal constant `0`, for convenience.
-pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
-/// Literal constant `1`, for convenience.
-pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
-/// a constant represents an empty string, for convenience.
-pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
-
-impl Display for Sugg<'_> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
- match *self {
- Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
- }
- }
-}
-
-#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
-impl<'a> Sugg<'a> {
- /// Prepare a suggestion from an expression.
- pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
- snippet_opt(cx, expr.span).map(|snippet| {
- let snippet = Cow::Owned(snippet);
- Self::hir_from_snippet(expr, snippet)
- })
- }
-
- /// Convenience function around `hir_opt` for suggestions with a default
- /// text.
- pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
- Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
- }
-
- /// Same as `hir`, but it adapts the applicability level by following rules:
- ///
- /// - Applicability level `Unspecified` will never be changed.
- /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
- /// - If the default value is used and the applicability level is `MachineApplicable`, change it
- /// to
- /// `HasPlaceholders`
- pub fn hir_with_applicability(
- cx: &LateContext<'_>,
- expr: &hir::Expr<'_>,
- default: &'a str,
- applicability: &mut Applicability,
- ) -> Self {
- if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
- *applicability = Applicability::MaybeIncorrect;
- }
- Self::hir_opt(cx, expr).unwrap_or_else(|| {
- if *applicability == Applicability::MachineApplicable {
- *applicability = Applicability::HasPlaceholders;
- }
- Sugg::NonParen(Cow::Borrowed(default))
- })
- }
-
- /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
- pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
- let snippet = snippet_with_macro_callsite(cx, expr.span, default);
-
- Self::hir_from_snippet(expr, snippet)
- }
-
- /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
- /// function variants of `Sugg`, since these use different snippet functions.
- fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
- if let Some(range) = higher::range(expr) {
- let op = match range.limits {
- ast::RangeLimits::HalfOpen => AssocOp::DotDot,
- ast::RangeLimits::Closed => AssocOp::DotDotEq,
- };
- return Sugg::BinOp(op, snippet);
- }
-
- match expr.kind {
- hir::ExprKind::AddrOf(..)
- | hir::ExprKind::Box(..)
- | hir::ExprKind::If(..)
- | hir::ExprKind::Closure(..)
- | hir::ExprKind::Unary(..)
- | hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
- hir::ExprKind::Continue(..)
- | hir::ExprKind::Yield(..)
- | hir::ExprKind::Array(..)
- | hir::ExprKind::Block(..)
- | hir::ExprKind::Break(..)
- | hir::ExprKind::Call(..)
- | hir::ExprKind::Field(..)
- | hir::ExprKind::Index(..)
- | hir::ExprKind::InlineAsm(..)
- | hir::ExprKind::LlvmInlineAsm(..)
- | hir::ExprKind::ConstBlock(..)
- | hir::ExprKind::Lit(..)
- | hir::ExprKind::Loop(..)
- | hir::ExprKind::MethodCall(..)
- | hir::ExprKind::Path(..)
- | hir::ExprKind::Repeat(..)
- | hir::ExprKind::Ret(..)
- | hir::ExprKind::Struct(..)
- | hir::ExprKind::Tup(..)
- | hir::ExprKind::DropTemps(_)
- | hir::ExprKind::Err => Sugg::NonParen(snippet),
- hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
- hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
- hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
- hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
- hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
- }
- }
-
- /// Prepare a suggestion from an expression.
- pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
- use rustc_ast::ast::RangeLimits;
-
- let snippet = if expr.span.from_expansion() {
- snippet_with_macro_callsite(cx, expr.span, default)
- } else {
- snippet(cx, expr.span, default)
- };
-
- match expr.kind {
- ast::ExprKind::AddrOf(..)
- | ast::ExprKind::Box(..)
- | ast::ExprKind::Closure(..)
- | ast::ExprKind::If(..)
- | ast::ExprKind::Let(..)
- | ast::ExprKind::Unary(..)
- | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
- ast::ExprKind::Async(..)
- | ast::ExprKind::Block(..)
- | ast::ExprKind::Break(..)
- | ast::ExprKind::Call(..)
- | ast::ExprKind::Continue(..)
- | ast::ExprKind::Yield(..)
- | ast::ExprKind::Field(..)
- | ast::ExprKind::ForLoop(..)
- | ast::ExprKind::Index(..)
- | ast::ExprKind::InlineAsm(..)
- | ast::ExprKind::LlvmInlineAsm(..)
- | ast::ExprKind::ConstBlock(..)
- | ast::ExprKind::Lit(..)
- | ast::ExprKind::Loop(..)
- | ast::ExprKind::MacCall(..)
- | ast::ExprKind::MethodCall(..)
- | ast::ExprKind::Paren(..)
- | ast::ExprKind::Underscore
- | ast::ExprKind::Path(..)
- | ast::ExprKind::Repeat(..)
- | ast::ExprKind::Ret(..)
- | ast::ExprKind::Struct(..)
- | ast::ExprKind::Try(..)
- | ast::ExprKind::TryBlock(..)
- | ast::ExprKind::Tup(..)
- | ast::ExprKind::Array(..)
- | ast::ExprKind::While(..)
- | ast::ExprKind::Await(..)
- | ast::ExprKind::Err => Sugg::NonParen(snippet),
- ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
- ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
- ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
- ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
- ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
- ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
- ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
- }
- }
-
- /// Convenience method to create the `<lhs> && <rhs>` suggestion.
- pub fn and(self, rhs: &Self) -> Sugg<'static> {
- make_binop(ast::BinOpKind::And, &self, rhs)
- }
-
- /// Convenience method to create the `<lhs> & <rhs>` suggestion.
- pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
- make_binop(ast::BinOpKind::BitAnd, &self, rhs)
- }
-
- /// Convenience method to create the `<lhs> as <rhs>` suggestion.
- pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
- make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
- }
-
- /// Convenience method to create the `&<expr>` suggestion.
- pub fn addr(self) -> Sugg<'static> {
- make_unop("&", self)
- }
-
- /// Convenience method to create the `&mut <expr>` suggestion.
- pub fn mut_addr(self) -> Sugg<'static> {
- make_unop("&mut ", self)
- }
-
- /// Convenience method to create the `*<expr>` suggestion.
- pub fn deref(self) -> Sugg<'static> {
- make_unop("*", self)
- }
-
- /// Convenience method to create the `&*<expr>` suggestion. Currently this
- /// is needed because `sugg.deref().addr()` produces an unnecessary set of
- /// parentheses around the deref.
- pub fn addr_deref(self) -> Sugg<'static> {
- make_unop("&*", self)
- }
-
- /// Convenience method to create the `&mut *<expr>` suggestion. Currently
- /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
- /// set of parentheses around the deref.
- pub fn mut_addr_deref(self) -> Sugg<'static> {
- make_unop("&mut *", self)
- }
-
- /// Convenience method to transform suggestion into a return call
- pub fn make_return(self) -> Sugg<'static> {
- Sugg::NonParen(Cow::Owned(format!("return {}", self)))
- }
-
- /// Convenience method to transform suggestion into a block
- /// where the suggestion is a trailing expression
- pub fn blockify(self) -> Sugg<'static> {
- Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
- }
-
- /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
- /// suggestion.
- #[allow(dead_code)]
- pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
- match limit {
- ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
- ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
- }
- }
-
- /// Adds parenthesis to any expression that might need them. Suitable to the
- /// `self` argument of a method call
- /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
- pub fn maybe_par(self) -> Self {
- match self {
- Sugg::NonParen(..) => self,
- // `(x)` and `(x).y()` both don't need additional parens.
- Sugg::MaybeParen(sugg) => {
- if sugg.starts_with('(') && sugg.ends_with(')') {
- Sugg::MaybeParen(sugg)
- } else {
- Sugg::NonParen(format!("({})", sugg).into())
- }
- },
- Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
- }
- }
-}
-
-// Copied from the rust standart library, and then edited
-macro_rules! forward_binop_impls_to_ref {
- (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
- impl $imp<$t> for &$t {
- type Output = $o;
-
- fn $method(self, other: $t) -> $o {
- $imp::$method(self, &other)
- }
- }
-
- impl $imp<&$t> for $t {
- type Output = $o;
-
- fn $method(self, other: &$t) -> $o {
- $imp::$method(&self, other)
- }
- }
-
- impl $imp for $t {
- type Output = $o;
-
- fn $method(self, other: $t) -> $o {
- $imp::$method(&self, &other)
- }
- }
- };
-}
-
-impl Add for &Sugg<'_> {
- type Output = Sugg<'static>;
- fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
- make_binop(ast::BinOpKind::Add, self, rhs)
- }
-}
-
-impl Sub for &Sugg<'_> {
- type Output = Sugg<'static>;
- fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
- make_binop(ast::BinOpKind::Sub, self, rhs)
- }
-}
-
-forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
-forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
-
-impl Neg for Sugg<'_> {
- type Output = Sugg<'static>;
- fn neg(self) -> Sugg<'static> {
- make_unop("-", self)
- }
-}
-
-impl Not for Sugg<'_> {
- type Output = Sugg<'static>;
- fn not(self) -> Sugg<'static> {
- make_unop("!", self)
- }
-}
-
-/// Helper type to display either `foo` or `(foo)`.
-struct ParenHelper<T> {
- /// `true` if parentheses are needed.
- paren: bool,
- /// The main thing to display.
- wrapped: T,
-}
-
-impl<T> ParenHelper<T> {
- /// Builds a `ParenHelper`.
- fn new(paren: bool, wrapped: T) -> Self {
- Self { paren, wrapped }
- }
-}
-
-impl<T: Display> Display for ParenHelper<T> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
- if self.paren {
- write!(f, "({})", self.wrapped)
- } else {
- self.wrapped.fmt(f)
- }
- }
-}
-
-/// Builds the string for `<op><expr>` adding parenthesis when necessary.
-///
-/// For convenience, the operator is taken as a string because all unary
-/// operators have the same
-/// precedence.
-pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
- Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
-}
-
-/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
-///
-/// Precedence of shift operator relative to other arithmetic operation is
-/// often confusing so
-/// parenthesis will always be added for a mix of these.
-pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
- /// Returns `true` if the operator is a shift operator `<<` or `>>`.
- fn is_shift(op: AssocOp) -> bool {
- matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
- }
-
- /// Returns `true` if the operator is a arithmetic operator
- /// (i.e., `+`, `-`, `*`, `/`, `%`).
- fn is_arith(op: AssocOp) -> bool {
- matches!(
- op,
- AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
- )
- }
-
- /// Returns `true` if the operator `op` needs parenthesis with the operator
- /// `other` in the direction `dir`.
- fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
- other.precedence() < op.precedence()
- || (other.precedence() == op.precedence()
- && ((op != other && associativity(op) != dir)
- || (op == other && associativity(op) != Associativity::Both)))
- || is_shift(op) && is_arith(other)
- || is_shift(other) && is_arith(op)
- }
-
- let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
- needs_paren(op, lop, Associativity::Left)
- } else {
- false
- };
-
- let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
- needs_paren(op, rop, Associativity::Right)
- } else {
- false
- };
-
- let lhs = ParenHelper::new(lhs_paren, lhs);
- let rhs = ParenHelper::new(rhs_paren, rhs);
- let sugg = match op {
- AssocOp::Add
- | AssocOp::BitAnd
- | AssocOp::BitOr
- | AssocOp::BitXor
- | AssocOp::Divide
- | AssocOp::Equal
- | AssocOp::Greater
- | AssocOp::GreaterEqual
- | AssocOp::LAnd
- | AssocOp::LOr
- | AssocOp::Less
- | AssocOp::LessEqual
- | AssocOp::Modulus
- | AssocOp::Multiply
- | AssocOp::NotEqual
- | AssocOp::ShiftLeft
- | AssocOp::ShiftRight
- | AssocOp::Subtract => format!(
- "{} {} {}",
- lhs,
- op.to_ast_binop().expect("Those are AST ops").to_string(),
- rhs
- ),
- AssocOp::Assign => format!("{} = {}", lhs, rhs),
- AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
- AssocOp::As => format!("{} as {}", lhs, rhs),
- AssocOp::DotDot => format!("{}..{}", lhs, rhs),
- AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
- AssocOp::Colon => format!("{}: {}", lhs, rhs),
- };
-
- Sugg::BinOp(op, sugg.into())
-}
-
-/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
-pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
- make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-/// Operator associativity.
-enum Associativity {
- /// The operator is both left-associative and right-associative.
- Both,
- /// The operator is left-associative.
- Left,
- /// The operator is not associative.
- None,
- /// The operator is right-associative.
- Right,
-}
-
-/// Returns the associativity/fixity of an operator. The difference with
-/// `AssocOp::fixity` is that an operator can be both left and right associative
-/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
-///
-/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
-/// they are considered
-/// associative.
-#[must_use]
-fn associativity(op: AssocOp) -> Associativity {
- use rustc_ast::util::parser::AssocOp::{
- Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
- GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
- };
-
- match op {
- Assign | AssignOp(_) => Associativity::Right,
- Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
- Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
- | Subtract => Associativity::Left,
- DotDot | DotDotEq => Associativity::None,
- }
-}
-
-/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
-fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
- use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
-
- AssocOp::AssignOp(match op.node {
- hir::BinOpKind::Add => Plus,
- hir::BinOpKind::BitAnd => And,
- hir::BinOpKind::BitOr => Or,
- hir::BinOpKind::BitXor => Caret,
- hir::BinOpKind::Div => Slash,
- hir::BinOpKind::Mul => Star,
- hir::BinOpKind::Rem => Percent,
- hir::BinOpKind::Shl => Shl,
- hir::BinOpKind::Shr => Shr,
- hir::BinOpKind::Sub => Minus,
-
- hir::BinOpKind::And
- | hir::BinOpKind::Eq
- | hir::BinOpKind::Ge
- | hir::BinOpKind::Gt
- | hir::BinOpKind::Le
- | hir::BinOpKind::Lt
- | hir::BinOpKind::Ne
- | hir::BinOpKind::Or => panic!("This operator does not exist"),
- })
-}
-
-/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
-fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
- use rustc_ast::ast::BinOpKind::{
- Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
- };
- use rustc_ast::token::BinOpToken;
-
- AssocOp::AssignOp(match op.node {
- Add => BinOpToken::Plus,
- BitAnd => BinOpToken::And,
- BitOr => BinOpToken::Or,
- BitXor => BinOpToken::Caret,
- Div => BinOpToken::Slash,
- Mul => BinOpToken::Star,
- Rem => BinOpToken::Percent,
- Shl => BinOpToken::Shl,
- Shr => BinOpToken::Shr,
- Sub => BinOpToken::Minus,
- And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
- })
-}
-
-/// Returns the indentation before `span` if there are nothing but `[ \t]`
-/// before it on its line.
-fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
- let lo = cx.sess().source_map().lookup_char_pos(span.lo());
- lo.file
- .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
- .and_then(|line| {
- if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
- // We can mix char and byte positions here because we only consider `[ \t]`.
- if lo.col == CharPos(pos) {
- Some(line[..pos].into())
- } else {
- None
- }
- } else {
- None
- }
- })
-}
-
-/// Convenience extension trait for `DiagnosticBuilder`.
-pub trait DiagnosticBuilderExt<T: LintContext> {
- /// Suggests to add an attribute to an item.
- ///
- /// Correctly handles indentation of the attribute and item.
- ///
- /// # Example
- ///
- /// ```rust,ignore
- /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
- /// ```
- fn suggest_item_with_attr<D: Display + ?Sized>(
- &mut self,
- cx: &T,
- item: Span,
- msg: &str,
- attr: &D,
- applicability: Applicability,
- );
-
- /// Suggest to add an item before another.
- ///
- /// The item should not be indented (except for inner indentation).
- ///
- /// # Example
- ///
- /// ```rust,ignore
- /// diag.suggest_prepend_item(cx, item,
- /// "fn foo() {
- /// bar();
- /// }");
- /// ```
- fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
-
- /// Suggest to completely remove an item.
- ///
- /// This will remove an item and all following whitespace until the next non-whitespace
- /// character. This should work correctly if item is on the same indentation level as the
- /// following item.
- ///
- /// # Example
- ///
- /// ```rust,ignore
- /// diag.suggest_remove_item(cx, item, "remove this")
- /// ```
- fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
-}
-
-impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
- fn suggest_item_with_attr<D: Display + ?Sized>(
- &mut self,
- cx: &T,
- item: Span,
- msg: &str,
- attr: &D,
- applicability: Applicability,
- ) {
- if let Some(indent) = indentation(cx, item) {
- let span = item.with_hi(item.lo());
-
- self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
- }
- }
-
- fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
- if let Some(indent) = indentation(cx, item) {
- let span = item.with_hi(item.lo());
-
- let mut first = true;
- let new_item = new_item
- .lines()
- .map(|l| {
- if first {
- first = false;
- format!("{}\n", l)
- } else {
- format!("{}{}\n", indent, l)
- }
- })
- .collect::<String>();
-
- self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
- }
- }
-
- fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
- let mut remove_span = item;
- let hi = cx.sess().source_map().next_point(remove_span).hi();
- let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
-
- if let Some(ref src) = fmpos.sf.src {
- let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
-
- if let Some(non_whitespace_offset) = non_whitespace_offset {
- remove_span = remove_span
- .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
- }
- }
-
- self.span_suggestion(remove_span, msg, String::new(), applicability);
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::Sugg;
- use std::borrow::Cow;
-
- const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
-
- #[test]
- fn make_return_transform_sugg_into_a_return_call() {
- assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
- }
-
- #[test]
- fn blockify_transforms_sugg_into_a_block() {
- assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
- }
-}
+++ /dev/null
-#[macro_export]
-/// Convenience wrapper around rustc's `Symbol::intern`
-macro_rules! sym {
- ($tt:tt) => {
- rustc_span::symbol::Symbol::intern(stringify!($tt))
- };
-}
+++ /dev/null
-use crate::utils;
-use rustc_data_structures::fx::FxHashSet;
-use rustc_hir as hir;
-use rustc_hir::def::Res;
-use rustc_hir::intravisit;
-use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
-use rustc_hir::{Expr, ExprKind, HirId, Path};
-use rustc_infer::infer::TyCtxtInferExt;
-use rustc_lint::LateContext;
-use rustc_middle::hir::map::Map;
-use rustc_middle::ty;
-use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
-
-/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
-pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> {
- let mut delegate = MutVarsDelegate {
- used_mutably: FxHashSet::default(),
- skip: false,
- };
- cx.tcx.infer_ctxt().enter(|infcx| {
- ExprUseVisitor::new(
- &mut delegate,
- &infcx,
- expr.hir_id.owner,
- cx.param_env,
- cx.typeck_results(),
- )
- .walk_expr(expr);
- });
-
- if delegate.skip {
- return None;
- }
- Some(delegate.used_mutably)
-}
-
-pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
- if let Res::Local(id) = variable.res {
- mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
- } else {
- true
- }
-}
-
-struct MutVarsDelegate {
- used_mutably: FxHashSet<HirId>,
- skip: bool,
-}
-
-impl<'tcx> MutVarsDelegate {
- #[allow(clippy::similar_names)]
- fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
- match cat.place.base {
- PlaceBase::Local(id) => {
- self.used_mutably.insert(id);
- },
- PlaceBase::Upvar(_) => {
- //FIXME: This causes false negatives. We can't get the `NodeId` from
- //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
- //`while`-body, not just the ones in the condition.
- self.skip = true
- },
- _ => {},
- }
- }
-}
-
-impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
- fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {}
-
- fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
- if let ty::BorrowKind::MutBorrow = bk {
- self.update(&cmt)
- }
- }
-
- fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
- self.update(&cmt)
- }
-}
-
-pub struct ParamBindingIdCollector {
- binding_hir_ids: Vec<hir::HirId>,
-}
-impl<'tcx> ParamBindingIdCollector {
- fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
- let mut hir_ids: Vec<hir::HirId> = Vec::new();
- for param in body.params.iter() {
- let mut finder = ParamBindingIdCollector {
- binding_hir_ids: Vec::new(),
- };
- finder.visit_param(param);
- for hir_id in &finder.binding_hir_ids {
- hir_ids.push(*hir_id);
- }
- }
- hir_ids
- }
-}
-impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
- type Map = Map<'tcx>;
-
- fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
- if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
- self.binding_hir_ids.push(hir_id);
- }
- intravisit::walk_pat(self, pat);
- }
-
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::None
- }
-}
-
-pub struct BindingUsageFinder<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- binding_ids: Vec<hir::HirId>,
- usage_found: bool,
-}
-impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
- pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
- let mut finder = BindingUsageFinder {
- cx,
- binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
- usage_found: false,
- };
- finder.visit_body(body);
- finder.usage_found
- }
-}
-impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
- if !self.usage_found {
- intravisit::walk_expr(self, expr);
- }
- }
-
- fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
- if let hir::def::Res::Local(id) = path.res {
- if self.binding_ids.contains(&id) {
- self.usage_found = true;
- }
- }
- }
-
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
- }
-}
-
-struct ReturnBreakContinueMacroVisitor {
- seen_return_break_continue: bool,
-}
-
-impl ReturnBreakContinueMacroVisitor {
- fn new() -> ReturnBreakContinueMacroVisitor {
- ReturnBreakContinueMacroVisitor {
- seen_return_break_continue: false,
- }
- }
-}
-
-impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
- type Map = Map<'tcx>;
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-
- fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
- if self.seen_return_break_continue {
- // No need to look farther if we've already seen one of them
- return;
- }
- match &ex.kind {
- ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
- self.seen_return_break_continue = true;
- },
- // Something special could be done here to handle while or for loop
- // desugaring, as this will detect a break if there's a while loop
- // or a for loop inside the expression.
- _ => {
- if utils::in_macro(ex.span) {
- self.seen_return_break_continue = true;
- } else {
- rustc_hir::intravisit::walk_expr(self, ex);
- }
- },
- }
- }
-}
-
-pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
- let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
- recursive_visitor.visit_expr(expression);
- recursive_visitor.seen_return_break_continue
-}
+++ /dev/null
-use crate::utils::path_to_local_id;
-use rustc_hir as hir;
-use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
-use rustc_hir::{Arm, Body, Expr, HirId, Stmt};
-use rustc_lint::LateContext;
-use rustc_middle::hir::map::Map;
-
-/// returns `true` if expr contains match expr desugared from try
-fn contains_try(expr: &hir::Expr<'_>) -> bool {
- struct TryFinder {
- found: bool,
- }
-
- impl<'hir> intravisit::Visitor<'hir> for TryFinder {
- type Map = Map<'hir>;
-
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::None
- }
-
- fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
- if self.found {
- return;
- }
- match expr.kind {
- hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
- _ => intravisit::walk_expr(self, expr),
- }
- }
- }
-
- let mut visitor = TryFinder { found: false };
- visitor.visit_expr(expr);
- visitor.found
-}
-
-pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
-where
- F: FnMut(&'hir hir::Expr<'hir>) -> bool,
-{
- struct RetFinder<F> {
- in_stmt: bool,
- failed: bool,
- cb: F,
- }
-
- struct WithStmtGuarg<'a, F> {
- val: &'a mut RetFinder<F>,
- prev_in_stmt: bool,
- }
-
- impl<F> RetFinder<F> {
- fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
- let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
- WithStmtGuarg {
- val: self,
- prev_in_stmt,
- }
- }
- }
-
- impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
- type Target = RetFinder<F>;
-
- fn deref(&self) -> &Self::Target {
- self.val
- }
- }
-
- impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.val
- }
- }
-
- impl<F> Drop for WithStmtGuarg<'_, F> {
- fn drop(&mut self) {
- self.val.in_stmt = self.prev_in_stmt;
- }
- }
-
- impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
- type Map = Map<'hir>;
-
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::None
- }
-
- fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
- intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
- }
-
- fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
- if self.failed {
- return;
- }
- if self.in_stmt {
- match expr.kind {
- hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
- _ => intravisit::walk_expr(self, expr),
- }
- } else {
- match expr.kind {
- hir::ExprKind::If(cond, then, else_opt) => {
- self.inside_stmt(true).visit_expr(cond);
- self.visit_expr(then);
- if let Some(el) = else_opt {
- self.visit_expr(el);
- }
- },
- hir::ExprKind::Match(cond, arms, _) => {
- self.inside_stmt(true).visit_expr(cond);
- for arm in arms {
- self.visit_expr(arm.body);
- }
- },
- hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
- hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
- _ => self.failed |= !(self.cb)(expr),
- }
- }
- }
- }
-
- !contains_try(expr) && {
- let mut ret_finder = RetFinder {
- in_stmt: false,
- failed: false,
- cb: callback,
- };
- ret_finder.visit_expr(expr);
- !ret_finder.failed
- }
-}
-
-pub struct LocalUsedVisitor<'hir> {
- hir: Map<'hir>,
- pub local_hir_id: HirId,
- pub used: bool,
-}
-
-impl<'hir> LocalUsedVisitor<'hir> {
- pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
- Self {
- hir: cx.tcx.hir(),
- local_hir_id,
- used: false,
- }
- }
-
- fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
- visit(self, t);
- std::mem::replace(&mut self.used, false)
- }
-
- pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
- self.check(arm, Self::visit_arm)
- }
-
- pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
- self.check(body, Self::visit_body)
- }
-
- pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
- self.check(expr, Self::visit_expr)
- }
-
- pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
- self.check(stmt, Self::visit_stmt)
- }
-}
-
-impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
- type Map = Map<'v>;
-
- fn visit_expr(&mut self, expr: &'v Expr<'v>) {
- if self.used {
- return;
- }
- if path_to_local_id(expr, self.local_hir_id) {
- self.used = true;
- } else {
- walk_expr(self, expr);
- }
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::OnlyBodies(self.hir)
- }
-}
-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! {
WithCapacity(u64),
}
struct VecPushSearcher {
+ local_id: HirId,
init: VecInitKind,
- name: Symbol,
lhs_is_local: bool,
lhs_span: Span,
err_span: Span,
}
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,
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,
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,
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
- ///
/// // Bad
/// writeln!(buf, "");
///
/// # use std::fmt::Write;
/// # let mut buf = String::new();
/// # let name = "World";
- ///
/// // Bad
/// write!(buf, "Hello {}!\n", name);
///
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
- ///
/// // Bad
/// writeln!(buf, "{}", "foo");
///
--- /dev/null
+[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 = []
--- /dev/null
+//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
+//!
+//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
+
+#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use crate::{both, over};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, *};
+use rustc_span::symbol::Ident;
+use std::mem;
+
+pub mod ident_iter;
+pub use ident_iter::IdentIter;
+
+pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
+ use BinOpKind::*;
+ matches!(
+ kind,
+ Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
+ )
+}
+
+/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
+pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
+}
+
+pub fn eq_id(l: Ident, r: Ident) -> bool {
+ l.name == r.name
+}
+
+pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
+ use PatKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_pat(l, r),
+ (_, Paren(r)) => eq_pat(l, r),
+ (Wild, Wild) | (Rest, Rest) => true,
+ (Lit(l), Lit(r)) => eq_expr(l, r),
+ (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
+ (Range(lf, lt, le), Range(rf, rt, re)) => {
+ eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
+ },
+ (Box(l), Box(r))
+ | (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
+ | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
+ (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
+ (TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)),
+ (Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => {
+ lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf))
+ },
+ (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
+ match (l, r) {
+ (RangeEnd::Excluded, RangeEnd::Excluded) => true,
+ (RangeEnd::Included(l), RangeEnd::Included(r)) => {
+ matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_pat(&l.pat, &r.pat)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+}
+
+pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
+ l.position == r.position && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_path(l: &Path, r: &Path) -> bool {
+ over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r))
+}
+
+pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
+ eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
+}
+
+pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
+ match (l, r) {
+ (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => {
+ over(&l.args, &r.args, |l, r| eq_angle_arg(l, r))
+ },
+ (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
+ over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
+ match (l, r) {
+ (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
+ (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
+ match (l, r) {
+ (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
+ (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
+ (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
+ _ => false,
+ }
+}
+
+pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
+ both(l, r, |l, r| eq_expr(l, r))
+}
+
+pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
+ match (l, r) {
+ (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
+ (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
+ use ExprKind::*;
+ if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) {
+ return false;
+ }
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_expr(l, r),
+ (_, Paren(r)) => eq_expr(l, r),
+ (Err, Err) => true,
+ (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
+ (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
+ (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
+ (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
+ (MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
+ (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
+ (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
+ (Lit(l), Lit(r)) => l.kind == r.kind,
+ (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
+ (Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re),
+ (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
+ (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
+ (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
+ eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
+ },
+ (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
+ (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
+ (TryBlock(l), TryBlock(r)) => eq_block(l, r),
+ (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
+ (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
+ (Continue(ll), Continue(rl)) => eq_label(ll, rl),
+ (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
+ (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
+ (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
+ (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)),
+ (Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
+ lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
+ },
+ (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
+ (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
+ (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => {
+ eq_path(lp, rp) && eq_struct_rest(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field(l: &Field, r: &Field) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_expr(&l.expr, &r.expr)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+}
+
+pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_expr(&l.body, &r.body)
+ && eq_expr_opt(&l.guard, &r.guard)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+}
+
+pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
+ both(l, r, |l, r| eq_id(l.ident, r.ident))
+}
+
+pub fn eq_block(l: &Block, r: &Block) -> bool {
+ l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
+}
+
+pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
+ use StmtKind::*;
+ match (&l.kind, &r.kind) {
+ (Local(l), Local(r)) => {
+ eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
+ && eq_expr_opt(&l.init, &r.init)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ },
+ (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
+ (Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
+ (Empty, Empty) => true,
+ (MacCall(l), MacCall(r)) => {
+ l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
+ eq_id(l.ident, r.ident)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && eq_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
+ use ItemKind::*;
+ 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),
+ (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,
+ }
+ },
+ (ForeignMod(l), ForeignMod(r)) => {
+ both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
+ && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
+ },
+ (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (Enum(le, lg), Enum(re, rg)) => {
+ over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
+ },
+ (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
+ eq_variant_data(lv, rv) && eq_generics(lg, rg)
+ },
+ (Trait(box TraitKind(la, lu, lg, lb, li)), Trait(box TraitKind(ra, ru, rg, rb, ri))) => {
+ la == ra
+ && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
+ (
+ Impl(box ImplKind {
+ unsafety: lu,
+ polarity: lp,
+ defaultness: ld,
+ constness: lc,
+ generics: lg,
+ of_trait: lot,
+ self_ty: lst,
+ items: li,
+ }),
+ Impl(box ImplKind {
+ unsafety: ru,
+ polarity: rp,
+ defaultness: rd,
+ constness: rc,
+ generics: rg,
+ of_trait: rot,
+ self_ty: rst,
+ items: ri,
+ }),
+ ) => {
+ matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
+ && eq_defaultness(*ld, *rd)
+ && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
+ && eq_generics(lg, rg)
+ && both(lot, rot, |l, r| eq_path(&l.path, &r.path))
+ && eq_ty(lst, rst)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
+ _ => false,
+ }
+}
+
+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),
+ (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))
+ },
+ (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
+ use AssocItemKind::*;
+ match (l, r) {
+ (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))
+ },
+ (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, |l, r| eq_generic_bound(l, r))
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && eq_id(l.ident, r.ident)
+ && eq_variant_data(&l.data, &r.data)
+ && both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
+}
+
+pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
+ use VariantData::*;
+ match (l, r) {
+ (Unit(_), Unit(_)) => true,
+ (Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ && eq_vis(&l.vis, &r.vis)
+ && both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
+ && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
+ eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
+}
+
+pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
+ matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
+ && l.asyncness.is_async() == r.asyncness.is_async()
+ && matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
+ && eq_ext(&l.ext, &r.ext)
+}
+
+pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
+ over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
+ && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
+ eq_where_predicate(l, r)
+ })
+}
+
+pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
+ use WherePredicate::*;
+ match (l, r) {
+ (BoundPredicate(l), BoundPredicate(r)) => {
+ over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
+ && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ },
+ (RegionPredicate(l), RegionPredicate(r)) => {
+ eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ },
+ (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
+ _ => false,
+ }
+}
+
+pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
+ eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
+ use UseTreeKind::*;
+ match (l, r) {
+ (Glob, Glob) => true,
+ (Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
+ (Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
+ eq_expr(&l.value, &r.value)
+}
+
+pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
+ matches!(
+ (l, r),
+ (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
+ )
+}
+
+pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
+ use VisibilityKind::*;
+ match (&l.kind, &r.kind) {
+ (Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
+ (Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
+ eq_fn_ret_ty(&l.output, &r.output)
+ && over(&l.inputs, &r.inputs, |l, r| {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_ty(&l.ty, &r.ty)
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+ })
+}
+
+pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
+ match (l, r) {
+ (FnRetTy::Default(_), FnRetTy::Default(_)) => true,
+ (FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
+ use TyKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_ty(l, r),
+ (_, Paren(r)) => eq_ty(l, r),
+ (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
+ (Slice(l), Slice(r)) => eq_ty(l, r),
+ (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
+ (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
+ (Rptr(ll, l), Rptr(rl, r)) => {
+ both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
+ },
+ (BareFn(l), BareFn(r)) => {
+ l.unsafety == r.unsafety
+ && eq_ext(&l.ext, &r.ext)
+ && over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
+ && eq_fn_decl(&l.decl, &r.decl)
+ },
+ (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
+ (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
+ (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
+ (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
+ use Extern::*;
+ match (l, r) {
+ (None, None) | (Implicit, Implicit) => true,
+ (Explicit(l), Explicit(r)) => eq_str_lit(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
+ l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
+}
+
+pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
+ eq_path(&l.trait_ref.path, &r.trait_ref.path)
+ && over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ })
+}
+
+pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
+ use GenericParamKind::*;
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
+ && match (&l.kind, &r.kind) {
+ (Lifetime, Lifetime) => true,
+ (Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
+ (
+ Const {
+ ty: lt,
+ kw_span: _,
+ default: ld,
+ },
+ Const {
+ ty: rt,
+ kw_span: _,
+ default: rd,
+ },
+ ) => eq_ty(lt, rt) && both(ld, rd, |ld, rd| eq_anon_const(ld, rd)),
+ _ => false,
+ }
+ && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
+}
+
+pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
+ use GenericBound::*;
+ match (l, r) {
+ (Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
+ (Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
+ use AssocTyConstraintKind::*;
+ eq_id(l.ident, r.ident)
+ && match (&l.kind, &r.kind) {
+ (Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
+ (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
+ eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
+}
+
+pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
+ use AttrKind::*;
+ l.style == r.style
+ && match (&l.kind, &r.kind) {
+ (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
+ (Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
+ use MacArgs::*;
+ match (l, r) {
+ (Empty, Empty) => true,
+ (Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
+ (Eq(_, lt), Eq(_, rt)) => lt.kind == rt.kind,
+ _ => false,
+ }
+}
--- /dev/null
+use core::iter::FusedIterator;
+use rustc_ast::visit::{walk_attribute, walk_expr, Visitor};
+use rustc_ast::{Attribute, Expr};
+use rustc_span::symbol::Ident;
+
+pub struct IdentIter(std::vec::IntoIter<Ident>);
+
+impl Iterator for IdentIter {
+ type Item = Ident;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+}
+
+impl FusedIterator for IdentIter {}
+
+impl From<&Expr> for IdentIter {
+ fn from(expr: &Expr) -> Self {
+ let mut visitor = IdentCollector::default();
+
+ walk_expr(&mut visitor, expr);
+
+ IdentIter(visitor.0.into_iter())
+ }
+}
+
+impl From<&Attribute> for IdentIter {
+ fn from(attr: &Attribute) -> Self {
+ let mut visitor = IdentCollector::default();
+
+ walk_attribute(&mut visitor, attr);
+
+ IdentIter(visitor.0.into_iter())
+ }
+}
+
+#[derive(Default)]
+struct IdentCollector(Vec<Ident>);
+
+impl Visitor<'_> for IdentCollector {
+ fn visit_ident(&mut self, ident: Ident) {
+ self.0.push(ident);
+ }
+}
--- /dev/null
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_session::Session;
+use rustc_span::sym;
+use std::str::FromStr;
+
+/// Deprecation status of attributes known by Clippy.
+#[allow(dead_code)]
+pub enum DeprecationStatus {
+ /// Attribute is deprecated
+ Deprecated,
+ /// Attribute is deprecated and was replaced by the named attribute
+ Replaced(&'static str),
+ None,
+}
+
+pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
+ ("author", DeprecationStatus::None),
+ ("cognitive_complexity", DeprecationStatus::None),
+ (
+ "cyclomatic_complexity",
+ DeprecationStatus::Replaced("cognitive_complexity"),
+ ),
+ ("dump", DeprecationStatus::None),
+ ("msrv", DeprecationStatus::None),
+];
+
+pub struct LimitStack {
+ stack: Vec<u64>,
+}
+
+impl Drop for LimitStack {
+ fn drop(&mut self) {
+ assert_eq!(self.stack.len(), 1);
+ }
+}
+
+impl LimitStack {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self { stack: vec![limit] }
+ }
+ pub fn limit(&self) -> u64 {
+ *self.stack.last().expect("there should always be a value in the stack")
+ }
+ pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| stack.push(val));
+ }
+ pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
+ }
+}
+
+pub fn get_attr<'a>(
+ sess: &'a Session,
+ attrs: &'a [ast::Attribute],
+ name: &'static str,
+) -> impl Iterator<Item = &'a ast::Attribute> {
+ attrs.iter().filter(move |attr| {
+ let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr
+ } else {
+ return false;
+ };
+ let attr_segments = &attr.path.segments;
+ if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
+ BUILTIN_ATTRIBUTES
+ .iter()
+ .find_map(|&(builtin_name, ref deprecation_status)| {
+ if attr_segments[1].ident.name.as_str() == builtin_name {
+ Some(deprecation_status)
+ } else {
+ None
+ }
+ })
+ .map_or_else(
+ || {
+ sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
+ false
+ },
+ |deprecation_status| {
+ let mut diag =
+ sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
+ match *deprecation_status {
+ DeprecationStatus::Deprecated => {
+ diag.emit();
+ false
+ },
+ DeprecationStatus::Replaced(new_name) => {
+ diag.span_suggestion(
+ attr_segments[1].ident.span,
+ "consider using",
+ new_name.to_string(),
+ Applicability::MachineApplicable,
+ );
+ diag.emit();
+ false
+ },
+ DeprecationStatus::None => {
+ diag.cancel();
+ attr_segments[1].ident.name.as_str() == name
+ },
+ }
+ },
+ )
+ } else {
+ false
+ }
+ })
+}
+
+fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
+ for attr in get_attr(sess, attrs, name) {
+ if let Some(ref value) = attr.value_str() {
+ if let Ok(value) = FromStr::from_str(&value.as_str()) {
+ f(value)
+ } else {
+ sess.span_err(attr.span, "not a number");
+ }
+ } else {
+ sess.span_err(attr.span, "bad clippy attribute");
+ }
+ }
+}
+
+pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
+ let mut unique_attr = None;
+ for attr in get_attr(sess, attrs, name) {
+ match attr.style {
+ ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
+ ast::AttrStyle::Inner => {
+ sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
+ .span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
+ .emit();
+ },
+ ast::AttrStyle::Outer => {
+ sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
+ },
+ }
+ }
+ unique_attr
+}
+
+/// Return true if the attributes contain any of `proc_macro`,
+/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
+pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
+}
--- /dev/null
+/// Returns the index of the character after the first camel-case component of `s`.
+#[must_use]
+pub fn until(s: &str) -> usize {
+ let mut iter = s.char_indices();
+ if let Some((_, first)) = iter.next() {
+ if !first.is_uppercase() {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ let mut up = true;
+ let mut last_i = 0;
+ for (i, c) in iter {
+ if up {
+ if c.is_lowercase() {
+ up = false;
+ } else {
+ return last_i;
+ }
+ } else if c.is_uppercase() {
+ up = true;
+ last_i = i;
+ } else if !c.is_lowercase() {
+ return i;
+ }
+ }
+ if up {
+ last_i
+ } else {
+ s.len()
+ }
+}
+
+/// Returns index of the last camel-case component of `s`.
+#[must_use]
+pub fn from(s: &str) -> usize {
+ let mut iter = s.char_indices().rev();
+ if let Some((_, first)) = iter.next() {
+ if !first.is_lowercase() {
+ return s.len();
+ }
+ } else {
+ return s.len();
+ }
+ let mut down = true;
+ let mut last_i = s.len();
+ for (i, c) in iter {
+ if down {
+ if c.is_uppercase() {
+ down = false;
+ last_i = i;
+ } else if !c.is_lowercase() {
+ return last_i;
+ }
+ } else if c.is_lowercase() {
+ down = true;
+ } else if c.is_uppercase() {
+ last_i = i;
+ } else {
+ return last_i;
+ }
+ }
+ last_i
+}
+
+#[cfg(test)]
+mod test {
+ use super::{from, until};
+
+ #[test]
+ fn from_full() {
+ assert_eq!(from("AbcDef"), 0);
+ assert_eq!(from("Abc"), 0);
+ assert_eq!(from("ABcd"), 0);
+ assert_eq!(from("ABcdEf"), 0);
+ assert_eq!(from("AabABcd"), 0);
+ }
+
+ #[test]
+ fn from_partial() {
+ assert_eq!(from("abcDef"), 3);
+ assert_eq!(from("aDbc"), 1);
+ assert_eq!(from("aabABcd"), 3);
+ }
+
+ #[test]
+ fn from_not() {
+ assert_eq!(from("AbcDef_"), 7);
+ assert_eq!(from("AbcDD"), 5);
+ }
+
+ #[test]
+ fn from_caps() {
+ assert_eq!(from("ABCD"), 4);
+ }
+
+ #[test]
+ fn until_full() {
+ assert_eq!(until("AbcDef"), 6);
+ assert_eq!(until("Abc"), 3);
+ }
+
+ #[test]
+ fn until_not() {
+ assert_eq!(until("abcDef"), 0);
+ assert_eq!(until("aDbc"), 0);
+ }
+
+ #[test]
+ fn until_partial() {
+ assert_eq!(until("AbcDef_"), 6);
+ assert_eq!(until("CallTypeC"), 8);
+ assert_eq!(until("AbcDD"), 3);
+ }
+
+ #[test]
+ fn until_caps() {
+ assert_eq!(until("ABCD"), 0);
+ }
+}
--- /dev/null
+//! Utility functions about comparison operators.
+
+#![deny(clippy::missing_docs_in_private_items)]
+
+use rustc_hir::{BinOpKind, Expr};
+
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+/// Represent a normalized comparison operator.
+pub enum Rel {
+ /// `<`
+ Lt,
+ /// `<=`
+ Le,
+ /// `==`
+ Eq,
+ /// `!=`
+ Ne,
+}
+
+/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or
+/// `lhs != rhs`.
+pub fn normalize_comparison<'a>(
+ op: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> {
+ match op {
+ BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)),
+ BinOpKind::Le => Some((Rel::Le, lhs, rhs)),
+ BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)),
+ BinOpKind::Ge => Some((Rel::Le, rhs, lhs)),
+ BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)),
+ BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)),
+ _ => None,
+ }
+}
--- /dev/null
+#![allow(clippy::float_cmp)]
+
+use crate::{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 don’t 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 don’t 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,
+ }
+}
--- /dev/null
+//! Clippy wrappers around rustc's diagnostic functions.
+
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir::HirId;
+use rustc_lint::{LateContext, Lint, LintContext};
+use rustc_span::source_map::{MultiSpan, Span};
+use std::env;
+
+fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
+ if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
+ diag.help(&format!(
+ "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
+ &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
+ // extract just major + minor version and ignore patch versions
+ format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
+ }),
+ lint.name_lower().replacen("clippy::", "", 1)
+ ));
+ }
+}
+
+/// Emit a basic lint message with a `msg` and a `span`.
+///
+/// This is the most primitive of our lint emission methods and can
+/// be a good way to get a new lint started.
+///
+/// Usually it's nicer to provide more context for lint messages.
+/// Be sure the output is understandable when you use this method.
+///
+/// # Example
+///
+/// ```ignore
+/// error: usage of mem::forget on Drop type
+/// --> $DIR/mem_forget.rs:17:5
+/// |
+/// 17 | std::mem::forget(seven);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Same as `span_lint` but with an extra `help` message.
+///
+/// Use this if you want to provide some general help but
+/// can't provide a specific machine applicable suggestion.
+///
+/// The `help` message can be optionally attached to a `Span`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: constant division of 0.0 with 0.0 will always result in NaN
+/// --> $DIR/zero_div_zero.rs:6:25
+/// |
+/// 6 | let other_f64_nan = 0.0f64 / 0.0;
+/// | ^^^^^^^^^^^^
+/// |
+/// = help: Consider using `f64::NAN` if you would like a constant representing NaN
+/// ```
+pub fn span_lint_and_help<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ span: Span,
+ msg: &str,
+ help_span: Option<Span>,
+ help: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(help_span) = help_span {
+ diag.span_help(help_span, help);
+ } else {
+ diag.help(help);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but with a `note` section instead of a `help` message.
+///
+/// The `note` message is presented separately from the main lint message
+/// and is attached to a specific span:
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
+/// --> $DIR/drop_forget_ref.rs:10:5
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^^^^^^^^^
+/// |
+/// = note: `-D clippy::forget-ref` implied by `-D warnings`
+/// note: argument has type &SomeStruct
+/// --> $DIR/drop_forget_ref.rs:10:12
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^
+/// ```
+pub fn span_lint_and_note<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ span: impl Into<MultiSpan>,
+ msg: &str,
+ note_span: Option<Span>,
+ note: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(note_span) = note_span {
+ diag.span_note(note_span, note);
+ } else {
+ diag.note(note);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
+///
+/// If you need to customize your lint output a lot, use this function.
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
+where
+ F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
+{
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir_and_then(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ hir_id: HirId,
+ sp: Span,
+ msg: &str,
+ f: impl FnOnce(&mut DiagnosticBuilder<'_>),
+) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Add a span lint with a suggestion on how to fix it.
+///
+/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
+/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
+/// 2)"`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: This `.fold` can be more succinctly expressed as `.any`
+/// --> $DIR/methods.rs:390:13
+/// |
+/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
+/// |
+/// = note: `-D fold-any` implied by `-D warnings`
+/// ```
+#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
+pub fn span_lint_and_sugg<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ help: &str,
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ diag.span_suggestion(sp, help, sugg, applicability);
+ });
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// Note: in the JSON format (used by `compiletest_rs`), the help message will
+/// appear once per
+/// replacement. In human-readable format though, it only appears once before
+/// the whole suggestion.
+pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
+where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg)
+}
+
+pub fn multispan_sugg_with_applicability<I>(
+ diag: &mut DiagnosticBuilder<'_>,
+ help_msg: &str,
+ applicability: Applicability,
+ sugg: I,
+) where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
+}
--- /dev/null
+//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
+//!
+//! Things to consider:
+//! - has the expression side-effects?
+//! - is the expression computationally expensive?
+//!
+//! See lints:
+//! - unnecessary-lazy-evaluations
+//! - or-fun-call
+//! - option-if-let-else
+
+use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
+use rustc_hir::def::{DefKind, Res};
+
+use rustc_hir::intravisit;
+use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+
+use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+/// Is the expr pure (is it free from side-effects)?
+/// This function is named so to stress that it isn't exhaustive and returns FNs.
+fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
+ match expr.kind {
+ ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
+ ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
+ ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)),
+ ExprKind::Struct(_, fields, expr) => {
+ fields.iter().all(|f| identify_some_pure_patterns(f.expr))
+ && expr.map_or(true, |e| identify_some_pure_patterns(e))
+ },
+ ExprKind::Call(
+ &Expr {
+ kind:
+ ExprKind::Path(QPath::Resolved(
+ _,
+ Path {
+ res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
+ ..
+ },
+ )),
+ ..
+ },
+ args,
+ ) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
+ ExprKind::Block(
+ &Block {
+ stmts,
+ expr: Some(expr),
+ ..
+ },
+ _,
+ ) => stmts.is_empty() && identify_some_pure_patterns(expr),
+ ExprKind::Box(..)
+ | ExprKind::Array(..)
+ | ExprKind::Call(..)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::DropTemps(..)
+ | ExprKind::Loop(..)
+ | ExprKind::If(..)
+ | ExprKind::Match(..)
+ | ExprKind::Closure(..)
+ | ExprKind::Block(..)
+ | ExprKind::Assign(..)
+ | ExprKind::AssignOp(..)
+ | ExprKind::Index(..)
+ | ExprKind::Break(..)
+ | ExprKind::Continue(..)
+ | ExprKind::Ret(..)
+ | ExprKind::InlineAsm(..)
+ | ExprKind::LlvmInlineAsm(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Yield(..)
+ | ExprKind::Err => false,
+ }
+}
+
+/// Identify some potentially computationally expensive patterns.
+/// This function is named so to stress that its implementation is non-exhaustive.
+/// It returns FNs and FPs.
+fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ // Searches an expression for method calls or function calls that aren't ctors
+ struct FunCallFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ found: bool,
+ }
+
+ impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ let call_found = match &expr.kind {
+ // ignore enum and struct constructors
+ ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
+ ExprKind::Index(obj, _) => {
+ let ty = self.cx.typeck_results().expr_ty(obj);
+ is_type_diagnostic_item(self.cx, ty, sym!(hashmap_type))
+ || match_type(self.cx, ty, &paths::BTREEMAP)
+ },
+ ExprKind::MethodCall(..) => true,
+ _ => false,
+ };
+
+ if call_found {
+ self.found |= true;
+ }
+
+ if !self.found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+ }
+
+ let mut finder = FunCallFinder { cx, found: false };
+ finder.visit_expr(expr);
+ finder.found
+}
+
+pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
+}
+
+pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ identify_some_potentially_expensive_patterns(cx, expr)
+}
--- /dev/null
+//! This module contains functions for retrieve the original AST from lowered
+//! `hir`.
+
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::{is_expn_of, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+
+/// Converts a hir binary operator to the corresponding `ast` type.
+#[must_use]
+pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
+ match op {
+ hir::BinOpKind::Eq => ast::BinOpKind::Eq,
+ hir::BinOpKind::Ge => ast::BinOpKind::Ge,
+ hir::BinOpKind::Gt => ast::BinOpKind::Gt,
+ hir::BinOpKind::Le => ast::BinOpKind::Le,
+ hir::BinOpKind::Lt => ast::BinOpKind::Lt,
+ hir::BinOpKind::Ne => ast::BinOpKind::Ne,
+ hir::BinOpKind::Or => ast::BinOpKind::Or,
+ hir::BinOpKind::Add => ast::BinOpKind::Add,
+ hir::BinOpKind::And => ast::BinOpKind::And,
+ hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
+ hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
+ hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
+ hir::BinOpKind::Div => ast::BinOpKind::Div,
+ hir::BinOpKind::Mul => ast::BinOpKind::Mul,
+ hir::BinOpKind::Rem => ast::BinOpKind::Rem,
+ hir::BinOpKind::Shl => ast::BinOpKind::Shl,
+ hir::BinOpKind::Shr => ast::BinOpKind::Shr,
+ hir::BinOpKind::Sub => ast::BinOpKind::Sub,
+ }
+}
+
+/// Represent a range akin to `ast::ExprKind::Range`.
+#[derive(Debug, Copy, Clone)]
+pub struct Range<'a> {
+ /// The lower bound of the range, or `None` for ranges such as `..X`.
+ pub start: Option<&'a hir::Expr<'a>>,
+ /// The upper bound of the range, or `None` for ranges such as `X..`.
+ pub end: Option<&'a hir::Expr<'a>>,
+ /// Whether the interval is open or closed.
+ pub limits: ast::RangeLimits,
+}
+
+/// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
+pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
+ /// Finds the field named `name` in the field. Always return `Some` for
+ /// convenience.
+ fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> {
+ let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
+
+ Some(expr)
+ }
+
+ match expr.kind {
+ hir::ExprKind::Call(ref path, ref args)
+ if matches!(
+ path.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
+ ) =>
+ {
+ Some(Range {
+ start: Some(&args[0]),
+ end: Some(&args[1]),
+ limits: ast::RangeLimits::Closed,
+ })
+ },
+ hir::ExprKind::Struct(ref path, ref fields, None) => match path {
+ hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
+ start: None,
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::Closed,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+/// Checks if a `let` statement is from a `for` loop desugaring.
+pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
+ // This will detect plain for-loops without an actual variable binding:
+ //
+ // ```
+ // for x in some_vec {
+ // // do stuff
+ // }
+ // ```
+ if_chain! {
+ if let Some(ref expr) = local.init;
+ if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
+ then {
+ return true;
+ }
+ }
+
+ // This detects a variable binding in for loop to avoid `let_unit_value`
+ // lint (see issue #1964).
+ //
+ // ```
+ // for _ in vec![()] {
+ // // anything
+ // }
+ // ```
+ if let hir::LocalSource::ForLoopDesugar = local.source {
+ return true;
+ }
+
+ false
+}
+
+/// Recover the essential nodes of a desugared for loop as well as the entire span:
+/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
+pub fn for_loop<'tcx>(
+ expr: &'tcx hir::Expr<'tcx>,
+) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
+ if_chain! {
+ if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
+ if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
+ if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
+ if let hir::ExprKind::Loop(ref block, ..) = arms[0].body.kind;
+ if block.expr.is_none();
+ if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
+ if let hir::StmtKind::Local(ref local) = let_stmt.kind;
+ if let hir::StmtKind::Expr(ref expr) = body.kind;
+ then {
+ return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
+ }
+ }
+ None
+}
+
+/// Recover the essential nodes of a desugared while loop:
+/// `while cond { body }` becomes `(cond, body)`.
+pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
+ if_chain! {
+ if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
+ if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
+ if let hir::ExprKind::DropTemps(cond) = &cond.kind;
+ if let [hir::Arm { body, .. }, ..] = &arms[..];
+ then {
+ return Some((cond, body));
+ }
+ }
+ None
+}
+
+/// Represent the pre-expansion arguments of a `vec!` invocation.
+pub enum VecArgs<'a> {
+ /// `vec![elem; len]`
+ Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
+ /// `vec![a, b, c]`
+ Vec(&'a [hir::Expr<'a>]),
+}
+
+/// Returns the arguments of the `vec!` macro if this expression was expanded
+/// from `vec!`.
+pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
+ if_chain! {
+ if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if is_expn_of(fun.span, "vec").is_some();
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ then {
+ return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
+ // `vec![elem; size]` case
+ Some(VecArgs::Repeat(&args[0], &args[1]))
+ }
+ else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
+ // `vec![a, b, c]` case
+ if_chain! {
+ if let hir::ExprKind::Box(ref boxed) = args[0].kind;
+ if let hir::ExprKind::Array(ref args) = boxed.kind;
+ then {
+ return Some(VecArgs::Vec(&*args));
+ }
+ }
+
+ None
+ }
+ else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
+ Some(VecArgs::Vec(&[]))
+ }
+ else {
+ None
+ };
+ }
+ }
+
+ None
+}
+
+/// Extract args from an assert-like macro.
+/// Currently working with:
+/// - `assert!`, `assert_eq!` and `assert_ne!`
+/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
+/// For example:
+/// `assert!(expr)` will return Some([expr])
+/// `debug_assert_eq!(a, b)` will return Some([a, b])
+pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
+ /// Try to match the AST for a pattern that contains a match, for example when two args are
+ /// compared
+ fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
+ if_chain! {
+ if let ExprKind::Match(ref headerexpr, _, _) = &matchblock_expr.kind;
+ if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
+ then {
+ return Some(vec![lhs, rhs]);
+ }
+ }
+ None
+ }
+
+ if let ExprKind::Block(ref block, _) = e.kind {
+ if block.stmts.len() == 1 {
+ if let StmtKind::Semi(ref matchexpr) = block.stmts.get(0)?.kind {
+ // macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
+ if_chain! {
+ if let ExprKind::If(ref clause, _, _) = matchexpr.kind;
+ if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
+ then {
+ return Some(vec![condition]);
+ }
+ }
+
+ // debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
+ if_chain! {
+ if let ExprKind::Block(ref matchblock,_) = matchexpr.kind;
+ if let Some(ref matchblock_expr) = matchblock.expr;
+ then {
+ return ast_matchblock(matchblock_expr);
+ }
+ }
+ }
+ } else if let Some(matchblock_expr) = block.expr {
+ // macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
+ return ast_matchblock(&matchblock_expr);
+ }
+ }
+ None
+}
--- /dev/null
+use crate::consts::{constant_context, constant_simple};
+use crate::differing_macro_contexts;
+use rustc_ast::ast::InlineAsmTemplatePiece;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_hir::def::Res;
+use rustc_hir::{
+ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat, FnRetTy,
+ GenericArg, GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
+ PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
+};
+use rustc_lint::LateContext;
+use rustc_middle::ich::StableHashingContextProvider;
+use rustc_middle::ty::TypeckResults;
+use rustc_span::Symbol;
+use std::hash::Hash;
+
+/// Type used to check whether two ast are the same. This is different from the
+/// operator
+/// `==` on ast types as this operator would compare true equality with ID and
+/// span.
+///
+/// Note that some expressions kinds are not considered but could be added.
+pub struct SpanlessEq<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ allow_side_effects: bool,
+ expr_fallback: Option<Box<dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
+}
+
+impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ allow_side_effects: true,
+ expr_fallback: None,
+ }
+ }
+
+ /// Consider expressions containing potential side effects as not equal.
+ pub fn deny_side_effects(self) -> Self {
+ Self {
+ allow_side_effects: false,
+ ..self
+ }
+ }
+
+ pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
+ Self {
+ expr_fallback: Some(Box::new(expr_fallback)),
+ ..self
+ }
+ }
+
+ /// Use this method to wrap comparisons that may involve inter-expression context.
+ /// See `self.locals`.
+ fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
+ HirEqInterExpr {
+ inner: self,
+ locals: FxHashMap::default(),
+ }
+ }
+
+ pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ self.inter_expr().eq_block(left, right)
+ }
+
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ self.inter_expr().eq_expr(left, right)
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ self.inter_expr().eq_path_segment(left, right)
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ self.inter_expr().eq_path_segments(left, right)
+ }
+
+ pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
+ self.inter_expr().eq_ty_kind(left, right)
+ }
+}
+
+struct HirEqInterExpr<'a, 'b, 'tcx> {
+ inner: &'a mut SpanlessEq<'b, 'tcx>,
+
+ // When binding are declared, the binding ID in the left expression is mapped to the one on the
+ // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
+ // these blocks are considered equal since `x` is mapped to `y`.
+ locals: FxHashMap<HirId, HirId>,
+}
+
+impl HirEqInterExpr<'_, '_, '_> {
+ fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
+ self.eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
+ && both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
+ },
+ (&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
+ self.eq_expr(l, r)
+ },
+ _ => false,
+ }
+ }
+
+ /// Checks whether two blocks are the same.
+ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
+ && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ if !self.inner.allow_side_effects && differing_macro_contexts(left.span, right.span) {
+ return false;
+ }
+
+ if let Some(typeck_results) = self.inner.maybe_typeck_results {
+ if let (Some(l), Some(r)) = (
+ constant_simple(self.inner.cx, typeck_results, left),
+ constant_simple(self.inner.cx, typeck_results, right),
+ ) {
+ if l == r {
+ return true;
+ }
+ }
+ }
+
+ let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
+ (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
+ lb == rb && l_mut == r_mut && self.eq_expr(le, re)
+ },
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
+ self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
+ self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
+ (&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
+ l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
+ l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ })
+ },
+ (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
+ (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
+ self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
+ | (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
+ self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
+ },
+ (&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
+ l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
+ },
+ (&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
+ self.eq_expr(la, ra) && self.eq_expr(li, ri)
+ },
+ (&ExprKind::If(ref lc, ref lt, ref le), &ExprKind::If(ref rc, ref rt, ref re)) => {
+ self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
+ (&ExprKind::Loop(ref lb, ref ll, ref lls, _), &ExprKind::Loop(ref rb, ref rl, ref rls, _)) => {
+ lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
+ ls == rs
+ && self.eq_expr(le, re)
+ && over(la, ra, |l, r| {
+ self.eq_pat(&l.pat, &r.pat)
+ && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
+ && self.eq_expr(&l.body, &r.body)
+ })
+ },
+ (&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
+ self.inner.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
+ let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(ll_id.body));
+ let ll = celcx.expr(&self.inner.cx.tcx.hir().body(ll_id.body).value);
+ let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(rl_id.body));
+ let rl = celcx.expr(&self.inner.cx.tcx.hir().body(rl_id.body).value);
+
+ self.eq_expr(le, re) && ll == rl
+ },
+ (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
+ (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
+ self.eq_qpath(l_path, r_path)
+ && both(lo, ro, |l, r| self.eq_expr(l, r))
+ && over(lf, rf, |l, r| self.eq_field(l, r))
+ },
+ (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
+ (&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
+ (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
+ (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
+ _ => false,
+ };
+ is_eq || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
+ }
+
+ fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
+ over(left, right, |l, r| self.eq_expr(l, r))
+ }
+
+ fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
+ }
+
+ fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
+ match (left, right) {
+ (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
+ (Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
+ _ => false,
+ }
+ }
+
+ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
+ match (left, right) {
+ (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
+ (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
+ _ => false,
+ }
+ }
+
+ fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
+ left.name == right.name
+ }
+
+ fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
+ let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
+ li.name == ri.name && self.eq_pat(lp, rp)
+ }
+
+ /// Checks whether two patterns are the same.
+ fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
+ (&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_fieldpat(l, r))
+ },
+ (&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
+ },
+ (&PatKind::Binding(lb, li, _, ref lp), &PatKind::Binding(rb, ri, _, ref rp)) => {
+ let eq = lb == rb && both(lp, rp, |l, r| self.eq_pat(l, r));
+ if eq {
+ self.locals.insert(li, ri);
+ }
+ eq
+ },
+ (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
+ (&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
+ ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
+ both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
+ },
+ (&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
+ (&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
+ over(ls, rs, |l, r| self.eq_pat(l, r))
+ && over(le, re, |l, r| self.eq_pat(l, r))
+ && both(li, ri, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Wild, &PatKind::Wild) => true,
+ _ => false,
+ }
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
+ match (left, right) {
+ (&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
+ both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
+ },
+ (&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
+ self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
+ },
+ (&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
+ _ => false,
+ }
+ }
+
+ fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+ match (left.res, right.res) {
+ (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
+ (Res::Local(_), _) | (_, Res::Local(_)) => false,
+ _ => over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r)),
+ }
+ }
+
+ fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
+ if !(left.parenthesized || right.parenthesized) {
+ over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
+ && over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
+ } else if left.parenthesized && right.parenthesized {
+ over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
+ && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
+ self.eq_ty(l, r)
+ })
+ } else {
+ false
+ }
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
+ }
+
+ fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
+ self.eq_ty_kind(&left.kind, &right.kind)
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
+ match (left, right) {
+ (&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
+ (&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
+ let cx = self.inner.cx;
+ let eval_const =
+ |body| constant_context(cx, cx.tcx.typeck_body(body)).expr(&cx.tcx.hir().body(body).value);
+ self.eq_ty(lt, rt) && eval_const(ll_id.body) == eval_const(rl_id.body)
+ },
+ (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
+ l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
+ },
+ (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
+ },
+ (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
+ (&TyKind::Infer, &TyKind::Infer) => true,
+ _ => false,
+ }
+ }
+
+ fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
+ }
+}
+
+/// Some simple reductions like `{ return }` => `return`
+fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
+ if let ExprKind::Block(block, _) = kind {
+ match (block.stmts, block.expr) {
+ // `{}` => `()`
+ ([], None) => &ExprKind::Tup(&[]),
+ ([], Some(expr)) => match expr.kind {
+ // `{ return .. }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ ([stmt], None) => match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ // `{ return ..; }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ _ => kind,
+ },
+ _ => kind,
+ }
+ } else {
+ kind
+ }
+}
+
+fn swap_binop<'a>(
+ binop: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
+ match binop {
+ BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
+ Some((binop, rhs, lhs))
+ },
+ BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
+ BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
+ BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
+ BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
+ BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Rem
+ | BinOpKind::Sub
+ | BinOpKind::Div
+ | BinOpKind::And
+ | BinOpKind::Or => None,
+ }
+}
+
+/// Checks if the two `Option`s are both `None` or some equal values as per
+/// `eq_fn`.
+pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ l.as_ref()
+ .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
+}
+
+/// Checks if two slices are equal as per `eq_fn`.
+pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
+}
+
+/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
+pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
+}
+
+/// Type used to hash an ast element. This is different from the `Hash` trait
+/// on ast types as this
+/// trait would consider IDs and spans.
+///
+/// All expressions kind are hashed, but some might have a weaker hash.
+pub struct SpanlessHash<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ s: StableHasher,
+}
+
+impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ s: StableHasher::new(),
+ }
+ }
+
+ pub fn finish(self) -> u64 {
+ self.s.finish()
+ }
+
+ pub fn hash_block(&mut self, b: &Block<'_>) {
+ for s in b.stmts {
+ self.hash_stmt(s);
+ }
+
+ if let Some(ref e) = b.expr {
+ self.hash_expr(e);
+ }
+
+ match b.rules {
+ BlockCheckMode::DefaultBlock => 0,
+ BlockCheckMode::UnsafeBlock(_) => 1,
+ BlockCheckMode::PushUnsafeBlock(_) => 2,
+ BlockCheckMode::PopUnsafeBlock(_) => 3,
+ }
+ .hash(&mut self.s);
+ }
+
+ #[allow(clippy::many_single_char_names, clippy::too_many_lines)]
+ pub fn hash_expr(&mut self, e: &Expr<'_>) {
+ let simple_const = self
+ .maybe_typeck_results
+ .and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
+
+ // const hashing may result in the same hash as some unrelated node, so add a sort of
+ // discriminant depending on which path we're choosing next
+ simple_const.is_some().hash(&mut self.s);
+
+ if let Some(e) = simple_const {
+ return e.hash(&mut self.s);
+ }
+
+ std::mem::discriminant(&e.kind).hash(&mut self.s);
+
+ match e.kind {
+ ExprKind::AddrOf(kind, m, ref e) => {
+ match kind {
+ BorrowKind::Ref => 0,
+ BorrowKind::Raw => 1,
+ }
+ .hash(&mut self.s);
+ m.hash(&mut self.s);
+ self.hash_expr(e);
+ },
+ ExprKind::Continue(i) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Assign(ref l, ref r, _) => {
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::AssignOp(ref o, ref l, ref r) => {
+ o.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Block(ref b, _) => {
+ self.hash_block(b);
+ },
+ ExprKind::Binary(op, ref l, ref r) => {
+ op.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Break(i, ref j) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ if let Some(ref j) = *j {
+ self.hash_expr(&*j);
+ }
+ },
+ ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
+ self.hash_expr(e);
+ },
+ ExprKind::Call(ref fun, args) => {
+ self.hash_expr(fun);
+ self.hash_exprs(args);
+ },
+ ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
+ self.hash_expr(e);
+ self.hash_ty(ty);
+ },
+ ExprKind::Closure(cap, _, eid, _, _) => {
+ match cap {
+ CaptureBy::Value => 0,
+ CaptureBy::Ref => 1,
+ }
+ .hash(&mut self.s);
+ // closures inherit TypeckResults
+ self.hash_expr(&self.cx.tcx.hir().body(eid).value);
+ },
+ ExprKind::Field(ref e, ref f) => {
+ self.hash_expr(e);
+ self.hash_name(f.name);
+ },
+ ExprKind::Index(ref a, ref i) => {
+ self.hash_expr(a);
+ self.hash_expr(i);
+ },
+ ExprKind::InlineAsm(ref asm) => {
+ for piece in asm.template {
+ match piece {
+ InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
+ InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span: _,
+ } => {
+ operand_idx.hash(&mut self.s);
+ modifier.hash(&mut self.s);
+ },
+ }
+ }
+ asm.options.hash(&mut self.s);
+ for (op, _op_sp) in asm.operands {
+ match op {
+ InlineAsmOperand::In { reg, expr } => {
+ reg.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::Out { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ if let Some(expr) = expr {
+ self.hash_expr(expr);
+ }
+ },
+ InlineAsmOperand::InOut { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::SplitInOut {
+ reg,
+ late,
+ in_expr,
+ out_expr,
+ } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(in_expr);
+ if let Some(out_expr) = out_expr {
+ self.hash_expr(out_expr);
+ }
+ },
+ InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
+ }
+ }
+ },
+ ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
+ ExprKind::Lit(ref l) => {
+ l.node.hash(&mut self.s);
+ },
+ ExprKind::Loop(ref b, ref i, ..) => {
+ self.hash_block(b);
+ if let Some(i) = *i {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::If(ref cond, ref then, ref else_opt) => {
+ let c: fn(_, _, _) -> _ = ExprKind::If;
+ c.hash(&mut self.s);
+ self.hash_expr(cond);
+ self.hash_expr(&**then);
+ if let Some(ref e) = *else_opt {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Match(ref e, arms, ref s) => {
+ self.hash_expr(e);
+
+ for arm in arms {
+ // TODO: arm.pat?
+ if let Some(ref e) = arm.guard {
+ self.hash_guard(e);
+ }
+ self.hash_expr(&arm.body);
+ }
+
+ s.hash(&mut self.s);
+ },
+ ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
+ self.hash_name(path.ident.name);
+ self.hash_exprs(args);
+ },
+ ExprKind::ConstBlock(ref l_id) => {
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Repeat(ref e, ref l_id) => {
+ self.hash_expr(e);
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Ret(ref e) => {
+ if let Some(ref e) = *e {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
+ },
+ ExprKind::Struct(ref path, fields, ref expr) => {
+ self.hash_qpath(path);
+
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_expr(&f.expr);
+ }
+
+ if let Some(ref e) = *expr {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Tup(tup) => {
+ self.hash_exprs(tup);
+ },
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
+ ExprKind::Unary(lop, ref le) => {
+ lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(le);
+ },
+ }
+ }
+
+ pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
+ for e in e {
+ self.hash_expr(e);
+ }
+ }
+
+ pub fn hash_name(&mut self, n: Symbol) {
+ n.as_str().hash(&mut self.s);
+ }
+
+ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
+ match *p {
+ QPath::Resolved(_, ref path) => {
+ self.hash_path(path);
+ },
+ QPath::TypeRelative(_, ref path) => {
+ self.hash_name(path.ident.name);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ },
+ }
+ // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
+ }
+
+ pub fn hash_path(&mut self, path: &Path<'_>) {
+ match path.res {
+ // constant hash since equality is dependant on inter-expression context
+ Res::Local(_) => 1_usize.hash(&mut self.s),
+ _ => {
+ for seg in path.segments {
+ self.hash_name(seg.ident.name);
+ }
+ },
+ }
+ }
+
+ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
+ std::mem::discriminant(&b.kind).hash(&mut self.s);
+
+ match &b.kind {
+ StmtKind::Local(local) => {
+ if let Some(ref init) = local.init {
+ self.hash_expr(init);
+ }
+ },
+ StmtKind::Item(..) => {},
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_guard(&mut self, g: &Guard<'_>) {
+ match g {
+ Guard::If(ref expr) | Guard::IfLet(_, ref expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
+ std::mem::discriminant(&lifetime.name).hash(&mut self.s);
+ if let LifetimeName::Param(ref name) = lifetime.name {
+ std::mem::discriminant(name).hash(&mut self.s);
+ match name {
+ ParamName::Plain(ref ident) => {
+ ident.name.hash(&mut self.s);
+ },
+ ParamName::Fresh(ref size) => {
+ size.hash(&mut self.s);
+ },
+ ParamName::Error => {},
+ }
+ }
+ }
+
+ pub fn hash_ty(&mut self, ty: &Ty<'_>) {
+ self.hash_tykind(&ty.kind);
+ }
+
+ pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
+ std::mem::discriminant(ty).hash(&mut self.s);
+ match ty {
+ TyKind::Slice(ty) => {
+ self.hash_ty(ty);
+ },
+ TyKind::Array(ty, anon_const) => {
+ self.hash_ty(ty);
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Ptr(mut_ty) => {
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::Rptr(lifetime, mut_ty) => {
+ self.hash_lifetime(lifetime);
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::BareFn(bfn) => {
+ bfn.unsafety.hash(&mut self.s);
+ bfn.abi.hash(&mut self.s);
+ for arg in bfn.decl.inputs {
+ self.hash_ty(&arg);
+ }
+ match bfn.decl.output {
+ FnRetTy::DefaultReturn(_) => {
+ ().hash(&mut self.s);
+ },
+ FnRetTy::Return(ref ty) => {
+ self.hash_ty(ty);
+ },
+ }
+ bfn.decl.c_variadic.hash(&mut self.s);
+ },
+ TyKind::Tup(ty_list) => {
+ for ty in *ty_list {
+ self.hash_ty(ty);
+ }
+ },
+ TyKind::Path(qpath) => match qpath {
+ QPath::Resolved(ref maybe_ty, ref path) => {
+ if let Some(ref ty) = maybe_ty {
+ self.hash_ty(ty);
+ }
+ for segment in path.segments {
+ segment.ident.name.hash(&mut self.s);
+ self.hash_generic_args(segment.args().args);
+ }
+ },
+ QPath::TypeRelative(ref ty, ref segment) => {
+ self.hash_ty(ty);
+ segment.ident.name.hash(&mut self.s);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ lang_item.hash(&mut self.s);
+ },
+ },
+ TyKind::OpaqueDef(_, arg_list) => {
+ self.hash_generic_args(arg_list);
+ },
+ TyKind::TraitObject(_, lifetime) => {
+ self.hash_lifetime(lifetime);
+ },
+ TyKind::Typeof(anon_const) => {
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Err | TyKind::Infer | TyKind::Never => {},
+ }
+ }
+
+ pub fn hash_body(&mut self, body_id: BodyId) {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
+ self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
+ for arg in arg_list {
+ match arg {
+ GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
+ GenericArg::Type(ref ty) => self.hash_ty(&ty),
+ GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
+ }
+ }
+ }
+}
--- /dev/null
+#![feature(box_patterns)]
+#![feature(in_band_lifetimes)]
+#![feature(or_patterns)]
+#![feature(rustc_private)]
+#![recursion_limit = "512"]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
+
+// FIXME: switch to something more ergonomic here, once available.
+// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_infer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+extern crate rustc_typeck;
+
+#[macro_use]
+pub mod sym_helper;
+
+#[allow(clippy::module_name_repetitions)]
+pub mod ast_utils;
+pub mod attrs;
+pub mod camel_case;
+pub mod comparisons;
+pub mod consts;
+mod diagnostics;
+pub mod eager_or_lazy;
+pub mod higher;
+mod hir_utils;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod qualify_min_const_fn;
+pub mod sugg;
+pub mod usage;
+pub mod visitors;
+
+pub use self::attrs::*;
+pub use self::diagnostics::*;
+pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash};
+
+use std::borrow::Cow;
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind, Mutability};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::Node;
+use rustc_hir::{
+ def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind,
+ MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety,
+};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::exports::Export;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
+use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::source_map::original_sp;
+use rustc_span::sym;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
+use rustc_target::abi::Integer;
+use rustc_trait_selection::traits::query::normalize::AtExt;
+use smallvec::SmallVec;
+
+use crate::consts::{constant, Constant};
+
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+ if let Ok(version) = RustcVersion::parse(msrv) {
+ return Some(version);
+ } else if let Some(sess) = sess {
+ if let Some(span) = span {
+ sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
+ }
+ }
+ None
+}
+
+pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
+ msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
+}
+
+#[macro_export]
+macro_rules! extract_msrv_attr {
+ (LateContext) => {
+ extract_msrv_attr!(@LateContext, ());
+ };
+ (EarlyContext) => {
+ extract_msrv_attr!(@EarlyContext);
+ };
+ (@$context:ident$(, $call:tt)?) => {
+ fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
+ use $crate::get_unique_inner_attr;
+ match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
+ Some(msrv_attr) => {
+ if let Some(msrv) = msrv_attr.value_str() {
+ self.msrv = $crate::parse_msrv(
+ &msrv.to_string(),
+ Some(cx.sess$($call)?),
+ Some(msrv_attr.span),
+ );
+ } else {
+ cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+ },
+ _ => (),
+ }
+ }
+ };
+}
+
+/// Returns `true` if the two spans come from differing expansions (i.e., one is
+/// from a macro and one isn't).
+#[must_use]
+pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
+ rhs.ctxt() != lhs.ctxt()
+}
+
+/// Returns `true` if the given `NodeId` is inside a constant context
+///
+/// # Example
+///
+/// ```rust,ignore
+/// if in_constant(cx, expr.hir_id) {
+/// // Do something
+/// }
+/// ```
+pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(id);
+ match cx.tcx.hir().get(parent_id) {
+ Node::Item(&Item {
+ kind: ItemKind::Const(..) | ItemKind::Static(..),
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ ..
+ })
+ | Node::AnonConst(_) => true,
+ Node::Item(&Item {
+ kind: ItemKind::Fn(ref sig, ..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(ref sig, _),
+ ..
+ }) => sig.header.constness == Constness::Const,
+ _ => false,
+ }
+}
+
+/// Returns `true` if this `span` was expanded by any macro.
+#[must_use]
+pub fn in_macro(span: Span) -> bool {
+ if span.from_expansion() {
+ !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
+ } else {
+ false
+ }
+}
+
+// If the snippet is empty, it's an attribute that was inserted during macro
+// expansion and we want to ignore those, because they could come from external
+// sources that the user has no control over.
+// For some reason these attributes don't have any expansion info on them, so
+// we have to check it this way until there is a better way.
+pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
+ if let Some(snippet) = snippet_opt(cx, span) {
+ if snippet.is_empty() {
+ return false;
+ }
+ }
+ true
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild<'tcx>(pat: &impl std::ops::Deref<Target = Pat<'tcx>>) -> bool {
+ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if type is struct, enum or union type with the given def path.
+///
+/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
+pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
+ match ty.kind() {
+ ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a diagnostic item
+///
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
+pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a lang item
+pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).unwrap() == adt.did,
+ _ => false,
+ }
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ let trt_id = cx.tcx.trait_of_item(def_id);
+ trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
+}
+
+/// Checks if an expression references a variable of the given name.
+pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool {
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
+ if let [p] = path.segments {
+ return p.ident.name == var;
+ }
+ }
+ false
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, ref seg) => seg,
+ QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
+ }
+}
+
+pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.get(0),
+ QPath::TypeRelative(_, ref seg) => Some(seg),
+ QPath::LangItem(..) => None,
+ }
+}
+
+/// Matches a `QPath` against a slice of segment string literals.
+///
+/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
+/// `rustc_hir::QPath`.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_qpath(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
+ match *path {
+ QPath::Resolved(_, ref path) => match_path(path, segments),
+ QPath::TypeRelative(ref ty, ref segment) => match ty.kind {
+ TyKind::Path(ref inner_path) => {
+ if let [prefix @ .., end] = segments {
+ if match_qpath(inner_path, prefix) {
+ return segment.ident.name.as_str() == *end;
+ }
+ }
+ false
+ },
+ _ => false,
+ },
+ QPath::LangItem(..) => false,
+ }
+}
+
+/// Matches a `Path` against a slice of segment string literals.
+///
+/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
+/// `rustc_hir::Path`.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// if match_path(&trait_ref.path, &paths::HASH) {
+/// // This is the `std::hash::Hash` trait.
+/// }
+///
+/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
+/// // This is a `rustc_middle::lint::Lint`.
+/// }
+/// ```
+pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// Matches a `Path` against a slice of segment string literals, e.g.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_path_ast(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// If the expression is a path to a local, returns the canonical `HirId` of the local.
+pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
+ if let Res::Local(id) = path.res {
+ return Some(id);
+ }
+ }
+ None
+}
+
+/// Returns true if the expression is a path to a local with the specified `HirId`.
+/// Use this function to see if an expression matches a function argument or a match binding.
+pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
+ path_to_local(expr) == Some(id)
+}
+
+/// Gets the definition associated to a path.
+#[allow(clippy::shadow_unrelated)] // false positive #6563
+pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
+ macro_rules! try_res {
+ ($e:expr) => {
+ match $e {
+ Some(e) => e,
+ None => return Res::Err,
+ }
+ };
+ }
+ fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export<HirId>> {
+ tcx.item_children(def_id)
+ .iter()
+ .find(|item| item.ident.name.as_str() == name)
+ }
+
+ let (krate, first, path) = match *path {
+ [krate, first, ref path @ ..] => (krate, first, path),
+ _ => return Res::Err,
+ };
+ let tcx = cx.tcx;
+ let crates = tcx.crates();
+ let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate));
+ let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first));
+ let last = path
+ .iter()
+ .copied()
+ // `get_def_path` seems to generate these empty segments for extern blocks.
+ // We can just ignore them.
+ .filter(|segment| !segment.is_empty())
+ // for each segment, find the child item
+ .try_fold(first, |item, segment| {
+ let def_id = item.res.def_id();
+ if let Some(item) = item_child_by_name(tcx, def_id, segment) {
+ Some(item)
+ } else if matches!(item.res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
+ // it is not a child item so check inherent impl items
+ tcx.inherent_impls(def_id)
+ .iter()
+ .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment))
+ } else {
+ None
+ }
+ });
+ try_res!(last).res
+}
+
+/// Convenience function to get the `DefId` of a trait by path.
+/// It could be a trait or trait alias.
+pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
+ match path_to_res(cx, path) {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
+ }
+}
+
+/// Checks whether a type implements a trait.
+/// See also `get_trait_def_id`.
+pub fn implements_trait<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ // Do not check on infer_types to avoid panic in evaluate_obligation.
+ if ty.has_infer_types() {
+ return false;
+ }
+ let ty = cx.tcx.erase_regions(ty);
+ if ty.has_escaping_bound_vars() {
+ return false;
+ }
+ let ty_params = cx.tcx.mk_substs(ty_params.iter());
+ cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
+}
+
+/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
+///
+/// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
+///
+/// ```rust
+/// struct Point(isize, isize);
+///
+/// impl std::ops::Add for Point {
+/// type Output = Self;
+///
+/// fn add(self, other: Self) -> Self {
+/// Point(0, 0)
+/// }
+/// }
+/// ```
+pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> {
+ // Get the implemented trait for the current function
+ let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
+ if_chain! {
+ if parent_impl != hir::CRATE_HIR_ID;
+ if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl);
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ then { return impl_.of_trait.as_ref(); }
+ }
+ None
+}
+
+/// Checks whether this type implements `Drop`.
+pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.ty_adt_def() {
+ Some(def) => def.has_dtor(cx.tcx),
+ None => false,
+ }
+}
+
+/// Returns the method names and argument list of nested method call expressions that make up
+/// `expr`. method/span lists are sorted with the most recent call first.
+pub fn method_calls<'tcx>(
+ expr: &'tcx Expr<'tcx>,
+ max_depth: usize,
+) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
+ let mut method_names = Vec::with_capacity(max_depth);
+ let mut arg_lists = Vec::with_capacity(max_depth);
+ let mut spans = Vec::with_capacity(max_depth);
+
+ let mut current = expr;
+ for _ in 0..max_depth {
+ if let ExprKind::MethodCall(path, span, args, _) = ¤t.kind {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ break;
+ }
+ method_names.push(path.ident.name);
+ arg_lists.push(&**args);
+ spans.push(*span);
+ current = &args[0];
+ } else {
+ break;
+ }
+ }
+
+ (method_names, arg_lists, spans)
+}
+
+/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
+///
+/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
+/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
+/// containing the `Expr`s for
+/// `.bar()` and `.baz()`
+pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {
+ let mut current = expr;
+ let mut matched = Vec::with_capacity(methods.len());
+ for method_name in methods.iter().rev() {
+ // method chains are stored last -> first
+ if let ExprKind::MethodCall(ref path, _, ref args, _) = current.kind {
+ if path.ident.name.as_str() == *method_name {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ return None;
+ }
+ matched.push(&**args); // build up `matched` backwards
+ current = &args[0] // go to parent expression
+ } else {
+ return None;
+ }
+ } else {
+ return None;
+ }
+ }
+ // Reverse `matched` so that it is in the same order as `methods`.
+ matched.reverse();
+ Some(matched)
+}
+
+/// Returns `true` if the provided `def_id` is an entrypoint to a program.
+pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx
+ .entry_fn(LOCAL_CRATE)
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id())
+}
+
+/// Returns `true` if the expression is in the program's `#[panic_handler]`.
+pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+ let def_id = cx.tcx.hir().local_def_id(parent).to_def_id();
+ Some(def_id) == cx.tcx.lang_items().panic_impl()
+}
+
+/// Gets the name of the item the expression is in, if available.
+pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
+ let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ match cx.tcx.hir().find(parent_id) {
+ Some(
+ Node::Item(Item { ident, .. })
+ | Node::TraitItem(TraitItem { ident, .. })
+ | Node::ImplItem(ImplItem { ident, .. }),
+ ) => Some(ident.name),
+ _ => None,
+ }
+}
+
+/// Gets the name of a `Pat`, if any.
+pub fn get_pat_name(pat: &Pat<'_>) -> Option<Symbol> {
+ match pat.kind {
+ PatKind::Binding(.., ref spname, _) => Some(spname.name),
+ PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
+ PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
+ _ => None,
+ }
+}
+
+struct ContainsName {
+ name: Symbol,
+ result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ type Map = Map<'tcx>;
+
+ fn visit_name(&mut self, _: Span, name: Symbol) {
+ if self.name == name {
+ self.result = true;
+ }
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Checks if an `Expr` contains a certain name.
+pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
+ let mut cn = ContainsName { name, result: false };
+ cn.visit_expr(expr);
+ cn.result
+}
+
+/// Returns `true` if `expr` contains a return expression
+pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
+ struct RetCallFinder {
+ found: bool,
+ }
+
+ impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if self.found {
+ return;
+ }
+ if let hir::ExprKind::Ret(..) = &expr.kind {
+ self.found = true;
+ } else {
+ hir::intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
+ hir::intravisit::NestedVisitorMap::None
+ }
+ }
+
+ let mut visitor = RetCallFinder { found: false };
+ visitor.visit_expr(expr);
+ visitor.found
+}
+
+struct FindMacroCalls<'a, 'b> {
+ names: &'a [&'b str],
+ result: Vec<Span>,
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
+ self.result.push(expr.span);
+ }
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Finds calls of the specified macros in a function body.
+pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
+ let mut fmc = FindMacroCalls {
+ names,
+ result: Vec::new(),
+ };
+ fmc.visit_expr(&body.value);
+ fmc.result
+}
+
+/// Converts a span to a code snippet if available, otherwise use default.
+///
+/// This is useful if you want to provide suggestions for your lint or more generally, if you want
+/// to convert a given `Span` to a `str`.
+///
+/// # Example
+/// ```rust,ignore
+/// snippet(cx, expr.span, "..")
+/// ```
+pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
+}
+
+/// Same as `snippet`, but it adapts the applicability level by following rules:
+///
+/// - Applicability level `Unspecified` will never be changed.
+/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
+/// `HasPlaceholders`
+pub fn snippet_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ if *applicability != Applicability::Unspecified && span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ snippet_opt(cx, span).map_or_else(
+ || {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Cow::Borrowed(default)
+ },
+ From::from,
+ )
+}
+
+/// Same as `snippet`, but should only be used when it's clear that the input span is
+/// not a macro argument.
+pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet(cx, span.source_callsite(), default)
+}
+
+/// Converts a span to a code snippet. Returns `None` if not available.
+pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ cx.sess().source_map().span_to_snippet(span).ok()
+}
+
+/// Converts a span (from a block) to a code snippet if available, otherwise use default.
+///
+/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
+/// things which need to be printed as such.
+///
+/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
+/// resulting snippet of the given span.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", None)
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// }
+/// ```
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", Some(if_expr.span))
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// } // aligned with `if`
+/// ```
+/// Note that the first line of the snippet always has 0 indentation.
+pub fn snippet_block<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let snip = snippet(cx, span, default);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_block`, but adapts the applicability level by the rules of
+/// `snippet_with_applicability`.
+pub fn snippet_block_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ let snip = snippet_with_applicability(cx, span, default, applicability);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
+/// line.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^
+/// ```
+pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
+}
+
+fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
+ let line_span = line_span(cx, span);
+ snippet_opt(cx, line_span).and_then(|snip| {
+ snip.find(|c: char| !c.is_whitespace())
+ .map(|pos| line_span.lo() + BytePos::from_usize(pos))
+ })
+}
+
+/// Returns the indentation of the line of a span
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^ -- will return 0
+/// let x = ();
+/// // ^^ -- will return 4
+/// ```
+pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
+ snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
+}
+
+/// Returns the positon just before rarrow
+///
+/// ```rust,ignore
+/// fn into(self) -> () {}
+/// ^
+/// // in case of unformatted code
+/// fn into2(self)-> () {}
+/// ^
+/// fn into3(self) -> () {}
+/// ^
+/// ```
+pub fn position_before_rarrow(s: &str) -> Option<usize> {
+ s.rfind("->").map(|rpos| {
+ let mut rpos = rpos;
+ let chars: Vec<char> = s.chars().collect();
+ while rpos > 1 {
+ if let Some(c) = chars.get(rpos - 1) {
+ if c.is_whitespace() {
+ rpos -= 1;
+ continue;
+ }
+ }
+ break;
+ }
+ rpos
+ })
+}
+
+/// Extends the span to the beginning of the spans line, incl. whitespaces.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^^^^^
+/// ```
+fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ let span = original_sp(span, DUMMY_SP);
+ let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
+ let line_no = source_map_and_line.line;
+ let line_start = source_map_and_line.sf.lines[line_no];
+ Span::new(line_start, span.hi(), span.ctxt())
+}
+
+/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
+/// Also takes an `Option<String>` which can be put inside the braces.
+pub fn expr_block<'a, T: LintContext>(
+ cx: &T,
+ expr: &Expr<'_>,
+ option: Option<String>,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let code = snippet_block(cx, expr.span, default, indent_relative_to);
+ let string = option.unwrap_or_default();
+ if expr.span.from_expansion() {
+ Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
+ } else if let ExprKind::Block(_, _) = expr.kind {
+ Cow::Owned(format!("{}{}", code, string))
+ } else if string.is_empty() {
+ Cow::Owned(format!("{{ {} }}", code))
+ } else {
+ Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
+ }
+}
+
+/// Reindent a multiline string with possibility of ignoring the first line.
+#[allow(clippy::needless_pass_by_value)]
+pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
+ let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
+ let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
+ reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
+}
+
+fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
+ let x = s
+ .lines()
+ .skip(ignore_first as usize)
+ .filter_map(|l| {
+ if l.is_empty() {
+ None
+ } else {
+ // ignore empty lines
+ Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
+ }
+ })
+ .min()
+ .unwrap_or(0);
+ let indent = indent.unwrap_or(0);
+ s.lines()
+ .enumerate()
+ .map(|(i, l)| {
+ if (ignore_first && i == 0) || l.is_empty() {
+ l.to_owned()
+ } else if x > indent {
+ l.split_at(x - indent).1.to_owned()
+ } else {
+ " ".repeat(indent - x) + l
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n")
+}
+
+/// Gets the parent expression, if any –- this is useful to constrain a lint.
+pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ let map = &cx.tcx.hir();
+ let hir_id = e.hir_id;
+ let parent_id = map.get_parent_node(hir_id);
+ if hir_id == parent_id {
+ return None;
+ }
+ map.find(parent_id).and_then(|node| {
+ if let Node::Expr(parent) = node {
+ Some(parent)
+ } else {
+ None
+ }
+ })
+}
+
+pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
+ let map = &cx.tcx.hir();
+ let enclosing_node = map
+ .get_enclosing_scope(hir_id)
+ .and_then(|enclosing_id| map.find(enclosing_id));
+ enclosing_node.and_then(|node| match node {
+ Node::Block(block) => Some(block),
+ Node::Item(&Item {
+ kind: ItemKind::Fn(_, _, eid),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(_, eid),
+ ..
+ }) => match cx.tcx.hir().body(eid).value.kind {
+ ExprKind::Block(ref block, _) => Some(block),
+ _ => None,
+ },
+ _ => None,
+ })
+}
+
+/// Returns the base type for HIR references and pointers.
+pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
+ match ty.kind {
+ TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty),
+ _ => ty,
+ }
+}
+
+/// Returns the base type for references and raw pointers, and count reference
+/// depth.
+pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) {
+ match ty.kind() {
+ ty::Ref(_, ty, _) => inner(ty, depth + 1),
+ _ => (ty, depth),
+ }
+ }
+ inner(ty, 0)
+}
+
+/// Checks whether the given expression is a constant integer of the given value.
+/// unlike `is_integer_literal`, this version does const folding
+pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
+ if is_integer_literal(e, value) {
+ return true;
+ }
+ let map = cx.tcx.hir();
+ let parent_item = map.get_parent_item(e.hir_id);
+ if let Some((Constant::Int(v), _)) = map
+ .maybe_body_owned_by(parent_item)
+ .and_then(|body_id| constant(cx, cx.tcx.typeck_body(body_id), e))
+ {
+ value == v
+ } else {
+ false
+ }
+}
+
+/// Checks whether the given expression is a constant literal of the given value.
+pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
+ // FIXME: use constant folding
+ if let ExprKind::Lit(ref spanned) = expr.kind {
+ if let LitKind::Int(v, _) = spanned.node {
+ return v == value;
+ }
+ }
+ false
+}
+
+/// Returns `true` if the given `Expr` has been coerced before.
+///
+/// Examples of coercions can be found in the Nomicon at
+/// <https://doc.rust-lang.org/nomicon/coercions.html>.
+///
+/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more
+/// information on adjustments and coercions.
+pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ cx.typeck_results().adjustments().get(e.hir_id).is_some()
+}
+
+/// Returns the pre-expansion span if is this comes from an expansion of the
+/// macro `name`.
+/// See also `is_direct_expn_of`.
+#[must_use]
+pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
+ loop {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+
+ span = new_span;
+ } else {
+ return None;
+ }
+ }
+}
+
+/// Returns the pre-expansion span if the span directly comes from an expansion
+/// of the macro `name`.
+/// The difference with `is_expn_of` is that in
+/// ```rust,ignore
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// `bar!` by
+/// `is_direct_expn_of`.
+#[must_use]
+pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+ }
+
+ None
+}
+
+/// Convenience function to get the return type of a function.
+pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
+ cx.tcx.erase_late_bound_regions(ret_ty)
+}
+
+/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
+pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool {
+ ty.walk().any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
+/// Returns `true` if the given type is an `unsafe` function.
+pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe,
+ _ => false,
+ }
+}
+
+pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env)
+}
+
+/// Checks if an expression is constructing a tuple-like enum variant or struct
+pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(ref fun, _) = expr.kind {
+ if let ExprKind::Path(ref qp) = fun.kind {
+ let res = cx.qpath_res(qp, fun.hir_id);
+ return match res {
+ def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
+ def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
+ _ => false,
+ };
+ }
+ }
+ false
+}
+
+/// Returns `true` if a pattern is refutable.
+// TODO: should be implemented using rustc/mir_build/thir machinery
+pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
+ fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
+ matches!(
+ cx.qpath_res(qpath, id),
+ def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
+ )
+ }
+
+ fn are_refutable<'a, I: Iterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, mut i: I) -> bool {
+ i.any(|pat| is_refutable(cx, pat))
+ }
+
+ match pat.kind {
+ PatKind::Wild => false,
+ PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
+ PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat),
+ PatKind::Lit(..) | PatKind::Range(..) => true,
+ PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
+ PatKind::Or(ref pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
+ PatKind::Struct(ref qpath, ref fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, ref pats, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Slice(ref head, ref middle, ref tail) => {
+ match &cx.typeck_results().node_type(pat.hir_id).kind() {
+ ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)),
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
+/// implementations have.
+pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| attr.has_name(sym::automatically_derived))
+}
+
+/// Remove blocks around an expression.
+///
+/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return
+/// themselves.
+pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ while let ExprKind::Block(ref block, ..) = expr.kind {
+ match (block.stmts.is_empty(), block.expr.as_ref()) {
+ (true, Some(e)) => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+pub fn is_self(slf: &Param<'_>) -> bool {
+ if let PatKind::Binding(.., name, _) = slf.pat.kind {
+ name.name == kw::SelfLower
+ } else {
+ false
+ }
+}
+
+pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind;
+ if let Res::SelfTy(..) = path.res;
+ then {
+ return true
+ }
+ }
+ false
+}
+
+pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
+ (0..decl.inputs.len()).map(move |i| &body.params[i])
+}
+
+/// Checks if a given expression is a match expression expanded from the `?`
+/// operator or the `try` macro.
+pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind;
+ if match_qpath(path, &paths::RESULT_OK[1..]);
+ if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
+ if path_to_local_id(arm.body, hir_id);
+ then {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn is_err(arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ match_qpath(path, &paths::RESULT_ERR[1..])
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, ref arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if let MatchSource::TryDesugar = *source {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(&arms[0]) && is_err(&arms[1])) ||
+ (is_ok(&arms[1]) && is_err(&arms[0]));
+ then {
+ return Some(expr);
+ }
+ }
+ }
+
+ None
+}
+
+/// Returns `true` if the lint is allowed in the current context
+///
+/// Useful for skipping long running code when it's unnecessary
+pub fn is_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
+ cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
+}
+
+pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
+ while let PatKind::Ref(subpat, _) = pat.kind {
+ pat = subpat;
+ }
+ pat
+}
+
+pub fn int_bits(tcx: TyCtxt<'_>, ity: ty::IntTy) -> u64 {
+ Integer::from_int_ty(&tcx, ity).size().bits()
+}
+
+#[allow(clippy::cast_possible_wrap)]
+/// Turn a constant int byte representation into an i128
+pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ty::IntTy) -> i128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as i128) << amt) >> amt
+}
+
+#[allow(clippy::cast_sign_loss)]
+/// clip unused bytes
+pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ty::IntTy) -> u128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as u128) << amt) >> amt
+}
+
+/// clip unused bytes
+pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: ty::UintTy) -> u128 {
+ let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
+/// Removes block comments from the given `Vec` of lines.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// without_block_comments(vec!["/*", "foo", "*/"]);
+/// // => vec![]
+///
+/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
+/// // => vec!["bar"]
+/// ```
+pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
+ let mut without = vec![];
+
+ let mut nest_level = 0;
+
+ for line in lines {
+ if line.contains("/*") {
+ nest_level += 1;
+ continue;
+ } else if line.contains("*/") {
+ nest_level -= 1;
+ continue;
+ }
+
+ if nest_level == 0 {
+ without.push(line);
+ }
+ }
+
+ without
+}
+
+pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
+ if is_automatically_derived(map.attrs(enclosing_node)) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.get_parent_item(enclosing_node);
+ }
+ false
+}
+
+/// Returns true if ty has `iter` or `iter_mut` methods
+pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> {
+ // FIXME: instead of this hard-coded list, we should check if `<adt>::iter`
+ // exists and has the desired signature. Unfortunately FnCtxt is not exported
+ // so we can't use its `lookup_method` method.
+ let into_iter_collections: [&[&str]; 13] = [
+ &paths::VEC,
+ &paths::OPTION,
+ &paths::RESULT,
+ &paths::BTREESET,
+ &paths::BTREEMAP,
+ &paths::VEC_DEQUE,
+ &paths::LINKED_LIST,
+ &paths::BINARY_HEAP,
+ &paths::HASHSET,
+ &paths::HASHMAP,
+ &paths::PATH_BUF,
+ &paths::PATH,
+ &paths::RECEIVER,
+ ];
+
+ let ty_to_check = match probably_ref_ty.kind() {
+ ty::Ref(_, ty_to_check, _) => ty_to_check,
+ _ => probably_ref_ty,
+ };
+
+ let def_id = match ty_to_check.kind() {
+ ty::Array(..) => return Some("array"),
+ ty::Slice(..) => return Some("slice"),
+ ty::Adt(adt, _) => adt.did,
+ _ => return None,
+ };
+
+ for path in &into_iter_collections {
+ if match_def_path(cx, def_id, path) {
+ return Some(*path.last().unwrap());
+ }
+ }
+ None
+}
+
+/// Matches a function call with the given path and returns the arguments.
+///
+/// Usage:
+///
+/// ```rust,ignore
+/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
+/// ```
+pub fn match_function_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ path: &[&str],
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(ref fun, ref args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ if match_def_path(cx, fun_def_id, path);
+ then {
+ return Some(&args)
+ }
+ };
+ None
+}
+
+/// Checks if `Ty` is normalizable. This function is useful
+/// to avoid crashes on `layout_of`.
+pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ infcx.at(&cause, param_env).normalize(ty).is_ok()
+ })
+}
+
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+ // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path`
+ // accepts only that. We should probably move to Symbols in Clippy as well.
+ let syms = syms.iter().map(|p| Symbol::intern(p)).collect::<Vec<Symbol>>();
+ cx.match_def_path(did, &syms)
+}
+
+pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> {
+ match_function_call(cx, expr, &paths::BEGIN_PANIC)
+ .or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT))
+ .or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY))
+ .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC))
+ .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT))
+ .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR))
+}
+
+pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
+ match_def_path(cx, did, &paths::BEGIN_PANIC)
+ || match_def_path(cx, did, &paths::BEGIN_PANIC_FMT)
+ || match_def_path(cx, did, &paths::PANIC_ANY)
+ || match_def_path(cx, did, &paths::PANICKING_PANIC)
+ || match_def_path(cx, did, &paths::PANICKING_PANIC_FMT)
+ || match_def_path(cx, did, &paths::PANICKING_PANIC_STR)
+}
+
+/// Returns the list of condition expressions and the list of blocks in a
+/// sequence of `if/else`.
+/// E.g., this returns `([a, b], [c, d, e])` for the expression
+/// `if a { c } else if b { d } else { e }`.
+pub fn if_sequence<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) {
+ let mut conds = SmallVec::new();
+ let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new();
+
+ while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind {
+ conds.push(&**cond);
+ if let ExprKind::Block(ref block, _) = then_expr.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(ref else_expr) = *else_expr {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(ref block, _) = expr.kind {
+ blocks.push(&**block);
+ }
+ }
+
+ (conds, blocks)
+}
+
+pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
+ let map = cx.tcx.hir();
+ let parent_id = map.get_parent_node(expr.hir_id);
+ let parent_node = map.get(parent_id);
+ matches!(
+ parent_node,
+ Node::Expr(Expr {
+ kind: ExprKind::If(_, _, _),
+ ..
+ })
+ )
+}
+
+// Finds the attribute with the given name, if any
+pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> {
+ attrs
+ .iter()
+ .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name))
+}
+
+// Finds the `#[must_use]` attribute, if any
+pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
+ attr_by_name(attrs, "must_use")
+}
+
+// Returns whether the type has #[must_use] attribute
+pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ match ty.kind() {
+ ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(),
+ ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(),
+ ty::Slice(ref ty)
+ | ty::Array(ref ty, _)
+ | ty::RawPtr(ty::TypeAndMut { ref ty, .. })
+ | ty::Ref(_, ref ty, _) => {
+ // for the Array case we don't need to care for the len == 0 case
+ // because we don't want to lint functions returning empty arrays
+ is_must_use_ty(cx, *ty)
+ },
+ ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)),
+ ty::Opaque(ref def_id, _) => {
+ for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
+ if let ty::PredicateKind::Trait(trait_predicate, _) = predicate.kind().skip_binder() {
+ if must_use_attr(&cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ ty::Dynamic(binder, _) => {
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
+ if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+// check if expr is calling method or function with #[must_use] attribute
+pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let did = match expr.kind {
+ ExprKind::Call(ref path, _) => if_chain! {
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id);
+ then {
+ Some(did)
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+
+ did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
+}
+
+pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
+ krate.item.attrs.iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr.path == sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
+/// Check if parent of a hir node is a trait implementation block.
+/// For example, `f` in
+/// ```rust,ignore
+/// impl Trait for S {
+/// fn f() {}
+/// }
+/// ```
+pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ }
+}
+
+/// Check if it's even possible to satisfy the `where` clause for the item.
+///
+/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example:
+///
+/// ```ignore
+/// fn foo() where i32: Iterator {
+/// for _ in 2i32 {}
+/// }
+/// ```
+pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
+ use rustc_trait_selection::traits;
+ let predicates =
+ cx.tcx
+ .predicates_of(did)
+ .predicates
+ .iter()
+ .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
+ traits::impossible_predicates(
+ cx.tcx,
+ traits::elaborate_predicates(cx.tcx, predicates)
+ .map(|o| o.predicate)
+ .collect::<Vec<_>>(),
+ )
+}
+
+/// Returns the `DefId` of the callee if the given expression is a function or method call.
+pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
+ match &expr.kind {
+ ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(qpath),
+ hir_id: path_hir_id,
+ ..
+ },
+ ..,
+ ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(),
+ _ => None,
+ }
+}
+
+pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool {
+ lints.iter().any(|lint| {
+ matches!(
+ cx.tcx.lint_level_at_node(lint, id),
+ (Level::Forbid | Level::Deny | Level::Warn, _)
+ )
+ })
+}
+
+/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
+/// number type, a str, or an array, slice, or tuple of those types).
+pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
+ match ty.kind() {
+ ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
+ ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true,
+ ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
+ ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type),
+ _ => false,
+ }
+}
+
+/// Returns Option<String> where String is a textual representation of the type encapsulated in the
+/// slice iff the given expression is a slice of primitives (as defined in the
+/// `is_recursively_primitive_type` function) and None otherwise.
+pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
+ let expr_kind = expr_type.kind();
+ let is_primitive = match expr_kind {
+ ty::Slice(element_type) => is_recursively_primitive_type(element_type),
+ ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &ty::Slice(_)) => {
+ if let ty::Slice(element_type) = inner_ty.kind() {
+ is_recursively_primitive_type(element_type)
+ } else {
+ unreachable!()
+ }
+ },
+ _ => false,
+ };
+
+ if is_primitive {
+ // if we have wrappers like Array, Slice or Tuple, print these
+ // and get the type enclosed in the slice ref
+ match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
+ ty::Slice(..) => return Some("slice".into()),
+ ty::Array(..) => return Some("array".into()),
+ ty::Tuple(..) => return Some("tuple".into()),
+ _ => {
+ // is_recursively_primitive_type() should have taken care
+ // of the rest and we can rely on the type that is found
+ let refs_peeled = expr_type.peel_refs();
+ return Some(refs_peeled.walk().last().unwrap().to_string());
+ },
+ }
+ }
+ None
+}
+
+/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
+/// `hash` must be comformed with `eq`
+pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
+where
+ Hash: Fn(&T) -> u64,
+ Eq: Fn(&T, &T) -> bool,
+{
+ if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) {
+ return vec![(&exprs[0], &exprs[1])];
+ }
+
+ let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+ let mut map: FxHashMap<_, Vec<&_>> =
+ FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+
+ for expr in exprs {
+ match map.entry(hash(expr)) {
+ Entry::Occupied(mut o) => {
+ for o in o.get() {
+ if eq(o, expr) {
+ match_expr_list.push((o, expr));
+ }
+ }
+ o.get_mut().push(expr);
+ },
+ Entry::Vacant(v) => {
+ v.insert(vec![expr]);
+ },
+ }
+ }
+
+ match_expr_list
+}
+
+/// Peels off all references on the pattern. Returns the underlying pattern and the number of
+/// references removed.
+pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+ fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
+ if let PatKind::Ref(pat, _) = pat.kind {
+ peel(pat, count + 1)
+ } else {
+ (pat, count)
+ }
+ }
+ peel(pat, 0)
+}
+
+/// Peels off up to the given number of references on the expression. Returns the underlying
+/// expression and the number of references removed.
+pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
+ match expr.kind {
+ ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
+ _ => (expr, count),
+ }
+ }
+ f(expr, 0, count)
+}
+
+/// Peels off all references on the expression. Returns the underlying expression and the number of
+/// references removed.
+pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
+ fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ match expr.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
+ _ => (expr, count),
+ }
+ }
+ f(expr, 0)
+}
+
+/// Peels off all references on the type. Returns the underlying type and the number of references
+/// removed.
+pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
+ if let ty::Ref(_, ty, _) = ty.kind() {
+ peel(ty, count + 1)
+ } else {
+ (ty, count)
+ }
+ }
+ peel(ty, 0)
+}
+
+/// Peels off all references on the type.Returns the underlying type, the number of references
+/// removed, and whether the pointer is ultimately mutable or not.
+pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
+ fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) {
+ match ty.kind() {
+ ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability),
+ ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not),
+ _ => (ty, count, mutability),
+ }
+ }
+ f(ty, 0, Mutability::Mut)
+}
+
+#[macro_export]
+macro_rules! unwrap_cargo_metadata {
+ ($cx: ident, $lint: ident, $deps: expr) => {{
+ let mut command = cargo_metadata::MetadataCommand::new();
+ if !$deps {
+ command.no_deps();
+ }
+
+ match command.exec() {
+ Ok(metadata) => metadata,
+ Err(err) => {
+ span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err));
+ return;
+ },
+ }
+ }};
+}
+
+pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
+ if let Res::Def(_, def_id) = path.res;
+ then {
+ cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr)
+ } else {
+ false
+ }
+ }
+}
+
+/// Check if the resolution of a given path is an `Ok` variant of `Result`.
+pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
+ if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
+ if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
+ if let Some(variant_id) = cx.tcx.parent(id) {
+ return variant_id == ok_id;
+ }
+ }
+ }
+ false
+}
+
+/// Check if the resolution of a given path is a `Some` variant of `Option`.
+pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
+ if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
+ if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
+ if let Some(variant_id) = cx.tcx.parent(id) {
+ return variant_id == some_id;
+ }
+ }
+ }
+ false
+}
+
+#[cfg(test)]
+mod test {
+ use super::{reindent_multiline, without_block_comments};
+
+ #[test]
+ fn test_reindent_multiline_single_line() {
+ assert_eq!("", reindent_multiline("".into(), false, None));
+ assert_eq!("...", reindent_multiline("...".into(), false, None));
+ assert_eq!("...", reindent_multiline(" ...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", reindent_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_lines_deeper() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline("\
+ if x {
+ y
+ } else {
+ z
+ }".into(), true, Some(8)));
+ }
+
+ #[test]
+ fn test_without_block_comments_lines_without_block_comments() {
+ let result = without_block_comments(vec!["/*", "", "*/"]);
+ println!("result: {:?}", result);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
+ assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
+
+ let result = without_block_comments(vec!["/* rust", "", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* one-line comment */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["foo", "bar", "baz"]);
+ assert_eq!(result, vec!["foo", "bar", "baz"]);
+ }
+}
--- /dev/null
+use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum Radix {
+ Binary,
+ Octal,
+ Decimal,
+ Hexadecimal,
+}
+
+impl Radix {
+ /// Returns a reasonable digit group size for this radix.
+ #[must_use]
+ fn suggest_grouping(self) -> usize {
+ match self {
+ Self::Binary | Self::Hexadecimal => 4,
+ Self::Octal | Self::Decimal => 3,
+ }
+ }
+}
+
+/// A helper method to format numeric literals with digit grouping.
+/// `lit` must be a valid numeric literal without suffix.
+pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
+ NumericLiteral::new(lit, type_suffix, float).format()
+}
+
+#[derive(Debug)]
+pub struct NumericLiteral<'a> {
+ /// Which radix the literal was represented in.
+ pub radix: Radix,
+ /// The radix prefix, if present.
+ pub prefix: Option<&'a str>,
+
+ /// The integer part of the number.
+ pub integer: &'a str,
+ /// The fraction part of the number.
+ pub fraction: Option<&'a str>,
+ /// The exponent separator (b'e' or b'E') including preceding underscore if present
+ /// and the exponent part.
+ pub exponent: Option<(&'a str, &'a str)>,
+
+ /// The type suffix, including preceding underscore if present.
+ pub suffix: Option<&'a str>,
+}
+
+impl<'a> NumericLiteral<'a> {
+ pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
+ NumericLiteral::from_lit_kind(src, &lit.kind)
+ }
+
+ pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
+ if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
+ let (unsuffixed, suffix) = split_suffix(&src, lit_kind);
+ let float = matches!(lit_kind, LitKind::Float(..));
+ Some(NumericLiteral::new(unsuffixed, suffix, float))
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
+ // Determine delimiter for radix prefix, if present, and radix.
+ let radix = if lit.starts_with("0x") {
+ Radix::Hexadecimal
+ } else if lit.starts_with("0b") {
+ Radix::Binary
+ } else if lit.starts_with("0o") {
+ Radix::Octal
+ } else {
+ Radix::Decimal
+ };
+
+ // Grab part of the literal after prefix, if present.
+ let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
+ (None, lit)
+ } else {
+ let (p, s) = lit.split_at(2);
+ (Some(p), s)
+ };
+
+ if suffix.is_some() && sans_prefix.ends_with('_') {
+ // The '_' before the suffix isn't part of the digits
+ sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
+ }
+
+ let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
+
+ Self {
+ radix,
+ prefix,
+ integer,
+ fraction,
+ exponent,
+ suffix,
+ }
+ }
+
+ pub fn is_decimal(&self) -> bool {
+ self.radix == Radix::Decimal
+ }
+
+ pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) {
+ let mut integer = digits;
+ let mut fraction = None;
+ let mut exponent = None;
+
+ if float {
+ for (i, c) in digits.char_indices() {
+ match c {
+ '.' => {
+ integer = &digits[..i];
+ fraction = Some(&digits[i + 1..]);
+ },
+ 'e' | 'E' => {
+ let exp_start = if digits[..i].ends_with('_') { i - 1 } else { i };
+
+ if integer.len() > exp_start {
+ integer = &digits[..exp_start];
+ } else {
+ fraction = Some(&digits[integer.len() + 1..exp_start]);
+ };
+ exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
+ break;
+ },
+ _ => {},
+ }
+ }
+ }
+
+ (integer, fraction, exponent)
+ }
+
+ /// Returns literal formatted in a sensible way.
+ pub fn format(&self) -> String {
+ let mut output = String::new();
+
+ if let Some(prefix) = self.prefix {
+ output.push_str(prefix);
+ }
+
+ let group_size = self.radix.suggest_grouping();
+
+ Self::group_digits(
+ &mut output,
+ self.integer,
+ group_size,
+ true,
+ self.radix == Radix::Hexadecimal,
+ );
+
+ if let Some(fraction) = self.fraction {
+ output.push('.');
+ Self::group_digits(&mut output, fraction, group_size, false, false);
+ }
+
+ if let Some((separator, exponent)) = self.exponent {
+ output.push_str(separator);
+ Self::group_digits(&mut output, exponent, group_size, true, false);
+ }
+
+ if let Some(suffix) = self.suffix {
+ output.push('_');
+ output.push_str(suffix);
+ }
+
+ output
+ }
+
+ pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
+ debug_assert!(group_size > 0);
+
+ let mut digits = input.chars().filter(|&c| c != '_');
+
+ let first_group_size;
+
+ if partial_group_first {
+ first_group_size = (digits.clone().count() - 1) % group_size + 1;
+ if pad {
+ for _ in 0..group_size - first_group_size {
+ output.push('0');
+ }
+ }
+ } else {
+ first_group_size = group_size;
+ }
+
+ for _ in 0..first_group_size {
+ if let Some(digit) = digits.next() {
+ output.push(digit);
+ }
+ }
+
+ for (c, i) in digits.zip((0..group_size).cycle()) {
+ if i == 0 {
+ output.push('_');
+ }
+ output.push(c);
+ }
+ }
+}
+
+fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
+ debug_assert!(lit_kind.is_numeric());
+ lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
+ let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
+ (unsuffixed, Some(suffix))
+ })
+}
+
+fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
+ debug_assert!(lit_kind.is_numeric());
+ let suffix = match lit_kind {
+ LitKind::Int(_, int_lit_kind) => match int_lit_kind {
+ LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
+ LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
+ LitIntType::Unsuffixed => None,
+ },
+ LitKind::Float(_, float_lit_kind) => match float_lit_kind {
+ LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
+ LitFloatType::Unsuffixed => None,
+ },
+ _ => None,
+ };
+
+ suffix.map(str::len)
+}
--- /dev/null
+//! This module contains paths to types and functions Clippy needs to know
+//! about.
+//!
+//! Whenever possible, please consider diagnostic items over hardcoded paths.
+//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
+
+pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
+pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
+pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
+pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
+pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"];
+pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
+pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
+pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
+pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
+pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
+pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
+pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
+pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
+pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
+pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
+pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
+pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
+pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
+pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
+pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
+pub const DROP: [&str; 3] = ["core", "mem", "drop"];
+pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
+#[cfg(feature = "internal-lints")]
+pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
+pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
+pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
+pub const FN: [&str; 3] = ["core", "ops", "Fn"];
+pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
+pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
+pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
+pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
+pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
+pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
+pub const INTO: [&str; 3] = ["core", "convert", "Into"];
+pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
+pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
+pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
+pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
+pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
+#[cfg(feature = "internal-lints")]
+pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
+#[cfg(feature = "internal-lints")]
+pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
+pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
+#[cfg(feature = "internal-lints")]
+pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
+pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
+pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
+pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
+pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
+pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
+pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
+pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
+pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
+pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
+pub const OPTION: [&str; 3] = ["core", "option", "Option"];
+pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
+pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
+pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
+pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
+pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
+pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
+pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
+pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
+pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
+pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
+pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
+pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
+pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
+pub const PATH: [&str; 3] = ["std", "path", "Path"];
+pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
+pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
+pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
+pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
+pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
+pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
+pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
+pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
+pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"];
+pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
+pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
+pub const RESULT: [&str; 3] = ["core", "result", "Result"];
+pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
+pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
+pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
+pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
+pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
+pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
+pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
+pub const STRING: [&str; 3] = ["alloc", "string", "String"];
+pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
+pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
+pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
+pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
+pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
+pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
+#[cfg(feature = "internal-lints")]
+pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
+#[cfg(feature = "internal-lints")]
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
+pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
+pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
+pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
+pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
+pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
+pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
+pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
+pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
+pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
+pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
--- /dev/null
+use crate::{get_pat_name, match_var, snippet};
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Body, BodyId, Expr, ExprKind, Param};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+use rustc_span::{Span, Symbol};
+use std::borrow::Cow;
+
+pub fn get_spans(
+ cx: &LateContext<'_>,
+ opt_body_id: Option<BodyId>,
+ idx: usize,
+ replacements: &[(&'static str, &'static str)],
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
+ if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
+ get_binding_name(&body.params[idx]).map_or_else(
+ || Some(vec![]),
+ |name| extract_clone_suggestions(cx, name, replacements, body),
+ )
+ } else {
+ Some(vec![])
+ }
+}
+
+fn extract_clone_suggestions<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: Symbol,
+ replace: &[(&'static str, &'static str)],
+ body: &'tcx Body<'_>,
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
+ let mut visitor = PtrCloneVisitor {
+ cx,
+ name,
+ replace,
+ spans: vec![],
+ abort: false,
+ };
+ visitor.visit_body(body);
+ if visitor.abort {
+ None
+ } else {
+ Some(visitor.spans)
+ }
+}
+
+struct PtrCloneVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ name: Symbol,
+ replace: &'a [(&'static str, &'static str)],
+ spans: Vec<(Span, Cow<'static, str>)>,
+ abort: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.abort {
+ return;
+ }
+ if let ExprKind::MethodCall(ref seg, _, ref args, _) = expr.kind {
+ if args.len() == 1 && match_var(&args[0], self.name) {
+ if seg.ident.name.as_str() == "capacity" {
+ self.abort = true;
+ return;
+ }
+ for &(fn_name, suffix) in self.replace {
+ if seg.ident.name.as_str() == fn_name {
+ self.spans
+ .push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
+ return;
+ }
+ }
+ }
+ }
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn get_binding_name(arg: &Param<'_>) -> Option<Symbol> {
+ get_pat_name(&arg.pat)
+}
--- /dev/null
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_middle::mir::{
+ Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
+ TerminatorKind,
+};
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use rustc_target::spec::abi::Abi::RustIntrinsic;
+use std::borrow::Cow;
+
+type McfResult = Result<(), (Span, Cow<'static, str>)>;
+
+pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
+ let def_id = body.source.def_id();
+ let mut current = def_id;
+ loop {
+ let predicates = tcx.predicates_of(current);
+ for (predicate, _) in predicates.predicates {
+ match predicate.kind().skip_binder() {
+ ty::PredicateKind::RegionOutlives(_)
+ | ty::PredicateKind::TypeOutlives(_)
+ | ty::PredicateKind::WellFormed(_)
+ | ty::PredicateKind::Projection(_)
+ | ty::PredicateKind::ConstEvaluatable(..)
+ | ty::PredicateKind::ConstEquate(..)
+ | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
+ ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
+ ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
+ ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
+ ty::PredicateKind::Trait(pred, _) => {
+ if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
+ continue;
+ }
+ match pred.self_ty().kind() {
+ ty::Param(ref p) => {
+ let generics = tcx.generics_of(current);
+ let def = generics.type_param(p, tcx);
+ let span = tcx.def_span(def.def_id);
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ },
+ // other kinds of bounds are either tautologies
+ // or cause errors in other passes
+ _ => continue,
+ }
+ },
+ }
+ }
+ match predicates.parent {
+ Some(parent) => current = parent,
+ None => break,
+ }
+ }
+
+ for local in &body.local_decls {
+ check_ty(tcx, local.ty, local.source_info.span)?;
+ }
+ // impl trait is gone in MIR, so check the return type manually
+ check_ty(
+ tcx,
+ tcx.fn_sig(def_id).output().skip_binder(),
+ body.local_decls.iter().next().unwrap().source_info.span,
+ )?;
+
+ for bb in body.basic_blocks() {
+ check_terminator(tcx, body, bb.terminator())?;
+ for stmt in &bb.statements {
+ check_statement(tcx, body, def_id, stmt)?;
+ }
+ }
+ Ok(())
+}
+
+fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
+ for arg in ty.walk() {
+ let ty = match arg.unpack() {
+ GenericArgKind::Type(ty) => ty,
+
+ // No constraints on lifetimes or constants, except potentially
+ // constants' types, but `walk` will get to them as well.
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
+ };
+
+ match ty.kind() {
+ ty::Ref(_, _, hir::Mutability::Mut) => {
+ return Err((span, "mutable references in const fn are unstable".into()));
+ },
+ ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
+ ty::FnPtr(..) => {
+ return Err((span, "function pointers in const fn are unstable".into()));
+ },
+ ty::Dynamic(preds, _) => {
+ for pred in preds.iter() {
+ match pred.skip_binder() {
+ ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ },
+ ty::ExistentialPredicate::Trait(trait_ref) => {
+ if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ }
+ },
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ Ok(())
+}
+
+fn check_rvalue(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span) -> McfResult {
+ match rvalue {
+ Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
+ Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => check_operand(tcx, operand, span, body),
+ Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+ check_place(tcx, *place, span, body)
+ },
+ Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
+ use rustc_middle::ty::cast::CastTy;
+ let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
+ let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
+ match (cast_in, cast_out) {
+ (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
+ Err((span, "casting pointers to ints is unstable in const fn".into()))
+ },
+ _ => check_operand(tcx, operand, span, body),
+ }
+ },
+ Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _) => {
+ check_operand(tcx, operand, span, body)
+ },
+ Rvalue::Cast(
+ CastKind::Pointer(
+ PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
+ ),
+ _,
+ _,
+ ) => Err((span, "function pointer casts are not allowed in const fn".into())),
+ Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
+ let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
+ deref_ty.ty
+ } else {
+ // We cannot allow this for now.
+ return Err((span, "unsizing casts are only allowed for references right now".into()));
+ };
+ let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
+ if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
+ check_operand(tcx, op, span, body)?;
+ // Casting/coercing things to slices is fine.
+ Ok(())
+ } else {
+ // We just can't allow trait objects until we have figured out trait method calls.
+ Err((span, "unsizing casts are not allowed in const fn".into()))
+ }
+ },
+ // binops are fine on integers
+ Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
+ check_operand(tcx, lhs, span, body)?;
+ check_operand(tcx, rhs, span, body)?;
+ let ty = lhs.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() || ty.is_char() {
+ Ok(())
+ } else {
+ Err((
+ span,
+ "only int, `bool` and `char` operations are stable in const fn".into(),
+ ))
+ }
+ },
+ Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
+ Rvalue::NullaryOp(NullOp::Box, _) => Err((span, "heap allocations are not allowed in const fn".into())),
+ Rvalue::UnaryOp(_, operand) => {
+ let ty = operand.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() {
+ check_operand(tcx, operand, span, body)
+ } else {
+ Err((span, "only int and `bool` operations are stable in const fn".into()))
+ }
+ },
+ Rvalue::Aggregate(_, operands) => {
+ for operand in operands {
+ check_operand(tcx, operand, span, body)?;
+ }
+ Ok(())
+ },
+ }
+}
+
+fn check_statement(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>) -> McfResult {
+ let span = statement.source_info.span;
+ match &statement.kind {
+ StatementKind::Assign(box (place, rval)) => {
+ check_place(tcx, *place, span, body)?;
+ check_rvalue(tcx, body, def_id, rval, span)
+ },
+
+ StatementKind::FakeRead(_, place) |
+ // just an assignment
+ StatementKind::SetDiscriminant { place, .. } => check_place(tcx, **place, span, body),
+
+ StatementKind::LlvmInlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
+
+ // These are all NOPs
+ StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Retag { .. }
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Coverage(..)
+ | StatementKind::Nop => Ok(()),
+ }
+}
+
+fn check_operand(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ match operand {
+ Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
+ Operand::Constant(c) => match c.check_static_ptr(tcx) {
+ Some(_) => Err((span, "cannot access `static` items in const fn".into())),
+ None => Ok(()),
+ },
+ }
+}
+
+fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ let mut cursor = place.projection.as_ref();
+ while let [ref proj_base @ .., elem] = *cursor {
+ cursor = proj_base;
+ match elem {
+ ProjectionElem::Field(..) => {
+ let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
+ if let Some(def) = base_ty.ty_adt_def() {
+ // No union field accesses in `const fn`
+ if def.is_union() {
+ return Err((span, "accessing union fields is unstable".into()));
+ }
+ }
+ },
+ ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Downcast(..)
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Deref
+ | ProjectionElem::Index(_) => {},
+ }
+ }
+
+ Ok(())
+}
+
+fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult {
+ let span = terminator.source_info.span;
+ match &terminator.kind {
+ TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::Return
+ | TerminatorKind::Resume
+ | TerminatorKind::Unreachable => Ok(()),
+
+ TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
+ TerminatorKind::DropAndReplace { place, value, .. } => {
+ check_place(tcx, *place, span, body)?;
+ check_operand(tcx, value, span, body)
+ },
+
+ TerminatorKind::SwitchInt {
+ discr,
+ switch_ty: _,
+ targets: _,
+ } => check_operand(tcx, discr, span, body),
+
+ TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
+ TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
+ Err((span, "const fn generators are unstable".into()))
+ },
+
+ TerminatorKind::Call {
+ func,
+ args,
+ from_hir_call: _,
+ destination: _,
+ cleanup: _,
+ fn_span: _,
+ } => {
+ let fn_ty = func.ty(body, tcx);
+ if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
+ if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) {
+ return Err((
+ span,
+ format!(
+ "can only call other `const fn` within a `const fn`, \
+ but `{:?}` is not stable as `const fn`",
+ func,
+ )
+ .into(),
+ ));
+ }
+
+ // HACK: This is to "unstabilize" the `transmute` intrinsic
+ // within const fns. `transmute` is allowed in all other const contexts.
+ // This won't really scale to more intrinsics or functions. Let's allow const
+ // transmutes in const fn before we add more hacks to this.
+ if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute {
+ return Err((
+ span,
+ "can only call `transmute` from const items, not `const fn`".into(),
+ ));
+ }
+
+ check_operand(tcx, func, span, body)?;
+
+ for arg in args {
+ check_operand(tcx, arg, span, body)?;
+ }
+ Ok(())
+ } else {
+ Err((span, "can only call other const fns within const fn".into()))
+ }
+ },
+
+ TerminatorKind::Assert {
+ cond,
+ expected: _,
+ msg: _,
+ target: _,
+ cleanup: _,
+ } => check_operand(tcx, cond, span, body),
+
+ TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
+ }
+}
--- /dev/null
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
+use rustc_ast::util::parser::AssocOp;
+use rustc_ast::{ast, token};
+use rustc_ast_pretty::pprust::token_kind_to_string;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{EarlyContext, LateContext, LintContext};
+use rustc_span::source_map::{CharPos, Span};
+use rustc_span::{BytePos, Pos};
+use std::borrow::Cow;
+use std::convert::TryInto;
+use std::fmt::Display;
+use std::ops::{Add, Neg, Not, Sub};
+
+/// A helper type to build suggestion correctly handling parenthesis.
+#[derive(Clone, PartialEq)]
+pub enum Sugg<'a> {
+ /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
+ NonParen(Cow<'a, str>),
+ /// An expression that does not fit in other variants.
+ MaybeParen(Cow<'a, str>),
+ /// A binary operator expression, including `as`-casts and explicit type
+ /// coercion.
+ BinOp(AssocOp, Cow<'a, str>),
+}
+
+/// Literal constant `0`, for convenience.
+pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
+/// Literal constant `1`, for convenience.
+pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
+/// a constant represents an empty string, for convenience.
+pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
+
+impl Display for Sugg<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ match *self {
+ Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
+ }
+ }
+}
+
+#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
+impl<'a> Sugg<'a> {
+ /// Prepare a suggestion from an expression.
+ pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
+ snippet_opt(cx, expr.span).map(|snippet| {
+ let snippet = Cow::Owned(snippet);
+ Self::hir_from_snippet(expr, snippet)
+ })
+ }
+
+ /// Convenience function around `hir_opt` for suggestions with a default
+ /// text.
+ pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
+ }
+
+ /// Same as `hir`, but it adapts the applicability level by following rules:
+ ///
+ /// - Applicability level `Unspecified` will never be changed.
+ /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+ /// - If the default value is used and the applicability level is `MachineApplicable`, change it
+ /// to
+ /// `HasPlaceholders`
+ pub fn hir_with_applicability(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ Self::hir_opt(cx, expr).unwrap_or_else(|| {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Sugg::NonParen(Cow::Borrowed(default))
+ })
+ }
+
+ /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
+ pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ let snippet = snippet_with_macro_callsite(cx, expr.span, default);
+
+ Self::hir_from_snippet(expr, snippet)
+ }
+
+ /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
+ /// function variants of `Sugg`, since these use different snippet functions.
+ fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
+ if let Some(range) = higher::range(expr) {
+ let op = match range.limits {
+ ast::RangeLimits::HalfOpen => AssocOp::DotDot,
+ ast::RangeLimits::Closed => AssocOp::DotDotEq,
+ };
+ return Sugg::BinOp(op, snippet);
+ }
+
+ match expr.kind {
+ hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Closure(..)
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
+ hir::ExprKind::Continue(..)
+ | hir::ExprKind::Yield(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Call(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Index(..)
+ | hir::ExprKind::InlineAsm(..)
+ | hir::ExprKind::LlvmInlineAsm(..)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Lit(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Path(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::DropTemps(_)
+ | hir::ExprKind::Err => Sugg::NonParen(snippet),
+ hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
+ hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
+ hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
+ hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
+ hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
+ }
+ }
+
+ /// Prepare a suggestion from an expression.
+ pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
+ use rustc_ast::ast::RangeLimits;
+
+ let snippet = if expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, expr.span, default)
+ } else {
+ snippet(cx, expr.span, default)
+ };
+
+ match expr.kind {
+ ast::ExprKind::AddrOf(..)
+ | ast::ExprKind::Box(..)
+ | ast::ExprKind::Closure(..)
+ | ast::ExprKind::If(..)
+ | ast::ExprKind::Let(..)
+ | ast::ExprKind::Unary(..)
+ | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
+ ast::ExprKind::Async(..)
+ | ast::ExprKind::Block(..)
+ | ast::ExprKind::Break(..)
+ | ast::ExprKind::Call(..)
+ | ast::ExprKind::Continue(..)
+ | ast::ExprKind::Yield(..)
+ | ast::ExprKind::Field(..)
+ | ast::ExprKind::ForLoop(..)
+ | ast::ExprKind::Index(..)
+ | ast::ExprKind::InlineAsm(..)
+ | ast::ExprKind::LlvmInlineAsm(..)
+ | ast::ExprKind::ConstBlock(..)
+ | ast::ExprKind::Lit(..)
+ | ast::ExprKind::Loop(..)
+ | ast::ExprKind::MacCall(..)
+ | ast::ExprKind::MethodCall(..)
+ | ast::ExprKind::Paren(..)
+ | ast::ExprKind::Underscore
+ | ast::ExprKind::Path(..)
+ | ast::ExprKind::Repeat(..)
+ | ast::ExprKind::Ret(..)
+ | ast::ExprKind::Struct(..)
+ | ast::ExprKind::Try(..)
+ | ast::ExprKind::TryBlock(..)
+ | ast::ExprKind::Tup(..)
+ | ast::ExprKind::Array(..)
+ | ast::ExprKind::While(..)
+ | ast::ExprKind::Await(..)
+ | ast::ExprKind::Err => Sugg::NonParen(snippet),
+ ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
+ ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
+ ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
+ ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
+ ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
+ ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
+ ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
+ }
+ }
+
+ /// Convenience method to create the `<lhs> && <rhs>` suggestion.
+ pub fn and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::And, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> & <rhs>` suggestion.
+ pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::BitAnd, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> as <rhs>` suggestion.
+ pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
+ make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
+ }
+
+ /// Convenience method to create the `&<expr>` suggestion.
+ pub fn addr(self) -> Sugg<'static> {
+ make_unop("&", self)
+ }
+
+ /// Convenience method to create the `&mut <expr>` suggestion.
+ pub fn mut_addr(self) -> Sugg<'static> {
+ make_unop("&mut ", self)
+ }
+
+ /// Convenience method to create the `*<expr>` suggestion.
+ pub fn deref(self) -> Sugg<'static> {
+ make_unop("*", self)
+ }
+
+ /// Convenience method to create the `&*<expr>` suggestion. Currently this
+ /// is needed because `sugg.deref().addr()` produces an unnecessary set of
+ /// parentheses around the deref.
+ pub fn addr_deref(self) -> Sugg<'static> {
+ make_unop("&*", self)
+ }
+
+ /// Convenience method to create the `&mut *<expr>` suggestion. Currently
+ /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
+ /// set of parentheses around the deref.
+ pub fn mut_addr_deref(self) -> Sugg<'static> {
+ make_unop("&mut *", self)
+ }
+
+ /// Convenience method to transform suggestion into a return call
+ pub fn make_return(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("return {}", self)))
+ }
+
+ /// Convenience method to transform suggestion into a block
+ /// where the suggestion is a trailing expression
+ pub fn blockify(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
+ }
+
+ /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
+ /// suggestion.
+ #[allow(dead_code)]
+ pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
+ match limit {
+ ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
+ ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
+ }
+ }
+
+ /// Adds parenthesis to any expression that might need them. Suitable to the
+ /// `self` argument of a method call
+ /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
+ pub fn maybe_par(self) -> Self {
+ match self {
+ Sugg::NonParen(..) => self,
+ // `(x)` and `(x).y()` both don't need additional parens.
+ Sugg::MaybeParen(sugg) => {
+ if sugg.starts_with('(') && sugg.ends_with(')') {
+ Sugg::MaybeParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({})", sugg).into())
+ }
+ },
+ Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
+ }
+ }
+}
+
+// Copied from the rust standart library, and then edited
+macro_rules! forward_binop_impls_to_ref {
+ (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
+ impl $imp<$t> for &$t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(self, &other)
+ }
+ }
+
+ impl $imp<&$t> for $t {
+ type Output = $o;
+
+ fn $method(self, other: &$t) -> $o {
+ $imp::$method(&self, other)
+ }
+ }
+
+ impl $imp for $t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(&self, &other)
+ }
+ }
+ };
+}
+
+impl Add for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Add, self, rhs)
+ }
+}
+
+impl Sub for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Sub, self, rhs)
+ }
+}
+
+forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
+forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
+
+impl Neg for Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn neg(self) -> Sugg<'static> {
+ make_unop("-", self)
+ }
+}
+
+impl Not for Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn not(self) -> Sugg<'static> {
+ make_unop("!", self)
+ }
+}
+
+/// Helper type to display either `foo` or `(foo)`.
+struct ParenHelper<T> {
+ /// `true` if parentheses are needed.
+ paren: bool,
+ /// The main thing to display.
+ wrapped: T,
+}
+
+impl<T> ParenHelper<T> {
+ /// Builds a `ParenHelper`.
+ fn new(paren: bool, wrapped: T) -> Self {
+ Self { paren, wrapped }
+ }
+}
+
+impl<T: Display> Display for ParenHelper<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ if self.paren {
+ write!(f, "({})", self.wrapped)
+ } else {
+ self.wrapped.fmt(f)
+ }
+ }
+}
+
+/// Builds the string for `<op><expr>` adding parenthesis when necessary.
+///
+/// For convenience, the operator is taken as a string because all unary
+/// operators have the same
+/// precedence.
+pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
+ Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
+}
+
+/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
+///
+/// Precedence of shift operator relative to other arithmetic operation is
+/// often confusing so
+/// parenthesis will always be added for a mix of these.
+pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ /// Returns `true` if the operator is a shift operator `<<` or `>>`.
+ fn is_shift(op: AssocOp) -> bool {
+ matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+ }
+
+ /// Returns `true` if the operator is a arithmetic operator
+ /// (i.e., `+`, `-`, `*`, `/`, `%`).
+ fn is_arith(op: AssocOp) -> bool {
+ matches!(
+ op,
+ AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+ )
+ }
+
+ /// Returns `true` if the operator `op` needs parenthesis with the operator
+ /// `other` in the direction `dir`.
+ fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
+ other.precedence() < op.precedence()
+ || (other.precedence() == op.precedence()
+ && ((op != other && associativity(op) != dir)
+ || (op == other && associativity(op) != Associativity::Both)))
+ || is_shift(op) && is_arith(other)
+ || is_shift(other) && is_arith(op)
+ }
+
+ let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
+ needs_paren(op, lop, Associativity::Left)
+ } else {
+ false
+ };
+
+ let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
+ needs_paren(op, rop, Associativity::Right)
+ } else {
+ false
+ };
+
+ let lhs = ParenHelper::new(lhs_paren, lhs);
+ let rhs = ParenHelper::new(rhs_paren, rhs);
+ let sugg = match op {
+ AssocOp::Add
+ | AssocOp::BitAnd
+ | AssocOp::BitOr
+ | AssocOp::BitXor
+ | AssocOp::Divide
+ | AssocOp::Equal
+ | AssocOp::Greater
+ | AssocOp::GreaterEqual
+ | AssocOp::LAnd
+ | AssocOp::LOr
+ | AssocOp::Less
+ | AssocOp::LessEqual
+ | AssocOp::Modulus
+ | AssocOp::Multiply
+ | AssocOp::NotEqual
+ | AssocOp::ShiftLeft
+ | AssocOp::ShiftRight
+ | AssocOp::Subtract => format!(
+ "{} {} {}",
+ lhs,
+ op.to_ast_binop().expect("Those are AST ops").to_string(),
+ rhs
+ ),
+ AssocOp::Assign => format!("{} = {}", lhs, rhs),
+ AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
+ AssocOp::As => format!("{} as {}", lhs, rhs),
+ AssocOp::DotDot => format!("{}..{}", lhs, rhs),
+ AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
+ AssocOp::Colon => format!("{}: {}", lhs, rhs),
+ };
+
+ Sugg::BinOp(op, sugg.into())
+}
+
+/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
+pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+/// Operator associativity.
+enum Associativity {
+ /// The operator is both left-associative and right-associative.
+ Both,
+ /// The operator is left-associative.
+ Left,
+ /// The operator is not associative.
+ None,
+ /// The operator is right-associative.
+ Right,
+}
+
+/// Returns the associativity/fixity of an operator. The difference with
+/// `AssocOp::fixity` is that an operator can be both left and right associative
+/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
+///
+/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
+/// they are considered
+/// associative.
+#[must_use]
+fn associativity(op: AssocOp) -> Associativity {
+ use rustc_ast::util::parser::AssocOp::{
+ Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
+ GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+ };
+
+ match op {
+ Assign | AssignOp(_) => Associativity::Right,
+ Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
+ Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
+ | Subtract => Associativity::Left,
+ DotDot | DotDotEq => Associativity::None,
+ }
+}
+
+/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
+fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
+ use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
+
+ AssocOp::AssignOp(match op.node {
+ hir::BinOpKind::Add => Plus,
+ hir::BinOpKind::BitAnd => And,
+ hir::BinOpKind::BitOr => Or,
+ hir::BinOpKind::BitXor => Caret,
+ hir::BinOpKind::Div => Slash,
+ hir::BinOpKind::Mul => Star,
+ hir::BinOpKind::Rem => Percent,
+ hir::BinOpKind::Shl => Shl,
+ hir::BinOpKind::Shr => Shr,
+ hir::BinOpKind::Sub => Minus,
+
+ hir::BinOpKind::And
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
+fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
+ use rustc_ast::ast::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ use rustc_ast::token::BinOpToken;
+
+ AssocOp::AssignOp(match op.node {
+ Add => BinOpToken::Plus,
+ BitAnd => BinOpToken::And,
+ BitOr => BinOpToken::Or,
+ BitXor => BinOpToken::Caret,
+ Div => BinOpToken::Slash,
+ Mul => BinOpToken::Star,
+ Rem => BinOpToken::Percent,
+ Shl => BinOpToken::Shl,
+ Shr => BinOpToken::Shr,
+ Sub => BinOpToken::Minus,
+ And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Returns the indentation before `span` if there are nothing but `[ \t]`
+/// before it on its line.
+fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ let lo = cx.sess().source_map().lookup_char_pos(span.lo());
+ lo.file
+ .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
+ .and_then(|line| {
+ if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
+ // We can mix char and byte positions here because we only consider `[ \t]`.
+ if lo.col == CharPos(pos) {
+ Some(line[..pos].into())
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+}
+
+/// Convenience extension trait for `DiagnosticBuilder`.
+pub trait DiagnosticBuilderExt<T: LintContext> {
+ /// Suggests to add an attribute to an item.
+ ///
+ /// Correctly handles indentation of the attribute and item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
+ /// ```
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ );
+
+ /// Suggest to add an item before another.
+ ///
+ /// The item should not be indented (except for inner indentation).
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_prepend_item(cx, item,
+ /// "fn foo() {
+ /// bar();
+ /// }");
+ /// ```
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
+
+ /// Suggest to completely remove an item.
+ ///
+ /// This will remove an item and all following whitespace until the next non-whitespace
+ /// character. This should work correctly if item is on the same indentation level as the
+ /// following item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_remove_item(cx, item, "remove this")
+ /// ```
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
+}
+
+impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ ) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
+ }
+ }
+
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ let mut first = true;
+ let new_item = new_item
+ .lines()
+ .map(|l| {
+ if first {
+ first = false;
+ format!("{}\n", l)
+ } else {
+ format!("{}{}\n", indent, l)
+ }
+ })
+ .collect::<String>();
+
+ self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
+ }
+ }
+
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
+ let mut remove_span = item;
+ let hi = cx.sess().source_map().next_point(remove_span).hi();
+ let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
+
+ if let Some(ref src) = fmpos.sf.src {
+ let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
+
+ if let Some(non_whitespace_offset) = non_whitespace_offset {
+ remove_span = remove_span
+ .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
+ }
+ }
+
+ self.span_suggestion(remove_span, msg, String::new(), applicability);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Sugg;
+ use std::borrow::Cow;
+
+ const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
+
+ #[test]
+ fn make_return_transform_sugg_into_a_return_call() {
+ assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
+ }
+
+ #[test]
+ fn blockify_transforms_sugg_into_a_block() {
+ assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
+ }
+}
--- /dev/null
+#[macro_export]
+/// Convenience wrapper around rustc's `Symbol::intern`
+macro_rules! sym {
+ ($tt:tt) => {
+ rustc_span::symbol::Symbol::intern(stringify!($tt))
+ };
+}
--- /dev/null
+use crate as utils;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit;
+use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use rustc_hir::{Expr, ExprKind, HirId, Path};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+
+/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
+pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> {
+ let mut delegate = MutVarsDelegate {
+ used_mutably: FxHashSet::default(),
+ skip: false,
+ };
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ expr.hir_id.owner,
+ cx.param_env,
+ cx.typeck_results(),
+ )
+ .walk_expr(expr);
+ });
+
+ if delegate.skip {
+ return None;
+ }
+ Some(delegate.used_mutably)
+}
+
+pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
+ if let Res::Local(id) = variable.res {
+ mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
+ } else {
+ true
+ }
+}
+
+struct MutVarsDelegate {
+ used_mutably: FxHashSet<HirId>,
+ skip: bool,
+}
+
+impl<'tcx> MutVarsDelegate {
+ #[allow(clippy::similar_names)]
+ fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
+ match cat.place.base {
+ PlaceBase::Local(id) => {
+ self.used_mutably.insert(id);
+ },
+ PlaceBase::Upvar(_) => {
+ //FIXME: This causes false negatives. We can't get the `NodeId` from
+ //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
+ //`while`-body, not just the ones in the condition.
+ self.skip = true
+ },
+ _ => {},
+ }
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
+ if let ty::BorrowKind::MutBorrow = bk {
+ self.update(&cmt)
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ self.update(&cmt)
+ }
+}
+
+pub struct ParamBindingIdCollector {
+ binding_hir_ids: Vec<hir::HirId>,
+}
+impl<'tcx> ParamBindingIdCollector {
+ fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
+ let mut hir_ids: Vec<hir::HirId> = Vec::new();
+ for param in body.params.iter() {
+ let mut finder = ParamBindingIdCollector {
+ binding_hir_ids: Vec::new(),
+ };
+ finder.visit_param(param);
+ for hir_id in &finder.binding_hir_ids {
+ hir_ids.push(*hir_id);
+ }
+ }
+ hir_ids
+ }
+}
+impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
+ type Map = Map<'tcx>;
+
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
+ self.binding_hir_ids.push(hir_id);
+ }
+ intravisit::walk_pat(self, pat);
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+}
+
+pub struct BindingUsageFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ binding_ids: Vec<hir::HirId>,
+ usage_found: bool,
+}
+impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
+ pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
+ let mut finder = BindingUsageFinder {
+ cx,
+ binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
+ usage_found: false,
+ };
+ finder.visit_body(body);
+ finder.usage_found
+ }
+}
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if !self.usage_found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ if let hir::def::Res::Local(id) = path.res {
+ if self.binding_ids.contains(&id) {
+ self.usage_found = true;
+ }
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+struct ReturnBreakContinueMacroVisitor {
+ seen_return_break_continue: bool,
+}
+
+impl ReturnBreakContinueMacroVisitor {
+ fn new() -> ReturnBreakContinueMacroVisitor {
+ ReturnBreakContinueMacroVisitor {
+ seen_return_break_continue: false,
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+ if self.seen_return_break_continue {
+ // No need to look farther if we've already seen one of them
+ return;
+ }
+ match &ex.kind {
+ ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
+ self.seen_return_break_continue = true;
+ },
+ // Something special could be done here to handle while or for loop
+ // desugaring, as this will detect a break if there's a while loop
+ // or a for loop inside the expression.
+ _ => {
+ if utils::in_macro(ex.span) {
+ self.seen_return_break_continue = true;
+ } else {
+ rustc_hir::intravisit::walk_expr(self, ex);
+ }
+ },
+ }
+ }
+}
+
+pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
+ let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
+ recursive_visitor.visit_expr(expression);
+ recursive_visitor.seen_return_break_continue
+}
--- /dev/null
+use crate::path_to_local_id;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Arm, Body, Expr, HirId, Stmt};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+/// returns `true` if expr contains match expr desugared from try
+fn contains_try(expr: &hir::Expr<'_>) -> bool {
+ struct TryFinder {
+ found: bool,
+ }
+
+ impl<'hir> intravisit::Visitor<'hir> for TryFinder {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ if self.found {
+ return;
+ }
+ match expr.kind {
+ hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
+ _ => intravisit::walk_expr(self, expr),
+ }
+ }
+ }
+
+ let mut visitor = TryFinder { found: false };
+ visitor.visit_expr(expr);
+ visitor.found
+}
+
+pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
+where
+ F: FnMut(&'hir hir::Expr<'hir>) -> bool,
+{
+ struct RetFinder<F> {
+ in_stmt: bool,
+ failed: bool,
+ cb: F,
+ }
+
+ struct WithStmtGuarg<'a, F> {
+ val: &'a mut RetFinder<F>,
+ prev_in_stmt: bool,
+ }
+
+ impl<F> RetFinder<F> {
+ fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
+ let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
+ WithStmtGuarg {
+ val: self,
+ prev_in_stmt,
+ }
+ }
+ }
+
+ impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
+ type Target = RetFinder<F>;
+
+ fn deref(&self) -> &Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> Drop for WithStmtGuarg<'_, F> {
+ fn drop(&mut self) {
+ self.val.in_stmt = self.prev_in_stmt;
+ }
+ }
+
+ impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+
+ fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
+ intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
+ if self.failed {
+ return;
+ }
+ if self.in_stmt {
+ match expr.kind {
+ hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
+ _ => intravisit::walk_expr(self, expr),
+ }
+ } else {
+ match expr.kind {
+ hir::ExprKind::If(cond, then, else_opt) => {
+ self.inside_stmt(true).visit_expr(cond);
+ self.visit_expr(then);
+ if let Some(el) = else_opt {
+ self.visit_expr(el);
+ }
+ },
+ hir::ExprKind::Match(cond, arms, _) => {
+ self.inside_stmt(true).visit_expr(cond);
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
+ hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
+ _ => self.failed |= !(self.cb)(expr),
+ }
+ }
+ }
+ }
+
+ !contains_try(expr) && {
+ let mut ret_finder = RetFinder {
+ in_stmt: false,
+ failed: false,
+ cb: callback,
+ };
+ ret_finder.visit_expr(expr);
+ !ret_finder.failed
+ }
+}
+
+pub struct LocalUsedVisitor<'hir> {
+ hir: Map<'hir>,
+ pub local_hir_id: HirId,
+ pub used: bool,
+}
+
+impl<'hir> LocalUsedVisitor<'hir> {
+ pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
+ Self {
+ hir: cx.tcx.hir(),
+ local_hir_id,
+ used: false,
+ }
+ }
+
+ fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
+ visit(self, t);
+ std::mem::replace(&mut self.used, false)
+ }
+
+ pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
+ self.check(arm, Self::visit_arm)
+ }
+
+ pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
+ self.check(body, Self::visit_body)
+ }
+
+ pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
+ self.check(expr, Self::visit_expr)
+ }
+
+ pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
+ self.check(stmt, Self::visit_stmt)
+ }
+}
+
+impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
+ type Map = Map<'v>;
+
+ fn visit_expr(&mut self, expr: &'v Expr<'v>) {
+ if self.used {
+ return;
+ }
+ if path_to_local_id(expr, self.local_hir_id) {
+ self.used = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.hir)
+ }
+}
specific lint you are creating/editing. Note that if the generated files are
empty, they should be removed.
+Note that you can run multiple test files by specifying a comma separated list:
+`TESTNAME=foo_functions,test2,test3`.
+
### Cargo lints
For cargo lints, the process of testing differs in that we are interested in
Depending on how complex we want our lint message to be, we can choose from a
variety of lint emission functions. They can all be found in
-[`clippy_lints/src/utils/diagnostics.rs`][diagnostics].
+[`clippy_utils/src/diagnostics.rs`][diagnostics].
`span_lint_and_help` seems most appropriate in this case. It allows us to
provide an extra help message and we can't really suggest a better name
surrounded with single grave accents \`.
[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn
-[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/diagnostics.rs
+[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs
[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html
## Adding the lint logic
behavior that can be seen as a false positive for some users. Adding a configuration is done
in the following steps:
-1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs)
+1. Adding a new configuration entry to [clippy_utils::conf](/clippy_utils/src/conf.rs)
like this:
```rust
/// Lint: LINT_NAME. <The configuration field doc comment>
get away with copying things from existing similar lints. If you are stuck,
don't hesitate to ask on [Zulip] or in the issue/PR.
-[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/mod.rs
+[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs
[if_chain]: https://docs.rs/if_chain/*/if_chain/
[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html
There are two ways to do this, depending if the target trait is part of lang items.
```rust
-use crate::utils::{implements_trait, match_trait_method, paths};
+use clippy_utils::{implements_trait, match_trait_method, paths};
impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
To check if our type defines a method called `some_method`:
```rust
-use crate::utils::{is_type_diagnostic_item, return_ty};
+use clippy_utils::{is_type_diagnostic_item, return_ty};
impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
# Dealing with macros
-There are several helpers in Clippy's utils to deal with macros:
+There are several helpers in [`clippy_utils`][utils] to deal with macros:
- `in_macro()`: detect if the given span is expanded by a macro
[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
-[paths]: ../clippy_lints/src/utils/paths.rs
+[paths]: ../clippy_utils/src/paths.rs
+[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs
-clippy 0.1.51 (7f5bb7fd0 2021-02-06)
+clippy 0.1.52 (697f3b6d4 2021-02-22)
-cargo-0.49.0//home/matthias/.rustup/toolchains/nightly-2021-02-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:409:34 clippy::match_same_arms "this `match` has identical arm bodies"
cargo-0.49.0/build.rs:1:null clippy::cargo_common_metadata "package `cargo` is missing `package.categories` metadata"
cargo-0.49.0/build.rs:1:null clippy::cargo_common_metadata "package `cargo` is missing `package.keywords` metadata"
cargo-0.49.0/src/bin/cargo/cli.rs:104:34 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/bin/cargo/commands/clean.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
cargo-0.49.0/src/bin/cargo/commands/doc.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
cargo-0.49.0/src/bin/cargo/commands/fetch.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
+cargo-0.49.0/src/bin/cargo/commands/fetch.rs:22:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/bin/cargo/commands/fix.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
cargo-0.49.0/src/bin/cargo/commands/generate_lockfile.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
cargo-0.49.0/src/bin/cargo/commands/git_checkout.rs:1:5 clippy::wildcard_imports "usage of wildcard import"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:420:69 clippy::doc_markdown "you should put `mode/target_kind` between ticks in the documentation"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:423:19 clippy::doc_markdown "you should put `CrateTypes` between ticks in the documentation"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:424:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:424:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:469:58 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:603:19 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:665:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
+cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:697:12 clippy::inconsistent_struct_constructor "inconsistent struct constructor"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:82:31 clippy::doc_markdown "you should put `FileType` between ticks in the documentation"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:83:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:84:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:374:43 clippy::doc_markdown "you should put `RunCustomBuild` between ticks in the documentation"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:378:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:383:41 clippy::doc_markdown "you should put `RunCustomBuild` between ticks in the documentation"
-cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:384:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:384:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:391:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:397:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:481:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:48:56 clippy::doc_markdown "you should put `RunCustomBuild` between ticks in the documentation"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:561:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:561:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:567:20 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:576:28 clippy::shadow_unrelated "`mut value` is being shadowed"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:606:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:688:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:756:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:762:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
-cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:762:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:762:5 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:823:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/compiler/fingerprint.rs:1021:51 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/compiler/fingerprint.rs:1656:16 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/core/compiler/links.rs:8:1 clippy::module_name_repetitions "item name ends with its containing module's name"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:1016:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:1094:19 clippy::redundant_closure_for_method_calls "redundant closure found"
-cargo-0.49.0/src/cargo/core/compiler/mod.rs:1131:1 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/core/compiler/mod.rs:1131:1 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:1268:34 clippy::case_sensitive_file_extension_comparisons "case-sensitive file extension comparison"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:1277:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:179:1 clippy::too_many_lines "this function has too many lines (162/100)"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:267:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:324:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:364:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
-cargo-0.49.0/src/cargo/core/compiler/mod.rs:364:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/core/compiler/mod.rs:364:5 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:392:45 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:415:23 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:464:18 clippy::ptr_arg "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do."
cargo-0.49.0/src/cargo/core/compiler/mod.rs:488:61 clippy::ptr_arg "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do."
cargo-0.49.0/src/cargo/core/compiler/mod.rs:667:15 clippy::similar_names "binding's name is too similar to existing binding"
-cargo-0.49.0/src/cargo/core/compiler/mod.rs:693:1 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/core/compiler/mod.rs:693:1 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:725:42 clippy::match_same_arms "this `match` has identical arm bodies"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:736:1 clippy::too_many_lines "this function has too many lines (141/100)"
cargo-0.49.0/src/cargo/core/compiler/mod.rs:73:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/compiler/unit_graph.rs:65:1 clippy::module_name_repetitions "item name ends with its containing module's name"
cargo-0.49.0/src/cargo/core/dependency.rs:157:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/dependency.rs:182:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/dependency.rs:203:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/dependency.rs:203:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:224:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:23:1 clippy::struct_excessive_bools "more than 3 bools in a struct"
cargo-0.49.0/src/cargo/core/dependency.rs:278:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:287:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:291:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/core/dependency.rs:296:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/dependency.rs:305:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:311:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:319:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/core/dependency.rs:323:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/dependency.rs:337:75 clippy::redundant_closure_for_method_calls "redundant closure found"
-cargo-0.49.0/src/cargo/core/dependency.rs:379:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/dependency.rs:397:56 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/dependency.rs:403:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/dependency.rs:408:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/package.rs:385:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/package.rs:421:5 clippy::needless_lifetimes "explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)"
cargo-0.49.0/src/cargo/core/package.rs:425:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/package.rs:425:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/package.rs:452:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/package.rs:453:60 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/core/package.rs:459:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/package.rs:473:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/package.rs:552:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/package.rs:587:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/package.rs:588:9 clippy::needless_question_mark "Question mark operator is useless here"
cargo-0.49.0/src/cargo/core/package.rs:682:46 clippy::cast_possible_truncation "casting `f64` to `u64` may truncate the value"
cargo-0.49.0/src/cargo/core/package_id_spec.rs:51:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/package_id_spec.rs:77:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/package_id_spec.rs:88:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/core/profiles.rs:1004:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/profiles.rs:1004:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/profiles.rs:1014:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/profiles.rs:1018:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/profiles.rs:1028:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/profiles.rs:106:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
-cargo-0.49.0/src/cargo/core/profiles.rs:143:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/core/profiles.rs:143:5 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/core/profiles.rs:286:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/profiles.rs:286:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/profiles.rs:294:40 clippy::if_not_else "unnecessary boolean `not` operation"
cargo-0.49.0/src/cargo/core/registry.rs:240:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/registry.rs:26:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/registry.rs:344:49 clippy::redundant_closure_for_method_calls "redundant closure found"
-cargo-0.49.0/src/cargo/core/registry.rs:358:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/registry.rs:369:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/core/registry.rs:424:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/registry.rs:424:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/registry.rs:49:1 clippy::module_name_repetitions "item name ends with its containing module's name"
cargo-0.49.0/src/cargo/core/registry.rs:520:17 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/core/resolver/context.rs:42:1 clippy::module_name_repetitions "item name starts with its containing module's name"
cargo-0.49.0/src/cargo/core/resolver/context.rs:74:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/resolver/encode.rs:156:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/resolver/encode.rs:156:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/resolver/encode.rs:156:5 clippy::too_many_lines "this function has too many lines (164/100)"
cargo-0.49.0/src/cargo/core/resolver/encode.rs:339:17 clippy::match_wildcard_for_single_variants "wildcard match will miss any future added variants"
cargo-0.49.0/src/cargo/core/resolver/encode.rs:438:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:259:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:263:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:269:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/core/resolver/resolve.rs:273:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:273:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:274:9 clippy::map_unwrap_or "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling `map_or_else(<g>, <f>)` instead"
cargo-0.49.0/src/cargo/core/resolver/resolve.rs:280:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/shell.rs:322:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/shell.rs:330:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/shell.rs:345:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
+cargo-0.49.0/src/cargo/core/shell.rs:459:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/core/shell.rs:98:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/source/mod.rs:103:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/source/mod.rs:247:1 clippy::module_name_repetitions "item name starts with its containing module's name"
cargo-0.49.0/src/cargo/core/source/source_id.rs:252:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/source/source_id.rs:257:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/source/source_id.rs:262:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/source/source_id.rs:262:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/core/source/source_id.rs:305:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/source/source_id.rs:310:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/source/source_id.rs:318:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/core/workspace.rs:893:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/core/workspace.rs:906:24 clippy::redundant_else "redundant else block"
cargo-0.49.0/src/cargo/core/workspace.rs:932:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/core/workspace.rs:932:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/lib.rs:177:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/lib.rs:177:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/lib.rs:180:36 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:258:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:267:16 clippy::needless_question_mark "Question mark operator is useless here"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:275:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/ops/cargo_compile.rs:275:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:275:1 clippy::too_many_lines "this function has too many lines (219/100)"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:468:9 clippy::default_trait_access "calling `std::collections::HashMap::default()` is more clear than this expression"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:548:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:613:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:618:9 clippy::similar_names "binding's name is too similar to existing binding"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:641:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/ops/cargo_compile.rs:652:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:652:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:655:50 clippy::match_same_arms "this `match` has identical arm bodies"
cargo-0.49.0/src/cargo/ops/cargo_compile.rs:673:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/ops/cargo_package.rs:425:61 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/ops/cargo_package.rs:459:5 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/ops/cargo_package.rs:66:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
+cargo-0.49.0/src/cargo/ops/cargo_package.rs:69:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/ops/cargo_package.rs:93:20 clippy::if_not_else "unnecessary boolean `not` operation"
cargo-0.49.0/src/cargo/ops/cargo_pkgid.rs:5:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/cargo_read_manifest.rs:14:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/cargo_run.rs:53:9 clippy::if_not_else "unnecessary boolean `not` operation"
cargo-0.49.0/src/cargo/ops/cargo_run.rs:65:16 clippy::redundant_else "redundant else block"
cargo-0.49.0/src/cargo/ops/cargo_run.rs:9:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/ops/cargo_run.rs:9:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/ops/cargo_test.rs:16:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/cargo_test.rs:43:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/cargo_test.rs:84:17 clippy::similar_names "binding's name is too similar to existing binding"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:244:22 clippy::doc_markdown "you should put `PackageId` between ticks in the documentation"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:244:63 clippy::doc_markdown "you should put `PackageId` between ticks in the documentation"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:253:17 clippy::if_not_else "unnecessary boolean `not` operation"
-cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:370:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:370:5 clippy::unnecessary_wraps "this function's return value is unnecessary"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:505:8 clippy::map_unwrap_or "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling `map_or_else(<g>, <f>)` instead"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:525:10 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body"
cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:542:27 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/ops/registry.rs:828:14 clippy::doc_markdown "you should put `SourceId` between ticks in the documentation"
cargo-0.49.0/src/cargo/ops/registry.rs:848:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::module_name_repetitions "item name starts with its containing module's name"
cargo-0.49.0/src/cargo/ops/resolve.rs:199:1 clippy::too_many_lines "this function has too many lines (137/100)"
cargo-0.49.0/src/cargo/ops/resolve.rs:241:28 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/ops/tree/mod.rs:360:30 clippy::match_same_arms "this `match` has identical arm bodies"
cargo-0.49.0/src/cargo/ops/tree/mod.rs:58:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/ops/vendor.rs:14:1 clippy::module_name_repetitions "item name starts with its containing module's name"
+cargo-0.49.0/src/cargo/ops/vendor.rs:215:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/ops/vendor.rs:21:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/ops/vendor.rs:21:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/ops/vendor.rs:314:34 clippy::match_same_arms "this `match` has identical arm bodies"
cargo-0.49.0/src/cargo/sources/directory.rs:90:56 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/sources/git/source.rs:14:1 clippy::module_name_repetitions "item name ends with its containing module's name"
cargo-0.49.0/src/cargo/sources/git/source.rs:25:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/sources/git/source.rs:25:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/sources/git/source.rs:49:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/sources/git/source.rs:53:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/git/source.rs:53:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/sources/git/utils.rs:188:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/git/utils.rs:242:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/git/utils.rs:253:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/sources/git/utils.rs:253:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/sources/git/utils.rs:262:13 clippy::if_not_else "unnecessary boolean `not` operation"
cargo-0.49.0/src/cargo/sources/git/utils.rs:289:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/git/utils.rs:294:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/sources/path.rs:460:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/sources/path.rs:473:43 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/sources/path.rs:482:43 clippy::redundant_closure_for_method_calls "redundant closure found"
-cargo-0.49.0/src/cargo/sources/path.rs:55:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/sources/path.rs:63:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/path.rs:77:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/sources/path.rs:98:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1049:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1064:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1090:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/util/config/mod.rs:1090:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1166:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1179:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1181:33 clippy::needless_question_mark "Question mark operator is useless here"
cargo-0.49.0/src/cargo/util/config/mod.rs:1229:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:124:1 clippy::struct_excessive_bools "more than 3 bools in a struct"
cargo-0.49.0/src/cargo/util/config/mod.rs:1254:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-cargo-0.49.0/src/cargo/util/config/mod.rs:1263:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1279:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/config/mod.rs:1281:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
cargo-0.49.0/src/cargo/util/config/mod.rs:1323:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/util/dependency_queue.rs:156:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/dependency_queue.rs:168:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/dependency_queue.rs:46:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/util/dependency_queue.rs:66:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/dependency_queue.rs:91:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/util/diagnostic_server.rs:218:1 clippy::module_name_repetitions "item name ends with its containing module's name"
cargo-0.49.0/src/cargo/util/diagnostic_server.rs:230:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/errors.rs:150:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/errors.rs:15:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/errors.rs:237:5 clippy::pub_enum_variant_names "variant name ends with the enum's name"
-cargo-0.49.0/src/cargo/util/errors.rs:245:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/errors.rs:245:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/errors.rs:321:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/errors.rs:328:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/flock.rs:335:23 clippy::cast_sign_loss "casting `i64` to `u32` may lose the sign of the value"
cargo-0.49.0/src/cargo/util/flock.rs:335:44 clippy::cast_possible_truncation "casting `i64` to `u32` may truncate the value"
cargo-0.49.0/src/cargo/util/flock.rs:379:35 clippy::match_same_arms "this `match` has identical arm bodies"
-cargo-0.49.0/src/cargo/util/flock.rs:37:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/flock.rs:37:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/util/flock.rs:43:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/flock.rs:43:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/flock.rs:52:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/flock.rs:52:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
+cargo-0.49.0/src/cargo/util/flock.rs:96:17 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/util/graph.rs:10:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/graph.rs:41:51 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/util/graph.rs:45:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/paths.rs:459:45 clippy::redundant_closure_for_method_calls "redundant closure found"
cargo-0.49.0/src/cargo/util/paths.rs:469:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/paths.rs:469:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
+cargo-0.49.0/src/cargo/util/paths.rs:514:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/util/paths.rs:54:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
-cargo-0.49.0/src/cargo/util/paths.rs:61:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
cargo-0.49.0/src/cargo/util/paths.rs:61:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/paths.rs:63:19 clippy::option_if_let_else "use Option::map_or_else instead of an if let/else"
cargo-0.49.0/src/cargo/util/paths.rs:88:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/process_builder.rs:190:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/process_builder.rs:218:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/process_builder.rs:218:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
+cargo-0.49.0/src/cargo/util/process_builder.rs:278:22 clippy::inconsistent_struct_constructor "inconsistent struct constructor"
cargo-0.49.0/src/cargo/util/process_builder.rs:307:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
cargo-0.49.0/src/cargo/util/process_builder.rs:343:39 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body"
cargo-0.49.0/src/cargo/util/progress.rs:122:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/rustc.rs:162:17 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
cargo-0.49.0/src/cargo/util/rustc.rs:39:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/sha256.rs:10:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
+cargo-0.49.0/src/cargo/util/sha256.rs:16:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
cargo-0.49.0/src/cargo/util/sha256.rs:20:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/sha256.rs:31:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
cargo-0.49.0/src/cargo/util/sha256.rs:40:24 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore"
iron-0.6.1/src/iron.rs:143:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
iron-0.6.1/src/iron.rs:149:13 clippy::redundant_field_names "redundant field names in struct initialization"
iron-0.6.1/src/iron.rs:167:49 clippy::similar_names "binding's name is too similar to existing binding"
+iron-0.6.1/src/iron.rs:196:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
iron-0.6.1/src/iron.rs:80:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
iron-0.6.1/src/iron.rs:85:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
iron-0.6.1/src/iron.rs:90:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
log-0.4.11/src/lib.rs:1177:1 clippy::inline_always "you have declared `#[inline(always)]` on `max_level`. This is usually a bad idea"
log-0.4.11/src/lib.rs:1178:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
log-0.4.11/src/lib.rs:1306:1 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-log-0.4.11/src/lib.rs:1306:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
log-0.4.11/src/lib.rs:1358:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
log-0.4.11/src/lib.rs:1359:5 clippy::if_not_else "unnecessary `!=` operation"
log-0.4.11/src/lib.rs:1407:1 clippy::must_use_candidate "this function could have a `#[must_use]` attribute"
-log-0.4.11/src/lib.rs:329:27 clippy::derive_hash_xor_eq "you are deriving `Hash` but have implemented `PartialEq` explicitly"
log-0.4.11/src/lib.rs:356:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type"
log-0.4.11/src/lib.rs:448:12 clippy::manual_range_contains "manual `RangeInclusive::contains` implementation"
log-0.4.11/src/lib.rs:500:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
log-0.4.11/src/lib.rs:506:28 clippy::trivially_copy_pass_by_ref "this argument (8 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)"
log-0.4.11/src/lib.rs:506:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
log-0.4.11/src/lib.rs:506:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-log-0.4.11/src/lib.rs:520:27 clippy::derive_hash_xor_eq "you are deriving `Hash` but have implemented `PartialEq` explicitly"
log-0.4.11/src/lib.rs:538:1 clippy::expl_impl_clone_on_copy "you are implementing `Clone` explicitly on a `Copy` type"
log-0.4.11/src/lib.rs:653:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
log-0.4.11/src/lib.rs:661:21 clippy::trivially_copy_pass_by_ref "this argument (8 byte) is passed by reference, but would be more efficient if passed by value (limit: 8 byte)"
puffin-02dd4a3/puffin/src/data.rs:177:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
puffin-02dd4a3/puffin/src/data.rs:211:21 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers"
puffin-02dd4a3/puffin/src/data.rs:24:5 clippy::wildcard_imports "usage of wildcard import"
-puffin-02dd4a3/puffin/src/data.rs:75:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
puffin-02dd4a3/puffin/src/lib.rs:113:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
puffin-02dd4a3/puffin/src/lib.rs:147:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
puffin-02dd4a3/puffin/src/lib.rs:147:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
quote-1.0.7/src/ext.rs:7:5 clippy::doc_markdown "you should put `TokenStream` between ticks in the documentation"
quote-1.0.7/src/ident_fragment.rs:13:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
quote-1.0.7/src/ident_fragment.rs:51:31 clippy::manual_strip "stripping a prefix manually"
-quote-1.0.7/src/runtime.rs:332:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
quote-1.0.7/src/runtime.rs:52:5 clippy::module_name_repetitions "item name ends with its containing module's name"
quote-1.0.7/src/runtime.rs:63:5 clippy::module_name_repetitions "item name ends with its containing module's name"
quote-1.0.7/src/runtime.rs:66:33 clippy::doc_markdown "you should put `DoesNotHaveIter` between ticks in the documentation"
rand-0.7.3/src/distributions/binomial.rs:234:27 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)"
rand-0.7.3/src/distributions/binomial.rs:251:22 clippy::cast_sign_loss "casting `i64` to `u64` may lose the sign of the value"
rand-0.7.3/src/distributions/binomial.rs:255:9 clippy::if_not_else "unnecessary `!=` operation"
-rand-0.7.3/src/distributions/binomial.rs:35:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/binomial.rs:35:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/binomial.rs:45:17 clippy::cast_precision_loss "casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)"
rand-0.7.3/src/distributions/binomial.rs:46:5 clippy::cast_possible_truncation "casting `f64` to `i64` may truncate the value"
rand-0.7.3/src/distributions/binomial.rs:82:32 clippy::cast_possible_truncation "casting `u64` to `i32` may truncate the value"
rand-0.7.3/src/distributions/binomial.rs:88:26 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)"
rand-0.7.3/src/distributions/binomial.rs:99:21 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)"
-rand-0.7.3/src/distributions/cauchy.rs:33:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/cauchy.rs:33:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/dirichlet.rs:52:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/dirichlet.rs:64:32 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore"
rand-0.7.3/src/distributions/dirichlet.rs:65:23 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore"
-rand-0.7.3/src/distributions/exponential.rs:76:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/exponential.rs:76:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/float.rs:73:1 clippy::module_name_repetitions "item name ends with its containing module's name"
rand-0.7.3/src/distributions/gamma.rs:13:5 clippy::enum_glob_use "usage of wildcard import for enum variants"
rand-0.7.3/src/distributions/gamma.rs:14:5 clippy::enum_glob_use "usage of wildcard import for enum variants"
-rand-0.7.3/src/distributions/gamma.rs:189:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/gamma.rs:189:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-rand-0.7.3/src/distributions/gamma.rs:230:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/gamma.rs:230:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-rand-0.7.3/src/distributions/gamma.rs:259:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/gamma.rs:259:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-rand-0.7.3/src/distributions/gamma.rs:287:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/gamma.rs:287:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-rand-0.7.3/src/distributions/gamma.rs:90:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/gamma.rs:90:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/integer.rs:23:9 clippy::cast_possible_truncation "casting `u32` to `u8` may truncate the value"
rand-0.7.3/src/distributions/integer.rs:30:9 clippy::cast_possible_truncation "casting `u32` to `u16` may truncate the value"
rand-0.7.3/src/distributions/normal.rs:48:25 clippy::unseparated_literal_suffix "float type suffix should be separated by an underscore"
rand-0.7.3/src/distributions/other.rs:89:9 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value"
rand-0.7.3/src/distributions/pareto.rs:32:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
-rand-0.7.3/src/distributions/poisson.rs:35:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/distributions/poisson.rs:35:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand-0.7.3/src/distributions/poisson.rs:87:30 clippy::cast_possible_truncation "casting `f64` to `u64` may truncate the value"
rand-0.7.3/src/distributions/poisson.rs:87:30 clippy::cast_sign_loss "casting `f64` to `u64` may lose the sign of the value"
rand-0.7.3/src/rngs/adapter/reseeding.rs:117:5 clippy::inline_always "you have declared `#[inline(always)]` on `next_u64`. This is usually a bad idea"
rand-0.7.3/src/rngs/adapter/reseeding.rs:198:13 clippy::cast_possible_wrap "casting `u64` to `i64` may wrap around the value"
rand-0.7.3/src/rngs/adapter/reseeding.rs:231:9 clippy::cast_possible_wrap "casting `usize` to `isize` may wrap around the value"
+rand-0.7.3/src/rngs/adapter/reseeding.rs:249:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
rand-0.7.3/src/rngs/adapter/reseeding.rs:27:28 clippy::doc_markdown "you should put `ChaCha` between ticks in the documentation"
rand-0.7.3/src/rngs/adapter/reseeding.rs:79:1 clippy::module_name_repetitions "item name starts with its containing module's name"
rand-0.7.3/src/rngs/entropy.rs:24:1 clippy::module_name_repetitions "item name starts with its containing module's name"
rand-0.7.3/src/seq/index.rs:159:1 clippy::module_name_repetitions "item name starts with its containing module's name"
rand-0.7.3/src/seq/index.rs:171:13 clippy::enum_glob_use "usage of wildcard import for enum variants"
rand-0.7.3/src/seq/index.rs:180:13 clippy::enum_glob_use "usage of wildcard import for enum variants"
-rand-0.7.3/src/seq/index.rs:213:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand-0.7.3/src/seq/index.rs:223:18 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers"
rand-0.7.3/src/seq/index.rs:224:18 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers"
rand-0.7.3/src/seq/index.rs:233:25 clippy::cast_precision_loss "casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)"
rand-0.7.3/src/seq/mod.rs:527:26 clippy::cast_possible_truncation "casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers"
rand_core-0.6.0/src/block.rs:117:1 clippy::module_name_repetitions "item name starts with its containing module's name"
rand_core-0.6.0/src/block.rs:153:5 clippy::inline_always "you have declared `#[inline(always)]` on `index`. This is usually a bad idea"
-rand_core-0.6.0/src/block.rs:168:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand_core-0.6.0/src/block.rs:230:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:240:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_seed`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:245:5 clippy::inline_always "you have declared `#[inline(always)]` on `seed_from_u64`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:250:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_rng`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:280:1 clippy::module_name_repetitions "item name starts with its containing module's name"
rand_core-0.6.0/src/block.rs:319:5 clippy::inline_always "you have declared `#[inline(always)]` on `index`. This is usually a bad idea"
-rand_core-0.6.0/src/block.rs:335:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand_core-0.6.0/src/block.rs:405:5 clippy::inline_always "you have declared `#[inline(always)]` on `try_fill_bytes`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:415:5 clippy::inline_always "you have declared `#[inline(always)]` on `from_seed`. This is usually a bad idea"
rand_core-0.6.0/src/block.rs:420:5 clippy::inline_always "you have declared `#[inline(always)]` on `seed_from_u64`. This is usually a bad idea"
rand_core-0.6.0/src/error.rs:106:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand_core-0.6.0/src/error.rs:87:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand_core-0.6.0/src/error.rs:95:74 clippy::cast_possible_wrap "casting `u32` to `i32` may wrap around the value"
-rand_core-0.6.0/src/le.rs:18:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
-rand_core-0.6.0/src/le.rs:27:1 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
rand_core-0.6.0/src/lib.rs:179:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
rand_core-0.6.0/src/lib.rs:301:5 clippy::must_use_candidate "this method could have a `#[must_use]` attribute"
rand_core-0.6.0/src/lib.rs:303:26 clippy::unreadable_literal "long literal lacking separators"
rayon-1.5.0/src/iter/chain.rs:78:14 clippy::shadow_unrelated "`a` is being shadowed"
rayon-1.5.0/src/iter/chain.rs:78:17 clippy::shadow_unrelated "`b` is being shadowed"
rayon-1.5.0/src/iter/chain.rs:97:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
+rayon-1.5.0/src/iter/chunks.rs:29:9 clippy::inconsistent_struct_constructor "inconsistent struct constructor"
rayon-1.5.0/src/iter/chunks.rs:3:5 clippy::wildcard_imports "usage of wildcard import"
rayon-1.5.0/src/iter/chunks.rs:4:5 clippy::wildcard_imports "usage of wildcard import"
rayon-1.5.0/src/iter/chunks.rs:77:9 clippy::items_after_statements "adding items after statements is confusing, since items exist from the start of the scope"
regex-1.3.2/src/compile.rs:1051:25 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore"
regex-1.3.2/src/compile.rs:1071:8 clippy::cast_lossless "casting `u32` to `u64` may become silently lossy if you later change the type"
regex-1.3.2/src/compile.rs:112:5 clippy::missing_errors_doc "docs for function returning `Result` missing `# Errors` section"
-regex-1.3.2/src/compile.rs:112:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
regex-1.3.2/src/compile.rs:154:30 clippy::redundant_closure_for_method_calls "redundant closure found"
regex-1.3.2/src/compile.rs:156:30 clippy::redundant_closure_for_method_calls "redundant closure found"
regex-1.3.2/src/compile.rs:185:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
regex-1.3.2/src/utf8.rs:92:23 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four"
regex-1.3.2/src/utf8.rs:92:9 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four"
regex-1.3.2/src/utf8.rs:97:16 clippy::unusual_byte_groupings "digits of hex or binary literal not grouped by four"
-ripgrep-12.1.1//home/matthias/.rustup/toolchains/nightly-2021-02-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:30:27 clippy::match_same_arms "this `match` has identical arm bodies"
-ripgrep-12.1.1//home/matthias/.rustup/toolchains/nightly-2021-02-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:30:27 clippy::match_same_arms "this `match` has identical arm bodies"
-ripgrep-12.1.1//home/matthias/.rustup/toolchains/nightly-2021-02-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:30:27 clippy::match_same_arms "this `match` has identical arm bodies"
-ripgrep-12.1.1//home/matthias/.rustup/toolchains/nightly-2021-02-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:30:27 clippy::match_same_arms "this `match` has identical arm bodies"
ripgrep-12.1.1/build.rs:133:19 clippy::option_as_ref_deref "called `.as_ref().map(|x| &**x)` on an Option value. This can be done more directly by calling `githash.as_deref()` instead"
ripgrep-12.1.1/build.rs:18:18 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
ripgrep-12.1.1/build.rs:225:14 clippy::redundant_closure_for_method_calls "redundant closure found"
ripgrep-12.1.1/crates/core/args.rs:1635:14 clippy::doc_markdown "you should put `values_of_lossy` between ticks in the documentation"
ripgrep-12.1.1/crates/core/args.rs:1693:41 clippy::redundant_closure_for_method_calls "redundant closure found"
ripgrep-12.1.1/crates/core/args.rs:1770:17 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers"
+ripgrep-12.1.1/crates/core/args.rs:1829:5 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
ripgrep-12.1.1/crates/core/args.rs:287:13 clippy::similar_names "binding's name is too similar to existing binding"
ripgrep-12.1.1/crates/core/args.rs:33:1 clippy::single_component_path_imports "this import is redundant"
ripgrep-12.1.1/crates/core/args.rs:34:1 clippy::single_component_path_imports "this import is redundant"
ripgrep-12.1.1/crates/core/config.rs:79:6 clippy::type_complexity "very complex type used. Consider factoring parts into `type` definitions"
ripgrep-12.1.1/crates/core/logger.rs:11:30 clippy::doc_markdown "you should put `max_level` between ticks in the documentation"
ripgrep-12.1.1/crates/core/logger.rs:15:16 clippy::redundant_static_lifetimes "constants have by default a `'static` lifetime"
+ripgrep-12.1.1/crates/core/main.rs:114:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+ripgrep-12.1.1/crates/core/main.rs:189:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
ripgrep-12.1.1/crates/core/main.rs:55:19 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body"
ripgrep-12.1.1/crates/core/main.rs:56:9 clippy::enum_glob_use "usage of wildcard import for enum variants"
ripgrep-12.1.1/crates/core/messages.rs:46:1 clippy::module_name_repetitions "item name ends with its containing module's name"
ripgrep-12.1.1/crates/core/search.rs:533:5 clippy::cast_precision_loss "casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)"
ripgrep-12.1.1/crates/core/subject.rs:20:1 clippy::module_name_repetitions "item name starts with its containing module's name"
ripgrep-12.1.1/crates/core/subject.rs:4:1 clippy::single_component_path_imports "this import is redundant"
+serde-1.0.118/src/de/mod.rs:1592:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:1616:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:1627:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:1638:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:1649:9 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:952:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
+serde-1.0.118/src/de/mod.rs:986:13 clippy::let_underscore_drop "non-binding `let` on a type that implements `Drop`"
syn-1.0.54/build.rs:1:null clippy::cargo_common_metadata "package `syn` is missing `package.keywords` metadata"
-syn-1.0.54/build.rs:1:null clippy::multiple_crate_versions "could not read cargo metadata: `cargo metadata` exited with an error: Downloading crates ...\n Downloaded syn-test-suite v0.0.0\nerror: failed to verify the checksum of `syn-test-suite v0.0.0`"
-syn-1.0.54/src/generics.rs:174:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
syn-1.0.54/src/lib.rs:1:null clippy::cargo_common_metadata "package `syn` is missing `package.keywords` metadata"
-syn-1.0.54/src/lib.rs:1:null clippy::multiple_crate_versions "could not read cargo metadata: `cargo metadata` exited with an error: Downloading crates ...\n Downloaded syn-test-suite v0.0.0\nerror: failed to verify the checksum of `syn-test-suite v0.0.0`"
syn-1.0.54/src/lit.rs:1397:40 clippy::redundant_else "redundant else block"
syn-1.0.54/src/lit.rs:1405:28 clippy::redundant_else "redundant else block"
syn-1.0.54/src/lit.rs:1485:32 clippy::redundant_else "redundant else block"
-syn-1.0.54/src/lit.rs:343:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
-syn-1.0.54/src/lit.rs:437:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
-syn-1.0.54/src/lit.rs:916:9 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
-syn-1.0.54/src/token.rs:974:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
-syn-1.0.54/src/token.rs:996:5 clippy::missing_panics_doc "docs for function which may panic missing `# Panics` section"
unicode-xid-0.2.1/src/lib.rs:1:null clippy::cargo_common_metadata "package `unicode-xid` is missing `package.categories` metadata"
unicode-xid-0.2.1/src/lib.rs:56:11 clippy::upper_case_acronyms "name `UnicodeXID` contains a capitalized acronym"
unicode-xid-0.2.1/src/lib.rs:57:64 clippy::doc_markdown "you should put `XID_Start` between ticks in the documentation"
xsv-0.13.0/src/cmd/count.rs:32:9 clippy::similar_names "binding's name is too similar to existing binding"
xsv-0.13.0/src/cmd/count.rs:38:9 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
xsv-0.13.0/src/cmd/count.rs:42:33 clippy::unseparated_literal_suffix "integer type suffix should be separated by an underscore"
-xsv-0.13.0/src/cmd/count.rs:50:5 clippy::unit_arg "passing a unit value to a function"
xsv-0.13.0/src/cmd/count.rs:7:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime"
xsv-0.13.0/src/cmd/fixlengths.rs:45:9 clippy::similar_names "binding's name is too similar to existing binding"
xsv-0.13.0/src/cmd/fixlengths.rs:50:18 clippy::single_match_else "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"
xsv-0.13.0/src/cmd/sort.rs:91:14 clippy::explicit_into_iter_loop "it is more concise to loop over containers instead of using explicit iteration methods"
xsv-0.13.0/src/cmd/split.rs:14:16 clippy::redundant_static_lifetimes "statics have by default a `'static` lifetime"
xsv-0.13.0/src/cmd/split.rs:61:9 clippy::similar_names "binding's name is too similar to existing binding"
-xsv-0.13.0/src/cmd/split.rs:94:5 clippy::unnecessary_wraps "this function's return value is unnecessarily wrapped by `Result`"
+xsv-0.13.0/src/cmd/split.rs:94:5 clippy::unnecessary_wraps "this function's return value is unnecessary"
xsv-0.13.0/src/cmd/split.rs:96:14 clippy::needless_pass_by_value "this argument is passed by value, but not consumed in the function body"
xsv-0.13.0/src/cmd/split.rs:99:13 clippy::cast_possible_truncation "casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers"
xsv-0.13.0/src/cmd/stats.rs:110:36 clippy::redundant_closure_for_method_calls "redundant closure found"
clippy::same_item_push 1
clippy::should_implement_trait 1
clippy::stable_sort_primitive 1
-clippy::unit_arg 1
clippy::unnecessary_lazy_evaluations 1
clippy::unsafe_derive_deserialize 1
clippy::used_underscore_binding 1
clippy::verbose_bit_mask 1
clippy::while_let_on_iterator 1
clippy::comparison_to_empty 2
-clippy::derive_hash_xor_eq 2
clippy::expl_impl_clone_on_copy 2
clippy::filter_map 2
clippy::len_zero 2
clippy::filter_map_next 3
clippy::fn_params_excessive_bools 3
clippy::if_same_then_else 3
+clippy::inconsistent_struct_constructor 3
clippy::mut_mut 3
clippy::ptr_arg 3
clippy::zero_ptr 3
-clippy::let_underscore_drop 4
clippy::too_many_arguments 4
clippy::explicit_iter_loop 5
clippy::field_reassign_with_default 5
clippy::invalid_upcast_comparisons 8
clippy::needless_question_mark 8
clippy::wrong_self_convention 8
+clippy::multiple_crate_versions 9
clippy::manual_range_contains 10
clippy::match_wildcard_for_single_variants 10
clippy::missing_safety_doc 10
clippy::needless_doctest_main 10
-clippy::multiple_crate_versions 11
clippy::needless_lifetimes 12
clippy::cargo_common_metadata 13
clippy::shadow_unrelated 13
clippy::redundant_static_lifetimes 21
clippy::default_trait_access 22
clippy::cast_lossless 23
+clippy::let_underscore_drop 23
clippy::trivially_copy_pass_by_ref 26
clippy::redundant_else 29
clippy::too_many_lines 32
clippy::unseparated_literal_suffix 41
clippy::cast_precision_loss 44
clippy::single_match_else 45
+clippy::missing_panics_doc 54
clippy::inline_always 59
-clippy::match_same_arms 65
+clippy::match_same_arms 60
clippy::similar_names 78
clippy::cast_possible_truncation 95
-clippy::missing_panics_doc 108
clippy::redundant_field_names 111
clippy::redundant_closure_for_method_calls 135
clippy::items_after_statements 139
clippy::missing_errors_doc 343
clippy::unreadable_literal 365
clippy::must_use_candidate 565
+ICEs:
[toolchain]
-channel = "nightly-2021-02-11"
+channel = "nightly-2021-02-25"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"]
fn default_config() -> compiletest::Config {
let mut config = compiletest::Config::default();
- if let Ok(name) = env::var("TESTNAME") {
- config.filter = Some(name);
+ if let Ok(filters) = env::var("TESTNAME") {
+ config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
}
if let Some(path) = option_env!("RUSTC_LIB_PATH") {
fn run_ui_cargo(config: &mut compiletest::Config) {
fn run_tests(
config: &compiletest::Config,
- filter: &Option<String>,
+ filters: &[String],
mut tests: Vec<tester::TestDescAndFn>,
) -> Result<bool, io::Error> {
let mut result = true;
// Use the filter if provided
let dir_path = dir.path();
- match &filter {
- Some(name) if !dir_path.ends_with(name) => continue,
- _ => {},
+ for filter in filters {
+ if !dir_path.ends_with(filter) {
+ continue;
+ }
}
for case in fs::read_dir(&dir_path)? {
let current_dir = env::current_dir().unwrap();
let conf_dir = var("CLIPPY_CONF_DIR").unwrap_or_default();
- let filter = env::var("TESTNAME").ok();
- let res = run_tests(&config, &filter, tests);
+ let res = run_tests(&config, &config.filters, tests);
env::set_current_dir(current_dir).unwrap();
set_var("CLIPPY_CONF_DIR", conf_dir);
-error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `unreadable-literal-lint-fractions`, `cargo-ignore-publish`, `third-party` at line 5 column 1
+error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `third-party` at line 5 column 1
error: aborting due to previous error
--- /dev/null
+upper-case-acronyms-aggressive = true
--- /dev/null
+#![warn(clippy::upper_case_acronyms)]
+
+struct HTTPResponse; // not linted by default, but with cfg option
+
+struct CString; // not linted
+
+enum Flags {
+ NS, // not linted
+ CWR,
+ ECE,
+ URG,
+ ACK,
+ PSH,
+ RST,
+ SYN,
+ FIN,
+}
+
+struct GCCLLVMSomething; // linted with cfg option, beware that lint suggests `GccllvmSomething` instead of
+ // `GccLlvmSomething`
+
+fn main() {}
--- /dev/null
+error: name `HTTPResponse` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:3:8
+ |
+LL | struct HTTPResponse; // not linted by default, but with cfg option
+ | ^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `HttpResponse`
+ |
+ = note: `-D clippy::upper-case-acronyms` implied by `-D warnings`
+
+error: name `NS` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:8:5
+ |
+LL | NS, // not linted
+ | ^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ns`
+
+error: name `CWR` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:9:5
+ |
+LL | CWR,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Cwr`
+
+error: name `ECE` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:10:5
+ |
+LL | ECE,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Ece`
+
+error: name `URG` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:11:5
+ |
+LL | URG,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Urg`
+
+error: name `ACK` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:12:5
+ |
+LL | ACK,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ack`
+
+error: name `PSH` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:13:5
+ |
+LL | PSH,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Psh`
+
+error: name `RST` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:14:5
+ |
+LL | RST,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Rst`
+
+error: name `SYN` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:15:5
+ |
+LL | SYN,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Syn`
+
+error: name `FIN` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:16:5
+ |
+LL | FIN,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Fin`
+
+error: name `GCCLLVMSomething` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:19:8
+ |
+LL | struct GCCLLVMSomething; // linted with cfg option, beware that lint suggests `GccllvmSomething` instead of
+ | ^^^^^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `GccllvmSomething`
+
+error: aborting due to 11 previous errors
+
}
}
}
+
+#[proc_macro_derive(StructAUseSelf)]
+pub fn derive_use_self(_input: TokenStream) -> proc_macro::TokenStream {
+ quote! {
+ struct A;
+ impl A {
+ fn new() -> A {
+ A
+ }
+ }
+ }
+}
}
}
-fn main() {}
+#[rustfmt::skip]
+fn main() {
+ let mut range = 0..10;
+ range.all(|i| {i < 10} );
+
+ let v = vec![1, 2, 3];
+ if v.into_iter().any(|x| {x == 4}) {
+ println!("contains 4!");
+ }
+}
else {
println!("!")
}
+
+ if x == "hello" {
+ print!("Hello ");
+ } else {
+ #[cfg(not(roflol))]
+ if y == "world" {
+ println!("world!")
+ }
+ }
}
println!("!")
}
}
+
+ if x == "hello" {
+ print!("Hello ");
+ } else {
+ #[cfg(not(roflol))]
+ if y == "world" {
+ println!("world!")
+ }
+ }
}
// Fix #5962
if matches!(true, true) && matches!(true, true) {}
+
+ if true {
+ #[cfg(not(teehee))]
+ if true {
+ println!("Hello world!");
+ }
+ }
}
if matches!(true, true) {
if matches!(true, true) {}
}
+
+ if true {
+ #[cfg(not(teehee))]
+ if true {
+ println!("Hello world!");
+ }
+ }
}
};
}
}
+ let _: &dyn std::any::Any = match &Some(Some(1)) {
+ Some(e) => match e {
+ Some(e) => e,
+ e => e,
+ },
+ // else branch looks the same but the binding is different
+ e => e,
+ };
}
fn make<T>() -> T {
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:7:20
|
LL | Ok(val) => match val {
| |_________^
|
= note: `-D clippy::collapsible-match` implied by `-D warnings`
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:7:12
|
LL | Ok(val) => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:16:20
|
LL | Ok(val) => match val {
LL | | },
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:16:12
|
LL | Ok(val) => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:25:9
|
LL | / if let Some(n) = val {
LL | | }
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:24:15
|
LL | if let Ok(val) = res_opt {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:32:9
|
LL | / if let Some(n) = val {
LL | | }
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:31:15
|
LL | if let Ok(val) = res_opt {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:43:9
|
LL | / match val {
LL | | }
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:42:15
|
LL | if let Ok(val) = res_opt {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | match val {
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:52:13
|
LL | / if let Some(n) = val {
LL | | }
| |_____________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:51:12
|
LL | Ok(val) => {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:61:9
|
LL | / match val {
LL | | }
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:60:15
|
LL | if let Ok(val) = res_opt {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | match val {
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:72:13
|
LL | / if let Some(n) = val {
LL | | }
| |_____________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:71:12
|
LL | Ok(val) => {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:83:20
|
LL | Ok(val) => match val {
LL | | },
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:83:12
|
LL | Ok(val) => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match.rs:92:22
|
LL | Some(val) => match val {
LL | | },
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match.rs:92:14
|
LL | Some(val) => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match2.rs:8:34
|
LL | Ok(val) if make() => match val {
| |_____________^
|
= note: `-D clippy::collapsible-match` implied by `-D warnings`
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match2.rs:8:16
|
LL | Ok(val) if make() => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match2.rs:15:24
|
LL | Ok(val) => match val {
LL | | },
| |_____________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match2.rs:15:16
|
LL | Ok(val) => match val {
- | ^^^ Replace this binding
+ | ^^^ replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match2.rs:29:29
|
LL | $pat => match $e {
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
| ------------------------------------------------- in this macro invocation
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match2.rs:41:28
|
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
| ^^^ ^^^^^^^ with this pattern
| |
- | Replace this binding
+ | replace this binding
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match2.rs:46:20
|
LL | Some(s) => match *s {
LL | | },
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match2.rs:46:14
|
LL | Some(s) => match *s {
- | ^ Replace this binding
+ | ^ replace this binding
LL | [n] => foo(n),
| ^^^ with this pattern
-error: Unnecessary nested match
+error: unnecessary nested match
--> $DIR/collapsible_match2.rs:55:24
|
LL | Some(ref s) => match &*s {
LL | | },
| |_________^
|
-help: The outer pattern can be modified to include the inner pattern.
+help: the outer pattern can be modified to include the inner pattern
--> $DIR/collapsible_match2.rs:55:14
|
LL | Some(ref s) => match &*s {
- | ^^^^^ Replace this binding
+ | ^^^^^ replace this binding
LL | [n] => foo(n),
| ^^^ with this pattern
--- /dev/null
+//! This is a minimal reproducer for the ICE in https://github.com/rust-lang/rust-clippy/pull/6179.
+//! The ICE is mainly caused by using `hir_ty_to_ty`. See the discussion in the PR for details.
+
+#![warn(clippy::use_self)]
+#![allow(dead_code)]
+
+struct Foo {}
+
+impl Foo {
+ fn foo() -> Self {
+ impl Foo {
+ fn bar() {}
+ }
+
+ let _: _ = 1;
+
+ Self {}
+ }
+}
+
+fn main() {}
--- /dev/null
+#![warn(clippy::default_numeric_fallback)]
+#![allow(unused)]
+#![allow(clippy::never_loop)]
+#![allow(clippy::no_effect)]
+#![allow(clippy::unnecessary_operation)]
+
+mod basic_expr {
+ fn test() {
+ // Should lint unsuffixed literals typed `i32`.
+ let x = 22;
+ let x = [1, 2, 3];
+ let x = if true { (1, 2) } else { (3, 4) };
+ let x = match 1 {
+ 1 => 1,
+ _ => 2,
+ };
+
+ // Should lint unsuffixed literals typed `f64`.
+ let x = 0.12;
+
+ // Should NOT lint suffixed literals.
+ let x = 22_i32;
+ let x = 0.12_f64;
+
+ // Should NOT lint literals in init expr if `Local` has a type annotation.
+ let x: f64 = 0.1;
+ let x: [i32; 3] = [1, 2, 3];
+ let x: (i32, i32) = if true { (1, 2) } else { (3, 4) };
+ let x: _ = 1;
+ }
+}
+
+mod nested_local {
+ fn test() {
+ let x: _ = {
+ // Should lint this because this literal is not bound to any types.
+ let y = 1;
+
+ // Should NOT lint this because this literal is bound to `_` of outer `Local`.
+ 1
+ };
+
+ let x: _ = if true {
+ // Should lint this because this literal is not bound to any types.
+ let y = 1;
+
+ // Should NOT lint this because this literal is bound to `_` of outer `Local`.
+ 1
+ } else {
+ // Should lint this because this literal is not bound to any types.
+ let y = 1;
+
+ // Should NOT lint this because this literal is bound to `_` of outer `Local`.
+ 2
+ };
+ }
+}
+
+mod function_def {
+ fn ret_i32() -> i32 {
+ // Even though the output type is specified,
+ // this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
+ 1
+ }
+
+ fn test() {
+ // Should lint this because return type is inferred to `i32` and NOT bound to a concrete
+ // type.
+ let f = || -> _ { 1 };
+
+ // Even though the output type is specified,
+ // this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
+ let f = || -> i32 { 1 };
+ }
+}
+
+mod function_calls {
+ fn concrete_arg(x: i32) {}
+
+ fn generic_arg<T>(t: T) {}
+
+ fn test() {
+ // Should NOT lint this because the argument type is bound to a concrete type.
+ concrete_arg(1);
+
+ // Should lint this because the argument type is inferred to `i32` and NOT bound to a concrete type.
+ generic_arg(1);
+
+ // Should lint this because the argument type is inferred to `i32` and NOT bound to a concrete type.
+ let x: _ = generic_arg(1);
+ }
+}
+
+mod struct_ctor {
+ struct ConcreteStruct {
+ x: i32,
+ }
+
+ struct GenericStruct<T> {
+ x: T,
+ }
+
+ fn test() {
+ // Should NOT lint this because the field type is bound to a concrete type.
+ ConcreteStruct { x: 1 };
+
+ // Should lint this because the field type is inferred to `i32` and NOT bound to a concrete type.
+ GenericStruct { x: 1 };
+
+ // Should lint this because the field type is inferred to `i32` and NOT bound to a concrete type.
+ let _ = GenericStruct { x: 1 };
+ }
+}
+
+mod method_calls {
+ struct StructForMethodCallTest {}
+
+ impl StructForMethodCallTest {
+ fn concrete_arg(&self, x: i32) {}
+
+ fn generic_arg<T>(&self, t: T) {}
+ }
+
+ fn test() {
+ let s = StructForMethodCallTest {};
+
+ // Should NOT lint this because the argument type is bound to a concrete type.
+ s.concrete_arg(1);
+
+ // Should lint this because the argument type is bound to a concrete type.
+ s.generic_arg(1);
+ }
+}
+
+fn main() {}
--- /dev/null
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:10:17
+ |
+LL | let x = 22;
+ | ^^ help: consider adding suffix: `22_i32`
+ |
+ = note: `-D clippy::default-numeric-fallback` implied by `-D warnings`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:11:18
+ |
+LL | let x = [1, 2, 3];
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:11:21
+ |
+LL | let x = [1, 2, 3];
+ | ^ help: consider adding suffix: `2_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:11:24
+ |
+LL | let x = [1, 2, 3];
+ | ^ help: consider adding suffix: `3_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:12:28
+ |
+LL | let x = if true { (1, 2) } else { (3, 4) };
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:12:31
+ |
+LL | let x = if true { (1, 2) } else { (3, 4) };
+ | ^ help: consider adding suffix: `2_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:12:44
+ |
+LL | let x = if true { (1, 2) } else { (3, 4) };
+ | ^ help: consider adding suffix: `3_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:12:47
+ |
+LL | let x = if true { (1, 2) } else { (3, 4) };
+ | ^ help: consider adding suffix: `4_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:13:23
+ |
+LL | let x = match 1 {
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:14:13
+ |
+LL | 1 => 1,
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:14:18
+ |
+LL | 1 => 1,
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:15:18
+ |
+LL | _ => 2,
+ | ^ help: consider adding suffix: `2_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:19:17
+ |
+LL | let x = 0.12;
+ | ^^^^ help: consider adding suffix: `0.12_f64`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:37:21
+ |
+LL | let y = 1;
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:45:21
+ |
+LL | let y = 1;
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:51:21
+ |
+LL | let y = 1;
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:63:9
+ |
+LL | 1
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:69:27
+ |
+LL | let f = || -> _ { 1 };
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:73:29
+ |
+LL | let f = || -> i32 { 1 };
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:87:21
+ |
+LL | generic_arg(1);
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:90:32
+ |
+LL | let x: _ = generic_arg(1);
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:108:28
+ |
+LL | GenericStruct { x: 1 };
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:111:36
+ |
+LL | let _ = GenericStruct { x: 1 };
+ | ^ help: consider adding suffix: `1_i32`
+
+error: default numeric fallback might occur
+ --> $DIR/default_numeric_fallback.rs:131:23
+ |
+LL | s.generic_arg(1);
+ | ^ help: consider adding suffix: `1_i32`
+
+error: aborting due to 24 previous errors
+
}
/// This tests allowed identifiers.
+/// KiB MiB GiB TiB PiB EiB
/// DirectX
/// ECMAScript
+/// GPLv2 GPLv3
+/// GitHub GitLab
+/// IPv4 IPv6
+/// ClojureScript CoffeeScript JavaScript PureScript TypeScript
+/// NaN NaNs
/// OAuth GraphQL
+/// OCaml
+/// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS
/// WebGL
+/// TensorFlow
+/// TrueType
+/// iOS macOS
/// TeX LaTeX BibTeX BibLaTeX
+/// MinGW
/// CamelCase (see also #2395)
/// be_sure_we_got_to_the_end_of_it
fn test_allowed() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:59:5
+ --> $DIR/doc.rs:71:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `link_with_underscores` between ticks in the documentation
- --> $DIR/doc.rs:63:22
+ --> $DIR/doc.rs:75:22
|
LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823.
| ^^^^^^^^^^^^^^^^^^^^^
error: you should put `inline_link2` between ticks in the documentation
- --> $DIR/doc.rs:66:21
+ --> $DIR/doc.rs:78:21
|
LL | /// It can also be [inline_link2].
| ^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:76:5
+ --> $DIR/doc.rs:88:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `CamelCaseThing` between ticks in the documentation
- --> $DIR/doc.rs:84:8
+ --> $DIR/doc.rs:96:8
|
LL | /// ## CamelCaseThing
| ^^^^^^^^^^^^^^
error: you should put `CamelCaseThing` between ticks in the documentation
- --> $DIR/doc.rs:87:7
+ --> $DIR/doc.rs:99:7
|
LL | /// # CamelCaseThing
| ^^^^^^^^^^^^^^
error: you should put `CamelCaseThing` between ticks in the documentation
- --> $DIR/doc.rs:89:22
+ --> $DIR/doc.rs:101:22
|
LL | /// Not a title #897 CamelCaseThing
| ^^^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:90:5
+ --> $DIR/doc.rs:102:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:97:5
+ --> $DIR/doc.rs:109:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:110:5
+ --> $DIR/doc.rs:122:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `FooBar` between ticks in the documentation
- --> $DIR/doc.rs:121:43
+ --> $DIR/doc.rs:133:43
|
LL | /** E.g., serialization of an empty list: FooBar
| ^^^^^^
error: you should put `BarQuz` between ticks in the documentation
- --> $DIR/doc.rs:126:5
+ --> $DIR/doc.rs:138:5
|
LL | And BarQuz too.
| ^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:127:1
+ --> $DIR/doc.rs:139:1
|
LL | be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `FooBar` between ticks in the documentation
- --> $DIR/doc.rs:132:43
+ --> $DIR/doc.rs:144:43
|
LL | /** E.g., serialization of an empty list: FooBar
| ^^^^^^
error: you should put `BarQuz` between ticks in the documentation
- --> $DIR/doc.rs:137:5
+ --> $DIR/doc.rs:149:5
|
LL | And BarQuz too.
| ^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:138:1
+ --> $DIR/doc.rs:150:1
|
LL | be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation
- --> $DIR/doc.rs:149:5
+ --> $DIR/doc.rs:161:5
|
LL | /// be_sure_we_got_to_the_end_of_it
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put bare URLs between `<`/`>` or make a proper Markdown link
- --> $DIR/doc.rs:176:13
+ --> $DIR/doc.rs:188:13
|
LL | /// Not ok: http://www.unicode.org
| ^^^^^^^^^^^^^^^^^^^^^^
error: you should put bare URLs between `<`/`>` or make a proper Markdown link
- --> $DIR/doc.rs:177:13
+ --> $DIR/doc.rs:189:13
|
LL | /// Not ok: https://www.unicode.org
| ^^^^^^^^^^^^^^^^^^^^^^^
error: you should put bare URLs between `<`/`>` or make a proper Markdown link
- --> $DIR/doc.rs:178:13
+ --> $DIR/doc.rs:190:13
|
LL | /// Not ok: http://www.unicode.org/
| ^^^^^^^^^^^^^^^^^^^^^^
error: you should put bare URLs between `<`/`>` or make a proper Markdown link
- --> $DIR/doc.rs:179:13
+ --> $DIR/doc.rs:191:13
|
LL | /// Not ok: http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: you should put `mycrate::Collection` between ticks in the documentation
- --> $DIR/doc.rs:182:22
+ --> $DIR/doc.rs:194:22
|
LL | /// An iterator over mycrate::Collection's values.
| ^^^^^^^^^^^^^^^^^^^
});
}
+/// This needs to be documented
+pub fn unreachable_and_panic() {
+ if true {
+ unreachable!()
+ } else {
+ panic!()
+ }
+}
+
/// This is documented
///
/// # Panics
todo!()
}
+/// This is documented
+///
+/// # Panics
+///
+/// We still need to do this part
+pub fn unreachable_amd_panic_documented() {
+ if true {
+ unreachable!()
+ } else {
+ panic!()
+ }
+}
+
/// This is okay because it is private
fn unwrap_private() {
let result = Err("Hi");
}
});
}
+
+/// This is okay because unreachable
+pub fn unreachable() {
+ unreachable!("This function panics")
+}
| ^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
-error: aborting due to 4 previous errors
+error: docs for function which may panic missing `# Panics` section
+ --> $DIR/doc_panics.rs:32:1
+ |
+LL | / pub fn unreachable_and_panic() {
+LL | | if true {
+LL | | unreachable!()
+LL | | } else {
+LL | | panic!()
+LL | | }
+LL | | }
+ | |_^
+ |
+note: first possible panic found here
+ --> $DIR/doc_panics.rs:36:9
+ |
+LL | panic!()
+ | ^^^^^^^^
+ = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 5 previous errors
Layer3,
}
+// should lint suggesting `IData`, not only `Data` (see #4639)
+enum IDataRequest {
+ PutIData(String),
+ GetIData(String),
+ DeleteUnpubIData(String),
+}
+
+enum HIDataRequest {
+ PutHIData(String),
+ GetHIData(String),
+ DeleteUnpubHIData(String),
+}
+
fn main() {}
= note: `-D clippy::pub-enum-variant-names` implied by `-D warnings`
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: aborting due to 10 previous errors
+error: all variants have the same postfix: `IData`
+ --> $DIR/enum_variants.rs:137:1
+ |
+LL | / enum IDataRequest {
+LL | | PutIData(String),
+LL | | GetIData(String),
+LL | | DeleteUnpubIData(String),
+LL | | }
+ | |_^
+ |
+ = help: remove the postfixes and use full paths to the variants instead of glob imports
+
+error: all variants have the same postfix: `HIData`
+ --> $DIR/enum_variants.rs:143:1
+ |
+LL | / enum HIDataRequest {
+LL | | PutHIData(String),
+LL | | GetHIData(String),
+LL | | DeleteUnpubHIData(String),
+LL | | }
+ | |_^
+ |
+ = help: remove the postfixes and use full paths to the variants instead of glob imports
+
+error: aborting due to 12 previous errors
--- /dev/null
+#![warn(clippy::from_str_radix_10)]
+
+mod some_mod {
+ // fake function that shouldn't trigger the lint
+ pub fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
+ unimplemented!()
+ }
+}
+
+// fake function that shouldn't trigger the lint
+fn from_str_radix(_: &str, _: u32) -> Result<(), std::num::ParseIntError> {
+ unimplemented!()
+}
+
+// to test parenthesis addition
+struct Test;
+
+impl std::ops::Add<Test> for Test {
+ type Output = &'static str;
+
+ fn add(self, _: Self) -> Self::Output {
+ "304"
+ }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ // all of these should trigger the lint
+ u32::from_str_radix("30", 10)?;
+ i64::from_str_radix("24", 10)?;
+ isize::from_str_radix("100", 10)?;
+ u8::from_str_radix("7", 10)?;
+ u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
+ i128::from_str_radix(Test + Test, 10)?;
+
+ let string = "300";
+ i32::from_str_radix(string, 10)?;
+
+ let stringier = "400".to_string();
+ i32::from_str_radix(&stringier, 10)?;
+
+ // none of these should trigger the lint
+ u16::from_str_radix("20", 3)?;
+ i32::from_str_radix("45", 12)?;
+ usize::from_str_radix("10", 16)?;
+ i128::from_str_radix("10", 13)?;
+ some_mod::from_str_radix("50", 10)?;
+ some_mod::from_str_radix("50", 6)?;
+ from_str_radix("50", 10)?;
+ from_str_radix("50", 6)?;
+
+ Ok(())
+}
--- /dev/null
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:28:5
+ |
+LL | u32::from_str_radix("30", 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::<u32>()`
+ |
+ = note: `-D clippy::from-str-radix-10` implied by `-D warnings`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:29:5
+ |
+LL | i64::from_str_radix("24", 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::<i64>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:30:5
+ |
+LL | isize::from_str_radix("100", 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::<isize>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:31:5
+ |
+LL | u8::from_str_radix("7", 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::<u8>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:32:5
+ |
+LL | u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(("10".to_owned() + "5")).parse::<u16>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:33:5
+ |
+LL | i128::from_str_radix(Test + Test, 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::<i128>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:36:5
+ |
+LL | i32::from_str_radix(string, 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::<i32>()`
+
+error: this call to `from_str_radix` can be replaced with a call to `str::parse`
+ --> $DIR/from_str_radix_10.rs:39:5
+ |
+LL | i32::from_str_radix(&stringier, 10)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::<i32>()`
+
+error: aborting due to 8 previous errors
+
if true {
for _ in &[42] {
let foo: &Option<_> = &Some::<u8>(42);
- if true {
+ if foo.is_some() {
break;
} else {
continue;
} else {
//~ ERROR same body as `if` block
for _ in &[42] {
- let foo: &Option<_> = &Some::<u8>(42);
- if true {
+ let bar: &Option<_> = &Some::<u8>(42);
+ if bar.is_some() {
break;
} else {
continue;
| ____________^
LL | | //~ ERROR same body as `if` block
LL | | for _ in &[42] {
-LL | | let foo: &Option<_> = &Some::<u8>(42);
+LL | | let bar: &Option<_> = &Some::<u8>(42);
... |
LL | | }
LL | | }
| _____________^
LL | | for _ in &[42] {
LL | | let foo: &Option<_> = &Some::<u8>(42);
-LL | | if true {
+LL | | if foo.is_some() {
... |
LL | | }
LL | | } else {
--- /dev/null
+// run-rustfix
+// edition:2018
+#![warn(clippy::inconsistent_struct_constructor)]
+#![allow(clippy::redundant_field_names)]
+#![allow(clippy::unnecessary_operation)]
+#![allow(clippy::no_effect)]
+#![allow(dead_code)]
+
+#[derive(Default)]
+struct Foo {
+ x: i32,
+ y: i32,
+ z: i32,
+}
+
+mod without_base {
+ use super::Foo;
+
+ fn test() {
+ let x = 1;
+ let y = 1;
+ let z = 1;
+
+ // Should lint.
+ Foo { x, y, z };
+
+ // Shoule NOT lint because the order is the same as in the definition.
+ Foo { x, y, z };
+
+ // Should NOT lint because z is not a shorthand init.
+ Foo { y, x, z: z };
+ }
+}
+
+mod with_base {
+ use super::Foo;
+
+ fn test() {
+ let x = 1;
+ let z = 1;
+
+ // Should lint.
+ Foo { x, z, ..Default::default() };
+
+ // Should NOT lint because the order is consistent with the definition.
+ Foo {
+ x,
+ z,
+ ..Default::default()
+ };
+
+ // Should NOT lint because z is not a shorthand init.
+ Foo {
+ z: z,
+ x,
+ ..Default::default()
+ };
+ }
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+// edition:2018
+#![warn(clippy::inconsistent_struct_constructor)]
+#![allow(clippy::redundant_field_names)]
+#![allow(clippy::unnecessary_operation)]
+#![allow(clippy::no_effect)]
+#![allow(dead_code)]
+
+#[derive(Default)]
+struct Foo {
+ x: i32,
+ y: i32,
+ z: i32,
+}
+
+mod without_base {
+ use super::Foo;
+
+ fn test() {
+ let x = 1;
+ let y = 1;
+ let z = 1;
+
+ // Should lint.
+ Foo { y, x, z };
+
+ // Shoule NOT lint because the order is the same as in the definition.
+ Foo { x, y, z };
+
+ // Should NOT lint because z is not a shorthand init.
+ Foo { y, x, z: z };
+ }
+}
+
+mod with_base {
+ use super::Foo;
+
+ fn test() {
+ let x = 1;
+ let z = 1;
+
+ // Should lint.
+ Foo {
+ z,
+ x,
+ ..Default::default()
+ };
+
+ // Should NOT lint because the order is consistent with the definition.
+ Foo {
+ x,
+ z,
+ ..Default::default()
+ };
+
+ // Should NOT lint because z is not a shorthand init.
+ Foo {
+ z: z,
+ x,
+ ..Default::default()
+ };
+ }
+}
+
+fn main() {}
--- /dev/null
+error: inconsistent struct constructor
+ --> $DIR/inconsistent_struct_constructor.rs:25:9
+ |
+LL | Foo { y, x, z };
+ | ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }`
+ |
+ = note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
+
+error: inconsistent struct constructor
+ --> $DIR/inconsistent_struct_constructor.rs:43:9
+ |
+LL | / Foo {
+LL | | z,
+LL | | x,
+LL | | ..Default::default()
+LL | | };
+ | |_________^ help: try: `Foo { x, z, ..Default::default() }`
+
+error: aborting due to 2 previous errors
+
struct D;
struct E;
struct F;
+struct G;
impl A {
// Should be detected; emit warning
}
}
+impl G {
+ // Should not be detected, as it does not match the function signature
+ fn to_string<const _N: usize>(&self) -> String {
+ "G.to_string()".to_string()
+ }
+}
+
fn main() {
let a = A;
a.to_string();
let f = F;
f.to_string(1);
+
+ let g = G;
+ g.to_string::<1>();
}
error: implementation of inherent method `to_string(&self) -> String` for type `A`
- --> $DIR/inherent_to_string.rs:20:5
+ --> $DIR/inherent_to_string.rs:21:5
|
LL | / fn to_string(&self) -> String {
LL | | "A.to_string()".to_string()
= help: implement trait `Display` for type `A` instead
error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`
- --> $DIR/inherent_to_string.rs:44:5
+ --> $DIR/inherent_to_string.rs:45:5
|
LL | / fn to_string(&self) -> String {
LL | | "C.to_string()".to_string()
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::manual_map)]
+#![allow(clippy::no_effect, clippy::map_identity, clippy::unit_arg, clippy::match_ref_pats)]
+
+fn main() {
+ Some(0).map(|_| 2);
+
+ Some(0).map(|x| x + 1);
+
+ Some("").map(|x| x.is_empty());
+
+ Some(0).map(|x| !x);
+
+ #[rustfmt::skip]
+ Some(0).map(std::convert::identity);
+
+ Some(&String::new()).map(|x| str::len(x));
+
+ match Some(0) {
+ Some(x) if false => Some(x + 1),
+ _ => None,
+ };
+
+ Some([0, 1]).as_ref().map(|x| x[0]);
+
+ Some(0).map(|x| x * 2);
+
+ Some(String::new()).as_ref().map(|x| x.is_empty());
+
+ Some(String::new()).as_ref().map(|x| x.len());
+
+ Some(0).map(|x| x + x);
+
+ #[warn(clippy::option_map_unit_fn)]
+ match &mut Some(String::new()) {
+ Some(x) => Some(x.push_str("")),
+ None => None,
+ };
+
+ #[allow(clippy::option_map_unit_fn)]
+ {
+ Some(String::new()).as_mut().map(|x| x.push_str(""));
+ }
+
+ Some(String::new()).as_ref().map(|x| x.len());
+
+ Some(String::new()).as_ref().map(|x| x.is_empty());
+
+ Some((0, 1, 2)).map(|(x, y, z)| x + y + z);
+
+ Some([1, 2, 3]).map(|[first, ..]| first);
+
+ Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x));
+
+ match Some((String::new(), 0)) {
+ Some((ref x, y)) => Some((y, x)),
+ None => None,
+ };
+
+ match Some(Some(0)) {
+ Some(Some(_)) | Some(None) => Some(0),
+ None => None,
+ };
+
+ match Some(Some((0, 1))) {
+ Some(Some((x, 1))) => Some(x),
+ _ => None,
+ };
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::manual_map)]
+#![allow(clippy::no_effect, clippy::map_identity, clippy::unit_arg, clippy::match_ref_pats)]
+
+fn main() {
+ match Some(0) {
+ Some(_) => Some(2),
+ None::<u32> => None,
+ };
+
+ match Some(0) {
+ Some(x) => Some(x + 1),
+ _ => None,
+ };
+
+ match Some("") {
+ Some(x) => Some(x.is_empty()),
+ None => None,
+ };
+
+ if let Some(x) = Some(0) {
+ Some(!x)
+ } else {
+ None
+ };
+
+ #[rustfmt::skip]
+ match Some(0) {
+ Some(x) => { Some(std::convert::identity(x)) }
+ None => { None }
+ };
+
+ match Some(&String::new()) {
+ Some(x) => Some(str::len(x)),
+ None => None,
+ };
+
+ match Some(0) {
+ Some(x) if false => Some(x + 1),
+ _ => None,
+ };
+
+ match &Some([0, 1]) {
+ Some(x) => Some(x[0]),
+ &None => None,
+ };
+
+ match &Some(0) {
+ &Some(x) => Some(x * 2),
+ None => None,
+ };
+
+ match Some(String::new()) {
+ Some(ref x) => Some(x.is_empty()),
+ _ => None,
+ };
+
+ match &&Some(String::new()) {
+ Some(x) => Some(x.len()),
+ _ => None,
+ };
+
+ match &&Some(0) {
+ &&Some(x) => Some(x + x),
+ &&_ => None,
+ };
+
+ #[warn(clippy::option_map_unit_fn)]
+ match &mut Some(String::new()) {
+ Some(x) => Some(x.push_str("")),
+ None => None,
+ };
+
+ #[allow(clippy::option_map_unit_fn)]
+ {
+ match &mut Some(String::new()) {
+ Some(x) => Some(x.push_str("")),
+ None => None,
+ };
+ }
+
+ match &mut Some(String::new()) {
+ Some(ref x) => Some(x.len()),
+ None => None,
+ };
+
+ match &mut &Some(String::new()) {
+ Some(x) => Some(x.is_empty()),
+ &mut _ => None,
+ };
+
+ match Some((0, 1, 2)) {
+ Some((x, y, z)) => Some(x + y + z),
+ None => None,
+ };
+
+ match Some([1, 2, 3]) {
+ Some([first, ..]) => Some(first),
+ None => None,
+ };
+
+ match &Some((String::new(), "test")) {
+ Some((x, y)) => Some((y, x)),
+ None => None,
+ };
+
+ match Some((String::new(), 0)) {
+ Some((ref x, y)) => Some((y, x)),
+ None => None,
+ };
+
+ match Some(Some(0)) {
+ Some(Some(_)) | Some(None) => Some(0),
+ None => None,
+ };
+
+ match Some(Some((0, 1))) {
+ Some(Some((x, 1))) => Some(x),
+ _ => None,
+ };
+}
--- /dev/null
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:7:5
+ |
+LL | / match Some(0) {
+LL | | Some(_) => Some(2),
+LL | | None::<u32> => None,
+LL | | };
+ | |_____^ help: try this: `Some(0).map(|_| 2)`
+ |
+ = note: `-D clippy::manual-map` implied by `-D warnings`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:12:5
+ |
+LL | / match Some(0) {
+LL | | Some(x) => Some(x + 1),
+LL | | _ => None,
+LL | | };
+ | |_____^ help: try this: `Some(0).map(|x| x + 1)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:17:5
+ |
+LL | / match Some("") {
+LL | | Some(x) => Some(x.is_empty()),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some("").map(|x| x.is_empty())`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:22:5
+ |
+LL | / if let Some(x) = Some(0) {
+LL | | Some(!x)
+LL | | } else {
+LL | | None
+LL | | };
+ | |_____^ help: try this: `Some(0).map(|x| !x)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:29:5
+ |
+LL | / match Some(0) {
+LL | | Some(x) => { Some(std::convert::identity(x)) }
+LL | | None => { None }
+LL | | };
+ | |_____^ help: try this: `Some(0).map(std::convert::identity)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:34:5
+ |
+LL | / match Some(&String::new()) {
+LL | | Some(x) => Some(str::len(x)),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some(&String::new()).map(|x| str::len(x))`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:44:5
+ |
+LL | / match &Some([0, 1]) {
+LL | | Some(x) => Some(x[0]),
+LL | | &None => None,
+LL | | };
+ | |_____^ help: try this: `Some([0, 1]).as_ref().map(|x| x[0])`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:49:5
+ |
+LL | / match &Some(0) {
+LL | | &Some(x) => Some(x * 2),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some(0).map(|x| x * 2)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:54:5
+ |
+LL | / match Some(String::new()) {
+LL | | Some(ref x) => Some(x.is_empty()),
+LL | | _ => None,
+LL | | };
+ | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:59:5
+ |
+LL | / match &&Some(String::new()) {
+LL | | Some(x) => Some(x.len()),
+LL | | _ => None,
+LL | | };
+ | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:64:5
+ |
+LL | / match &&Some(0) {
+LL | | &&Some(x) => Some(x + x),
+LL | | &&_ => None,
+LL | | };
+ | |_____^ help: try this: `Some(0).map(|x| x + x)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:77:9
+ |
+LL | / match &mut Some(String::new()) {
+LL | | Some(x) => Some(x.push_str("")),
+LL | | None => None,
+LL | | };
+ | |_________^ help: try this: `Some(String::new()).as_mut().map(|x| x.push_str(""))`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:83:5
+ |
+LL | / match &mut Some(String::new()) {
+LL | | Some(ref x) => Some(x.len()),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:88:5
+ |
+LL | / match &mut &Some(String::new()) {
+LL | | Some(x) => Some(x.is_empty()),
+LL | | &mut _ => None,
+LL | | };
+ | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:93:5
+ |
+LL | / match Some((0, 1, 2)) {
+LL | | Some((x, y, z)) => Some(x + y + z),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some((0, 1, 2)).map(|(x, y, z)| x + y + z)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:98:5
+ |
+LL | / match Some([1, 2, 3]) {
+LL | | Some([first, ..]) => Some(first),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some([1, 2, 3]).map(|[first, ..]| first)`
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option.rs:103:5
+ |
+LL | / match &Some((String::new(), "test")) {
+LL | | Some((x, y)) => Some((y, x)),
+LL | | None => None,
+LL | | };
+ | |_____^ help: try this: `Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x))`
+
+error: aborting due to 17 previous errors
+
-#![allow(clippy::unnecessary_wraps)]
-#[warn(clippy::result_unit_err)]
-#[allow(unused)]
+#![warn(clippy::result_unit_err)]
pub fn returns_unit_error() -> Result<u32, ()> {
Err(())
}
}
+// https://github.com/rust-lang/rust-clippy/issues/6546
+pub mod issue_6546 {
+ type ResInv<A, B> = Result<B, A>;
+
+ pub fn should_lint() -> ResInv<(), usize> {
+ Ok(0)
+ }
+
+ pub fn should_not_lint() -> ResInv<usize, ()> {
+ Ok(())
+ }
+
+ type MyRes<A, B> = Result<(A, B), Box<dyn std::error::Error>>;
+
+ pub fn should_not_lint2(x: i32) -> MyRes<i32, ()> {
+ Ok((x, ()))
+ }
+}
+
fn main() {}
error: this returns a `Result<_, ()>
- --> $DIR/result_unit_error.rs:5:1
+ --> $DIR/result_unit_error.rs:3:1
|
LL | pub fn returns_unit_error() -> Result<u32, ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: use a custom Error type instead
error: this returns a `Result<_, ()>
- --> $DIR/result_unit_error.rs:14:5
+ --> $DIR/result_unit_error.rs:12:5
|
LL | fn get_that_error(&self) -> Result<bool, ()>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: use a custom Error type instead
error: this returns a `Result<_, ()>
- --> $DIR/result_unit_error.rs:16:5
+ --> $DIR/result_unit_error.rs:14:5
|
LL | fn get_this_one_too(&self) -> Result<bool, ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: use a custom Error type instead
error: this returns a `Result<_, ()>
- --> $DIR/result_unit_error.rs:34:5
+ --> $DIR/result_unit_error.rs:32:5
|
LL | pub fn unit_error(&self) -> Result<usize, ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use a custom Error type instead
-error: aborting due to 4 previous errors
+error: this returns a `Result<_, ()>
+ --> $DIR/result_unit_error.rs:41:5
+ |
+LL | pub fn should_lint() -> ResInv<(), usize> {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: use a custom Error type instead
+
+error: aborting due to 5 previous errors
})
}
+// should be linted
+fn issue_6640_1(a: bool, b: bool) -> Option<()> {
+ if a && b {
+ return Some(());
+ }
+ if a {
+ Some(());
+ Some(())
+ } else {
+ return Some(());
+ }
+}
+
+// should be linted
+fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> {
+ if a && b {
+ return Ok(());
+ }
+ if a {
+ Ok(())
+ } else {
+ return Ok(());
+ }
+}
+
+// should not be linted
+fn issue_6640_3() -> Option<()> {
+ if true {
+ Some(())
+ } else {
+ None
+ }
+}
+
+// should not be linted
+fn issue_6640_4() -> Result<(), ()> {
+ if true {
+ Ok(())
+ } else {
+ Err(())
+ }
+}
+
fn main() {
// method calls are not linted
func1(true, true);
func2(true, true);
+ issue_6640_1(true, true);
+ issue_6640_2(true, true);
}
|
LL | fn func1(a: bool, b: bool) -> i32 {
| ^^^
-help: ...and change the returning expressions
+help: ...and then change returning expressions
|
LL | return 42;
LL | }
|
LL | fn func2(a: bool, b: bool) -> i32 {
| ^^^
-help: ...and change the returning expressions
+help: ...and then change returning expressions
|
LL | return 10;
LL | }
|
LL | fn func5() -> i32 {
| ^^^
-help: ...and change the returning expressions
+help: ...and then change returning expressions
|
LL | 1
|
|
LL | fn func7() -> i32 {
| ^^^
-help: ...and change the returning expressions
+help: ...and then change returning expressions
|
LL | 1
|
|
LL | fn func12() -> i32 {
| ^^^
-help: ...and change the returning expressions
+help: ...and then change returning expressions
|
LL | 1
|
-error: aborting due to 5 previous errors
+error: this function's return value is unnecessary
+ --> $DIR/unnecessary_wraps.rs:120:1
+ |
+LL | / fn issue_6640_1(a: bool, b: bool) -> Option<()> {
+LL | | if a && b {
+LL | | return Some(());
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_^
+ |
+help: remove the return type...
+ |
+LL | fn issue_6640_1(a: bool, b: bool) -> Option<()> {
+ | ^^^^^^^^^^
+help: ...and then remove returned values
+ |
+LL | return ;
+LL | }
+LL | if a {
+LL | Some(());
+LL |
+LL | } else {
+ ...
+
+error: this function's return value is unnecessary
+ --> $DIR/unnecessary_wraps.rs:133:1
+ |
+LL | / fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> {
+LL | | if a && b {
+LL | | return Ok(());
+LL | | }
+... |
+LL | | }
+LL | | }
+ | |_^
+ |
+help: remove the return type...
+ |
+LL | fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> {
+ | ^^^^^^^^^^^^^^^
+help: ...and then remove returned values
+ |
+LL | return ;
+LL | }
+LL | if a {
+LL |
+LL | } else {
+LL | return ;
+ |
+
+error: aborting due to 7 previous errors
#![warn(clippy::upper_case_acronyms)]
-struct HTTPResponse; // linted
+struct HTTPResponse; // not linted by default, but with cfg option
struct CString; // not linted
enum Flags {
- NS, // linted
+ NS, // not linted
CWR,
ECE,
URG,
FIN,
}
-struct GCCLLVMSomething; // linted, beware that lint suggests `GccllvmSomething` instead of `GccLlvmSomething`
+struct GCCLLVMSomething; // linted with cfg option, beware that lint suggests `GccllvmSomething` instead of
+ // `GccLlvmSomething`
fn main() {}
-error: name `HTTPResponse` contains a capitalized acronym
- --> $DIR/upper_case_acronyms.rs:3:8
- |
-LL | struct HTTPResponse; // linted
- | ^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `HttpResponse`
- |
- = note: `-D clippy::upper-case-acronyms` implied by `-D warnings`
-
-error: name `NS` contains a capitalized acronym
- --> $DIR/upper_case_acronyms.rs:8:5
- |
-LL | NS, // linted
- | ^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ns`
-
error: name `CWR` contains a capitalized acronym
--> $DIR/upper_case_acronyms.rs:9:5
|
LL | CWR,
| ^^^ help: consider making the acronym lowercase, except the initial letter: `Cwr`
+ |
+ = note: `-D clippy::upper-case-acronyms` implied by `-D warnings`
error: name `ECE` contains a capitalized acronym
--> $DIR/upper_case_acronyms.rs:10:5
LL | FIN,
| ^^^ help: consider making the acronym lowercase, except the initial letter: `Fin`
-error: name `GCCLLVMSomething` contains a capitalized acronym
- --> $DIR/upper_case_acronyms.rs:19:8
- |
-LL | struct GCCLLVMSomething; // linted, beware that lint suggests `GccllvmSomething` instead of `GccLlvmSomething`
- | ^^^^^^^^^^^^^^^^ help: consider making the acronym lowercase, except the initial letter: `GccllvmSomething`
-
-error: aborting due to 11 previous errors
+error: aborting due to 8 previous errors
// run-rustfix
// edition:2018
+// aux-build:proc_macro_derive.rs
#![warn(clippy::use_self)]
#![allow(dead_code)]
-#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms)]
+#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms, clippy::from_over_into)]
+
+#[macro_use]
+extern crate proc_macro_derive;
fn main() {}
mod issue2894 {
trait IntoBytes {
- #[allow(clippy::wrong_self_convention)]
- fn into_bytes(&self) -> Vec<u8>;
+ fn to_bytes(&self) -> Vec<u8>;
}
// This should not be linted
impl IntoBytes for u8 {
- fn into_bytes(&self) -> Vec<u8> {
+ fn to_bytes(&self) -> Vec<u8> {
vec![*self]
}
}
mod macros {
macro_rules! use_self_expand {
() => {
- fn new() -> Self {
- Self {}
+ fn new() -> Foo {
+ Foo {}
}
};
}
struct Foo {}
impl Foo {
- use_self_expand!(); // Should lint in local macros
+ use_self_expand!(); // Should not lint in local macros
}
+
+ #[derive(StructAUseSelf)] // Should not lint in derives
+ struct A;
}
mod nesting {
struct B;
trait Trait<T> {
- fn a(v: T);
+ fn a(v: T) -> Self;
}
impl Trait<Vec<A>> for Vec<B> {
- fn a(_: Vec<A>) {}
+ fn a(_: Vec<A>) -> Self {
+ unimplemented!()
+ }
+ }
+
+ impl<T> Trait<Vec<A>> for Vec<T>
+ where
+ T: Trait<B>,
+ {
+ fn a(v: Vec<A>) -> Self {
+ <Vec<B>>::a(v).into_iter().map(Trait::a).collect()
+ }
}
}
}
}
}
+
+// reused from #1997
+mod generics {
+ struct Foo<T> {
+ value: T,
+ }
+
+ impl<T> Foo<T> {
+ // `Self` is applicable here
+ fn foo(value: T) -> Self {
+ Self { value }
+ }
+
+ // `Cannot` use `Self` as a return type as the generic types are different
+ fn bar(value: i32) -> Foo<i32> {
+ Foo { value }
+ }
+ }
+}
+
+mod issue4140 {
+ pub struct Error<From, To> {
+ _from: From,
+ _too: To,
+ }
+
+ pub trait From<T> {
+ type From;
+ type To;
+
+ fn from(value: T) -> Self;
+ }
+
+ pub trait TryFrom<T>
+ where
+ Self: Sized,
+ {
+ type From;
+ type To;
+
+ fn try_from(value: T) -> Result<Self, Error<Self::From, Self::To>>;
+ }
+
+ impl<F, T> TryFrom<F> for T
+ where
+ T: From<F>,
+ {
+ type From = Self;
+ type To = Self;
+
+ fn try_from(value: F) -> Result<Self, Error<Self::From, Self::To>> {
+ Ok(From::from(value))
+ }
+ }
+
+ impl From<bool> for i64 {
+ type From = bool;
+ type To = Self;
+
+ fn from(value: bool) -> Self {
+ if value {
+ 100
+ } else {
+ 0
+ }
+ }
+ }
+}
+
+mod issue2843 {
+ trait Foo {
+ type Bar;
+ }
+
+ impl Foo for usize {
+ type Bar = u8;
+ }
+
+ impl<T: Foo> Foo for Option<T> {
+ type Bar = Option<T::Bar>;
+ }
+}
+
+mod issue3859 {
+ pub struct Foo;
+ pub struct Bar([usize; 3]);
+
+ impl Foo {
+ pub const BAR: usize = 3;
+
+ pub fn foo() {
+ const _X: usize = Foo::BAR;
+ // const _Y: usize = Self::BAR;
+ }
+ }
+}
+
+mod issue4305 {
+ trait Foo: 'static {}
+
+ struct Bar;
+
+ impl Foo for Bar {}
+
+ impl<T: Foo> From<T> for Box<dyn Foo> {
+ fn from(t: T) -> Self {
+ Box::new(t)
+ }
+ }
+}
+
+mod lint_at_item_level {
+ struct Foo {}
+
+ #[allow(clippy::use_self)]
+ impl Foo {
+ fn new() -> Foo {
+ Foo {}
+ }
+ }
+
+ #[allow(clippy::use_self)]
+ impl Default for Foo {
+ fn default() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+mod lint_at_impl_item_level {
+ struct Foo {}
+
+ impl Foo {
+ #[allow(clippy::use_self)]
+ fn new() -> Foo {
+ Foo {}
+ }
+ }
+
+ impl Default for Foo {
+ #[allow(clippy::use_self)]
+ fn default() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+mod issue4734 {
+ #[repr(C, packed)]
+ pub struct X {
+ pub x: u32,
+ }
+
+ impl From<X> for u32 {
+ fn from(c: X) -> Self {
+ unsafe { core::mem::transmute(c) }
+ }
+ }
+}
+
+mod nested_paths {
+ use std::convert::Into;
+ mod submod {
+ pub struct B {}
+ pub struct C {}
+
+ impl Into<C> for B {
+ fn into(self) -> C {
+ C {}
+ }
+ }
+ }
+
+ struct A<T> {
+ t: T,
+ }
+
+ impl<T> A<T> {
+ fn new<V: Into<T>>(v: V) -> Self {
+ Self { t: Into::into(v) }
+ }
+ }
+
+ impl A<submod::C> {
+ fn test() -> Self {
+ Self::new::<submod::B>(submod::B {})
+ }
+ }
+}
// run-rustfix
// edition:2018
+// aux-build:proc_macro_derive.rs
#![warn(clippy::use_self)]
#![allow(dead_code)]
-#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms)]
+#![allow(clippy::should_implement_trait, clippy::upper_case_acronyms, clippy::from_over_into)]
+
+#[macro_use]
+extern crate proc_macro_derive;
fn main() {}
mod issue2894 {
trait IntoBytes {
- #[allow(clippy::wrong_self_convention)]
- fn into_bytes(&self) -> Vec<u8>;
+ fn to_bytes(&self) -> Vec<u8>;
}
// This should not be linted
impl IntoBytes for u8 {
- fn into_bytes(&self) -> Vec<u8> {
+ fn to_bytes(&self) -> Vec<u8> {
vec![*self]
}
}
struct Foo;
impl Foo {
- fn bad(foos: &[Self]) -> impl Iterator<Item = &Foo> {
+ fn bad(foos: &[Foo]) -> impl Iterator<Item = &Foo> {
foos.iter()
}
struct Foo {}
impl Foo {
- use_self_expand!(); // Should lint in local macros
+ use_self_expand!(); // Should not lint in local macros
}
+
+ #[derive(StructAUseSelf)] // Should not lint in derives
+ struct A;
}
mod nesting {
struct B;
trait Trait<T> {
- fn a(v: T);
+ fn a(v: T) -> Self;
}
impl Trait<Vec<A>> for Vec<B> {
- fn a(_: Vec<A>) {}
+ fn a(_: Vec<A>) -> Self {
+ unimplemented!()
+ }
+ }
+
+ impl<T> Trait<Vec<A>> for Vec<T>
+ where
+ T: Trait<B>,
+ {
+ fn a(v: Vec<A>) -> Self {
+ <Vec<B>>::a(v).into_iter().map(Trait::a).collect()
+ }
}
}
}
}
}
+
+// reused from #1997
+mod generics {
+ struct Foo<T> {
+ value: T,
+ }
+
+ impl<T> Foo<T> {
+ // `Self` is applicable here
+ fn foo(value: T) -> Foo<T> {
+ Foo { value }
+ }
+
+ // `Cannot` use `Self` as a return type as the generic types are different
+ fn bar(value: i32) -> Foo<i32> {
+ Foo { value }
+ }
+ }
+}
+
+mod issue4140 {
+ pub struct Error<From, To> {
+ _from: From,
+ _too: To,
+ }
+
+ pub trait From<T> {
+ type From;
+ type To;
+
+ fn from(value: T) -> Self;
+ }
+
+ pub trait TryFrom<T>
+ where
+ Self: Sized,
+ {
+ type From;
+ type To;
+
+ fn try_from(value: T) -> Result<Self, Error<Self::From, Self::To>>;
+ }
+
+ impl<F, T> TryFrom<F> for T
+ where
+ T: From<F>,
+ {
+ type From = T::From;
+ type To = T::To;
+
+ fn try_from(value: F) -> Result<Self, Error<Self::From, Self::To>> {
+ Ok(From::from(value))
+ }
+ }
+
+ impl From<bool> for i64 {
+ type From = bool;
+ type To = Self;
+
+ fn from(value: bool) -> Self {
+ if value {
+ 100
+ } else {
+ 0
+ }
+ }
+ }
+}
+
+mod issue2843 {
+ trait Foo {
+ type Bar;
+ }
+
+ impl Foo for usize {
+ type Bar = u8;
+ }
+
+ impl<T: Foo> Foo for Option<T> {
+ type Bar = Option<T::Bar>;
+ }
+}
+
+mod issue3859 {
+ pub struct Foo;
+ pub struct Bar([usize; 3]);
+
+ impl Foo {
+ pub const BAR: usize = 3;
+
+ pub fn foo() {
+ const _X: usize = Foo::BAR;
+ // const _Y: usize = Self::BAR;
+ }
+ }
+}
+
+mod issue4305 {
+ trait Foo: 'static {}
+
+ struct Bar;
+
+ impl Foo for Bar {}
+
+ impl<T: Foo> From<T> for Box<dyn Foo> {
+ fn from(t: T) -> Self {
+ Box::new(t)
+ }
+ }
+}
+
+mod lint_at_item_level {
+ struct Foo {}
+
+ #[allow(clippy::use_self)]
+ impl Foo {
+ fn new() -> Foo {
+ Foo {}
+ }
+ }
+
+ #[allow(clippy::use_self)]
+ impl Default for Foo {
+ fn default() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+mod lint_at_impl_item_level {
+ struct Foo {}
+
+ impl Foo {
+ #[allow(clippy::use_self)]
+ fn new() -> Foo {
+ Foo {}
+ }
+ }
+
+ impl Default for Foo {
+ #[allow(clippy::use_self)]
+ fn default() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+mod issue4734 {
+ #[repr(C, packed)]
+ pub struct X {
+ pub x: u32,
+ }
+
+ impl From<X> for u32 {
+ fn from(c: X) -> Self {
+ unsafe { core::mem::transmute(c) }
+ }
+ }
+}
+
+mod nested_paths {
+ use std::convert::Into;
+ mod submod {
+ pub struct B {}
+ pub struct C {}
+
+ impl Into<C> for B {
+ fn into(self) -> C {
+ C {}
+ }
+ }
+ }
+
+ struct A<T> {
+ t: T,
+ }
+
+ impl<T> A<T> {
+ fn new<V: Into<T>>(v: V) -> Self {
+ Self { t: Into::into(v) }
+ }
+ }
+
+ impl A<submod::C> {
+ fn test() -> Self {
+ A::new::<submod::B>(submod::B {})
+ }
+ }
+}
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:14:21
+ --> $DIR/use_self.rs:18:21
|
LL | fn new() -> Foo {
| ^^^ help: use the applicable keyword: `Self`
= note: `-D clippy::use-self` implied by `-D warnings`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:15:13
+ --> $DIR/use_self.rs:19:13
|
LL | Foo {}
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:17:22
+ --> $DIR/use_self.rs:21:22
|
LL | fn test() -> Foo {
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:18:13
+ --> $DIR/use_self.rs:22:13
|
LL | Foo::new()
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:23:25
+ --> $DIR/use_self.rs:27:25
|
LL | fn default() -> Foo {
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:24:13
+ --> $DIR/use_self.rs:28:13
|
LL | Foo::new()
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:90:56
+ --> $DIR/use_self.rs:93:24
|
-LL | fn bad(foos: &[Self]) -> impl Iterator<Item = &Foo> {
- | ^^^ help: use the applicable keyword: `Self`
+LL | fn bad(foos: &[Foo]) -> impl Iterator<Item = &Foo> {
+ | ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:105:13
+ --> $DIR/use_self.rs:93:55
+ |
+LL | fn bad(foos: &[Foo]) -> impl Iterator<Item = &Foo> {
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:108:13
|
LL | TS(0)
| ^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:113:25
- |
-LL | fn new() -> Foo {
- | ^^^ help: use the applicable keyword: `Self`
-...
-LL | use_self_expand!(); // Should lint in local macros
- | ------------------- in this macro invocation
+ --> $DIR/use_self.rs:143:29
|
- = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+LL | fn bar() -> Bar {
+ | ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:114:17
- |
-LL | Foo {}
- | ^^^ help: use the applicable keyword: `Self`
-...
-LL | use_self_expand!(); // Should lint in local macros
- | ------------------- in this macro invocation
+ --> $DIR/use_self.rs:144:21
|
- = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+LL | Bar { foo: Foo {} }
+ | ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:149:21
+ --> $DIR/use_self.rs:155:21
|
LL | fn baz() -> Foo {
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:150:13
+ --> $DIR/use_self.rs:156:13
|
LL | Foo {}
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:137:29
- |
-LL | fn bar() -> Bar {
- | ^^^ help: use the applicable keyword: `Self`
-
-error: unnecessary structure name repetition
- --> $DIR/use_self.rs:138:21
- |
-LL | Bar { foo: Foo {} }
- | ^^^ help: use the applicable keyword: `Self`
-
-error: unnecessary structure name repetition
- --> $DIR/use_self.rs:167:21
+ --> $DIR/use_self.rs:173:21
|
LL | let _ = Enum::B(42);
| ^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:168:21
+ --> $DIR/use_self.rs:174:21
|
LL | let _ = Enum::C { field: true };
| ^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:169:21
+ --> $DIR/use_self.rs:175:21
|
LL | let _ = Enum::A;
| ^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:200:13
+ --> $DIR/use_self.rs:217:13
|
LL | nested::A::fun_1();
| ^^^^^^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:201:13
+ --> $DIR/use_self.rs:218:13
|
LL | nested::A::A;
| ^^^^^^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:203:13
+ --> $DIR/use_self.rs:220:13
|
LL | nested::A {};
| ^^^^^^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:222:13
+ --> $DIR/use_self.rs:239:13
|
LL | TestStruct::from_something()
| ^^^^^^^^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:236:25
+ --> $DIR/use_self.rs:253:25
|
LL | async fn g() -> S {
| ^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:237:13
+ --> $DIR/use_self.rs:254:13
|
LL | S {}
| ^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:241:16
+ --> $DIR/use_self.rs:258:16
|
LL | &p[S::A..S::B]
| ^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
- --> $DIR/use_self.rs:241:22
+ --> $DIR/use_self.rs:258:22
|
LL | &p[S::A..S::B]
| ^ help: use the applicable keyword: `Self`
-error: aborting due to 25 previous errors
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:281:29
+ |
+LL | fn foo(value: T) -> Foo<T> {
+ | ^^^^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:282:13
+ |
+LL | Foo { value }
+ | ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:319:21
+ |
+LL | type From = T::From;
+ | ^^^^^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:320:19
+ |
+LL | type To = T::To;
+ | ^^^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+ --> $DIR/use_self.rs:457:13
+ |
+LL | A::new::<submod::B>(submod::B {})
+ | ^ help: use the applicable keyword: `Self`
+
+error: aborting due to 29 previous errors
impl Clone for Bad {
fn clone(&self) -> Self {
- Self
+ // FIXME: applicable here
+ Bad
}
}
impl Clone for Bad {
fn clone(&self) -> Self {
+ // FIXME: applicable here
Bad
}
}
LL | fn mul(self, rhs: Bad) -> Bad {
| ^^^ help: use the applicable keyword: `Self`
-error: unnecessary structure name repetition
- --> $DIR/use_self_trait.rs:50:9
- |
-LL | Bad
- | ^^^ help: use the applicable keyword: `Self`
-
-error: aborting due to 15 previous errors
+error: aborting due to 14 previous errors
cap_err.push(0);
cap_err.push(1);
cap_err.push(2);
+ if true {
+ // don't include this one
+ cap_err.push(3);
+ }
let mut cap_ok = Vec::with_capacity(10);
cap_ok.push(0);
new_err = Vec::new();
new_err.push(0);
+
+ let mut vec = Vec::new();
+ // control flow at block final expression
+ if true {
+ // no lint
+ vec.push(1);
+ }
+}
+
+pub fn no_lint() -> Vec<i32> {
+ let mut p = Some(1);
+ let mut vec = Vec::new();
+ loop {
+ match p {
+ None => return vec,
+ Some(i) => {
+ vec.push(i);
+ p = None;
+ },
+ }
+ }
}
| |____________________^ help: consider using the `vec![]` macro: `let mut cap_err = vec![..];`
error: calls to `push` immediately after creation
- --> $DIR/vec_init_then_push.rs:19:5
+ --> $DIR/vec_init_then_push.rs:23:5
|
LL | / new_err = Vec::new();
LL | | new_err.push(0);
use rustc_tools_util::VersionInfo;
#[test]
-fn check_that_clippy_lints_has_the_same_version_as_clippy() {
+fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() {
+ // do not run this test inside the upstream rustc repo:
+ // https://github.com/rust-lang/rust-clippy/issues/6683
+ if option_env!("RUSTC_TEST_SUITE").is_some() {
+ return;
+ }
+
let clippy_meta = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.expect("could not obtain cargo metadata");
- std::env::set_current_dir(std::env::current_dir().unwrap().join("clippy_lints")).unwrap();
- let clippy_lints_meta = cargo_metadata::MetadataCommand::new()
- .no_deps()
- .exec()
- .expect("could not obtain cargo metadata");
- assert_eq!(clippy_lints_meta.packages[0].version, clippy_meta.packages[0].version);
- for package in &clippy_meta.packages[0].dependencies {
- if package.name == "clippy_lints" {
- assert!(package.req.matches(&clippy_lints_meta.packages[0].version));
- return;
+
+ for krate in &["clippy_lints", "clippy_utils"] {
+ let krate_meta = clippy_meta
+ .packages
+ .iter()
+ .find(|package| package.name == *krate)
+ .expect("could not obtain cargo metadata");
+ assert_eq!(krate_meta.version, clippy_meta.packages[0].version);
+ for package in &clippy_meta.packages[0].dependencies {
+ if package.name == *krate {
+ assert!(package.req.matches(&krate_meta.version));
+ break;
+ }
}
}
}
let src_base = opt_path(matches, "src-base");
let run_ignored = matches.opt_present("ignored");
- let has_tidy = Command::new("tidy")
- .arg("--version")
- .stdout(Stdio::null())
- .status()
- .map_or(false, |status| status.success());
+ let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
+ let has_tidy = if mode == Mode::Rustdoc {
+ Command::new("tidy")
+ .arg("--version")
+ .stdout(Stdio::null())
+ .status()
+ .map_or(false, |status| status.success())
+ } else {
+ // Avoid spawning an external command when we know tidy won't be used.
+ false
+ };
Config {
bless: matches.opt_present("bless"),
compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
src_base,
build_base: opt_path(matches, "build-base"),
stage_id: matches.opt_str("stage-id").unwrap(),
- mode: matches.opt_str("mode").unwrap().parse().expect("invalid mode"),
+ mode,
suite: matches.opt_str("suite").unwrap(),
debugger: None,
run_ignored,
suppressible_tidy_err!(err, skip_undocumented_unsafe, "undocumented unsafe");
}
}
- if line.contains("// SAFETY:") || line.contains("// Safety:") {
+ if line.contains("// SAFETY:") {
last_safety_comment = true;
} else if line.trim().starts_with("//") || line.trim().is_empty() {
// keep previous value