diff options
Diffstat (limited to 'src')
162 files changed, 7104 insertions, 3332 deletions
diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index 4f7a101d2ac..29a267053b4 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -286,8 +286,8 @@ achieve that will result in false positive reports. 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; diff --git a/src/doc/unstable-book/src/language-features/lang-items.md b/src/doc/unstable-book/src/language-features/lang-items.md index d44c841d48c..22780804610 100644 --- a/src/doc/unstable-book/src/language-features/lang-items.md +++ b/src/doc/unstable-book/src/language-features/lang-items.md @@ -66,7 +66,7 @@ Other features provided by lang items include: 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 diff --git a/src/etc/pre-commit.sh b/src/etc/pre-commit.sh index 70b4e9d9908..9045adb54dc 100755 --- a/src/etc/pre-commit.sh +++ b/src/etc/pre-commit.sh @@ -1,7 +1,7 @@ #!/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. # diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index f9c63186544..9cfa72132cb 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -414,7 +414,10 @@ crate fn build_impl( 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 { @@ -428,11 +431,9 @@ crate fn build_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( diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 51bef344e67..7fc48cd5f0b 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -148,6 +148,22 @@ impl Item { 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); // `span_if_local()` lies about functions and only gives the span of the function signature @@ -164,7 +180,7 @@ impl Item { 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), } } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index cb11f22d0d8..9a054e29dd3 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -780,6 +780,31 @@ impl LangString { 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, @@ -793,11 +818,11 @@ impl LangString { 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; @@ -894,6 +919,7 @@ impl LangString { _ => seen_other_tags = true, } } + // ignore-foo overrides ignore if !ignores.is_empty() { data.ignore = Ignore::Some(ignores); diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 6b2cfe68575..59ca841715c 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -58,6 +58,9 @@ fn test_lang_string_parse() { 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 { @@ -75,16 +78,16 @@ fn test_lang_string_parse() { ..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() }); @@ -101,6 +104,18 @@ fn test_lang_string_parse() { ..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), ..Default::default() @@ -113,6 +128,29 @@ fn test_lang_string_parse() { } #[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) { let mut map = IdMap::new(); diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index b21f6a13392..6665f160dec 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1343,7 +1343,6 @@ impl AllTypes { </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 diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index abf5f05fe58..224c794fb3b 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -38,3 +38,14 @@ fn test_name_sorting() { 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()); +} diff --git a/src/librustdoc/html/static/normalize.css b/src/librustdoc/html/static/normalize.css index 0e042627918..da9a75e3e85 100644 --- a/src/librustdoc/html/static/normalize.css +++ b/src/librustdoc/html/static/normalize.css @@ -1,2 +1,2 @@ /* ignore-tidy-linelength */ -/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index c99e1ecac73..6d9e3d0b9ea 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -134,15 +134,20 @@ impl TryFrom<ResolveRes> for Res { } } -#[derive(Debug)] /// A link failed to resolve. +#[derive(Debug)] enum ResolutionFailure<'a> { /// This resolved, but with the wrong namespace. - /// - /// `Namespace` is the namespace specified with a disambiguator - /// (as opposed to the actual namespace of the `Res`). - WrongNamespace(Res, /* disambiguated */ Namespace), - /// The link failed to resolve. `resolution_failure` should look to see if there's + WrongNamespace { + /// What the link resolved to. + res: Res, + /// The expected namespace for the resolution, determined from the link's disambiguator. + /// + /// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`], + /// even though `Result`'s actual namespace is [`Namespace::TypeNS`]. + expected_ns: Namespace, + }, + /// The link failed to resolve. [`resolution_failure`] should look to see if there's /// a more helpful error that can be given. NotResolved { /// The scope the link was resolved in. @@ -157,12 +162,11 @@ enum ResolutionFailure<'a> { unresolved: Cow<'a, str>, }, /// This happens when rustdoc can't determine the parent scope for an item. - /// /// It is always a bug in rustdoc. NoParentItem, /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced. MalformedGenerics(MalformedGenerics), - /// Used to communicate that this should be ignored, but shouldn't be reported to the user + /// Used to communicate that this should be ignored, but shouldn't be reported to the user. /// /// This happens when there is no disambiguator and one of the namespaces /// failed to resolve. @@ -216,7 +220,7 @@ impl ResolutionFailure<'a> { /// Returns the full resolution of the link, if present. fn full_res(&self) -> Option<Res> { match self { - Self::WrongNamespace(res, _) => Some(*res), + Self::WrongNamespace { res, expected_ns: _ } => Some(*res), _ => None, } } @@ -1308,20 +1312,20 @@ impl LinkCollector<'_, '_> { let extra_fragment = &key.extra_fragment; match disambiguator.map(Disambiguator::ns) { - Some(ns @ (ValueNS | TypeNS)) => { - match self.resolve(path_str, ns, base_node, extra_fragment) { + Some(expected_ns @ (ValueNS | TypeNS)) => { + match self.resolve(path_str, expected_ns, base_node, extra_fragment) { Ok(res) => Some(res), Err(ErrorKind::Resolve(box mut kind)) => { // We only looked in one namespace. Try to give a better error if possible. if kind.full_res().is_none() { - let other_ns = if ns == ValueNS { TypeNS } else { ValueNS }; + let other_ns = if expected_ns == ValueNS { TypeNS } else { ValueNS }; // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator` // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach for &new_ns in &[other_ns, MacroNS] { if let Some(res) = self.check_full_res(new_ns, path_str, base_node, extra_fragment) { - kind = ResolutionFailure::WrongNamespace(res, ns); + kind = ResolutionFailure::WrongNamespace { res, expected_ns }; break; } } @@ -1396,7 +1400,7 @@ impl LinkCollector<'_, '_> { // Constructors are picked up in the type namespace. match res { Res::Def(DefKind::Ctor(..), _) => { - Err(ResolutionFailure::WrongNamespace(res, TypeNS)) + Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS }) } _ => { match (fragment, extra_fragment.clone()) { @@ -1457,7 +1461,8 @@ impl LinkCollector<'_, '_> { if let Some(res) = self.check_full_res(ns, path_str, base_node, extra_fragment) { - kind = ResolutionFailure::WrongNamespace(res, MacroNS); + kind = + ResolutionFailure::WrongNamespace { res, expected_ns: MacroNS }; break; } } @@ -1889,7 +1894,7 @@ fn resolution_failure( let note = match failure { ResolutionFailure::NotResolved { .. } => unreachable!("handled above"), ResolutionFailure::Dummy => continue, - ResolutionFailure::WrongNamespace(res, expected_ns) => { + ResolutionFailure::WrongNamespace { res, expected_ns } => { if let Res::Def(kind, _) = res { let disambiguator = Disambiguator::Kind(kind); suggest_disambiguator( @@ -1910,7 +1915,7 @@ fn resolution_failure( } ResolutionFailure::NoParentItem => { diag.level = rustc_errors::Level::Bug; - "all intra doc links should have a parent item".to_owned() + "all intra-doc links should have a parent item".to_owned() } ResolutionFailure::MalformedGenerics(variant) => match variant { MalformedGenerics::UnbalancedAngleBrackets => { diff --git a/src/test/ui/auxiliary/legacy-const-generics.rs b/src/test/ui/auxiliary/legacy-const-generics.rs new file mode 100644 index 00000000000..67352a2fbbb --- /dev/null +++ b/src/test/ui/auxiliary/legacy-const-generics.rs @@ -0,0 +1,6 @@ +#![feature(rustc_attrs)] + +#[rustc_legacy_const_generics(1)] +pub fn foo<const Y: usize>(x: usize, z: usize) -> [usize; 3] { + [x, Y, z] +} diff --git a/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-1.rs b/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-1.rs new file mode 100644 index 00000000000..d1bffae0d94 --- /dev/null +++ b/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-1.rs @@ -0,0 +1,27 @@ +// 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>(); +} diff --git a/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-2.rs b/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-2.rs new file mode 100644 index 00000000000..96dbac1fbef --- /dev/null +++ b/src/test/ui/const-generics/conservative_is_privately_uninhabited_uses_correct_param_env-2.rs @@ -0,0 +1,20 @@ +// 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; +} diff --git a/src/test/ui/consts/const-as-fn.rs b/src/test/ui/consts/const-as-fn.rs new file mode 100644 index 00000000000..388f907f8d2 --- /dev/null +++ b/src/test/ui/consts/const-as-fn.rs @@ -0,0 +1,5 @@ +const FOO: usize = 0; + +fn main() { + FOO(); //~ ERROR expected function, found `usize` +} diff --git a/src/test/ui/consts/const-as-fn.stderr b/src/test/ui/consts/const-as-fn.stderr new file mode 100644 index 00000000000..b8dd4134b28 --- /dev/null +++ b/src/test/ui/consts/const-as-fn.stderr @@ -0,0 +1,14 @@ +error[E0618]: expected function, found `usize` + --> $DIR/const-as-fn.rs:4:5 + | +LL | const FOO: usize = 0; + | --------------------- `FOO` defined here +... +LL | FOO(); + | ^^^-- + | | + | call expression requires function + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0618`. diff --git a/src/test/ui/error-codes/E0618.stderr b/src/test/ui/error-codes/E0618.stderr index 6ddda3bf8b5..714c8d1e4d7 100644 --- a/src/test/ui/error-codes/E0618.stderr +++ b/src/test/ui/error-codes/E0618.stderr @@ -18,7 +18,7 @@ error[E0618]: expected function, found `i32` --> $DIR/E0618.rs:9:5 | LL | let x = 0i32; - | - `i32` defined here + | - `x` has type `i32` LL | x(); | ^-- | | diff --git a/src/test/ui/inference/auxiliary/inference_unstable_iterator.rs b/src/test/ui/inference/auxiliary/inference_unstable_iterator.rs index 0dc09704e7f..91fc398f741 100644 --- a/src/test/ui/inference/auxiliary/inference_unstable_iterator.rs +++ b/src/test/ui/inference/auxiliary/inference_unstable_iterator.rs @@ -8,7 +8,11 @@ pub trait IpuIterator { fn ipu_flatten(&self) -> u32 { 0 } + #[unstable(feature = "assoc_const_ipu_iter", issue = "99999")] + const C: i32; } #[stable(feature = "ipu_iterator", since = "1.0.0")] -impl IpuIterator for char {} +impl IpuIterator for char { + const C: i32 = 42; +} diff --git a/src/test/ui/inference/auxiliary/inference_unstable_itertools.rs b/src/test/ui/inference/auxiliary/inference_unstable_itertools.rs index 964f35ddade..e00adda5c33 100644 --- a/src/test/ui/inference/auxiliary/inference_unstable_itertools.rs +++ b/src/test/ui/inference/auxiliary/inference_unstable_itertools.rs @@ -2,6 +2,10 @@ pub trait IpuItertools { fn ipu_flatten(&self) -> u32 { 1 } + + const C: i32; } -impl IpuItertools for char {} +impl IpuItertools for char { + const C: i32 = 1; +} diff --git a/src/test/ui/inference/inference_unstable.rs b/src/test/ui/inference/inference_unstable.rs index 0b957700182..86bb62b8a5f 100644 --- a/src/test/ui/inference/inference_unstable.rs +++ b/src/test/ui/inference/inference_unstable.rs @@ -14,6 +14,9 @@ use inference_unstable_itertools::IpuItertools; fn main() { assert_eq!('x'.ipu_flatten(), 1); - //~^ WARN a method with this name may be added to the standard library in the future - //~^^ WARN once this method is added to the standard library, the ambiguity may cause an error +//~^ WARN an associated function with this name may be added to the standard library in the future +//~| WARN once this associated item is added to the standard library, the ambiguity may cause an + assert_eq!(char::C, 1); +//~^ WARN an associated constant with this name may be added to the standard library in the future +//~| WARN once this associated item is added to the standard library, the ambiguity may cause an } diff --git a/src/test/ui/inference/inference_unstable.stderr b/src/test/ui/inference/inference_unstable.stderr index df520124632..2c282e610b2 100644 --- a/src/test/ui/inference/inference_unstable.stderr +++ b/src/test/ui/inference/inference_unstable.stderr @@ -1,14 +1,24 @@ -warning: a method with this name may be added to the standard library in the future +warning: an associated function with this name may be added to the standard library in the future --> $DIR/inference_unstable.rs:16:20 | LL | assert_eq!('x'.ipu_flatten(), 1); | ^^^^^^^^^^^ | = note: `#[warn(unstable_name_collisions)]` on by default - = warning: once this method is added to the standard library, the ambiguity may cause an error or change in behavior! + = warning: once this associated item is added to the standard library, the ambiguity may cause an error or change in behavior! = note: for more information, see issue #48919 <https://github.com/rust-lang/rust/issues/48919> = help: call with fully qualified syntax `inference_unstable_itertools::IpuItertools::ipu_flatten(...)` to keep using the current method = help: add `#![feature(ipu_flatten)]` to the crate attributes to enable `inference_unstable_iterator::IpuIterator::ipu_flatten` -warning: 1 warning emitted +warning: an associated constant with this name may be added to the standard library in the future + --> $DIR/inference_unstable.rs:19:16 + | +LL | assert_eq!(char::C, 1); + | ^^^^^^^ help: use the fully qualified path to the associated const: `<char as IpuItertools>::C` + | + = warning: once this associated item is added to the standard library, the ambiguity may cause an error or change in behavior! + = note: for more information, see issue #48919 <https://github.com/rust-lang/rust/issues/48919> + = help: add `#![feature(assoc_const_ipu_iter)]` to the crate attributes to enable `inference_unstable_iterator::IpuIterator::C` + +warning: 2 warnings emitted diff --git a/src/test/ui/invalid-rustc_args_required_const-arguments.rs b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.rs index 99508baeb00..99508baeb00 100644 --- a/src/test/ui/invalid-rustc_args_required_const-arguments.rs +++ b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.rs diff --git a/src/test/ui/invalid-rustc_args_required_const-arguments.stderr b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.stderr index 932344f0a33..932344f0a33 100644 --- a/src/test/ui/invalid-rustc_args_required_const-arguments.stderr +++ b/src/test/ui/invalid/invalid-rustc_args_required_const-arguments.stderr diff --git a/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs new file mode 100644 index 00000000000..3d8478f06db --- /dev/null +++ b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.rs @@ -0,0 +1,38 @@ +#![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() {} diff --git a/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr new file mode 100644 index 00000000000..1f55a8e72d2 --- /dev/null +++ b/src/test/ui/invalid/invalid-rustc_legacy_const_generics-arguments.stderr @@ -0,0 +1,87 @@ +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`. diff --git a/src/test/ui/issues/issue-10969.stderr b/src/test/ui/issues/issue-10969.stderr index 9e28a665e30..f64b61aaeb0 100644 --- a/src/test/ui/issues/issue-10969.stderr +++ b/src/test/ui/issues/issue-10969.stderr @@ -2,7 +2,7 @@ error[E0618]: expected function, found `i32` --> $DIR/issue-10969.rs:2:5 | LL | fn func(i: i32) { - | - `i32` defined here + | - `i` has type `i32` LL | i(); | ^-- | | @@ -12,7 +12,7 @@ error[E0618]: expected function, found `i32` --> $DIR/issue-10969.rs:6:5 | LL | let i = 0i32; - | - `i32` defined here + | - `i` has type `i32` LL | i(); | ^-- | | diff --git a/src/test/ui/issues/issue-21701.stderr b/src/test/ui/issues/issue-21701.stderr index 77bb3802d86..0405ce551b0 100644 --- a/src/test/ui/issues/issue-21701.stderr +++ b/src/test/ui/issues/issue-21701.stderr @@ -2,7 +2,7 @@ error[E0618]: expected function, found `U` --> $DIR/issue-21701.rs:2:13 | LL | fn foo<U>(t: U) { - | - `U` defined here + | - `t` has type `U` LL | let y = t(); | ^-- | | diff --git a/src/test/ui/issues/issue-22468.stderr b/src/test/ui/issues/issue-22468.stderr index 8d8601b3111..3fff91acbc2 100644 --- a/src/test/ui/issues/issue-22468.stderr +++ b/src/test/ui/issues/issue-22468.stderr @@ -2,7 +2,7 @@ error[E0618]: expected function, found `&str` --> $DIR/issue-22468.rs:3:13 | LL | let foo = "bar"; - | --- `&str` defined here + | --- `foo` has type `&str` LL | let x = foo("baz"); | ^^^------- | | diff --git a/src/test/ui/issues/issue-26237.stderr b/src/test/ui/issues/issue-26237.stderr index f58c1bfe644..91d28a5e1e1 100644 --- a/src/test/ui/issues/issue-26237.stderr +++ b/src/test/ui/issues/issue-26237.stderr @@ -5,7 +5,7 @@ LL | $not_a_function($some_argument) | ------------------------------- call expression requires function ... LL | let mut value_a = 0; - | ----------- `{integer}` defined here + | ----------- `value_a` has type `{integer}` LL | let mut value_b = 0; LL | macro_panic!(value_a, value_b); | ^^^^^^^ diff --git a/src/test/ui/legacy-const-generics-bad.rs b/src/test/ui/legacy-const-generics-bad.rs new file mode 100644 index 00000000000..538eee337cc --- /dev/null +++ b/src/test/ui/legacy-const-generics-bad.rs @@ -0,0 +1,16 @@ +// 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() {} diff --git a/src/test/ui/legacy-const-generics-bad.stderr b/src/test/ui/legacy-const-generics-bad.stderr new file mode 100644 index 00000000000..5a44b8e7065 --- /dev/null +++ b/src/test/ui/legacy-const-generics-bad.stderr @@ -0,0 +1,20 @@ +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`. diff --git a/src/test/ui/legacy-const-generics.rs b/src/test/ui/legacy-const-generics.rs new file mode 100644 index 00000000000..9abc72d98e6 --- /dev/null +++ b/src/test/ui/legacy-const-generics.rs @@ -0,0 +1,18 @@ +// 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]); +} diff --git a/src/test/ui/parser/parse-error-correct.stderr b/src/test/ui/parser/parse-error-correct.stderr index c54baf00b27..691df91268b 100644 --- a/src/test/ui/parser/parse-error-correct.stderr +++ b/src/test/ui/parser/parse-error-correct.stderr @@ -14,7 +14,7 @@ error[E0618]: expected function, found `{integer}` --> $DIR/parse-error-correct.rs:7:13 | LL | let y = 42; - | - `{integer}` defined here + | - `y` has type `{integer}` LL | let x = y.; LL | let x = y.(); | ^--- diff --git a/src/test/ui/structs/80853.rs b/src/test/ui/structs/80853.rs new file mode 100644 index 00000000000..242d0af959d --- /dev/null +++ b/src/test/ui/structs/80853.rs @@ -0,0 +1,7 @@ +struct S; + +fn repro_ref(thing: S) { + thing(); //~ ERROR expected function, found `S` +} + +fn main() {} diff --git a/src/test/ui/structs/80853.stderr b/src/test/ui/structs/80853.stderr new file mode 100644 index 00000000000..8a38e32c1d0 --- /dev/null +++ b/src/test/ui/structs/80853.stderr @@ -0,0 +1,13 @@ +error[E0618]: expected function, found `S` + --> $DIR/80853.rs:4:5 + | +LL | fn repro_ref(thing: S) { + | ----- `thing` has type `S` +LL | thing(); + | ^^^^^-- + | | + | call expression requires function + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0618`. diff --git a/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.rs b/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.rs new file mode 100644 index 00000000000..a25be862a5d --- /dev/null +++ b/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.rs @@ -0,0 +1,5 @@ +use std::result; +impl result { //~ ERROR expected type, found module `result` + fn into_future() -> Err {} //~ ERROR expected type, found variant `Err` +} +fn main() {} diff --git a/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.stderr b/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.stderr new file mode 100644 index 00000000000..59ccb29e090 --- /dev/null +++ b/src/test/ui/suggestions/do-not-attempt-to-add-suggestions-with-no-changes.stderr @@ -0,0 +1,20 @@ +error[E0573]: expected type, found module `result` + --> $DIR/do-not-attempt-to-add-suggestions-with-no-changes.rs:2:6 + | +LL | impl result { + | ^^^^^^ help: an enum with a similar name exists: `Result` + | + ::: $SRC_DIR/core/src/result.rs:LL:COL + | +LL | pub enum Result<T, E> { + | --------------------- similarly named enum `Result` defined here + +error[E0573]: expected type, found variant `Err` + --> $DIR/do-not-attempt-to-add-suggestions-with-no-changes.rs:3:25 + | +LL | fn into_future() -> Err {} + | ^^^ not a type + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0573`. diff --git a/src/test/ui/suggestions/issue-82361.fixed b/src/test/ui/suggestions/issue-82361.fixed new file mode 100644 index 00000000000..d72de982bf9 --- /dev/null +++ b/src/test/ui/suggestions/issue-82361.fixed @@ -0,0 +1,24 @@ +// run-rustfix + +fn main() { + let a: usize = 123; + let b: &usize = &a; + + if true { + a + } else { + *b //~ ERROR `if` and `else` have incompatible types [E0308] + }; + + if true { + 1 + } else { + 1 //~ ERROR `if` and `else` have incompatible types [E0308] + }; + + if true { + 1 + } else { + 1 //~ ERROR `if` and `else` have incompatible types [E0308] + }; +} diff --git a/src/test/ui/suggestions/issue-82361.rs b/src/test/ui/suggestions/issue-82361.rs new file mode 100644 index 00000000000..c068f6d22b4 --- /dev/null +++ b/src/test/ui/suggestions/issue-82361.rs @@ -0,0 +1,24 @@ +// run-rustfix + +fn main() { + let a: usize = 123; + let b: &usize = &a; + + if true { + a + } else { + b //~ ERROR `if` and `else` have incompatible types [E0308] + }; + + if true { + 1 + } else { + &1 //~ ERROR `if` and `else` have incompatible types [E0308] + }; + + if true { + 1 + } else { + &mut 1 //~ ERROR `if` and `else` have incompatible types [E0308] + }; +} diff --git a/src/test/ui/suggestions/issue-82361.stderr b/src/test/ui/suggestions/issue-82361.stderr new file mode 100644 index 00000000000..c19d59ccd4c --- /dev/null +++ b/src/test/ui/suggestions/issue-82361.stderr @@ -0,0 +1,48 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/issue-82361.rs:10:9 + | +LL | / if true { +LL | | a + | | - expected because of this +LL | | } else { +LL | | b + | | ^ + | | | + | | expected `usize`, found `&usize` + | | help: consider dereferencing the borrow: `*b` +LL | | }; + | |_____- `if` and `else` have incompatible types + +error[E0308]: `if` and `else` have incompatible types + --> $DIR/issue-82361.rs:16:9 + | +LL | / if true { +LL | | 1 + | | - expected because of this +LL | | } else { +LL | | &1 + | | -^ + | | | + | | expected integer, found `&{integer}` + | | help: consider removing the `&` +LL | | }; + | |_____- `if` and `else` have incompatible types + +error[E0308]: `if` and `else` have incompatible types + --> $DIR/issue-82361.rs:22:9 + | +LL | / if true { +LL | | 1 + | | - expected because of this +LL | | } else { +LL | | &mut 1 + | | -----^ + | | | + | | expected integer, found `&mut {integer}` + | | help: consider removing the `&mut` +LL | | }; + | |_____- `if` and `else` have incompatible types + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/treat-err-as-bug/delay_span_bug.rs b/src/test/ui/treat-err-as-bug/delay_span_bug.rs index 67846336aca..d4d44049c91 100644 --- a/src/test/ui/treat-err-as-bug/delay_span_bug.rs +++ b/src/test/ui/treat-err-as-bug/delay_span_bug.rs @@ -4,6 +4,7 @@ // 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)] diff --git a/src/test/ui/treat-err-as-bug/delay_span_bug.stderr b/src/test/ui/treat-err-as-bug/delay_span_bug.stderr index ed65b69ebca..c23c2b81b97 100644 --- a/src/test/ui/treat-err-as-bug/delay_span_bug.stderr +++ b/src/test/ui/treat-err-as-bug/delay_span_bug.stderr @@ -1,5 +1,5 @@ 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() {} | ^^^^^^^^^ diff --git a/src/test/ui/treat-err-as-bug/err.rs b/src/test/ui/treat-err-as-bug/err.rs index 5442d858594..de3e9ed6cf9 100644 --- a/src/test/ui/treat-err-as-bug/err.rs +++ b/src/test/ui/treat-err-as-bug/err.rs @@ -4,6 +4,7 @@ // 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"] diff --git a/src/test/ui/treat-err-as-bug/err.stderr b/src/test/ui/treat-err-as-bug/err.stderr index 61eb85c40a1..8f67571c299 100644 --- a/src/test/ui/treat-err-as-bug/err.stderr +++ b/src/test/ui/treat-err-as-bug/err.stderr @@ -1,5 +1,5 @@ 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 diff --git a/src/test/ui/typeck/auxiliary/issue-81943-lib.rs b/src/test/ui/typeck/auxiliary/issue-81943-lib.rs new file mode 100644 index 00000000000..521c54f8996 --- /dev/null +++ b/src/test/ui/typeck/auxiliary/issue-81943-lib.rs @@ -0,0 +1,7 @@ +pub fn g(t: i32) -> i32 { t } +// This function imitates `dbg!` so that future changes +// to its macro definition won't make this test a dud. +#[macro_export] +macro_rules! d { + ($e:expr) => { match $e { x => { $crate::g(x) } } } +} diff --git a/src/test/ui/typeck/issue-81943.rs b/src/test/ui/typeck/issue-81943.rs new file mode 100644 index 00000000000..18f5970a350 --- /dev/null +++ b/src/test/ui/typeck/issue-81943.rs @@ -0,0 +1,13 @@ +// aux-build:issue-81943-lib.rs +extern crate issue_81943_lib as lib; + +fn f<F: Fn(i32)>(f: F) { f(0); } +fn g(t: i32) -> i32 { t } +fn main() { + f(|x| lib::d!(x)); //~ERROR + f(|x| match x { tmp => { g(tmp) } }); //~ERROR + macro_rules! d { + ($e:expr) => { match $e { x => { g(x) } } } //~ERROR + } + f(|x| d!(x)); +} diff --git a/src/test/ui/typeck/issue-81943.stderr b/src/test/ui/typeck/issue-81943.stderr new file mode 100644 index 00000000000..a30facfeb6d --- /dev/null +++ b/src/test/ui/typeck/issue-81943.stderr @@ -0,0 +1,51 @@ +error[E0308]: mismatched types + --> $DIR/issue-81943.rs:7:9 + | +LL | f(|x| lib::d!(x)); + | ^^^^^^^^^^ expected `()`, found `i32` + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> $DIR/issue-81943.rs:8:28 + | +LL | f(|x| match x { tmp => { g(tmp) } }); + | -------------------^^^^^^---- + | | | + | | expected `()`, found `i32` + | expected this to be `()` + | +help: consider using a semicolon here + | +LL | f(|x| match x { tmp => { g(tmp); } }); + | ^ +help: consider using a semicolon here + | +LL | f(|x| match x { tmp => { g(tmp) } };); + | ^ + +error[E0308]: mismatched types + --> $DIR/issue-81943.rs:10:38 + | +LL | ($e:expr) => { match $e { x => { g(x) } } } + | ------------------^^^^---- + | | | + | | expected `()`, found `i32` + | expected this to be `()` +LL | } +LL | f(|x| d!(x)); + | ----- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider using a semicolon here + | +LL | ($e:expr) => { match $e { x => { g(x); } } } + | ^ +help: consider using a semicolon here + | +LL | ($e:expr) => { match $e { x => { g(x) } }; } + | ^ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md index d8f0c44148c..2bc87db123d 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug Report about: Create a bug report for Clippy -labels: L-bug +labels: C-bug --- <!-- Thank you for filing a bug report! 🐛 Please provide a short summary of the bug, diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md index f46828fec91..53341c7a928 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_negative.md @@ -1,7 +1,7 @@ --- name: Bug Report (False Negative) about: Create a bug report about missing warnings from a lint -labels: L-bug, L-false-negative +labels: C-bug, I-false-negative --- <!-- Thank you for filing a bug report! 🐛 Please provide a short summary of the bug, diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md index 92a7373fc27..34fd6f4bdb7 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/false_positive.md @@ -1,7 +1,7 @@ --- name: Bug Report (False Positive) about: Create a bug report about a wrongly emitted lint warning -labels: L-bug, L-false-positive +labels: C-bug, I-false-positive --- <!-- Thank you for filing a bug report! 🐛 Please provide a short summary of the bug, diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md index 3abe76bf2c4..0b7cd1ed0fb 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/ice.md @@ -1,7 +1,7 @@ --- name: Internal Compiler Error about: Create a report for an internal compiler error in Clippy. -labels: L-bug, L-crash +labels: C-bug, I-ICE --- <!-- Thank you for finding an Internal Compiler Error! 🧊 If possible, try to provide diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md b/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md index 98fd0df685f..e182c99ce06 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/new_lint.md @@ -1,7 +1,7 @@ --- name: New lint suggestion about: Suggest a new Clippy lint. -labels: L-lint +labels: A-lint --- ### What it does diff --git a/src/tools/clippy/.github/workflows/clippy.yml b/src/tools/clippy/.github/workflows/clippy.yml index 9d5e12aac5f..f6ac936df4d 100644 --- a/src/tools/clippy/.github/workflows/clippy.yml +++ b/src/tools/clippy/.github/workflows/clippy.yml @@ -53,16 +53,8 @@ jobs: - name: Test "--fix -Zunstable-options" run: cargo run --features deny-warnings,internal-lints --bin cargo-clippy -- clippy --fix -Zunstable-options - - name: Test - run: cargo test --features deny-warnings,internal-lints - - - name: Test clippy_lints - run: cargo test --features deny-warnings,internal-lints - working-directory: clippy_lints - - - name: Test rustc_tools_util - run: cargo test --features deny-warnings - working-directory: rustc_tools_util + - name: Test Workspace + run: cargo test --all --features deny-warnings,internal-lints - name: Test clippy_dev run: cargo test --features deny-warnings diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml index 5d846eb64c7..9d24b0293c4 100644 --- a/src/tools/clippy/.github/workflows/clippy_bors.yml +++ b/src/tools/clippy/.github/workflows/clippy_bors.yml @@ -112,16 +112,8 @@ jobs: - name: Build run: cargo build --features deny-warnings,internal-lints - - name: Test - run: cargo test --features deny-warnings,internal-lints - - - name: Test clippy_lints - run: cargo test --features deny-warnings,internal-lints - working-directory: clippy_lints - - - name: Test rustc_tools_util - run: cargo test --features deny-warnings - working-directory: rustc_tools_util + - name: Test Workspace + run: cargo test --all --features deny-warnings,internal-lints - name: Test clippy_dev run: cargo test --features deny-warnings diff --git a/src/tools/clippy/.gitignore b/src/tools/clippy/.gitignore index adf5e8feddf..139129d55e3 100644 --- a/src/tools/clippy/.gitignore +++ b/src/tools/clippy/.gitignore @@ -18,6 +18,7 @@ out *Cargo.lock /target /clippy_lints/target +/clippy_utils/target /clippy_workspace_tests/target /clippy_dev/target /rustc_tools_util/target diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index 7c79fe88816..d96c74b6e41 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -6,13 +6,128 @@ document. ## Unreleased / In Rust Nightly -[4911ab1...master](https://github.com/rust-lang/rust-clippy/compare/4911ab1...master) +[3e41797...master](https://github.com/rust-lang/rust-clippy/compare/3e41797...master) + +## Rust 1.51 + +Current beta, release 2021-03-25 + +[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797) + +### New Lints + +* [`upper_case_acronyms`] + [#6475](https://github.com/rust-lang/rust-clippy/pull/6475) +* [`from_over_into`] [#6476](https://github.com/rust-lang/rust-clippy/pull/6476) +* [`case_sensitive_file_extension_comparisons`] + [#6500](https://github.com/rust-lang/rust-clippy/pull/6500) +* [`needless_question_mark`] + [#6507](https://github.com/rust-lang/rust-clippy/pull/6507) +* [`missing_panics_doc`] + [#6523](https://github.com/rust-lang/rust-clippy/pull/6523) +* [`redundant_slicing`] + [#6528](https://github.com/rust-lang/rust-clippy/pull/6528) +* [`vec_init_then_push`] + [#6538](https://github.com/rust-lang/rust-clippy/pull/6538) +* [`ptr_as_ptr`] [#6542](https://github.com/rust-lang/rust-clippy/pull/6542) +* [`collapsible_else_if`] (split out from `collapsible_if`) + [#6544](https://github.com/rust-lang/rust-clippy/pull/6544) +* [`inspect_for_each`] [#6577](https://github.com/rust-lang/rust-clippy/pull/6577) +* [`manual_filter_map`] + [#6591](https://github.com/rust-lang/rust-clippy/pull/6591) +* [`exhaustive_enums`] + [#6617](https://github.com/rust-lang/rust-clippy/pull/6617) +* [`exhaustive_structs`] + [#6617](https://github.com/rust-lang/rust-clippy/pull/6617) + +### Moves and Deprecations + +* Replace [`find_map`] with [`manual_find_map`] + [#6591](https://github.com/rust-lang/rust-clippy/pull/6591) +* [`unknown_clippy_lints`] Now integrated in the `unknown_lints` rustc lint + [#6653](https://github.com/rust-lang/rust-clippy/pull/6653) + +### Enhancements + +* [`ptr_arg`] Now also suggests to use `&Path` instead of `&PathBuf` + [#6506](https://github.com/rust-lang/rust-clippy/pull/6506) +* [`cast_ptr_alignment`] Also lint when the `pointer::cast` method is used + [#6557](https://github.com/rust-lang/rust-clippy/pull/6557) +* [`collapsible_match`] Now also deals with `&` and `*` operators in the `match` + scrutinee [#6619](https://github.com/rust-lang/rust-clippy/pull/6619) + +### False Positive Fixes + +* [`similar_names`] Ignore underscore prefixed names + [#6403](https://github.com/rust-lang/rust-clippy/pull/6403) +* [`print_literal`] and [`write_literal`] No longer lint numeric literals + [#6408](https://github.com/rust-lang/rust-clippy/pull/6408) +* [`large_enum_variant`] No longer lints in external macros + [#6485](https://github.com/rust-lang/rust-clippy/pull/6485) +* [`empty_enum`] Only lint if `never_type` feature is enabled + [#6513](https://github.com/rust-lang/rust-clippy/pull/6513) +* [`field_reassign_with_default`] No longer lints in macros + [#6553](https://github.com/rust-lang/rust-clippy/pull/6553) +* [`size_of_in_element_count`] No longer lints when dividing by element size + [#6578](https://github.com/rust-lang/rust-clippy/pull/6578) +* [`needless_return`] No longer lints in macros + [#6586](https://github.com/rust-lang/rust-clippy/pull/6586) +* [`match_overlapping_arm`] No longer lint when first arm is completely included + in second arm [#6603](https://github.com/rust-lang/rust-clippy/pull/6603) +* [`doc_markdown`] Add `WebGL` to the default configuration as an allowed + identifier [#6605](https://github.com/rust-lang/rust-clippy/pull/6605) + +### Suggestion Fixes/Improvements + +* [`field_reassign_with_default`] Don't expand macro in lint suggestion + [#6531](https://github.com/rust-lang/rust-clippy/pull/6531) +* [`match_like_matches_macro`] Strip references in suggestion + [#6532](https://github.com/rust-lang/rust-clippy/pull/6532) +* [`single_match`] Suggest `if` over `if let` when possible + [#6574](https://github.com/rust-lang/rust-clippy/pull/6574) +* [`ref_in_deref`] Use parentheses correctly in suggestion + [#6609](https://github.com/rust-lang/rust-clippy/pull/6609) +* [`stable_sort_primitive`] Clarify error message + [#6611](https://github.com/rust-lang/rust-clippy/pull/6611) + +### ICE Fixes + +* [`zero_sized_map_values`] + [#6582](https://github.com/rust-lang/rust-clippy/pull/6582) + +### Documentation Improvements + +* Improve search performance on the Clippy website and make it possible to + directly search for lints on the GitHub issue tracker + [#6483](https://github.com/rust-lang/rust-clippy/pull/6483) +* Clean up `README.md` by removing outdated paragraph + [#6488](https://github.com/rust-lang/rust-clippy/pull/6488) +* [`await_holding_refcell_ref`] and [`await_holding_lock`] + [#6585](https://github.com/rust-lang/rust-clippy/pull/6585) +* [`as_conversions`] [#6608](https://github.com/rust-lang/rust-clippy/pull/6608) + +### Others + +* Clippy now has a [Roadmap] for 2021. If you like to get involved in a bigger + project, take a look at the [Roadmap project page]. All issues listed there + are actively mentored + [#6462](https://github.com/rust-lang/rust-clippy/pull/6462) +* The Clippy version number now corresponds to the Rust version number + [#6526](https://github.com/rust-lang/rust-clippy/pull/6526) +* Fix oversight which caused Clippy to lint deps in some environments, where + `CLIPPY_TESTS=true` was set somewhere + [#6575](https://github.com/rust-lang/rust-clippy/pull/6575) +* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool + [#6469](https://github.com/rust-lang/rust-clippy/pull/6469) + +[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md +[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3 ## Rust 1.50 -Current beta, release 2021-02-11 +Current stable, released 2021-02-11 -[b20d4c1...4911ab1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4911ab1) +[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1) ### New Lints @@ -90,6 +205,8 @@ Current beta, release 2021-02-11 * [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: Both now ignore enums with frozen variants [#6110](https://github.com/rust-lang/rust-clippy/pull/6110) +* [`field_reassign_with_default`] No longer lint for private fields + [#6537](https://github.com/rust-lang/rust-clippy/pull/6537) ### Suggestion Fixes/Improvements @@ -137,7 +254,7 @@ Current beta, release 2021-02-11 ## Rust 1.49 -Current stable, released 2020-12-31 +Released 2020-12-31 [e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1) @@ -1910,6 +2027,7 @@ Released 2018-09-13 [`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call [`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation [`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const +[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver @@ -1975,6 +2093,7 @@ Released 2018-09-13 [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref [`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect [`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into +[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10 [`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send [`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len [`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap @@ -1990,6 +2109,7 @@ Released 2018-09-13 [`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub [`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops [`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping +[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor [`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing [`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask [`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string @@ -2042,6 +2162,7 @@ Released 2018-09-13 [`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten +[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map [`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index e7755c46eb8..ea32a8edd1f 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -37,8 +37,8 @@ tempfile = { version = "3.1.0", optional = true } [dev-dependencies] cargo_metadata = "0.12" -compiletest_rs = { version = "0.5.0", features = ["tmp"] } -tester = "0.7" +compiletest_rs = { version = "0.6.0", features = ["tmp"] } +tester = "0.9" clippy-mini-macro-test = { version = "0.2", path = "mini-macro" } serde = { version = "1.0", features = ["derive"] } derive-new = "0.5" diff --git a/src/tools/clippy/README.md b/src/tools/clippy/README.md index a4928e17e6a..3cc03cf3603 100644 --- a/src/tools/clippy/README.md +++ b/src/tools/clippy/README.md @@ -98,6 +98,23 @@ If you want to run Clippy **only** on the given crate, use the `--no-deps` optio cargo clippy -p example -- --no-deps ``` +### As a rustc replacement (`clippy-driver`) + +Clippy can also be used in projects that do not use cargo. To do so, you will need to replace +your `rustc` compilation commands with `clippy-driver`. For example, if your project runs: + +```terminal +rustc --edition 2018 -Cpanic=abort foo.rs +``` + +Then, to enable Clippy, you will need to call: + +```terminal +clippy-driver --edition 2018 -Cpanic=abort foo.rs +``` + +Note that `rustc` will still run, i.e. it will still emit the output files it normally does. + ### Travis CI You can add Clippy to Travis CI in the same way you use it locally: diff --git a/src/tools/clippy/clippy_dev/Cargo.toml b/src/tools/clippy/clippy_dev/Cargo.toml index 5ac96e2210c..ebf157b80ac 100644 --- a/src/tools/clippy/clippy_dev/Cargo.toml +++ b/src/tools/clippy/clippy_dev/Cargo.toml @@ -19,8 +19,9 @@ shell-escape = "0.1" tar = { version = "0.4.30", optional = true } toml = { version = "0.5", optional = true } ureq = { version = "2.0.0-rc3", optional = true } +rayon = { version = "1.5.0", optional = true } walkdir = "2" [features] -lintcheck = ["flate2", "serde_json", "tar", "toml", "ureq", "serde", "fs_extra"] +lintcheck = ["flate2", "serde_json", "tar", "toml", "ureq", "serde", "fs_extra", "rayon"] deny-warnings = [] diff --git a/src/tools/clippy/clippy_dev/README.md b/src/tools/clippy/clippy_dev/README.md index 3846e8bd4cc..a5ed9e27bd2 100644 --- a/src/tools/clippy/clippy_dev/README.md +++ b/src/tools/clippy/clippy_dev/README.md @@ -1,28 +1,77 @@ -# 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. diff --git a/src/tools/clippy/clippy_dev/src/bless.rs b/src/tools/clippy/clippy_dev/src/bless.rs index 2a869e9d449..c4fa0a9aca7 100644 --- a/src/tools/clippy/clippy_dev/src/bless.rs +++ b/src/tools/clippy/clippy_dev/src/bless.rs @@ -42,9 +42,10 @@ pub fn bless(ignore_timestamp: bool) { .for_each(|f| { let test_name = f.path().strip_prefix(test_suite_dir).unwrap(); for &ext in &["stdout", "stderr", "fixed"] { + let test_name_ext = format!("stage-id.{}", ext); update_reference_file( f.path().with_extension(ext), - test_name.with_extension(ext), + test_name.with_extension(test_name_ext), ignore_timestamp, ); } diff --git a/src/tools/clippy/clippy_dev/src/lintcheck.rs b/src/tools/clippy/clippy_dev/src/lintcheck.rs index 749a791b280..b806f545284 100644 --- a/src/tools/clippy/clippy_dev/src/lintcheck.rs +++ b/src/tools/clippy/clippy_dev/src/lintcheck.rs @@ -11,20 +11,22 @@ use crate::clippy_project_root; use std::collections::HashMap; use std::process::Command; -use std::{fmt, fs::write, path::PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{env, fmt, fs::write, path::PathBuf}; use clap::ArgMatches; +use rayon::prelude::*; use serde::{Deserialize, Serialize}; use serde_json::Value; -// use this to store the crates when interacting with the crates.toml file +/// List of sources to check, loaded from a .toml file #[derive(Debug, Serialize, Deserialize)] -struct CrateList { +struct SourceList { crates: HashMap<String, TomlCrate>, } -// crate data we stored in the toml, can have multiple versions per crate -// A single TomlCrate is laster mapped to several CrateSources in that case +/// A crate source stored inside the .toml +/// will be translated into on one of the `CrateSource` variants #[derive(Debug, Serialize, Deserialize)] struct TomlCrate { name: String, @@ -32,27 +34,42 @@ struct TomlCrate { git_url: Option<String>, git_hash: Option<String>, path: Option<String>, + options: Option<Vec<String>>, } -// represents an archive we download from crates.io, or a git repo, or a local repo -#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] +/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder +/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` +#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] enum CrateSource { - CratesIo { name: String, version: String }, - Git { name: String, url: String, commit: String }, - Path { name: String, path: PathBuf }, + CratesIo { + name: String, + version: String, + options: Option<Vec<String>>, + }, + Git { + name: String, + url: String, + commit: String, + options: Option<Vec<String>>, + }, + Path { + name: String, + path: PathBuf, + options: Option<Vec<String>>, + }, } -// represents the extracted sourcecode of a crate -// we actually don't need to special-case git repos here because it does not matter for clippy, yay! -// (clippy only needs a simple path) +/// Represents the actual source code of a crate that we ran "cargo clippy" on #[derive(Debug)] struct Crate { version: String, name: String, // path to the extracted sources that clippy can check path: PathBuf, + options: Option<Vec<String>>, } +/// A single warning that clippy issued while checking a `Crate` #[derive(Debug)] struct ClippyWarning { crate_name: String, @@ -62,7 +79,7 @@ struct ClippyWarning { column: String, linttype: String, message: String, - ice: bool, + is_ice: bool, } impl std::fmt::Display for ClippyWarning { @@ -76,9 +93,12 @@ impl std::fmt::Display for ClippyWarning { } impl CrateSource { + /// Makes the sources available on the disk for clippy to check. + /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or + /// copies a local folder fn download_and_extract(&self) -> Crate { match self { - CrateSource::CratesIo { name, version } => { + CrateSource::CratesIo { name, version, options } => { let extract_dir = PathBuf::from("target/lintcheck/crates"); let krate_download_dir = PathBuf::from("target/lintcheck/downloads"); @@ -110,9 +130,15 @@ impl CrateSource { version: version.clone(), name: name.clone(), path: extract_dir.join(format!("{}-{}/", name, version)), + options: options.clone(), } }, - CrateSource::Git { name, url, commit } => { + CrateSource::Git { + name, + url, + commit, + options, + } => { let repo_path = { let mut repo_path = PathBuf::from("target/lintcheck/crates"); // add a -git suffix in case we have the same crate from crates.io and a git repo @@ -122,27 +148,37 @@ impl CrateSource { // clone the repo if we have not done so if !repo_path.is_dir() { println!("Cloning {} and checking out {}", url, commit); - Command::new("git") + if !Command::new("git") .arg("clone") .arg(url) .arg(&repo_path) - .output() - .expect("Failed to clone git repo!"); + .status() + .expect("Failed to clone git repo!") + .success() + { + eprintln!("Failed to clone {} into {}", url, repo_path.display()) + } } // check out the commit/branch/whatever - Command::new("git") + if !Command::new("git") .arg("checkout") .arg(commit) - .output() - .expect("Failed to check out commit"); + .current_dir(&repo_path) + .status() + .expect("Failed to check out commit") + .success() + { + eprintln!("Failed to checkout {} of repo at {}", commit, repo_path.display()) + } Crate { version: commit.clone(), name: name.clone(), path: repo_path, + options: options.clone(), } }, - CrateSource::Path { name, path } => { + CrateSource::Path { name, path, options } => { use fs_extra::dir; // simply copy the entire directory into our target dir @@ -171,6 +207,7 @@ impl CrateSource { version: String::from("local"), name: name.clone(), path: crate_root, + options: options.clone(), } }, } @@ -178,24 +215,56 @@ impl CrateSource { } impl Crate { - fn run_clippy_lints(&self, cargo_clippy_path: &PathBuf) -> Vec<ClippyWarning> { - println!("Linting {} {}...", &self.name, &self.version); + /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy + /// issued + fn run_clippy_lints( + &self, + cargo_clippy_path: &PathBuf, + target_dir_index: &AtomicUsize, + thread_limit: usize, + total_crates_to_lint: usize, + ) -> Vec<ClippyWarning> { + // advance the atomic index by one + let index = target_dir_index.fetch_add(1, Ordering::SeqCst); + // "loop" the index within 0..thread_limit + let target_dir_index = index % thread_limit; + let perc = ((index * 100) as f32 / total_crates_to_lint as f32) as u8; + + if thread_limit == 1 { + println!( + "{}/{} {}% Linting {} {}", + index, total_crates_to_lint, perc, &self.name, &self.version + ); + } else { + println!( + "{}/{} {}% Linting {} {} in target dir {:?}", + index, total_crates_to_lint, perc, &self.name, &self.version, target_dir_index + ); + } + let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap(); - let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir/"); + let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir"); + + let mut args = vec!["--", "--message-format=json", "--", "--cap-lints=warn"]; + + if let Some(options) = &self.options { + for opt in options { + args.push(opt); + } + } else { + args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"]) + } let all_output = std::process::Command::new(&cargo_clippy_path) - .env("CARGO_TARGET_DIR", shared_target_dir) + // use the looping index to create individual target dirs + .env( + "CARGO_TARGET_DIR", + shared_target_dir.join(format!("_{:?}", target_dir_index)), + ) // lint warnings will look like this: // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter` - .args(&[ - "--", - "--message-format=json", - "--", - "--cap-lints=warn", - "-Wclippy::pedantic", - "-Wclippy::cargo", - ]) + .args(&args) .current_dir(&self.path) .output() .unwrap_or_else(|error| { @@ -211,28 +280,69 @@ impl Crate { let warnings: Vec<ClippyWarning> = output_lines .into_iter() // get all clippy warnings and ICEs - .filter(|line| line.contains("clippy::") || line.contains("internal compiler error: ")) + .filter(|line| filter_clippy_warnings(&line)) .map(|json_msg| parse_json_message(json_msg, &self)) .collect(); warnings } } +/// 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 @@ -249,6 +359,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) { crate_sources.push(CrateSource::Path { name: tk.name.clone(), path: PathBuf::from(path), + options: tk.options.clone(), }); } @@ -258,6 +369,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) { crate_sources.push(CrateSource::CratesIo { name: tk.name.clone(), version: ver.to_string(), + options: tk.options.clone(), }); }) } @@ -267,6 +379,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) { name: tk.name.clone(), url: tk.git_url.clone().unwrap(), commit: tk.git_hash.clone().unwrap(), + options: tk.options.clone(), }); } // if we have a version as well as a git data OR only one git data, something is funky @@ -283,10 +396,13 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) { unreachable!("Failed to translate TomlCrate into CrateSource!"); } }); + // sort the crates + crate_sources.sort(); + (toml_filename, crate_sources) } -// extract interesting data from a json lint message +/// Parse the json output of clippy and return a `ClippyWarning` fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning { let jmsg: Value = serde_json::from_str(&json_message).unwrap_or_else(|e| panic!("Failed to parse json:\n{:?}", e)); @@ -307,18 +423,84 @@ fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning { .into(), linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(), message: jmsg["message"]["message"].to_string().trim_matches('"').into(), - ice: json_message.contains("internal compiler error: "), + is_ice: json_message.contains("internal compiler error: "), } } -// the main fn -pub fn run(clap_config: &ArgMatches) { - let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy"); +/// Generate a short list of occuring lints-types and their count +fn gather_stats(clippy_warnings: &[ClippyWarning]) -> String { + // count lint type occurrences + let mut counter: HashMap<&String, usize> = HashMap::new(); + clippy_warnings + .iter() + .for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1); + // collect into a tupled list for sorting + let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect(); + // sort by "000{count} {clippy::lintname}" + // to not have a lint with 200 and 2 warnings take the same spot + stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint)); + + stats + .iter() + .map(|(lint, count)| format!("{} {}\n", lint, count)) + .collect::<String>() +} + +/// check if the latest modification of the logfile is older than the modification date of the +/// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck +fn lintcheck_needs_rerun(toml_path: Option<&str>) -> bool { + let clippy_modified: std::time::SystemTime = { + let mut times = ["target/debug/clippy-driver", "target/debug/cargo-clippy"] + .iter() + .map(|p| { + std::fs::metadata(p) + .expect("failed to get metadata of file") + .modified() + .expect("failed to get modification date") + }); + // the lates modification of either of the binaries + std::cmp::max(times.next().unwrap(), times.next().unwrap()) + }; + + let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_config_toml(toml_path)) + .expect("failed to get metadata of file") + .modified() + .expect("failed to get modification date"); + + // if clippys modification time is bigger (older) than the logs mod time, we need to rerun lintcheck + clippy_modified > logs_modified +} + +/// lintchecks `main()` function +pub fn run(clap_config: &ArgMatches) { println!("Compiling clippy..."); build_clippy(); println!("Done compiling"); + let clap_toml_path = clap_config.value_of("crates-toml"); + + // if the clippy bin is newer than our logs, throw away target dirs to force clippy to + // refresh the logs + if lintcheck_needs_rerun(clap_toml_path) { + let shared_target_dir = "target/lintcheck/shared_target_dir"; + match std::fs::metadata(&shared_target_dir) { + Ok(metadata) => { + if metadata.is_dir() { + println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir..."); + std::fs::remove_dir_all(&shared_target_dir) + .expect("failed to remove target/lintcheck/shared_target_dir"); + } + }, + Err(_) => { // dir probably does not exist, don't remove anything + }, + } + } + + let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy") + .canonicalize() + .expect("failed to canonicalize path to clippy binary"); + // assert that clippy is found assert!( cargo_clippy_path.is_file(), @@ -335,7 +517,7 @@ pub fn run(clap_config: &ArgMatches) { // download and extract the crates, then run clippy on them and collect clippys warnings // flatten into one big list of warnings - let (filename, crates) = read_crates(clap_config.value_of("crates-toml")); + let (filename, crates) = read_crates(clap_toml_path); let clippy_warnings: Vec<ClippyWarning> = if let Some(only_one_crate) = clap_config.value_of("only") { // if we don't have the specified crate in the .toml, throw an error @@ -359,45 +541,60 @@ pub fn run(clap_config: &ArgMatches) { .into_iter() .map(|krate| krate.download_and_extract()) .filter(|krate| krate.name == only_one_crate) - .map(|krate| krate.run_clippy_lints(&cargo_clippy_path)) + .map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1)) .flatten() .collect() } else { + let counter = std::sync::atomic::AtomicUsize::new(0); + + // Ask rayon for thread count. Assume that half of that is the number of physical cores + // Use one target dir for each core so that we can run N clippys in parallel. + // We need to use different target dirs because cargo would lock them for a single build otherwise, + // killing the parallelism. However this also means that deps will only be reused half/a + // quarter of the time which might result in a longer wall clock runtime + + // This helps when we check many small crates with dep-trees that don't have a lot of branches in + // order to achive some kind of parallelism + + // by default, use a single thread + let num_cpus = match clap_config.value_of("threads") { + Some(threads) => { + let threads: usize = threads + .parse() + .expect(&format!("Failed to parse '{}' to a digit", threads)); + if threads == 0 { + // automatic choice + // Rayon seems to return thread count so half that for core count + (rayon::current_num_threads() / 2) as usize + } else { + threads + } + }, + // no -j passed, use a single thread + None => 1, + }; + + let num_crates = crates.len(); + // check all crates (default) crates - .into_iter() + .into_par_iter() .map(|krate| krate.download_and_extract()) - .map(|krate| krate.run_clippy_lints(&cargo_clippy_path)) + .map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates)) .flatten() .collect() }; - // generate some stats: + // generate some stats + let stats_formatted = gather_stats(&clippy_warnings); // grab crashes/ICEs, save the crate name and the ice message let ices: Vec<(&String, &String)> = clippy_warnings .iter() - .filter(|warning| warning.ice) + .filter(|warning| warning.is_ice) .map(|w| (&w.crate_name, &w.message)) .collect(); - // count lint type occurrences - let mut counter: HashMap<&String, usize> = HashMap::new(); - clippy_warnings - .iter() - .for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1); - - // collect into a tupled list for sorting - let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect(); - // sort by "000{count} {clippy::lintname}" - // to not have a lint with 200 and 2 warnings take the same spot - stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint)); - - let stats_formatted: String = stats - .iter() - .map(|(lint, count)| format!("{} {}\n", lint, count)) - .collect::<String>(); - let mut all_msgs: Vec<String> = clippy_warnings.iter().map(|warning| warning.to_string()).collect(); all_msgs.sort(); all_msgs.push("\n\n\n\nStats\n\n".into()); @@ -411,5 +608,6 @@ pub fn run(clap_config: &ArgMatches) { .for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg))); let file = format!("lintcheck-logs/{}_logs.txt", filename); + println!("Writing logs to {}", file); write(file, text).unwrap(); } diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs index 5dbd46935a5..505d465760c 100644 --- a/src/tools/clippy/clippy_dev/src/main.rs +++ b/src/tools/clippy/clippy_dev/src/main.rs @@ -69,6 +69,14 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { .value_name("CRATES-SOURCES-TOML-PATH") .long("crates-toml") .help("set the path for a crates.toml where lintcheck should read the sources from"), + ) + .arg( + Arg::with_name("threads") + .takes_value(true) + .value_name("N") + .short("j") + .long("jobs") + .help("number of threads to use, 0 automatic choice"), ); let app = App::new("Clippy developer tooling") diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index 840341fefc6..d5ec8597044 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" [dependencies] cargo_metadata = "0.12" +clippy_utils = { path = "../clippy_utils" } if_chain = "1.0.0" itertools = "0.9" pulldown-cmark = { version = "0.8", default-features = false } @@ -38,4 +39,4 @@ syn = { version = "1", features = ["full"] } [features] deny-warnings = [] # build clippy with internal lints enabled, off by default -internal-lints = [] +internal-lints = ["clippy_utils/internal-lints"] diff --git a/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs index 4efca10bcdf..b53f80fd8bc 100644 --- a/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs +++ b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs @@ -1,4 +1,8 @@ -use crate::utils::{differing_macro_contexts, snippet_block_with_applicability, span_lint, span_lint_and_sugg}; +use crate::utils::{ + differing_macro_contexts, get_parent_expr, get_trait_def_id, implements_trait, paths, + snippet_block_with_applicability, span_lint, span_lint_and_sugg, +}; +use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{BlockCheckMode, Expr, ExprKind}; @@ -52,6 +56,18 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { if let ExprKind::Closure(_, _, eid, _, _) = expr.kind { + // do not lint if the closure is called using an iterator (see #1141) + if_chain! { + if let Some(parent) = get_parent_expr(self.cx, expr); + if let ExprKind::MethodCall(_, _, args, _) = parent.kind; + let caller = self.cx.typeck_results().expr_ty(&args[0]); + if let Some(iter_id) = get_trait_def_id(self.cx, &paths::ITERATOR); + if implements_trait(self.cx, caller, iter_id, &[]); + then { + return; + } + } + let body = self.cx.tcx.hir().body(eid); let ex = &body.value; if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs index 93ccc76d0c9..34f0e6ab027 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_if.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -122,6 +122,7 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) { if let ast::ExprKind::Block(ref block, _) = else_.kind; if !block_starts_with_comment(cx, block); if let Some(else_) = expr_block(block); + if else_.attrs.is_empty(); if !else_.span.from_expansion(); if let ast::ExprKind::If(..) = else_.kind; then { @@ -143,16 +144,12 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: & if_chain! { if !block_starts_with_comment(cx, then); if let Some(inner) = expr_block(then); + if inner.attrs.is_empty(); if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind; + // Prevent triggering on `if c { if let a = b { .. } }`. + if !matches!(check_inner.kind, ast::ExprKind::Let(..)); + if expr.span.ctxt() == inner.span.ctxt(); then { - if let ast::ExprKind::Let(..) = check_inner.kind { - // Prevent triggering on `if c { if let a = b { .. } }`. - return; - } - - if expr.span.ctxt() != inner.span.ctxt() { - return; - } span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| { let lhs = Sugg::ast(cx, check, ".."); let rhs = Sugg::ast(cx, check_inner, ".."); diff --git a/src/tools/clippy/clippy_lints/src/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/collapsible_match.rs index 67282cb7900..3c45525684b 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_match.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_match.rs @@ -96,12 +96,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext cx, COLLAPSIBLE_MATCH, expr.span, - "Unnecessary nested match", + "unnecessary nested match", |diag| { let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]); - help_span.push_span_label(binding_span, "Replace this binding".into()); + help_span.push_span_label(binding_span, "replace this binding".into()); help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into()); - diag.span_help(help_span, "The outer pattern can be modified to include the inner pattern."); + diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern"); }, ); } diff --git a/src/tools/clippy/clippy_lints/src/consts.rs b/src/tools/clippy/clippy_lints/src/consts.rs index 1b89d0bbe38..7e87f53e3fb 100644 --- a/src/tools/clippy/clippy_lints/src/consts.rs +++ b/src/tools/clippy/clippy_lints/src/consts.rs @@ -1,574 +1 @@ -#![allow(clippy::float_cmp)] - -use crate::utils::{clip, sext, unsext}; -use if_chain::if_chain; -use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_data_structures::sync::Lrc; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; -use rustc_lint::LateContext; -use rustc_middle::mir::interpret::Scalar; -use rustc_middle::ty::subst::{Subst, SubstsRef}; -use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; -use rustc_middle::{bug, span_bug}; -use rustc_span::symbol::Symbol; -use std::cmp::Ordering::{self, Equal}; -use std::convert::TryInto; -use std::hash::{Hash, Hasher}; - -/// A `LitKind`-like enum to fold constant `Expr`s into. -#[derive(Debug, Clone)] -pub enum Constant { - /// A `String` (e.g., "abc"). - Str(String), - /// A binary string (e.g., `b"abc"`). - Binary(Lrc<[u8]>), - /// A single `char` (e.g., `'a'`). - Char(char), - /// An integer's bit representation. - Int(u128), - /// An `f32`. - F32(f32), - /// An `f64`. - F64(f64), - /// `true` or `false`. - Bool(bool), - /// An array of constants. - Vec(Vec<Constant>), - /// Also an array, but with only one constant, repeated N times. - Repeat(Box<Constant>, u64), - /// A tuple of constants. - Tuple(Vec<Constant>), - /// A raw pointer. - RawPtr(u128), - /// A reference - Ref(Box<Constant>), - /// A literal with syntax error. - Err(Symbol), -} - -impl PartialEq for Constant { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, - (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, - (&Self::Char(l), &Self::Char(r)) => l == r, - (&Self::Int(l), &Self::Int(r)) => l == r, - (&Self::F64(l), &Self::F64(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so 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::*; diff --git a/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs new file mode 100644 index 00000000000..6ace9aa6bdf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs @@ -0,0 +1,237 @@ +use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{ + intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor}, + Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{ + hir::map::Map, + ty::{self, FloatTy, IntTy, PolyFnSig, Ty}, +}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use if_chain::if_chain; + +use crate::utils::{snippet, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type + /// inference. + /// + /// Default numeric fallback means that if numeric types have not yet been bound to concrete + /// types at the end of type inference, then integer type is bound to `i32`, and similarly + /// floating type is bound to `f64`. + /// + /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. + /// + /// **Why is this bad?** For those who are very careful about types, default numeric fallback + /// can be a pitfall that cause unexpected runtime behavior. + /// + /// **Known problems:** This lint can only be allowed at the function level or above. + /// + /// **Example:** + /// ```rust + /// let i = 10; + /// let f = 1.23; + /// ``` + /// + /// Use instead: + /// ```rust + /// let i = 10i32; + /// let f = 1.23f64; + /// ``` + pub DEFAULT_NUMERIC_FALLBACK, + restriction, + "usage of unconstrained numeric literals which may cause default numeric fallback." +} + +declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); + +impl LateLintPass<'_> for DefaultNumericFallback { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + let mut visitor = NumericFallbackVisitor::new(cx); + visitor.visit_body(body); + } +} + +struct NumericFallbackVisitor<'a, 'tcx> { + /// Stack manages type bound of exprs. The top element holds current expr type. + ty_bounds: Vec<TyBound<'tcx>>, + + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + ty_bounds: vec![TyBound::Nothing], + cx, + } + } + + /// Check whether a passed literal has potential to cause fallback or not. + fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { + if_chain! { + if let Some(ty_bound) = self.ty_bounds.last(); + if matches!(lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); + if !ty_bound.is_integral(); + then { + let suffix = match lit_ty.kind() { + ty::Int(IntTy::I32) => "i32", + ty::Float(FloatTy::F64) => "f64", + // Default numeric fallback never results in other types. + _ => return, + }; + + let sugg = format!("{}_{}", snippet(self.cx, lit.span, ""), suffix); + span_lint_and_sugg( + self.cx, + DEFAULT_NUMERIC_FALLBACK, + lit.span, + "default numeric fallback might occur", + "consider adding suffix", + sugg, + Applicability::MaybeIncorrect, + ); + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + #[allow(clippy::too_many_lines)] + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match &expr.kind { + ExprKind::Call(func, args) => { + if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) { + for (expr, bound) in args.iter().zip(fn_sig.skip_binder().inputs().iter()) { + // Push found arg type, then visit arg. + self.ty_bounds.push(TyBound::Ty(bound)); + self.visit_expr(expr); + self.ty_bounds.pop(); + } + return; + } + }, + + ExprKind::MethodCall(_, _, args, _) => { + if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { + let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); + for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) { + self.ty_bounds.push(TyBound::Ty(bound)); + self.visit_expr(expr); + self.ty_bounds.pop(); + } + return; + } + }, + + ExprKind::Struct(qpath, fields, base) => { + if_chain! { + if let Some(def_id) = self.cx.qpath_res(qpath, expr.hir_id).opt_def_id(); + let ty = self.cx.tcx.type_of(def_id); + if let Some(adt_def) = ty.ty_adt_def(); + if adt_def.is_struct(); + if let Some(variant) = adt_def.variants.iter().next(); + then { + let fields_def = &variant.fields; + + // Push field type then visit each field expr. + for field in fields.iter() { + let bound = + fields_def + .iter() + .find_map(|f_def| { + if f_def.ident == field.ident + { Some(self.cx.tcx.type_of(f_def.did)) } + else { None } + }); + self.ty_bounds.push(bound.into()); + self.visit_expr(field.expr); + self.ty_bounds.pop(); + } + + // Visit base with no bound. + if let Some(base) = base { + self.ty_bounds.push(TyBound::Nothing); + self.visit_expr(base); + self.ty_bounds.pop(); + } + return; + } + } + }, + + ExprKind::Lit(lit) => { + let ty = self.cx.typeck_results().expr_ty(expr); + self.check_lit(lit, ty); + return; + }, + + _ => {}, + } + + walk_expr(self, expr); + } + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + match stmt.kind { + StmtKind::Local(local) => { + if local.ty.is_some() { + self.ty_bounds.push(TyBound::Any) + } else { + self.ty_bounds.push(TyBound::Nothing) + } + }, + + _ => self.ty_bounds.push(TyBound::Nothing), + } + + walk_stmt(self, stmt); + self.ty_bounds.pop(); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> { + let node_ty = cx.typeck_results().node_type_opt(hir_id)?; + // We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs. + match node_ty.kind() { + ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)), + ty::FnPtr(fn_sig) => Some(*fn_sig), + _ => None, + } +} + +#[derive(Debug, Clone, Copy)] +enum TyBound<'tcx> { + Any, + Ty(Ty<'tcx>), + Nothing, +} + +impl<'tcx> TyBound<'tcx> { + fn is_integral(self) -> bool { + match self { + TyBound::Any => true, + TyBound::Ty(t) => t.is_integral(), + TyBound::Nothing => false, + } + } +} + +impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> { + fn from(v: Option<Ty<'tcx>>) -> Self { + match v { + Some(t) => TyBound::Ty(t), + None => TyBound::Nothing, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs index 67b7cf91958..39a202f281c 100644 --- a/src/tools/clippy/clippy_lints/src/doc.rs +++ b/src/tools/clippy/clippy_lints/src/doc.rs @@ -1,6 +1,6 @@ use crate::utils::{ - implements_trait, is_entrypoint_fn, is_type_diagnostic_item, match_panic_def_id, method_chain_args, return_ty, - span_lint, span_lint_and_note, + implements_trait, is_entrypoint_fn, is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args, + return_ty, span_lint, span_lint_and_note, }; use if_chain::if_chain; use itertools::Itertools; @@ -216,9 +216,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { let headers = check_attrs(cx, &self.valid_idents, &item.attrs); match item.kind { hir::ItemKind::Fn(ref sig, _, body_id) => { - if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) - || in_external_macro(cx.tcx.sess, item.span)) - { + if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { let body = cx.tcx.hir().body(body_id); let mut fpu = FindPanicUnwrap { cx, @@ -226,7 +224,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { panic_span: None, }; fpu.visit_expr(&body.value); - lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span); + lint_for_missing_headers( + cx, + item.hir_id(), + item.span, + sig, + headers, + Some(body_id), + fpu.panic_span, + ); } }, hir::ItemKind::Impl(ref impl_) => { @@ -264,7 +270,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { panic_span: None, }; fpu.visit_expr(&body.value); - lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span); + lint_for_missing_headers( + cx, + item.hir_id(), + item.span, + sig, + headers, + Some(body_id), + fpu.panic_span, + ); } } } @@ -561,9 +575,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { | ItemKind::ExternCrate(..) | ItemKind::ForeignMod(..) => return false, // We found a main function ... - ItemKind::Fn(box FnKind(_, sig, _, Some(block))) - if item.ident.name == sym::main => - { + ItemKind::Fn(box FnKind(_, sig, _, Some(block))) if item.ident.name == sym::main => { let is_async = matches!(sig.header.asyncness, Async::Yes { .. }); let returns_nothing = match &sig.decl.output { FnRetTy::Default(..) => true, @@ -699,6 +711,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; if let Some(path_def_id) = path.res.opt_def_id(); if match_panic_def_id(self.cx, path_def_id); + if is_expn_of(expr.span, "unreachable").is_none(); then { self.panic_span = Some(expr.span); } diff --git a/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs new file mode 100644 index 00000000000..0933f983014 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/from_str_radix_10.rs @@ -0,0 +1,101 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +use crate::utils::is_type_diagnostic_item; +use crate::utils::span_lint_and_sugg; +use crate::utils::sugg::Sugg; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for function invocations of the form `primitive::from_str_radix(s, 10)` + /// + /// **Why is this bad?** + /// This specific common use case can be rewritten as `s.parse::<primitive>()` + /// (and in most cases, the turbofish can be removed), which reduces code length + /// and complexity. + /// + /// **Known problems:** + /// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly + /// in some cases, which is correct but adds unnecessary complexity to the code. + /// + /// **Example:** + /// + /// ```ignore + /// let input: &str = get_input(); + /// let num = u16::from_str_radix(input, 10)?; + /// ``` + /// Use instead: + /// ```ignore + /// let input: &str = get_input(); + /// let num: u16 = input.parse()?; + /// ``` + pub FROM_STR_RADIX_10, + style, + "from_str_radix with radix 10" +} + +declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]); + +impl LateLintPass<'tcx> for FromStrRadix10 { + fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) { + if_chain! { + if let ExprKind::Call(maybe_path, arguments) = &exp.kind; + if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind; + + // check if the first part of the path is some integer primitive + if let TyKind::Path(ty_qpath) = &ty.kind; + let ty_res = cx.qpath_res(ty_qpath, ty.hir_id); + if let def::Res::PrimTy(prim_ty) = ty_res; + if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)); + + // check if the second part of the path indeed calls the associated + // function `from_str_radix` + if pathseg.ident.name.as_str() == "from_str_radix"; + + // check if the second argument is a primitive `10` + if arguments.len() == 2; + if let ExprKind::Lit(lit) = &arguments[1].kind; + if let rustc_ast::ast::LitKind::Int(10, _) = lit.node; + + then { + let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind { + let ty = cx.typeck_results().expr_ty(expr); + if is_ty_stringish(cx, ty) { + expr + } else { + &arguments[0] + } + } else { + &arguments[0] + }; + + let sugg = Sugg::hir_with_applicability( + cx, + expr, + "<string>", + &mut Applicability::MachineApplicable + ).maybe_par(); + + span_lint_and_sugg( + cx, + FROM_STR_RADIX_10, + exp.span, + "this call to `from_str_radix` can be replaced with a call to `str::parse`", + "try", + format!("{}.parse::<{}>()", sugg, prim_ty.name_str()), + Applicability::MaybeIncorrect + ); + } + } + } +} + +/// Checks if a Ty is `String` or `&str` +fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str) +} diff --git a/src/tools/clippy/clippy_lints/src/functions.rs b/src/tools/clippy/clippy_lints/src/functions.rs index f770f1f812a..e4b3a9009f6 100644 --- a/src/tools/clippy/clippy_lints/src/functions.rs +++ b/src/tools/clippy/clippy_lints/src/functions.rs @@ -1,7 +1,7 @@ use crate::utils::{ attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item, iter_input_pats, - last_path_segment, match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint, - span_lint_and_help, span_lint_and_then, trait_ref_of_method, type_is_unsafe_function, + match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, + span_lint_and_then, trait_ref_of_method, type_is_unsafe_function, }; use if_chain::if_chain; use rustc_ast::ast::Attribute; @@ -470,12 +470,11 @@ fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span if_chain! { if !in_external_macro(cx.sess(), item_span); if let hir::FnRetTy::Return(ref ty) = decl.output; - if let hir::TyKind::Path(ref qpath) = ty.kind; - if is_type_diagnostic_item(cx, hir_ty_to_ty(cx.tcx, ty), sym::result_type); - if let Some(ref args) = last_path_segment(qpath).args; - if let [_, hir::GenericArg::Type(ref err_ty)] = args.args; - if let hir::TyKind::Tup(t) = err_ty.kind; - if t.is_empty(); + let ty = hir_ty_to_ty(cx.tcx, ty); + if is_type_diagnostic_item(cx, ty, sym::result_type); + if let ty::Adt(_, substs) = ty.kind(); + let err_ty = substs.type_at(1); + if err_ty.is_unit(); then { span_lint_and_help( cx, diff --git a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs new file mode 100644 index 00000000000..c5afdf530eb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs @@ -0,0 +1,134 @@ +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Symbol; + +use if_chain::if_chain; + +use crate::utils::{snippet, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for struct constructors where the order of the field init + /// shorthand in the constructor is inconsistent with the order in the struct definition. + /// + /// **Why is this bad?** Since the order of fields in a constructor doesn't affect the + /// resulted instance as the below example indicates, + /// + /// ```rust + /// #[derive(Debug, PartialEq, Eq)] + /// struct Foo { + /// x: i32, + /// y: i32, + /// } + /// let x = 1; + /// let y = 2; + /// + /// // This assertion never fails. + /// assert_eq!(Foo { x, y }, Foo { y, x }); + /// ``` + /// + /// inconsistent order means nothing and just decreases readability and consistency. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct Foo { + /// x: i32, + /// y: i32, + /// } + /// let x = 1; + /// let y = 2; + /// Foo { y, x }; + /// ``` + /// + /// Use instead: + /// ```rust + /// # struct Foo { + /// # x: i32, + /// # y: i32, + /// # } + /// # let x = 1; + /// # let y = 2; + /// Foo { x, y }; + /// ``` + pub INCONSISTENT_STRUCT_CONSTRUCTOR, + style, + "the order of the field init shorthand is inconsistent with the order in the struct definition" +} + +declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]); + +impl LateLintPass<'_> for InconsistentStructConstructor { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if let ExprKind::Struct(qpath, fields, base) = expr.kind; + if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id(); + let ty = cx.tcx.type_of(def_id); + if let Some(adt_def) = ty.ty_adt_def(); + if adt_def.is_struct(); + if let Some(variant) = adt_def.variants.iter().next(); + if fields.iter().all(|f| f.is_shorthand); + then { + let mut def_order_map = FxHashMap::default(); + for (idx, field) in variant.fields.iter().enumerate() { + def_order_map.insert(field.ident.name, idx); + } + + if is_consistent_order(fields, &def_order_map) { + return; + } + + let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect(); + ordered_fields.sort_unstable_by_key(|id| def_order_map[id]); + + let mut fields_snippet = String::new(); + let (last_ident, idents) = ordered_fields.split_last().unwrap(); + for ident in idents { + fields_snippet.push_str(&format!("{}, ", ident)); + } + fields_snippet.push_str(&last_ident.to_string()); + + let base_snippet = if let Some(base) = base { + format!(", ..{}", snippet(cx, base.span, "..")) + } else { + String::new() + }; + + let sugg = format!("{} {{ {}{} }}", + snippet(cx, qpath.span(), ".."), + fields_snippet, + base_snippet, + ); + + span_lint_and_sugg( + cx, + INCONSISTENT_STRUCT_CONSTRUCTOR, + expr.span, + "inconsistent struct constructor", + "try", + sugg, + Applicability::MachineApplicable, + ) + } + } + } +} + +// Check whether the order of the fields in the constructor is consistent with the order in the +// definition. +fn is_consistent_order<'tcx>(fields: &'tcx [hir::Field<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool { + let mut cur_idx = usize::MIN; + for f in fields { + let next_idx = def_order_map[&f.ident.name]; + if cur_idx > next_idx { + return false; + } + cur_idx = next_idx; + } + + true +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs index 76e7a4992d3..a95321ea7e2 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -106,6 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { let decl = &signature.decl; if decl.implicit_self.has_implicit_self(); if decl.inputs.len() == 1; + if impl_item.generics.params.is_empty(); // Check if return type is String if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::string_type); diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index d96911fac1a..176eeadcc63 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -1,10 +1,7 @@ // error-pattern:cargo-clippy -#![feature(bindings_after_at)] #![feature(box_patterns)] #![feature(box_syntax)] -#![feature(concat_idents)] -#![feature(crate_visibility_modifier)] #![feature(drain_filter)] #![feature(in_band_lifetimes)] #![feature(once_cell)] @@ -149,6 +146,20 @@ macro_rules! declare_clippy_lint { }; } +#[macro_export] +macro_rules! sym { + ( $($x:tt)* ) => { clippy_utils::sym!($($x)*) } +} + +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) } +} + +macro_rules! extract_msrv_attr { + ( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); } +} + mod consts; #[macro_use] mod utils; @@ -181,6 +192,7 @@ mod copy_iterator; mod create_dir; mod dbg_macro; mod default; +mod default_numeric_fallback; mod dereference; mod derive; mod disallowed_method; @@ -210,6 +222,7 @@ mod floating_point_arithmetic; mod format; mod formatting; mod from_over_into; +mod from_str_radix_10; mod functions; mod future_not_send; mod get_last_with_len; @@ -219,6 +232,7 @@ mod if_let_some_result; mod if_not_else; mod implicit_return; mod implicit_saturating_sub; +mod inconsistent_struct_constructor; mod indexing_slicing; mod infinite_iter; mod inherent_impl; @@ -239,6 +253,7 @@ mod loops; mod macro_use; mod main_recursion; mod manual_async_fn; +mod manual_map; mod manual_non_exhaustive; mod manual_ok_or; mod manual_strip; @@ -585,6 +600,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &dbg_macro::DBG_MACRO, &default::DEFAULT_TRAIT_ACCESS, &default::FIELD_REASSIGN_WITH_DEFAULT, + &default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK, &dereference::EXPLICIT_DEREF_METHODS, &derive::DERIVE_HASH_XOR_EQ, &derive::DERIVE_ORD_XOR_PARTIAL_ORD, @@ -637,6 +653,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &formatting::SUSPICIOUS_ELSE_FORMATTING, &formatting::SUSPICIOUS_UNARY_OP_FORMATTING, &from_over_into::FROM_OVER_INTO, + &from_str_radix_10::FROM_STR_RADIX_10, &functions::DOUBLE_MUST_USE, &functions::MUST_USE_CANDIDATE, &functions::MUST_USE_UNIT, @@ -652,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &if_not_else::IF_NOT_ELSE, &implicit_return::IMPLICIT_RETURN, &implicit_saturating_sub::IMPLICIT_SATURATING_SUB, + &inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR, &indexing_slicing::INDEXING_SLICING, &indexing_slicing::OUT_OF_BOUNDS_INDEXING, &infinite_iter::INFINITE_ITER, @@ -702,6 +720,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ¯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, @@ -1031,6 +1050,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box strings::StringAdd); store.register_late_pass(|| box implicit_return::ImplicitReturn); store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub); + store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback); + store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor); let msrv = conf.msrv.as_ref().and_then(|s| { parse_msrv(s, None, None).or_else(|| { @@ -1195,7 +1216,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let enum_variant_name_threshold = conf.enum_variant_name_threshold; store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold)); store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments); - store.register_early_pass(|| box upper_case_acronyms::UpperCaseAcronyms); + let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive; + store.register_early_pass(move || box upper_case_acronyms::UpperCaseAcronyms::new(upper_case_acronyms_aggressive)); store.register_late_pass(|| box default::Default::default()); store.register_late_pass(|| box unused_self::UnusedSelf); store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall); @@ -1256,6 +1278,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box types::PtrAsPtr::new(msrv)); store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons); store.register_late_pass(|| box redundant_slicing::RedundantSlicing); + store.register_late_pass(|| box from_str_radix_10::FromStrRadix10); + store.register_late_pass(|| box manual_map::ManualMap); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1265,6 +1289,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), LintId::of(&create_dir::CREATE_DIR), LintId::of(&dbg_macro::DBG_MACRO), + LintId::of(&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK), LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE), LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS), LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS), @@ -1389,6 +1414,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::PTR_AS_PTR), LintId::of(&unicode::NON_ASCII_LITERAL), LintId::of(&unicode::UNICODE_NOT_NFC), + LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(&unused_self::UNUSED_SELF), LintId::of(&wildcard_imports::ENUM_GLOB_USE), @@ -1468,6 +1494,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), LintId::of(&from_over_into::FROM_OVER_INTO), + LintId::of(&from_str_radix_10::FROM_STR_RADIX_10), LintId::of(&functions::DOUBLE_MUST_USE), LintId::of(&functions::MUST_USE_UNIT), LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), @@ -1477,6 +1504,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&identity_op::IDENTITY_OP), LintId::of(&if_let_mutex::IF_LET_MUTEX), LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(&infinite_iter::INFINITE_ITER), LintId::of(&inherent_to_string::INHERENT_TO_STRING), @@ -1512,6 +1540,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::WHILE_LET_ON_ITERATOR), LintId::of(&main_recursion::MAIN_RECURSION), LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), + LintId::of(&manual_map::MANUAL_MAP), LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), LintId::of(&manual_strip::MANUAL_STRIP), LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR), @@ -1682,7 +1711,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), - LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unused_unit::UNUSED_UNIT), @@ -1724,10 +1752,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), LintId::of(&from_over_into::FROM_OVER_INTO), + LintId::of(&from_str_radix_10::FROM_STR_RADIX_10), LintId::of(&functions::DOUBLE_MUST_USE), LintId::of(&functions::MUST_USE_UNIT), LintId::of(&functions::RESULT_UNIT_ERR), LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::COMPARISON_TO_EMPTY), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), @@ -1741,6 +1771,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::WHILE_LET_ON_ITERATOR), LintId::of(&main_recursion::MAIN_RECURSION), LintId::of(&manual_async_fn::MANUAL_ASYNC_FN), + LintId::of(&manual_map::MANUAL_MAP), LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), LintId::of(&map_clone::MAP_CLONE), LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), @@ -1899,7 +1930,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), - LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/src/tools/clippy/clippy_lints/src/manual_map.rs b/src/tools/clippy/clippy_lints/src/manual_map.rs new file mode 100644 index 00000000000..a50a3943bab --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_map.rs @@ -0,0 +1,274 @@ +use crate::{ + map_unit_fn::OPTION_MAP_UNIT_FN, + matches::MATCH_AS_REF, + utils::{ + is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs, + peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg, + }, +}; +use rustc_ast::util::parser::PREC_POSTFIX; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::{sym, Ident}; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `match` which could be implemented using `map` + /// + /// **Why is this bad?** Using the `map` method is clearer and more concise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// match Some(0) { + /// Some(x) => Some(x + 1), + /// None => None, + /// }; + /// ``` + /// Use instead: + /// ```rust + /// Some(0).map(|x| x + 1); + /// ``` + pub MANUAL_MAP, + style, + "reimplementation of `map`" +} + +declare_lint_pass!(ManualMap => [MANUAL_MAP]); + +impl LateLintPass<'_> for ManualMap { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Match(scrutinee, [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], _) = + expr.kind + { + let (scrutinee_ty, ty_ref_count, ty_mutability) = + peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); + if !is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) + || !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type) + { + return; + } + + let (some_expr, some_pat, pat_ref_count, is_wild_none) = + match (try_parse_pattern(cx, arm1.pat), try_parse_pattern(cx, arm2.pat)) { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, true) + }, + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, false) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, true) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, false) + }, + _ => return, + }; + + // Top level or patterns aren't allowed in closures. + if matches!(some_pat.kind, PatKind::Or(_)) { + return; + } + + let some_expr = match get_some_expr(cx, some_expr) { + Some(expr) => expr, + None => return, + }; + + if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit + && !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id) + { + return; + } + + // Determine which binding mode to use. + let explicit_ref = some_pat.contains_explicit_ref_binding(); + let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability)); + + let as_ref_str = match binding_ref { + Some(Mutability::Mut) => ".as_mut()", + Some(Mutability::Not) => ".as_ref()", + None => "", + }; + + let mut app = Applicability::MachineApplicable; + + // Remove address-of expressions from the scrutinee. `as_ref` will be called, + // the type is copyable, or the option is being passed by value. + let scrutinee = peel_hir_expr_refs(scrutinee).0; + let scrutinee_str = snippet_with_applicability(cx, scrutinee.span, "_", &mut app); + let scrutinee_str = if expr.precedence().order() < PREC_POSTFIX { + // Parens are needed to chain method calls. + format!("({})", scrutinee_str) + } else { + scrutinee_str.into() + }; + + let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind { + if let Some(func) = can_pass_as_func(cx, some_binding, some_expr) { + snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() + } else { + if match_var(some_expr, some_binding.name) + && !is_allowed(cx, MATCH_AS_REF, expr.hir_id) + && binding_ref.is_some() + { + return; + } + + // `ref` and `ref mut` annotations were handled earlier. + let annotation = if matches!(annotation, BindingAnnotation::Mutable) { + "mut " + } else { + "" + }; + format!( + "|{}{}| {}", + annotation, + some_binding, + snippet_with_applicability(cx, some_expr.span, "..", &mut app) + ) + } + } else if !is_wild_none && explicit_ref.is_none() { + // TODO: handle explicit reference annotations. + format!( + "|{}| {}", + snippet_with_applicability(cx, some_pat.span, "..", &mut app), + snippet_with_applicability(cx, some_expr.span, "..", &mut app) + ) + } else { + // Refutable bindings and mixed reference annotations can't be handled by `map`. + return; + }; + + span_lint_and_sugg( + cx, + MANUAL_MAP, + expr.span, + "manual implementation of `Option::map`", + "try this", + format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str), + app, + ); + } + } +} + +// Checks whether the expression could be passed as a function, or whether a closure is needed. +// Returns the function to be passed to `map` if it exists. +fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + match expr.kind { + ExprKind::Call(func, [arg]) + if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() => + { + Some(func) + }, + _ => None, + } +} + +enum OptionPat<'a> { + Wild, + None, + Some { + // The pattern contained in the `Some` tuple. + pattern: &'a Pat<'a>, + // The number of references before the `Some` tuple. + // e.g. `&&Some(_)` has a ref count of 2. + ref_count: usize, + }, +} + +// Try to parse into a recognized `Option` pattern. +// i.e. `_`, `None`, `Some(..)`, or a reference to any of those. +fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<OptionPat<'tcx>> { + fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize) -> Option<OptionPat<'tcx>> { + match pat.kind { + PatKind::Wild => Some(OptionPat::Wild), + PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1), + PatKind::Path(QPath::Resolved(None, path)) + if path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) => + { + Some(OptionPat::None) + }, + PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _) + if path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) => + { + Some(OptionPat::Some { pattern, ref_count }) + }, + _ => None, + } + } + f(cx, pat, 0) +} + +// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression. +fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + // TODO: Allow more complex expressions. + match expr.kind { + ExprKind::Call( + Expr { + kind: ExprKind::Path(QPath::Resolved(None, path)), + .. + }, + [arg], + ) => { + if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) { + Some(arg) + } else { + None + } + }, + ExprKind::Block( + Block { + stmts: [], + expr: Some(expr), + .. + }, + _, + ) => get_some_expr(cx, expr), + _ => None, + } +} + +// Checks for the `None` value. +fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(QPath::Resolved(None, path)) => path + .res + .opt_def_id() + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)), + ExprKind::Block( + Block { + stmts: [], + expr: Some(expr), + .. + }, + _, + ) => is_none_expr(cx, expr), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/matches.rs b/src/tools/clippy/clippy_lints/src/matches.rs index e33001b16bc..efc8b139425 100644 --- a/src/tools/clippy/clippy_lints/src/matches.rs +++ b/src/tools/clippy/clippy_lints/src/matches.rs @@ -3,19 +3,19 @@ use crate::utils::sugg::Sugg; use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{ expr_block, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, is_refutable, - is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local_id, - peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt, - snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, - strip_pat_refs, + is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local, + path_to_local_id, peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, + snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, + span_lint_and_then, strip_pat_refs, }; use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash}; use if_chain::if_chain; use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::Applicability; use rustc_hir::def::CtorKind; use rustc_hir::{ - Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, Local, MatchSource, Mutability, Node, Pat, + Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath, RangeEnd, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -24,7 +24,7 @@ use rustc_middle::ty::{self, Ty, TyS}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; -use rustc_span::{sym, Symbol}; +use rustc_span::sym; use std::cmp::Ordering; use std::collections::hash_map::Entry; use std::collections::Bound; @@ -1873,13 +1873,6 @@ fn test_overlapping() { /// Implementation of `MATCH_SAME_ARMS`. fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { - fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool { - lhs.len() == rhs.len() - && lhs - .iter() - .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty))) - } - if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { let mut h = SpanlessHash::new(cx); @@ -1891,12 +1884,38 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { let min_index = usize::min(lindex, rindex); let max_index = usize::max(lindex, rindex); + let mut local_map: FxHashMap<HirId, HirId> = FxHashMap::default(); + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + if_chain! { + if let Some(a_id) = path_to_local(a); + if let Some(b_id) = path_to_local(b); + let entry = match local_map.entry(a_id) { + Entry::Vacant(entry) => entry, + // check if using the same bindings as before + Entry::Occupied(entry) => return *entry.get() == b_id, + }; + // the names technically don't have to match; this makes the lint more conservative + if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); + if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); + if pat_contains_local(lhs.pat, a_id); + if pat_contains_local(rhs.pat, b_id); + then { + entry.insert(b_id); + true + } else { + false + } + } + }; // Arms with a guard are ignored, those 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(); @@ -1939,50 +1958,18 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { } } -/// Returns the list of bindings in a pattern. -fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> { - fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) { - match pat.kind { - PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map), - PatKind::TupleStruct(_, pats, _) => { - for pat in pats { - bindings_impl(cx, pat, map); - } - }, - PatKind::Binding(.., ident, ref as_pat) => { - if let Entry::Vacant(v) = map.entry(ident.name) { - v.insert(cx.typeck_results().pat_ty(pat)); - } - if let Some(ref as_pat) = *as_pat { - bindings_impl(cx, as_pat, map); - } - }, - PatKind::Or(fields) | PatKind::Tuple(fields, _) => { - for pat in fields { - bindings_impl(cx, pat, map); - } - }, - PatKind::Struct(_, fields, _) => { - for pat in fields { - bindings_impl(cx, &pat.pat, map); - } - }, - PatKind::Slice(lhs, ref mid, rhs) => { - for pat in lhs { - bindings_impl(cx, pat, map); - } - if let Some(ref mid) = *mid { - bindings_impl(cx, mid, map); - } - for pat in rhs { - bindings_impl(cx, pat, map); - } - }, - PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (), - } - } - - let mut result = FxHashMap::default(); - bindings_impl(cx, pat, &mut result); +fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { + let mut result = false; + pat.walk_short(|p| { + result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); + !result + }); result } + +/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa +fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet<HirId>) -> bool { + let mut result = true; + pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); + result && ids.is_empty() +} diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs index 9b45d38afd4..00a707107bc 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs @@ -212,10 +212,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> { if !expr_borrows(cx, left_expr) { return Some(LintTrigger::SortByKey(SortByKeyDetection { vec_name, - unstable, closure_arg, closure_body, - reverse + reverse, + unstable, })); } } diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs index 8ac5dd696b7..1e58576d059 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs @@ -1,5 +1,5 @@ use crate::utils::{ - contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then, + contains_return, in_macro, match_qpath, paths, return_ty, snippet, span_lint_and_then, visitors::find_all_ret_expressions, }; use if_chain::if_chain; @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; use rustc_span::Span; @@ -17,8 +17,8 @@ declare_clippy_lint! { /// /// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned. /// - /// **Known problems:** Since this lint changes function type signature, you may need to - /// adjust some code at callee side. + /// **Known problems:** There can be false positives if the function signature is designed to + /// fit some external requirement. /// /// **Example:** /// @@ -48,7 +48,7 @@ declare_clippy_lint! { /// } /// ``` pub UNNECESSARY_WRAPS, - complexity, + pedantic, "functions that only return `Ok` or `Some`" } @@ -64,6 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { span: Span, hir_id: HirId, ) { + // Abort if public function/method or closure. match fn_kind { FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => { if visibility.node.is_pub() { @@ -74,6 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { _ => (), } + // Abort if the method is implementing a trait or of it a trait method. if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { if matches!( item.kind, @@ -83,25 +85,44 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { } } - let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::option_type) { - ("Option", &paths::OPTION_SOME) - } else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) { - ("Result", &paths::RESULT_OK) + // Get the wrapper and inner types, if can't, abort. + let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() { + if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) { + ("Option", &paths::OPTION_SOME, subst.type_at(0)) + } else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) { + ("Result", &paths::RESULT_OK, subst.type_at(0)) + } else { + return; + } } else { return; }; + // Check if all return expression respect the following condition and collect them. let mut suggs = Vec::new(); let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { if_chain! { if !in_macro(ret_expr.span); + // Check if a function call. if let ExprKind::Call(ref func, ref args) = ret_expr.kind; + // Get the Path of the function call. if let ExprKind::Path(ref qpath) = func.kind; + // Check if OPTION_SOME or RESULT_OK, depending on return type. if match_qpath(qpath, path); if args.len() == 1; + // Make sure the function argument does not contain a return expression. if !contains_return(&args[0]); then { - suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string())); + suggs.push( + ( + ret_expr.span, + if inner_type.is_unit() { + "".to_string() + } else { + snippet(cx, args[0].span.source_callsite(), "..").to_string() + } + ) + ); true } else { false @@ -110,39 +131,34 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { }); if can_sugg && !suggs.is_empty() { - span_lint_and_then( - cx, - UNNECESSARY_WRAPS, - span, - format!( - "this function's return value is unnecessarily wrapped by `{}`", - return_type + let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() { + ( + "this function's return value is unnecessary".to_string(), + "remove the return type...".to_string(), + snippet(cx, fn_decl.output.span(), "..").to_string(), + "...and then remove returned values", ) - .as_str(), - |diag| { - let inner_ty = return_ty(cx, hir_id) - .walk() - .skip(1) // skip `std::option::Option` or `std::result::Result` - .take(1) // take the first outermost inner type - .filter_map(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()), - _ => None, - }); - inner_ty.for_each(|inner_ty| { - diag.span_suggestion( - fn_decl.output.span(), - format!("remove `{}` from the return type...", return_type).as_str(), - inner_ty, - Applicability::MaybeIncorrect, - ); - }); - diag.multipart_suggestion( - "...and change the returning expressions", - suggs, - Applicability::MaybeIncorrect, - ); - }, - ); + } else { + ( + format!( + "this function's return value is unnecessarily wrapped by `{}`", + return_type_label + ), + format!("remove `{}` from the return type...", return_type_label), + inner_type.to_string(), + "...and then change returning expressions", + ) + }; + + span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| { + diag.span_suggestion( + fn_decl.output.span(), + return_type_sugg_msg.as_str(), + return_type_sugg, + Applicability::MaybeIncorrect, + ); + diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect); + }); } } } diff --git a/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs index 61e7031716a..0470e1dbbb8 100644 --- a/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs +++ b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs @@ -5,16 +5,20 @@ use rustc_ast::ast::{Item, ItemKind, Variant}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::Ident; declare_clippy_lint! { - /// **What it does:** Checks for camel case name containing a capitalized acronym. + /// **What it does:** Checks for fully capitalized names and optionally names containing a capitalized acronym. /// /// **Why is this bad?** In CamelCase, acronyms count as one word. /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case) /// for more. /// + /// By default, the lint only triggers on fully-capitalized names. + /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting + /// on all camel case names + /// /// **Known problems:** When two acronyms are contiguous, the lint can't tell where /// the first acronym ends and the second starts, so it suggests to lowercase all of /// the letters in the second acronym. @@ -33,7 +37,20 @@ declare_clippy_lint! { "capitalized acronyms are against the naming convention" } -declare_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); +#[derive(Default)] +pub struct UpperCaseAcronyms { + upper_case_acronyms_aggressive: bool, +} + +impl UpperCaseAcronyms { + pub fn new(aggressive: bool) -> Self { + Self { + upper_case_acronyms_aggressive: aggressive, + } + } +} + +impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); fn correct_ident(ident: &str) -> String { let ident = ident.chars().rev().collect::<String>(); @@ -56,11 +73,18 @@ fn correct_ident(ident: &str) -> String { ident } -fn check_ident(cx: &EarlyContext<'_>, ident: &Ident) { +fn check_ident(cx: &EarlyContext<'_>, ident: &Ident, be_aggressive: bool) { let span = ident.span; let ident = &ident.as_str(); let corrected = correct_ident(ident); - if ident != &corrected { + // warn if we have pure-uppercase idents + // assume that two-letter words are some kind of valid abbreviation like FP for false positive + // (and don't warn) + if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2) + // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive + // upper-case-acronyms-aggressive config option enabled + || (be_aggressive && ident != &corrected) + { span_lint_and_sugg( cx, UPPER_CASE_ACRONYMS, @@ -82,12 +106,12 @@ impl EarlyLintPass for UpperCaseAcronyms { ItemKind::TyAlias(..) | ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Trait(..) ); then { - check_ident(cx, &it.ident); + check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive); } } } fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &Variant) { - check_ident(cx, &v.ident); + check_ident(cx, &v.ident, self.upper_case_acronyms_aggressive); } } diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs index f2ff8c959c2..be7b9e9ff2d 100644 --- a/src/tools/clippy/clippy_lints/src/use_self.rs +++ b/src/tools/clippy/clippy_lints/src/use_self.rs @@ -1,24 +1,24 @@ +use crate::utils::{in_macro, meets_msrv, snippet_opt, span_lint_and_sugg}; use if_chain::if_chain; + use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::intravisit::{walk_item, walk_path, walk_ty, NestedVisitorMap, Visitor}; +use rustc_hir::def::DefKind; use rustc_hir::{ - def, FnDecl, FnRetTy, FnSig, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Path, PathSegment, QPath, - TyKind, + def, + def_id::LocalDefId, + intravisit::{walk_ty, NestedVisitorMap, Visitor}, + Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Node, Path, PathSegment, + QPath, TyKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; -use rustc_middle::ty::{DefIdTree, Ty}; +use rustc_middle::ty::{AssocKind, Ty, TyS}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::kw; +use rustc_span::{BytePos, Span}; use rustc_typeck::hir_ty_to_ty; -use crate::utils::{differing_macro_contexts, meets_msrv, span_lint_and_sugg}; - declare_clippy_lint! { /// **What it does:** Checks for unnecessary repetition of structure name when a /// replacement with `Self` is applicable. @@ -28,10 +28,11 @@ declare_clippy_lint! { /// feels inconsistent. /// /// **Known problems:** - /// - False positive when using associated types ([#2843](https://github.com/rust-lang/rust-clippy/issues/2843)) - /// - False positives in some situations when using generics ([#3410](https://github.com/rust-lang/rust-clippy/issues/3410)) + /// - Unaddressed false negative in fn bodies of trait implementations + /// - False positive with assotiated types in traits (#4140) /// /// **Example:** + /// /// ```rust /// struct Foo {} /// impl Foo { @@ -54,234 +55,409 @@ declare_clippy_lint! { "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 + } } } diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs index 7d7b35c2168..9139a0966c5 100644 --- a/src/tools/clippy/clippy_lints/src/utils/conf.rs +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -126,7 +126,7 @@ define_Conf! { "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", - "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", + "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", @@ -173,6 +173,8 @@ define_Conf! { (disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()), /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators. (unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true), + /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other + (upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false), /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest. (cargo_ignore_publish, "cargo_ignore_publish": bool, false), } diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs index d8c602fab22..0a347516c3a 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs @@ -4,7 +4,7 @@ use crate::utils::{ span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, }; use if_chain::if_chain; -use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId}; +use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId}; use rustc_ast::visit::FnKind; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::Applicability; @@ -301,17 +301,12 @@ declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); impl EarlyLintPass for ClippyLintsInternal { fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) { - if let Some(utils) = krate - .module - .items - .iter() - .find(|item| item.ident.name.as_str() == "utils") - { - if let ItemKind::Mod(ref utils_mod) = utils.kind { - if let Some(paths) = utils_mod.items.iter().find(|item| item.ident.name.as_str() == "paths") { - if let ItemKind::Mod(ref paths_mod) = paths.kind { + if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") { + if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind { + if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") { + if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind { let mut last_name: Option<SymbolStr> = None; - for item in &*paths_mod.items { + for item in items { let name = item.ident.as_str(); if let Some(ref last_name) = last_name { if **last_name > *name { @@ -343,7 +338,7 @@ impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]); impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if !run_lints(cx, &[DEFAULT_LINT], item.hir_id) { + if !run_lints(cx, &[DEFAULT_LINT], item.hir_id()) { return; } @@ -393,7 +388,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { .find(|iiref| iiref.ident.as_str() == "get_lints") .expect("LintPass needs to implement get_lints") .id - .hir_id, + .hir_id(), ); collector.visit_expr(&cx.tcx.hir().body(body_id).value); } @@ -861,7 +856,7 @@ declare_lint_pass!(InvalidPaths => [INVALID_PATHS]); impl<'tcx> LateLintPass<'tcx> for InvalidPaths { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - let local_def_id = &cx.tcx.parent_module(item.hir_id); + let local_def_id = &cx.tcx.parent_module(item.hir_id()); let mod_name = &cx.tcx.item_name(local_def_id.to_def_id()); if_chain! { if mod_name.as_str() == "paths"; diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs index fafa1400156..be9a07f8d7c 100644 --- a/src/tools/clippy/clippy_lints/src/utils/mod.rs +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -1,1837 +1,7 @@ -#[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::*; diff --git a/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs b/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs index e632a7e57ee..8d111f98add 100644 --- a/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs +++ b/src/tools/clippy/clippy_lints/src/vec_init_then_push.rs @@ -1,12 +1,14 @@ -use crate::utils::{is_type_diagnostic_item, match_def_path, paths, snippet, span_lint_and_sugg}; +use crate::utils::{ + is_type_diagnostic_item, match_def_path, path_to_local, path_to_local_id, paths, snippet, span_lint_and_sugg, +}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Local, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{symbol::sym, Span, Symbol}; +use rustc_span::{symbol::sym, Span}; use std::convert::TryInto; declare_clippy_lint! { @@ -45,8 +47,8 @@ enum VecInitKind { WithCapacity(u64), } struct VecPushSearcher { + local_id: HirId, init: VecInitKind, - name: Symbol, lhs_is_local: bool, lhs_span: Span, err_span: Span, @@ -81,17 +83,20 @@ impl VecPushSearcher { } impl LateLintPass<'_> for VecInitThenPush { - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { self.searcher = None; + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { if_chain! { if !in_external_macro(cx.sess(), local.span); if let Some(init) = local.init; - if let PatKind::Binding(BindingAnnotation::Mutable, _, ident, None) = local.pat.kind; + if let PatKind::Binding(BindingAnnotation::Mutable, id, _, None) = local.pat.kind; if let Some(init_kind) = get_vec_init_kind(cx, init); then { self.searcher = Some(VecPushSearcher { + local_id: id, init: init_kind, - name: ident.name, lhs_is_local: true, lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)), err_span: local.span, @@ -106,13 +111,12 @@ impl LateLintPass<'_> for VecInitThenPush { if_chain! { if !in_external_macro(cx.sess(), expr.span); if let ExprKind::Assign(left, right, _) = expr.kind; - if let ExprKind::Path(QPath::Resolved(_, path)) = left.kind; - if let Some(name) = path.segments.get(0); + if let Some(id) = path_to_local(left); if let Some(init_kind) = get_vec_init_kind(cx, right); then { self.searcher = Some(VecPushSearcher { + local_id: id, init: init_kind, - name: name.ident.name, lhs_is_local: false, lhs_span: left.span, err_span: expr.span, @@ -128,10 +132,8 @@ impl LateLintPass<'_> for VecInitThenPush { if_chain! { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind; if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind; + if path_to_local_id(self_arg, searcher.local_id); if path.ident.name.as_str() == "push"; - if let ExprKind::Path(QPath::Resolved(_, self_path)) = self_arg.kind; - if let [self_name] = self_path.segments; - if self_name.ident.name == searcher.name; then { self.searcher = Some(VecPushSearcher { found: searcher.found + 1, diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index 978a232bcfb..553e6b000eb 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -149,7 +149,6 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// /// // Bad /// writeln!(buf, ""); /// @@ -176,7 +175,6 @@ declare_clippy_lint! { /// # use std::fmt::Write; /// # let mut buf = String::new(); /// # let name = "World"; - /// /// // Bad /// write!(buf, "Hello {}!\n", name); /// @@ -202,7 +200,6 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// /// // Bad /// writeln!(buf, "{}", "foo"); /// diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml new file mode 100644 index 00000000000..9c01badb04c --- /dev/null +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "clippy_utils" +version = "0.1.52" +authors = ["The Rust Clippy Developers"] +edition = "2018" +publish = false + +[dependencies] +if_chain = "1.0.0" +itertools = "0.9" +regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } +smallvec = { version = "1", features = ["union"] } +toml = "0.5.3" +unicode-normalization = "0.1" +rustc-semver="1.1.0" + +[features] +internal-lints = [] diff --git a/src/tools/clippy/clippy_lints/src/utils/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index 9ff7ef7cc3b..9ef1557ec06 100644 --- a/src/tools/clippy/clippy_lints/src/utils/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -4,7 +4,7 @@ #![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] -use crate::utils::{both, over}; +use crate::{both, over}; use rustc_ast::ptr::P; use rustc_ast::{self as ast, *}; use rustc_span::symbol::Ident; @@ -229,23 +229,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(lt, lm, le), Static(rt, rm, re)) => { - lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re) - } - (Const(ld, lt, le), Const(rd, rt, re)) => { - eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re) - } + (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), (Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => { - eq_defaultness(*ld, *rd) - && eq_fn_sig(lf, rf) - && eq_generics(lg, rg) - && both(lb, rb, |l, r| eq_block(l, r)) - } - (Mod(lu, lmk), Mod(ru, rmk)) => lu == ru && match (lmk, rmk) { - (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => - linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind)), - (ModKind::Unloaded, ModKind::Unloaded) => true, - _ => false, + eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) + }, + (Mod(lu, lmk), Mod(ru, rmk)) => { + lu == ru + && match (lmk, rmk) { + (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => { + linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind)) + }, + (ModKind::Unloaded, ModKind::Unloaded) => true, + _ => false, + } }, (ForeignMod(l), ForeignMod(r)) => { both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r)) @@ -311,15 +308,10 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { use ForeignItemKind::*; match (l, r) { - (Static(lt, lm, le), Static(rt, rm, re)) => { - lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re) - } + (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), (Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => { - eq_defaultness(*ld, *rd) - && eq_fn_sig(lf, rf) - && eq_generics(lg, rg) - && both(lb, rb, |l, r| eq_block(l, r)) - } + eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) + }, (TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => { eq_defaultness(*ld, *rd) && eq_generics(lg, rg) diff --git a/src/tools/clippy/clippy_lints/src/utils/ast_utils/ident_iter.rs b/src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs index eefcbabd835..eefcbabd835 100644 --- a/src/tools/clippy/clippy_lints/src/utils/ast_utils/ident_iter.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs index 8d28421d70d..8d28421d70d 100644 --- a/src/tools/clippy/clippy_lints/src/utils/attrs.rs +++ b/src/tools/clippy/clippy_utils/src/attrs.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/camel_case.rs b/src/tools/clippy/clippy_utils/src/camel_case.rs index 4192a26d3c8..ba1c01ebc9f 100644 --- a/src/tools/clippy/clippy_lints/src/utils/camel_case.rs +++ b/src/tools/clippy/clippy_utils/src/camel_case.rs @@ -55,6 +55,8 @@ pub fn from(s: &str) -> usize { } } else if c.is_lowercase() { down = true; + } else if c.is_uppercase() { + last_i = i; } else { return last_i; } @@ -70,12 +72,16 @@ mod 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] diff --git a/src/tools/clippy/clippy_lints/src/utils/comparisons.rs b/src/tools/clippy/clippy_utils/src/comparisons.rs index 7a18d5e818f..7a18d5e818f 100644 --- a/src/tools/clippy/clippy_lints/src/utils/comparisons.rs +++ b/src/tools/clippy/clippy_utils/src/comparisons.rs diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs new file mode 100644 index 00000000000..802c01055a6 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -0,0 +1,574 @@ +#![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, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs index 269be217c2d..269be217c2d 100644 --- a/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs index 2f157c5030f..52a33e9b170 100644 --- a/src/tools/clippy/clippy_lints/src/utils/eager_or_lazy.rs +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -9,7 +9,7 @@ //! - or-fun-call //! - option-if-let-else -use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths}; +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; diff --git a/src/tools/clippy/clippy_lints/src/utils/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs index 1cf1aa363d5..be22df7109a 100644 --- a/src/tools/clippy/clippy_lints/src/utils/higher.rs +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -3,7 +3,7 @@ #![deny(clippy::missing_docs_in_private_items)] -use crate::utils::{is_expn_of, match_def_path, paths}; +use crate::{is_expn_of, match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast; use rustc_hir as hir; diff --git a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index c5870dc5124..81be9254cbe 100644 --- a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -1,10 +1,12 @@ use crate::consts::{constant_context, constant_simple}; -use crate::utils::differing_macro_contexts; +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, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, + GenericArg, GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding, }; use rustc_lint::LateContext; @@ -24,7 +26,7 @@ pub struct SpanlessEq<'a, 'tcx> { 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>>, + expr_fallback: Option<Box<dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a>>, } impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { @@ -45,15 +47,54 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { } } - pub fn expr_fallback(self, expr_fallback: impl Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self { + pub fn expr_fallback(self, expr_fallback: impl FnMut(&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 { + /// 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) @@ -68,21 +109,21 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { } /// Checks whether two blocks are the same. - pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { + 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) { + 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.maybe_typeck_results { + if let Some(typeck_results) = self.inner.maybe_typeck_results { if let (Some(l), Some(r)) = ( - constant_simple(self.cx, typeck_results, left), - constant_simple(self.cx, typeck_results, right), + constant_simple(self.inner.cx, typeck_results, left), + constant_simple(self.inner.cx, typeck_results, right), ) { if l == r { return true; @@ -98,10 +139,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { 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) + 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.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, 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)) => { @@ -116,7 +157,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { }, (&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) + 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)) => { @@ -139,19 +180,19 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { ls == rs && self.eq_expr(le, re) && over(la, ra, |l, r| { - self.eq_expr(&l.body, &r.body) + self.eq_pat(&l.pat, &r.pat) && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r)) - && self.eq_pat(&l.pat, &r.pat) + && self.eq_expr(&l.body, &r.body) }) }, (&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) + 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.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); + 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 }, @@ -168,7 +209,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { (&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)) + 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 { @@ -199,13 +240,13 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { left.name == right.name } - pub fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool { + 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 { + 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, ..)) => { @@ -214,8 +255,12 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { (&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::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), @@ -251,8 +296,11 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { } 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)) + 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 { @@ -279,28 +327,19 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { 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 { + 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 { + 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 + 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) @@ -667,10 +706,15 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { // 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_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); + } + }, } } diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs new file mode 100644 index 00000000000..94b7339c7eb --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -0,0 +1,1883 @@ +#![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"]); + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs index d02603d7702..d02603d7702 100644 --- a/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs +++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index e6178679647..e6178679647 100644 --- a/src/tools/clippy/clippy_lints/src/utils/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/ptr.rs b/src/tools/clippy/clippy_utils/src/ptr.rs index b330f3d890e..baeff08e02c 100644 --- a/src/tools/clippy/clippy_lints/src/utils/ptr.rs +++ b/src/tools/clippy/clippy_utils/src/ptr.rs @@ -1,4 +1,4 @@ -use crate::utils::{get_pat_name, match_var, snippet}; +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; diff --git a/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index a482017afeb..a482017afeb 100644 --- a/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 03678db575f..d4f6f4281d3 100644 --- a/src/tools/clippy/clippy_lints/src/utils/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -1,7 +1,7 @@ //! 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 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; diff --git a/src/tools/clippy/clippy_lints/src/utils/sym_helper.rs b/src/tools/clippy/clippy_utils/src/sym_helper.rs index f47dc80ebad..f47dc80ebad 100644 --- a/src/tools/clippy/clippy_lints/src/utils/sym_helper.rs +++ b/src/tools/clippy/clippy_utils/src/sym_helper.rs diff --git a/src/tools/clippy/clippy_lints/src/utils/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index 7c7580a2c66..d577827dcf3 100644 --- a/src/tools/clippy/clippy_lints/src/utils/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -1,4 +1,4 @@ -use crate::utils; +use crate as utils; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::Res; diff --git a/src/tools/clippy/clippy_lints/src/utils/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 085c1f9c0cb..5a8c629e333 100644 --- a/src/tools/clippy/clippy_lints/src/utils/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -1,4 +1,4 @@ -use crate::utils::path_to_local_id; +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}; diff --git a/src/tools/clippy/doc/adding_lints.md b/src/tools/clippy/doc/adding_lints.md index 8fd1dea9aee..f62c2d29c70 100644 --- a/src/tools/clippy/doc/adding_lints.md +++ b/src/tools/clippy/doc/adding_lints.md @@ -108,6 +108,9 @@ should only commit files changed by `cargo dev bless` for the 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 @@ -289,7 +292,7 @@ the next section. Let's worry about the details later and emit our lint for 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 @@ -318,7 +321,7 @@ When code or an identifier must appear in a message or label, it should be 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 @@ -534,7 +537,7 @@ directory. Adding a configuration to a lint can be useful for thresholds or to c 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> @@ -633,7 +636,7 @@ documentation currently. This is unfortunate, but in most cases you can probably 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 diff --git a/src/tools/clippy/doc/common_tools_writing_lints.md b/src/tools/clippy/doc/common_tools_writing_lints.md index d56079a4ab7..abac1227b4f 100644 --- a/src/tools/clippy/doc/common_tools_writing_lints.md +++ b/src/tools/clippy/doc/common_tools_writing_lints.md @@ -78,7 +78,7 @@ impl LateLintPass<'_> for MyStructLint { 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<'_>) { @@ -112,7 +112,7 @@ We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`] 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<'_>) { @@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { # 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 @@ -199,4 +199,5 @@ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); [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 diff --git a/src/tools/clippy/lintcheck-logs/lintcheck_crates_logs.txt b/src/tools/clippy/lintcheck-logs/lintcheck_crates_logs.txt index 3bc7758033b..c23dd926f62 100644 --- a/src/tools/clippy/lintcheck-logs/lintcheck_crates_logs.txt +++ b/src/tools/clippy/lintcheck-logs/lintcheck_crates_logs.txt @@ -1,6 +1,5 @@ -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`" @@ -22,6 +21,7 @@ cargo-0.49.0/src/bin/cargo/commands/check.rs:1:5 clippy::wildcard_imports "usage 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" @@ -99,10 +99,10 @@ cargo-0.49.0/src/cargo/core/compiler/build_context/target_info.rs:411:9 clippy:: 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`" @@ -151,7 +151,6 @@ cargo-0.49.0/src/cargo/core/compiler/context/mod.rs:361:5 clippy::must_use_candi 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" @@ -173,14 +172,13 @@ cargo-0.49.0/src/cargo/core/compiler/custom_build.rs:481:5 clippy::missing_error 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`" @@ -236,7 +234,7 @@ cargo-0.49.0/src/cargo/core/compiler/job_queue.rs:93:24 clippy::doc_markdown "yo 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)" @@ -245,13 +243,13 @@ cargo-0.49.0/src/cargo/core/compiler/mod.rs:201:25 clippy::single_match_else "yo 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" @@ -290,7 +288,6 @@ cargo-0.49.0/src/cargo/core/compiler/unit_graph.rs:65:1 clippy::missing_errors_d 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" @@ -300,13 +297,10 @@ cargo-0.49.0/src/cargo/core/dependency.rs:274:5 clippy::must_use_candidate "this 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" @@ -452,12 +446,10 @@ cargo-0.49.0/src/cargo/core/package.rs:287:1 clippy::module_name_repetitions "it 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" @@ -490,13 +482,12 @@ cargo-0.49.0/src/cargo/core/package_id_spec.rs:51:5 clippy::missing_errors_doc " 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" @@ -522,9 +513,7 @@ cargo-0.49.0/src/cargo/core/registry.rs:19:5 clippy::missing_errors_doc "docs fo 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`" @@ -538,7 +527,6 @@ cargo-0.49.0/src/cargo/core/resolver/context.rs:274:53 clippy::redundant_closure 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" @@ -596,7 +584,6 @@ cargo-0.49.0/src/cargo/core/resolver/resolve.rs:255:5 clippy::must_use_candidate 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" @@ -639,6 +626,7 @@ cargo-0.49.0/src/cargo/core/shell.rs:314:5 clippy::must_use_candidate "this meth 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" @@ -682,7 +670,6 @@ cargo-0.49.0/src/cargo/core/source/source_id.rs:241:5 clippy::must_use_candidate 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" @@ -780,7 +767,6 @@ cargo-0.49.0/src/cargo/core/workspace.rs:849:5 clippy::missing_panics_doc "docs 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" @@ -808,7 +794,6 @@ cargo-0.49.0/src/cargo/ops/cargo_compile.rs:249:1 clippy::missing_errors_doc "do 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" @@ -824,7 +809,6 @@ cargo-0.49.0/src/cargo/ops/cargo_compile.rs:612:21 clippy::doc_markdown "you sho 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" @@ -879,6 +863,7 @@ cargo-0.49.0/src/cargo/ops/cargo_package.rs:394:5 clippy::items_after_statements 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" @@ -893,7 +878,6 @@ cargo-0.49.0/src/cargo/ops/cargo_run.rs:37:16 clippy::redundant_else "redundant 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" @@ -904,7 +888,7 @@ cargo-0.49.0/src/cargo/ops/common_for_install_and_uninstall.rs:233:21 clippy::si 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" @@ -967,7 +951,6 @@ cargo-0.49.0/src/cargo/ops/registry.rs:794:16 clippy::single_match_else "you see 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`" @@ -997,6 +980,7 @@ cargo-0.49.0/src/cargo/ops/tree/mod.rs:21:1 clippy::struct_excessive_bools "more 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" @@ -1017,7 +1001,6 @@ cargo-0.49.0/src/cargo/sources/directory.rs:14:1 clippy::module_name_repetitions 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" @@ -1032,7 +1015,6 @@ cargo-0.49.0/src/cargo/sources/git/utils.rs:184:5 clippy::missing_errors_doc "do 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" @@ -1062,7 +1044,6 @@ cargo-0.49.0/src/cargo/sources/path.rs:429:5 clippy::missing_errors_doc "docs fo 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" @@ -1142,7 +1123,6 @@ cargo-0.49.0/src/cargo/util/config/mod.rs:100:71 clippy::doc_markdown "you shoul 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" @@ -1157,7 +1137,6 @@ cargo-0.49.0/src/cargo/util/config/mod.rs:1225:5 clippy::missing_errors_doc "doc 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" @@ -1224,7 +1203,6 @@ cargo-0.49.0/src/cargo/util/dependency_queue.rs:151:5 clippy::must_use_candidate 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" @@ -1238,7 +1216,6 @@ cargo-0.49.0/src/cargo/util/errors.rs:143:5 clippy::must_use_candidate "this met 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" @@ -1263,12 +1240,11 @@ cargo-0.49.0/src/cargo/util/flock.rs:335:23 clippy::cast_possible_truncation "ca 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" @@ -1331,8 +1307,8 @@ cargo-0.49.0/src/cargo/util/paths.rs:445:1 clippy::missing_errors_doc "docs for 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" @@ -1346,6 +1322,7 @@ cargo-0.49.0/src/cargo/util/process_builder.rs:185:5 clippy::missing_errors_doc 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" @@ -1386,6 +1363,7 @@ cargo-0.49.0/src/cargo/util/rustc.rs:115:5 clippy::doc_markdown "you should put 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" @@ -1451,6 +1429,7 @@ iron-0.6.1/src/iron.rs:133:5 clippy::missing_errors_doc "docs for function retur 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" @@ -1991,18 +1970,15 @@ log-0.4.11/src/lib.rs:1118:5 clippy::must_use_candidate "this method could have 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)" @@ -2112,7 +2088,6 @@ puffin-02dd4a3/puffin/src/data.rs:137:24 clippy::match_same_arms "this `match` h 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" @@ -2146,7 +2121,6 @@ quote-1.0.7/src/ext.rs:10:1 clippy::module_name_repetitions "item name ends with 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" @@ -2183,7 +2157,6 @@ rand-0.7.3/src/distributions/binomial.rs:233:32 clippy::cast_precision_loss "cas 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" @@ -2194,25 +2167,18 @@ rand-0.7.3/src/distributions/binomial.rs:81:21 clippy::cast_precision_loss "cast 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" @@ -2226,7 +2192,6 @@ rand-0.7.3/src/distributions/normal.rs:47:25 clippy::unseparated_literal_suffix 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" @@ -2316,6 +2281,7 @@ rand-0.7.3/src/rngs/adapter/reseeding.rs:112:5 clippy::inline_always "you have d 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" @@ -2344,7 +2310,6 @@ rand-0.7.3/src/seq/index.rs:139:13 clippy::enum_glob_use "usage of wildcard impo 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)" @@ -2369,14 +2334,12 @@ rand-0.7.3/src/seq/mod.rs:45:4 clippy::needless_doctest_main "needless `fn main` 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" @@ -2386,8 +2349,6 @@ rand_core-0.6.0/src/block.rs:68:1 clippy::module_name_repetitions "item name sta 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" @@ -2433,6 +2394,7 @@ rayon-1.5.0/src/iter/chain.rs:58:17 clippy::shadow_unrelated "`b` is being shado 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" @@ -2699,7 +2661,6 @@ regex-1.3.2/src/compile.rs:1040:38 clippy::cast_possible_truncation "casting `u1 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`" @@ -3114,10 +3075,6 @@ regex-1.3.2/src/utf8.rs:85:19 clippy::cast_lossless "casting `u8` to `u32` may b 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" @@ -3171,6 +3128,7 @@ ripgrep-12.1.1/crates/core/args.rs:1524:5 clippy::unnecessary_wraps "this functi 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" @@ -3189,6 +3147,8 @@ ripgrep-12.1.1/crates/core/config.rs:58:6 clippy::type_complexity "very complex 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" @@ -3216,19 +3176,18 @@ ripgrep-12.1.1/crates/core/search.rs:533:36 clippy::cast_lossless "casting `u32` 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" @@ -3248,7 +3207,6 @@ xsv-0.13.0/src/cmd/cat.rs:7:16 clippy::redundant_static_lifetimes "statics have 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`" @@ -3324,7 +3282,7 @@ xsv-0.13.0/src/cmd/sort.rs:48:9 clippy::similar_names "binding's name is too sim 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" @@ -3438,14 +3396,12 @@ clippy::redundant_slicing 1 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 @@ -3463,10 +3419,10 @@ clippy::write_with_newline 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 @@ -3488,11 +3444,11 @@ clippy::range_plus_one 7 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 @@ -3511,6 +3467,7 @@ clippy::struct_excessive_bools 20 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 @@ -3519,11 +3476,11 @@ clippy::enum_glob_use 40 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 @@ -3533,3 +3490,4 @@ clippy::doc_markdown 178 clippy::missing_errors_doc 343 clippy::unreadable_literal 365 clippy::must_use_candidate 565 +ICEs: diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain index e73da595e19..865043b46d1 100644 --- a/src/tools/clippy/rust-toolchain +++ b/src/tools/clippy/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2021-02-11" +channel = "nightly-2021-02-25" components = ["llvm-tools-preview", "rustc-dev", "rust-src"] diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index c0b40add109..0594663786c 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -66,8 +66,8 @@ fn third_party_crates() -> String { 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") { @@ -167,7 +167,7 @@ fn run_ui_toml(config: &mut compiletest::Config) { 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; @@ -181,9 +181,10 @@ fn run_ui_cargo(config: &mut compiletest::Config) { // 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)? { @@ -243,8 +244,7 @@ fn run_ui_cargo(config: &mut compiletest::Config) { 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); diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 7ccd0b54845..d83080b69f5 100644 --- a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -1,4 +1,4 @@ -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 diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml new file mode 100644 index 00000000000..cc94ec53e13 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/clippy.toml @@ -0,0 +1 @@ +upper-case-acronyms-aggressive = true diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs new file mode 100644 index 00000000000..fdf8905f812 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.rs @@ -0,0 +1,22 @@ +#![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() {} diff --git a/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr new file mode 100644 index 00000000000..1cc59dc45f2 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/upper_case_acronyms_aggressive/upper_case_acronyms.stderr @@ -0,0 +1,70 @@ +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 + diff --git a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs index 24891682d36..aebeaf34679 100644 --- a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs +++ b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs @@ -41,3 +41,15 @@ pub fn derive_foo(_input: TokenStream) -> TokenStream { } } } + +#[proc_macro_derive(StructAUseSelf)] +pub fn derive_use_self(_input: TokenStream) -> proc_macro::TokenStream { + quote! { + struct A; + impl A { + fn new() -> A { + A + } + } + } +} diff --git a/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs index acbabfa20d7..2856943b9be 100644 --- a/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs +++ b/src/tools/clippy/tests/ui/blocks_in_if_conditions_closure.rs @@ -44,4 +44,13 @@ fn macro_in_closure() { } } -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!"); + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.fixed b/src/tools/clippy/tests/ui/collapsible_else_if.fixed index fa4bc30e933..c69a46f0a77 100644 --- a/src/tools/clippy/tests/ui/collapsible_else_if.fixed +++ b/src/tools/clippy/tests/ui/collapsible_else_if.fixed @@ -65,4 +65,13 @@ fn main() { else { println!("!") } + + if x == "hello" { + print!("Hello "); + } else { + #[cfg(not(roflol))] + if y == "world" { + println!("world!") + } + } } diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.rs b/src/tools/clippy/tests/ui/collapsible_else_if.rs index bf6c1d1f894..1359c7eb627 100644 --- a/src/tools/clippy/tests/ui/collapsible_else_if.rs +++ b/src/tools/clippy/tests/ui/collapsible_else_if.rs @@ -79,4 +79,13 @@ fn main() { println!("!") } } + + if x == "hello" { + print!("Hello "); + } else { + #[cfg(not(roflol))] + if y == "world" { + println!("world!") + } + } } diff --git a/src/tools/clippy/tests/ui/collapsible_if.fixed b/src/tools/clippy/tests/ui/collapsible_if.fixed index efd4187947b..e4c088bf6f0 100644 --- a/src/tools/clippy/tests/ui/collapsible_if.fixed +++ b/src/tools/clippy/tests/ui/collapsible_if.fixed @@ -138,4 +138,11 @@ fn main() { // Fix #5962 if matches!(true, true) && matches!(true, true) {} + + if true { + #[cfg(not(teehee))] + if true { + println!("Hello world!"); + } + } } diff --git a/src/tools/clippy/tests/ui/collapsible_if.rs b/src/tools/clippy/tests/ui/collapsible_if.rs index 657f32d38a3..d6cf01c8319 100644 --- a/src/tools/clippy/tests/ui/collapsible_if.rs +++ b/src/tools/clippy/tests/ui/collapsible_if.rs @@ -154,4 +154,11 @@ fn main() { if matches!(true, true) { if matches!(true, true) {} } + + if true { + #[cfg(not(teehee))] + if true { + println!("Hello world!"); + } + } } diff --git a/src/tools/clippy/tests/ui/collapsible_match.rs b/src/tools/clippy/tests/ui/collapsible_match.rs index 3294da7e814..55467cf4229 100644 --- a/src/tools/clippy/tests/ui/collapsible_match.rs +++ b/src/tools/clippy/tests/ui/collapsible_match.rs @@ -232,6 +232,14 @@ fn negative_cases(res_opt: Result<Option<u32>, String>, res_res: Result<Result<u }; } } + 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 { diff --git a/src/tools/clippy/tests/ui/collapsible_match.stderr b/src/tools/clippy/tests/ui/collapsible_match.stderr index 63ac6a1613d..77978884900 100644 --- a/src/tools/clippy/tests/ui/collapsible_match.stderr +++ b/src/tools/clippy/tests/ui/collapsible_match.stderr @@ -1,4 +1,4 @@ -error: Unnecessary nested match +error: unnecessary nested match --> $DIR/collapsible_match.rs:7:20 | LL | Ok(val) => match val { @@ -9,15 +9,15 @@ LL | | }, | |_________^ | = 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 { @@ -27,15 +27,15 @@ LL | | _ => return, 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 { @@ -43,15 +43,15 @@ LL | | take(n); 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 { @@ -61,15 +61,15 @@ LL | | return; 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 { @@ -78,16 +78,16 @@ LL | | _ => (), 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 { @@ -95,15 +95,15 @@ LL | | take(n); 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 { @@ -112,16 +112,16 @@ LL | | _ => return, 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 { @@ -131,15 +131,15 @@ LL | | return; 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 { @@ -149,15 +149,15 @@ LL | | None => return, 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 { @@ -167,11 +167,11 @@ LL | | _ => return, 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 diff --git a/src/tools/clippy/tests/ui/collapsible_match2.stderr b/src/tools/clippy/tests/ui/collapsible_match2.stderr index b2eb457d173..c8a445ef369 100644 --- a/src/tools/clippy/tests/ui/collapsible_match2.stderr +++ b/src/tools/clippy/tests/ui/collapsible_match2.stderr @@ -1,4 +1,4 @@ -error: Unnecessary nested match +error: unnecessary nested match --> $DIR/collapsible_match2.rs:8:34 | LL | Ok(val) if make() => match val { @@ -9,15 +9,15 @@ LL | | }, | |_____________^ | = 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 { @@ -27,15 +27,15 @@ LL | | _ => return, 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 { @@ -48,16 +48,16 @@ LL | | }, 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 { @@ -67,15 +67,15 @@ LL | | _ => (), 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 { @@ -85,11 +85,11 @@ LL | | _ => (), 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 diff --git a/src/tools/clippy/tests/ui/crashes/ice-6179.rs b/src/tools/clippy/tests/ui/crashes/ice-6179.rs new file mode 100644 index 00000000000..f8c866a49aa --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-6179.rs @@ -0,0 +1,21 @@ +//! 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() {} diff --git a/src/tools/clippy/tests/ui/default_numeric_fallback.rs b/src/tools/clippy/tests/ui/default_numeric_fallback.rs new file mode 100644 index 00000000000..0b3758952ac --- /dev/null +++ b/src/tools/clippy/tests/ui/default_numeric_fallback.rs @@ -0,0 +1,135 @@ +#![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() {} diff --git a/src/tools/clippy/tests/ui/default_numeric_fallback.stderr b/src/tools/clippy/tests/ui/default_numeric_fallback.stderr new file mode 100644 index 00000000000..b31aa4ebcf8 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_numeric_fallback.stderr @@ -0,0 +1,148 @@ +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 + diff --git a/src/tools/clippy/tests/ui/doc.rs b/src/tools/clippy/tests/ui/doc.rs index e30970ed952..d2c666bd290 100644 --- a/src/tools/clippy/tests/ui/doc.rs +++ b/src/tools/clippy/tests/ui/doc.rs @@ -50,11 +50,23 @@ fn test_units() { } /// 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() { diff --git a/src/tools/clippy/tests/ui/doc.stderr b/src/tools/clippy/tests/ui/doc.stderr index e1c1aa85a60..7eab8a85f09 100644 --- a/src/tools/clippy/tests/ui/doc.stderr +++ b/src/tools/clippy/tests/ui/doc.stderr @@ -55,133 +55,133 @@ 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: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. | ^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/doc_panics.rs b/src/tools/clippy/tests/ui/doc_panics.rs index 7ef932f367b..3008c2d5b85 100644 --- a/src/tools/clippy/tests/ui/doc_panics.rs +++ b/src/tools/clippy/tests/ui/doc_panics.rs @@ -28,6 +28,15 @@ pub fn inner_body(opt: Option<u32>) { }); } +/// This needs to be documented +pub fn unreachable_and_panic() { + if true { + unreachable!() + } else { + panic!() + } +} + /// This is documented /// /// # Panics @@ -69,6 +78,19 @@ pub fn todo_documented() { 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"); @@ -93,3 +115,8 @@ fn inner_body_private(opt: Option<u32>) { } }); } + +/// This is okay because unreachable +pub fn unreachable() { + unreachable!("This function panics") +} diff --git a/src/tools/clippy/tests/ui/doc_panics.stderr b/src/tools/clippy/tests/ui/doc_panics.stderr index c0c4e9e4fa7..287148690d2 100644 --- a/src/tools/clippy/tests/ui/doc_panics.stderr +++ b/src/tools/clippy/tests/ui/doc_panics.stderr @@ -63,5 +63,24 @@ LL | panic!() | ^^^^^^^^ = 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 diff --git a/src/tools/clippy/tests/ui/enum_variants.rs b/src/tools/clippy/tests/ui/enum_variants.rs index 89d99dcf0c8..4fefc0b43f1 100644 --- a/src/tools/clippy/tests/ui/enum_variants.rs +++ b/src/tools/clippy/tests/ui/enum_variants.rs @@ -133,4 +133,17 @@ pub enum NetworkLayer { 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() {} diff --git a/src/tools/clippy/tests/ui/enum_variants.stderr b/src/tools/clippy/tests/ui/enum_variants.stderr index b1d481190ff..ab7fff4507a 100644 --- a/src/tools/clippy/tests/ui/enum_variants.stderr +++ b/src/tools/clippy/tests/ui/enum_variants.stderr @@ -97,5 +97,29 @@ LL | | } = 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 diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.rs b/src/tools/clippy/tests/ui/from_str_radix_10.rs new file mode 100644 index 00000000000..2f2ea04847a --- /dev/null +++ b/src/tools/clippy/tests/ui/from_str_radix_10.rs @@ -0,0 +1,52 @@ +#![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(()) +} diff --git a/src/tools/clippy/tests/ui/from_str_radix_10.stderr b/src/tools/clippy/tests/ui/from_str_radix_10.stderr new file mode 100644 index 00000000000..471bf52a9a7 --- /dev/null +++ b/src/tools/clippy/tests/ui/from_str_radix_10.stderr @@ -0,0 +1,52 @@ +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 + diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.rs b/src/tools/clippy/tests/ui/if_same_then_else2.rs index e83ce47e563..a2ff1b741ca 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else2.rs +++ b/src/tools/clippy/tests/ui/if_same_then_else2.rs @@ -12,7 +12,7 @@ fn if_same_then_else2() -> Result<&'static str, ()> { if true { for _ in &[42] { let foo: &Option<_> = &Some::<u8>(42); - if true { + if foo.is_some() { break; } else { continue; @@ -21,8 +21,8 @@ fn if_same_then_else2() -> Result<&'static str, ()> { } 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; diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.stderr b/src/tools/clippy/tests/ui/if_same_then_else2.stderr index f98e30fa376..454322d8aac 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else2.stderr +++ b/src/tools/clippy/tests/ui/if_same_then_else2.stderr @@ -5,7 +5,7 @@ LL | } else { | ____________^ 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 | | } @@ -19,7 +19,7 @@ LL | if true { | _____________^ LL | | for _ in &[42] { LL | | let foo: &Option<_> = &Some::<u8>(42); -LL | | if true { +LL | | if foo.is_some() { ... | LL | | } LL | | } else { diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed new file mode 100644 index 00000000000..8d9c3110035 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.fixed @@ -0,0 +1,61 @@ +// 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() {} diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs new file mode 100644 index 00000000000..63fac910501 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.rs @@ -0,0 +1,65 @@ +// 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() {} diff --git a/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr new file mode 100644 index 00000000000..d7abe44f254 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_struct_constructor.stderr @@ -0,0 +1,20 @@ +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 + diff --git a/src/tools/clippy/tests/ui/inherent_to_string.rs b/src/tools/clippy/tests/ui/inherent_to_string.rs index e6cf337d1bb..6e65fdbd04e 100644 --- a/src/tools/clippy/tests/ui/inherent_to_string.rs +++ b/src/tools/clippy/tests/ui/inherent_to_string.rs @@ -14,6 +14,7 @@ struct C; struct D; struct E; struct F; +struct G; impl A { // Should be detected; emit warning @@ -73,6 +74,13 @@ impl F { } } +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(); @@ -93,4 +101,7 @@ fn main() { let f = F; f.to_string(1); + + let g = G; + g.to_string::<1>(); } diff --git a/src/tools/clippy/tests/ui/inherent_to_string.stderr b/src/tools/clippy/tests/ui/inherent_to_string.stderr index 4f331f5bec9..f5fcc193b4d 100644 --- a/src/tools/clippy/tests/ui/inherent_to_string.stderr +++ b/src/tools/clippy/tests/ui/inherent_to_string.stderr @@ -1,5 +1,5 @@ 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() @@ -10,7 +10,7 @@ LL | | } = 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() diff --git a/src/tools/clippy/tests/ui/manual_map_option.fixed b/src/tools/clippy/tests/ui/manual_map_option.fixed new file mode 100644 index 00000000000..19350906758 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.fixed @@ -0,0 +1,70 @@ +// 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, + }; +} diff --git a/src/tools/clippy/tests/ui/manual_map_option.rs b/src/tools/clippy/tests/ui/manual_map_option.rs new file mode 100644 index 00000000000..8b8187db0a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.rs @@ -0,0 +1,122 @@ +// 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, + }; +} diff --git a/src/tools/clippy/tests/ui/manual_map_option.stderr b/src/tools/clippy/tests/ui/manual_map_option.stderr new file mode 100644 index 00000000000..210a30d05d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_map_option.stderr @@ -0,0 +1,158 @@ +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 + diff --git a/src/tools/clippy/tests/ui/result_unit_error.rs b/src/tools/clippy/tests/ui/result_unit_error.rs index 5e57c752b5a..a4ec803024e 100644 --- a/src/tools/clippy/tests/ui/result_unit_error.rs +++ b/src/tools/clippy/tests/ui/result_unit_error.rs @@ -1,6 +1,4 @@ -#![allow(clippy::unnecessary_wraps)] -#[warn(clippy::result_unit_err)] -#[allow(unused)] +#![warn(clippy::result_unit_err)] pub fn returns_unit_error() -> Result<u32, ()> { Err(()) @@ -36,4 +34,23 @@ impl UnitErrorHolder { } } +// 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() {} diff --git a/src/tools/clippy/tests/ui/result_unit_error.stderr b/src/tools/clippy/tests/ui/result_unit_error.stderr index 12901b354f9..41d8b0a7cb7 100644 --- a/src/tools/clippy/tests/ui/result_unit_error.stderr +++ b/src/tools/clippy/tests/ui/result_unit_error.stderr @@ -1,5 +1,5 @@ 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, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ 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, ()>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ 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, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,12 +24,20 @@ 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 diff --git a/src/tools/clippy/tests/ui/unnecessary_wraps.rs b/src/tools/clippy/tests/ui/unnecessary_wraps.rs index a4570098d71..a510263e67d 100644 --- a/src/tools/clippy/tests/ui/unnecessary_wraps.rs +++ b/src/tools/clippy/tests/ui/unnecessary_wraps.rs @@ -116,8 +116,53 @@ fn issue_6384(s: &str) -> Option<&str> { }) } +// 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); } diff --git a/src/tools/clippy/tests/ui/unnecessary_wraps.stderr b/src/tools/clippy/tests/ui/unnecessary_wraps.stderr index 410f054b8ef..9a861c61a46 100644 --- a/src/tools/clippy/tests/ui/unnecessary_wraps.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_wraps.stderr @@ -15,7 +15,7 @@ help: remove `Option` from the return type... | LL | fn func1(a: bool, b: bool) -> i32 { | ^^^ -help: ...and change the returning expressions +help: ...and then change returning expressions | LL | return 42; LL | } @@ -41,7 +41,7 @@ help: remove `Option` from the return type... | LL | fn func2(a: bool, b: bool) -> i32 { | ^^^ -help: ...and change the returning expressions +help: ...and then change returning expressions | LL | return 10; LL | } @@ -63,7 +63,7 @@ help: remove `Option` from the return type... | LL | fn func5() -> i32 { | ^^^ -help: ...and change the returning expressions +help: ...and then change returning expressions | LL | 1 | @@ -80,7 +80,7 @@ help: remove `Result` from the return type... | LL | fn func7() -> i32 { | ^^^ -help: ...and change the returning expressions +help: ...and then change returning expressions | LL | 1 | @@ -97,10 +97,62 @@ help: remove `Option` from the return type... | 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 diff --git a/src/tools/clippy/tests/ui/upper_case_acronyms.rs b/src/tools/clippy/tests/ui/upper_case_acronyms.rs index af0b5776348..fdf8905f812 100644 --- a/src/tools/clippy/tests/ui/upper_case_acronyms.rs +++ b/src/tools/clippy/tests/ui/upper_case_acronyms.rs @@ -1,11 +1,11 @@ #![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, @@ -16,6 +16,7 @@ enum Flags { 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() {} diff --git a/src/tools/clippy/tests/ui/upper_case_acronyms.stderr b/src/tools/clippy/tests/ui/upper_case_acronyms.stderr index 2065fe10bb1..bbe38991e52 100644 --- a/src/tools/clippy/tests/ui/upper_case_acronyms.stderr +++ b/src/tools/clippy/tests/ui/upper_case_acronyms.stderr @@ -1,22 +1,10 @@ -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 @@ -60,11 +48,5 @@ error: name `FIN` contains a capitalized acronym 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 diff --git a/src/tools/clippy/tests/ui/use_self.fixed b/src/tools/clippy/tests/ui/use_self.fixed index bb2012441d9..95e7bc75431 100644 --- a/src/tools/clippy/tests/ui/use_self.fixed +++ b/src/tools/clippy/tests/ui/use_self.fixed @@ -1,9 +1,13 @@ // 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() {} @@ -71,13 +75,12 @@ mod lifetimes { 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] } } @@ -110,8 +113,8 @@ mod tuple_structs { mod macros { macro_rules! use_self_expand { () => { - fn new() -> Self { - Self {} + fn new() -> Foo { + Foo {} } }; } @@ -119,8 +122,11 @@ mod macros { 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 { @@ -177,11 +183,22 @@ mod issue3410 { 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() + } } } @@ -252,3 +269,192 @@ mod paths_created_by_lowering { } } } + +// 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 {}) + } + } +} diff --git a/src/tools/clippy/tests/ui/use_self.rs b/src/tools/clippy/tests/ui/use_self.rs index ddfd2beba31..75424f34159 100644 --- a/src/tools/clippy/tests/ui/use_self.rs +++ b/src/tools/clippy/tests/ui/use_self.rs @@ -1,9 +1,13 @@ // 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() {} @@ -71,13 +75,12 @@ mod lifetimes { 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] } } @@ -87,7 +90,7 @@ mod existential { struct Foo; impl Foo { - fn bad(foos: &[Self]) -> impl Iterator<Item = &Foo> { + fn bad(foos: &[Foo]) -> impl Iterator<Item = &Foo> { foos.iter() } @@ -119,8 +122,11 @@ mod macros { 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 { @@ -177,11 +183,22 @@ mod issue3410 { 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() + } } } @@ -252,3 +269,192 @@ mod paths_created_by_lowering { } } } + +// 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 {}) + } + } +} diff --git a/src/tools/clippy/tests/ui/use_self.stderr b/src/tools/clippy/tests/ui/use_self.stderr index 80e1bfc75e8..37dfef7cfe0 100644 --- a/src/tools/clippy/tests/ui/use_self.stderr +++ b/src/tools/clippy/tests/ui/use_self.stderr @@ -1,5 +1,5 @@ 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` @@ -7,158 +7,172 @@ LL | fn new() -> Foo { = 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 diff --git a/src/tools/clippy/tests/ui/use_self_trait.fixed b/src/tools/clippy/tests/ui/use_self_trait.fixed index 1582ae114bf..9bcd692fb35 100644 --- a/src/tools/clippy/tests/ui/use_self_trait.fixed +++ b/src/tools/clippy/tests/ui/use_self_trait.fixed @@ -47,7 +47,8 @@ impl Mul for Bad { impl Clone for Bad { fn clone(&self) -> Self { - Self + // FIXME: applicable here + Bad } } diff --git a/src/tools/clippy/tests/ui/use_self_trait.rs b/src/tools/clippy/tests/ui/use_self_trait.rs index 70667b9797e..de305d40f33 100644 --- a/src/tools/clippy/tests/ui/use_self_trait.rs +++ b/src/tools/clippy/tests/ui/use_self_trait.rs @@ -47,6 +47,7 @@ impl Mul for Bad { impl Clone for Bad { fn clone(&self) -> Self { + // FIXME: applicable here Bad } } diff --git a/src/tools/clippy/tests/ui/use_self_trait.stderr b/src/tools/clippy/tests/ui/use_self_trait.stderr index 4f2506cc119..55af3ff2a93 100644 --- a/src/tools/clippy/tests/ui/use_self_trait.stderr +++ b/src/tools/clippy/tests/ui/use_self_trait.stderr @@ -84,11 +84,5 @@ error: unnecessary structure name repetition 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 diff --git a/src/tools/clippy/tests/ui/vec_init_then_push.rs b/src/tools/clippy/tests/ui/vec_init_then_push.rs index 642ce504009..5099aad83bc 100644 --- a/src/tools/clippy/tests/ui/vec_init_then_push.rs +++ b/src/tools/clippy/tests/ui/vec_init_then_push.rs @@ -12,10 +12,35 @@ fn main() { 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; + }, + } + } } diff --git a/src/tools/clippy/tests/ui/vec_init_then_push.stderr b/src/tools/clippy/tests/ui/vec_init_then_push.stderr index 819ed47d099..9ec3e10e624 100644 --- a/src/tools/clippy/tests/ui/vec_init_then_push.stderr +++ b/src/tools/clippy/tests/ui/vec_init_then_push.stderr @@ -24,7 +24,7 @@ LL | | cap_err.push(2); | |____________________^ 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); diff --git a/src/tools/clippy/tests/versioncheck.rs b/src/tools/clippy/tests/versioncheck.rs index bc5ed0816cc..922a8207cea 100644 --- a/src/tools/clippy/tests/versioncheck.rs +++ b/src/tools/clippy/tests/versioncheck.rs @@ -2,21 +2,30 @@ 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; + } } } } diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 5f263ea87db..b32a6f08638 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -195,11 +195,17 @@ pub fn parse_config(args: Vec<String>) -> Config { 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")), @@ -218,7 +224,7 @@ pub fn parse_config(args: Vec<String>) -> Config { 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, diff --git a/src/tools/tidy/src/style.rs b/src/tools/tidy/src/style.rs index 9f68c55ec97..75c43343023 100644 --- a/src/tools/tidy/src/style.rs +++ b/src/tools/tidy/src/style.rs @@ -289,7 +289,7 @@ pub fn check(path: &Path, bad: &mut bool) { 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 |
