--- /dev/null
- version = "0.1.62"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
+dependencies = [
+ "compiler_builtins",
+ "gimli 0.25.0",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "adler"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
+dependencies = [
+ "getrandom 0.2.0",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc"
+version = "0.0.0"
+dependencies = [
+ "compiler_builtins",
+ "core",
+ "rand 0.7.3",
+ "rand_xorshift",
+]
+
+[[package]]
+name = "ammonia"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5ed2509ee88cc023cccee37a6fab35826830fe8b748b3869790e7720c2c4a74"
+dependencies = [
+ "html5ever",
+ "maplit",
+ "once_cell",
+ "tendril",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "annotate-snippets"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78ea013094e5ea606b1c05fe35f1dd7ea1eb1ea259908d040b25bd5ec677ee5"
+dependencies = [
+ "yansi-term",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+
+[[package]]
+name = "array_tool"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a2f58b0bb10c380af2b26e57212856b8c9a59e0925b4c20f4a174a49734eaf7"
+
+[[package]]
+name = "askama"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d8f355701c672c2ba3d718acbd213f740beea577cc4eae66accdffe15be1882"
+dependencies = [
+ "askama_derive",
+ "askama_escape",
+ "askama_shared",
+]
+
+[[package]]
+name = "askama_derive"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84704cab5b7ae0fd3a9f78ee5eb7b27f3749df445f04623db6633459ae283267"
+dependencies = [
+ "askama_shared",
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "askama_escape"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1bb320f97e6edf9f756bf015900038e43c7700e059688e5724a928c8f3b8d5"
+
+[[package]]
+name = "askama_shared"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dae03eebba55a2697a376e58b573a29fe36893157173ac8df312ad85f3c0e012"
+dependencies = [
+ "askama_escape",
+ "nom",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+ "toml",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "bootstrap"
+version = "0.0.0"
+dependencies = [
+ "cc",
+ "cmake",
+ "filetime",
+ "getopts",
+ "ignore",
+ "libc",
+ "once_cell",
+ "opener",
+ "pretty_assertions",
+ "serde",
+ "serde_json",
+ "tar",
+ "toml",
+ "winapi",
+ "xz2",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+]
+
+[[package]]
+name = "build-manifest"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "flate2",
+ "hex 0.4.2",
+ "rayon",
+ "serde",
+ "serde_json",
+ "sha2",
+ "tar",
+ "toml",
+]
+
+[[package]]
+name = "bump-stage0"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "curl",
+ "indexmap",
+ "serde",
+ "serde_json",
+ "toml",
+]
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "bytecount"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
+dependencies = [
+ "packed_simd_2",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "bytesize"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81a18687293a1546b67c246452202bbbf143d239cb43494cc163da14979082da"
+
+[[package]]
+name = "camino"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo"
+version = "0.63.0"
+dependencies = [
+ "anyhow",
+ "atty",
+ "bytesize",
+ "cargo-platform 0.1.2",
+ "cargo-test-macro",
+ "cargo-test-support",
+ "cargo-util",
+ "clap 3.1.1",
+ "crates-io",
+ "crossbeam-utils",
+ "curl",
+ "curl-sys",
+ "env_logger 0.9.0",
+ "filetime",
+ "flate2",
+ "fwdansi",
+ "git2",
+ "git2-curl",
+ "glob",
+ "hex 0.4.2",
+ "home",
+ "humantime 2.0.1",
+ "ignore",
+ "im-rc",
+ "indexmap",
+ "itertools",
+ "jobserver",
+ "lazy_static",
+ "lazycell",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "memchr",
+ "opener",
+ "openssl",
+ "os_info",
+ "pathdiff",
+ "percent-encoding 2.1.0",
+ "pretty_env_logger",
+ "rustc-workspace-hack",
+ "rustfix 0.6.0",
+ "semver",
+ "serde",
+ "serde_ignored",
+ "serde_json",
+ "shell-escape",
+ "snapbox",
+ "strip-ansi-escapes",
+ "tar",
+ "tempfile",
+ "termcolor",
+ "toml_edit",
+ "unicode-width",
+ "unicode-xid",
+ "url 2.2.2",
+ "walkdir",
+ "winapi",
+]
+
+[[package]]
+name = "cargo-credential"
+version = "0.1.0"
+
+[[package]]
+name = "cargo-credential-1password"
+version = "0.1.0"
+dependencies = [
+ "cargo-credential",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cargo-credential-macos-keychain"
+version = "0.1.0"
+dependencies = [
+ "cargo-credential",
+ "security-framework",
+]
+
+[[package]]
+name = "cargo-credential-wincred"
+version = "0.1.0"
+dependencies = [
+ "cargo-credential",
+ "winapi",
+]
+
+[[package]]
+name = "cargo-miri"
+version = "0.1.0"
+dependencies = [
+ "directories",
+ "rustc-workspace-hack",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "vergen",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-test-macro"
+version = "0.1.0"
+
+[[package]]
+name = "cargo-test-support"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "cargo-test-macro",
+ "cargo-util",
+ "filetime",
+ "flate2",
+ "git2",
+ "glob",
+ "itertools",
+ "lazy_static",
+ "remove_dir_all",
+ "serde_json",
+ "snapbox",
+ "tar",
+ "termcolor",
+ "toml_edit",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "cargo-util"
+version = "0.1.3"
+dependencies = [
+ "anyhow",
+ "core-foundation",
+ "crypto-hash",
+ "filetime",
+ "hex 0.4.2",
+ "jobserver",
+ "libc",
+ "log",
+ "miow",
+ "same-file",
+ "shell-escape",
+ "tempfile",
+ "walkdir",
+ "winapi",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c297bd3135f558552f99a0daa180876984ea2c4ffa7470314540dff8c654109a"
+dependencies = [
+ "camino",
+ "cargo-platform 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cargotest2"
+version = "0.1.0"
+
+[[package]]
+name = "cc"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chalk-derive"
+version = "0.80.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0001adf0cf12361e08b65e1898ea138f8f77d8f5177cbf29b6b3b3532252bd6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "chalk-engine"
+version = "0.80.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c44ee96f2d67cb5193d1503f185db1abad9933a1c6e6b4169c176f90baecd393"
+dependencies = [
+ "chalk-derive",
+ "chalk-ir",
+ "chalk-solve",
+ "rustc-hash",
+ "tracing",
+]
+
+[[package]]
+name = "chalk-ir"
+version = "0.80.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d8a95548f23618fda86426e4304e563ec2bb7ba0216139f0748d63c107b5f1"
+dependencies = [
+ "bitflags",
+ "chalk-derive",
+ "lazy_static",
+]
+
+[[package]]
+name = "chalk-solve"
+version = "0.80.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f37f492dacfafe2e21319b80827da2779932909bb392f0cc86b2bd5c07c1b4e1"
+dependencies = [
+ "chalk-derive",
+ "chalk-ir",
+ "ena",
+ "indexmap",
+ "itertools",
+ "petgraph",
+ "rustc-hash",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim 0.8.0",
+ "textwrap 0.11.0",
+ "unicode-width",
+ "vec_map",
+ "yaml-rust 0.3.5",
+]
+
+[[package]]
+name = "clap"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d76c22c9b9b215eeb8d016ad3a90417bd13cb24cf8142756e6472445876cab7"
+dependencies = [
+ "atty",
+ "bitflags",
+ "indexmap",
+ "lazy_static",
+ "os_str_bytes",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap 0.14.2",
+]
+
+[[package]]
+name = "clap_complete"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25"
+dependencies = [
+ "clap 3.1.1",
+]
+
+[[package]]
+name = "clippy"
- version = "0.1.62"
++version = "0.1.63"
+dependencies = [
+ "clippy_lints",
+ "clippy_utils",
+ "compiletest_rs",
+ "derive-new",
+ "filetime",
+ "futures 0.3.19",
+ "if_chain",
+ "itertools",
+ "parking_lot",
+ "quote",
+ "regex",
+ "rustc-semver",
+ "rustc-workspace-hack",
+ "rustc_tools_util 0.2.0",
+ "semver",
+ "serde",
+ "syn",
+ "tempfile",
++ "termize",
+ "tester",
+ "tokio",
+]
+
+[[package]]
+name = "clippy_dev"
+version = "0.0.1"
+dependencies = [
+ "aho-corasick",
+ "clap 2.34.0",
+ "indoc",
+ "itertools",
+ "opener",
+ "shell-escape",
+ "tempfile",
+ "walkdir",
+]
+
+[[package]]
+name = "clippy_lints"
- version = "0.1.62"
++version = "0.1.63"
+dependencies = [
+ "cargo_metadata",
+ "clippy_utils",
+ "if_chain",
+ "itertools",
+ "pulldown-cmark",
+ "quine-mc_cluskey",
+ "regex-syntax",
+ "rustc-semver",
+ "semver",
+ "serde",
+ "serde_json",
+ "toml",
+ "unicode-normalization",
+ "unicode-script",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "clippy_utils"
++version = "0.1.63"
+dependencies = [
+ "arrayvec",
+ "if_chain",
+ "rustc-semver",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "colored"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
+dependencies = [
+ "atty",
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "commoncrypto"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007"
+dependencies = [
+ "commoncrypto-sys",
+]
+
+[[package]]
+name = "commoncrypto-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "compiler_builtins"
+version = "0.1.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "163437f05ca8f29d7e9128ea728dedf5eb620e445fbca273641d3a3050305f23"
+dependencies = [
+ "cc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "compiletest"
+version = "0.0.0"
+dependencies = [
+ "colored",
+ "diff",
+ "getopts",
+ "glob",
+ "lazy_static",
+ "libc",
+ "miow",
+ "regex",
+ "rustfix 0.6.0",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+ "unified-diff",
+ "walkdir",
+ "winapi",
+]
+
+[[package]]
+name = "compiletest_rs"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29843cb8d351febf86557681d049d1e1652b81a086a190fa1173c07fd17fbf83"
+dependencies = [
+ "diff",
+ "filetime",
+ "getopts",
+ "lazy_static",
+ "libc",
+ "log",
+ "miow",
+ "regex",
+ "rustfix 0.5.1",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tempfile",
+ "tester",
+ "winapi",
+]
+
+[[package]]
+name = "concolor"
+version = "0.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "015267563b1df20adccdd00cb05257b1dfbea70a04928e9cf88ffb850c1a40af"
+dependencies = [
+ "atty",
+ "bitflags",
+ "concolor-query",
+]
+
+[[package]]
+name = "concolor-query"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6417fe6fc03a8b533fd2177742eeb39a90c7233eedec7bac96d4d6b69a09449"
+
+[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "core"
+version = "0.0.0"
+dependencies = [
+ "rand 0.7.3",
+ "rand_xorshift",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6"
+
+[[package]]
+name = "coverage_test_macros"
+version = "0.0.0"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crates-io"
+version = "0.34.0"
+dependencies = [
+ "anyhow",
+ "curl",
+ "percent-encoding 2.1.0",
+ "serde",
+ "serde_json",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "crypto-hash"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a77162240fd97248d19a564a565eb563a3f592b386e4136fb300909e67dddca"
+dependencies = [
+ "commoncrypto",
+ "hex 0.3.2",
+ "openssl",
+ "winapi",
+]
+
+[[package]]
+name = "cstr"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11a39d776a3b35896711da8a04dc1835169dcd36f710878187637314e47941b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "curl"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc6d233563261f8db6ffb83bbaad5a73837a6e6b28868e926337ebbdece0be3"
+dependencies = [
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.51+curl-7.80.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d130987e6a6a34fe0889e1083022fa48cd90e6709a84be3fb8dd95801de5af20"
+dependencies = [
+ "cc",
+ "libc",
+ "libnghttp2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "winapi",
+]
+
+[[package]]
+name = "datafrog"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0afaad2b26fa326569eb264b1363e8ae3357618c43982b3f285f0774ce76b69"
+
+[[package]]
+name = "derive-new"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
+
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
+dependencies = [
+ "block-buffer 0.10.2",
+ "crypto-common",
+]
+
+[[package]]
+name = "directories"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if 1.0.0",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dlmalloc"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6fe28e0bf9357092740362502f5cc7955d8dc125ebda71dec72336c2e15c62e"
+dependencies = [
+ "compiler_builtins",
+ "libc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
+
+[[package]]
+name = "either"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
+
+[[package]]
+name = "elasticlunr-rs"
+version = "2.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35622eb004c8f0c5e7e2032815f3314a93df0db30a1ce5c94e62c1ecc81e22b9"
+dependencies = [
+ "lazy_static",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "strum",
+ "strum_macros",
+]
+
+[[package]]
+name = "ena"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "enum-iterator"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7"
+dependencies = [
+ "enum-iterator-derive",
+]
+
+[[package]]
+name = "enum-iterator-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+dependencies = [
+ "atty",
+ "humantime 1.3.0",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "atty",
+ "humantime 2.0.1",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime 2.0.1",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "error_index_generator"
+version = "0.0.0"
+dependencies = [
+ "rustdoc",
+ "walkdir",
+]
+
+[[package]]
+name = "expand-yaml-anchors"
+version = "0.1.0"
+dependencies = [
+ "yaml-merge-keys",
+ "yaml-rust 0.4.4",
+]
+
+[[package]]
+name = "expect-test"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceb96f3eaa0d4e8769c52dacfd4eb60183b817ed2f176171b3c691d5022b0f2e"
+dependencies = [
+ "difference",
+ "once_cell",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "fallible-iterator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+
+[[package]]
+name = "filetime"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "flate2"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c90b0fc46cf89d227cc78b40e494ff81287a92dd07631e5af0d06fe3cf885e"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crc32fast",
+ "libc",
+ "libz-sys",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fluent-bundle"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash",
+ "self_cell",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding 2.1.0",
+]
+
+[[package]]
+name = "fortanix-sgx-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56c422ef86062869b2d57ae87270608dc5929969dd130a6e248979cf4fb6ca6"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "fs-err"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcd1163ae48bda72a20ae26d66a04d3094135cadab911cff418ae5e33f253431"
+
+[[package]]
+name = "fs_extra"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
+
+[[package]]
+name = "fst"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f"
+
+[[package]]
+name = "futf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "futures"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
+
+[[package]]
+name = "futures"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+ "num_cpus",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508"
+
+[[package]]
+name = "futures-task"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
+
+[[package]]
+name = "futures-util"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
+dependencies = [
+ "futures 0.1.31",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fwdansi"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c1f5787fe85505d1f7777268db5103d80a7a374d2316a7ce262e57baf8f208"
+dependencies = [
+ "memchr",
+ "termcolor",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "rustc-std-workspace-core",
+ "rustc-std-workspace-std",
+ "unicode-width",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getset"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "gimli"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+dependencies = [
+ "fallible-iterator",
+ "indexmap",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "git2"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "git2-curl"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee51709364c341fbb6fe2a385a290fb9196753bdde2fc45447d27cd31b11b13"
+dependencies = [
+ "curl",
+ "git2",
+ "log",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "globset"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "gsgdt"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0d876ce7262df96262a2a19531da6ff9a86048224d49580a585fc5c04617825"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "handlebars"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72a0ffab8c36d0436114310c7e10b59b3307e650ddfabf6d006028e29a70c6e6"
+dependencies = [
+ "log",
+ "pest",
+ "pest_derive",
+ "quick-error 2.0.0",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ab7905ea95c6d9af62940f9d7dd9596d54c334ae2c15300c482051292d5637f"
+dependencies = [
+ "compiler_builtins",
+ "libc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "hex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
+
+[[package]]
+name = "hex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
+
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "html-checker"
+version = "0.1.0"
+dependencies = [
+ "walkdir",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "humantime"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+dependencies = [
+ "quick-error 1.2.3",
+]
+
+[[package]]
+name = "humantime"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
+
+[[package]]
+name = "idna"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "if_chain"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3360c7b59e5ffa2653671fb74b4741a5d343c03f331c0a4aeda42b5c2b0ec7d"
+
+[[package]]
+name = "ignore"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
+dependencies = [
+ "crossbeam-utils",
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "im-rc"
+version = "15.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
+dependencies = [
+ "bitmaps",
+ "rand_core 0.5.1",
+ "rand_xoshiro 0.4.0",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.11.2",
+ "rustc-rayon",
+ "serde",
+]
+
+[[package]]
+name = "indoc"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136"
+dependencies = [
+ "unindent",
+]
+
+[[package]]
+name = "installer"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "clap 2.34.0",
+ "flate2",
+ "lazy_static",
+ "num_cpus",
+ "rayon",
+ "remove_dir_all",
+ "tar",
+ "walkdir",
+ "winapi",
+ "xz2",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "intl-memoizer"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf"
+dependencies = [
+ "tinystr",
+ "unic-langid",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+
+[[package]]
+name = "jobserver"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "json"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
+
+[[package]]
+name = "jsondocck"
+version = "0.1.0"
+dependencies = [
+ "fs-err",
+ "getopts",
+ "jsonpath_lib",
+ "once_cell",
+ "regex",
+ "serde_json",
+ "shlex",
+]
+
+[[package]]
+name = "jsonpath_lib"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61352ec23883402b7d30b3313c16cbabefb8907361c4eb669d990cbb87ceee5a"
+dependencies = [
+ "array_tool",
+ "env_logger 0.7.1",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "jsonrpc-client-transports"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a"
+dependencies = [
+ "derive_more",
+ "futures 0.3.19",
+ "jsonrpc-core",
+ "jsonrpc-pubsub",
+ "jsonrpc-server-utils",
+ "log",
+ "parity-tokio-ipc",
+ "serde",
+ "serde_json",
+ "tokio",
+ "url 1.7.2",
+]
+
+[[package]]
+name = "jsonrpc-core"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb"
+dependencies = [
+ "futures 0.3.19",
+ "futures-executor",
+ "futures-util",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "jsonrpc-core-client"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0"
+dependencies = [
+ "futures 0.3.19",
+ "jsonrpc-client-transports",
+]
+
+[[package]]
+name = "jsonrpc-derive"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "jsonrpc-ipc-server"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "382bb0206323ca7cda3dcd7e245cea86d37d02457a02a975e3378fb149a48845"
+dependencies = [
+ "futures 0.3.19",
+ "jsonrpc-core",
+ "jsonrpc-server-utils",
+ "log",
+ "parity-tokio-ipc",
+ "parking_lot",
+ "tower-service",
+]
+
+[[package]]
+name = "jsonrpc-pubsub"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011"
+dependencies = [
+ "futures 0.3.19",
+ "jsonrpc-core",
+ "lazy_static",
+ "log",
+ "parking_lot",
+ "rand 0.7.3",
+ "serde",
+]
+
+[[package]]
+name = "jsonrpc-server-utils"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4"
+dependencies = [
+ "bytes",
+ "futures 0.3.19",
+ "globset",
+ "jsonrpc-core",
+ "lazy_static",
+ "log",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "unicase",
+]
+
+[[package]]
+name = "kstring"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+dependencies = [
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "libgit2-sys"
+version = "0.13.2+1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b"
+dependencies = [
+ "cc",
+ "libc",
+ "libssh2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libloading"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
+[[package]]
+name = "libm"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
+
+[[package]]
+name = "libnghttp2-sys"
+version = "0.1.4+1.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03624ec6df166e79e139a2310ca213283d6b3c30810c54844f307086d4488df1"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "libssh2-sys"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linkchecker"
+version = "0.1.0"
+dependencies = [
+ "once_cell",
+ "regex",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "lint-docs"
+version = "0.1.0"
+dependencies = [
+ "serde_json",
+ "tempfile",
+ "walkdir",
+]
+
+[[package]]
+name = "lld-wrapper"
+version = "0.1.0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "lsp-codec"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa939d0b62476a5a19fb7fcb423a5c6ce8c7e09b851d37531e2fe3e0e6d9d257"
+dependencies = [
+ "bytes",
+ "serde_json",
+ "tokio-util",
+]
+
+[[package]]
+name = "lsp-types"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe3edefcd66dde1f7f1df706f46520a3c93adc5ca4bc5747da6621195e894efd"
+dependencies = [
+ "bitflags",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "url 2.2.2",
+]
+
+[[package]]
+name = "lzma-sys"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f24f76ec44a8ac23a31915d6e326bca17ce88da03096f1ff194925dc714dac99"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "macro-utils"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e72f7deb758fea9ea7d290aebfa788763d0bffae12caa6406a25baaf8fa68a8"
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "markup5ever"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
+dependencies = [
+ "log",
+ "phf",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "md-5"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae"
+dependencies = [
+ "digest 0.10.2",
+]
+
+[[package]]
+name = "mdbook"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74612ae81a3e5ee509854049dfa4c7975ae033c06f5fc4735c7dfbe60ee2a39d"
+dependencies = [
+ "ammonia",
+ "anyhow",
+ "chrono",
+ "clap 3.1.1",
+ "clap_complete",
+ "elasticlunr-rs",
+ "env_logger 0.7.1",
+ "handlebars",
+ "lazy_static",
+ "log",
+ "memchr",
+ "opener",
+ "pulldown-cmark",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "shlex",
+ "tempfile",
+ "toml",
+ "topological-sort",
+]
+
+[[package]]
+name = "measureme"
+version = "9.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78f7a41bc6f856a2cf0e95094ad5121f82500e2d9a0f3c0171d98f6566d8117d"
+dependencies = [
+ "log",
+ "memmap2",
+ "parking_lot",
+ "perf-event-open-sys",
+ "rustc-hash",
+ "smallvec",
+]
+
+[[package]]
+name = "measureme"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd460fad6e55ca82fa0cd9dab0d315294188fd9ec6efbf4105e5635d4872ef9c"
+dependencies = [
+ "log",
+ "memmap2",
+ "parking_lot",
+ "perf-event-open-sys",
+ "rustc-hash",
+ "smallvec",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "memmap2"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e3e85b970d650e2ae6d70592474087051c11c54da7f7b4949725c5735fbcc6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minifier"
+version = "0.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d81352bda6f4d04af1720afaa762054f66e16caffd93c1f86461a1c0ac4e695e"
+dependencies = [
+ "macro-utils",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
+dependencies = [
+ "adler",
+ "compiler_builtins",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "miri"
+version = "0.1.0"
+dependencies = [
+ "colored",
+ "compiletest_rs",
+ "env_logger 0.9.0",
+ "getrandom 0.2.0",
+ "libc",
+ "log",
+ "measureme 9.1.2",
+ "rand 0.8.5",
+ "rustc-workspace-hack",
+ "rustc_version",
+ "shell-escape",
+ "smallvec",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "nom"
+version = "7.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+ "version_check",
+]
+
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
+dependencies = [
+ "compiler_builtins",
+ "memchr",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "object"
+version = "0.28.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
+dependencies = [
+ "crc32fast",
+ "flate2",
+ "hashbrown 0.11.2",
+ "indexmap",
+ "memchr",
+]
+
+[[package]]
+name = "odht"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a518809ac14b25b569624d0268eba1e88498f71615893dca57982bed7621abb"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opener"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea3ebcd72a54701f56345f16785a6d3ac2df7e986d273eb4395c0b01db17952"
+dependencies = [
+ "bstr",
+ "winapi",
+]
+
+[[package]]
+name = "openssl"
+version = "0.10.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
+name = "openssl-src"
+version = "111.18.0+1.1.1n"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7897a926e1e8d00219127dc020130eca4292e5ca666dd592480d72c3eca2ff6c"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordslice"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd20eec3dbe4376829cb7d80ae6ac45e0a766831dca50202ff2d40db46a8a024"
+
+[[package]]
+name = "os_info"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac91020bfed8cc3f8aa450d4c3b5fa1d3373fc091c8a92009f3b27749d5a227"
+dependencies = [
+ "log",
+ "serde",
+ "winapi",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "output_vt100"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "packed_simd_2"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3278e0492f961fd4ae70909f56b2723a7e8d01a228427294e19cdfdebda89a17"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libm",
+]
+
+[[package]]
+name = "panic_abort"
+version = "0.0.0"
+dependencies = [
+ "alloc",
+ "cfg-if 0.1.10",
+ "compiler_builtins",
+ "core",
+ "libc",
+]
+
+[[package]]
+name = "panic_unwind"
+version = "0.0.0"
+dependencies = [
+ "alloc",
+ "cfg-if 0.1.10",
+ "compiler_builtins",
+ "core",
+ "libc",
+ "unwind",
+]
+
+[[package]]
+name = "parity-tokio-ipc"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6"
+dependencies = [
+ "futures 0.3.19",
+ "libc",
+ "log",
+ "rand 0.7.3",
+ "tokio",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "percent-encoding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "perf-event-open-sys"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
+
+[[package]]
+name = "polonius-engine"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4e8e505342045d397d0b6674dcb82d6faf5cf40484d30eeb88fc82ef14e903f"
+dependencies = [
+ "datafrog",
+ "log",
+ "rustc-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "pretty_assertions"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
+dependencies = [
+ "ansi_term",
+ "ctor",
+ "diff",
+ "output_vt100",
+]
+
+[[package]]
+name = "pretty_env_logger"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
+dependencies = [
+ "env_logger 0.7.1",
+ "log",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "proc_macro"
+version = "0.0.0"
+dependencies = [
+ "core",
+ "std",
+]
+
+[[package]]
+name = "profiler_builtins"
+version = "0.0.0"
+dependencies = [
+ "cc",
+ "compiler_builtins",
+ "core",
+]
+
+[[package]]
+name = "psm"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd136ff4382c4753fc061cb9e4712ab2af263376b95bbd5bd8cd50c020b78e69"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "punycode"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe"
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick-error"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
+
+[[package]]
+name = "quine-mc_cluskey"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45"
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "racer"
+version = "2.2.2"
+dependencies = [
+ "bitflags",
+ "clap 2.34.0",
+ "derive_more",
+ "env_logger 0.7.1",
+ "humantime 2.0.1",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "racer-cargo-metadata",
+ "rls-span",
+]
+
+[[package]]
+name = "racer-cargo-metadata"
+version = "0.1.2"
+dependencies = [
+ "racer-interner",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "racer-interner"
+version = "0.1.0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.14",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.0",
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.14",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom 0.2.0",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom 0.2.0",
+ "redox_syscall",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remote-test-client"
+version = "0.1.0"
+
+[[package]]
+name = "remote-test-server"
+version = "0.1.0"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rls"
+version = "1.41.0"
+dependencies = [
+ "anyhow",
+ "cargo",
+ "cargo-util",
+ "cargo_metadata",
+ "clippy_lints",
+ "crossbeam-channel",
+ "difference",
+ "env_logger 0.9.0",
+ "futures 0.3.19",
+ "heck",
+ "home",
+ "itertools",
+ "jsonrpc-core",
+ "lazy_static",
+ "log",
+ "lsp-codec",
+ "lsp-types",
+ "num_cpus",
+ "ordslice",
+ "racer",
+ "rand 0.8.5",
+ "rayon",
+ "regex",
+ "rls-analysis",
+ "rls-data",
+ "rls-ipc",
+ "rls-rustc",
+ "rls-span",
+ "rls-vfs",
+ "rustc-workspace-hack",
+ "rustc_tools_util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustfmt-nightly",
+ "serde",
+ "serde_derive",
+ "serde_ignored",
+ "serde_json",
+ "tempfile",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "toml",
+ "toml_edit",
+ "url 2.2.2",
+ "walkdir",
+]
+
+[[package]]
+name = "rls-analysis"
+version = "0.18.3"
+dependencies = [
+ "derive-new",
+ "env_logger 0.9.0",
+ "fst",
+ "itertools",
+ "json",
+ "lazy_static",
+ "log",
+ "rls-data",
+ "rls-span",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rls-data"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a58135eb039f3a3279a33779192f0ee78b56f57ae636e25cec83530e41debb99"
+dependencies = [
+ "rls-span",
+ "serde",
+]
+
+[[package]]
+name = "rls-ipc"
+version = "0.1.0"
+dependencies = [
+ "jsonrpc-core",
+ "jsonrpc-core-client",
+ "jsonrpc-derive",
+ "jsonrpc-ipc-server",
+ "rls-data",
+ "serde",
+]
+
+[[package]]
+name = "rls-rustc"
+version = "0.6.0"
+dependencies = [
+ "clippy_lints",
+ "env_logger 0.9.0",
+ "futures 0.3.19",
+ "log",
+ "rand 0.8.5",
+ "rls-data",
+ "rls-ipc",
+ "serde",
+ "tokio",
+]
+
+[[package]]
+name = "rls-span"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0eea58478fc06e15f71b03236612173a1b81e9770314edecfa664375e3e4c86"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "rls-vfs"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce4b57b25b4330ed5ec14028fc02141e083ddafda327e7eb598dc0569c8c83c9"
+dependencies = [
+ "log",
+ "rls-span",
+]
+
+[[package]]
+name = "rust-demangler"
+version = "0.0.1"
+dependencies = [
+ "regex",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "rustbook"
+version = "0.1.0"
+dependencies = [
+ "clap 2.34.0",
+ "env_logger 0.7.1",
+ "mdbook",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-main"
+version = "0.0.0"
+dependencies = [
+ "rustc_codegen_ssa",
+ "rustc_driver",
+ "tikv-jemalloc-sys",
+]
+
+[[package]]
+name = "rustc-rayon"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9974ab223660e61c1b4e7b43b827379df286736ca988308ce7e16f59f2d89246"
+dependencies = [
+ "crossbeam-deque",
+ "either",
+ "rustc-rayon-core",
+]
+
+[[package]]
+name = "rustc-rayon-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "564bfd27be8db888d0fa76aa4335e7851aaed0c2c11ad1e93aeb9349f6b88500"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "rustc-semver"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be1bdc7edf596692617627bbfeaba522131b18e06ca4df2b6b689e3c5d5ce84"
+
+[[package]]
+name = "rustc-std-workspace-alloc"
+version = "1.99.0"
+dependencies = [
+ "alloc",
+]
+
+[[package]]
+name = "rustc-std-workspace-core"
+version = "1.99.0"
+dependencies = [
+ "core",
+]
+
+[[package]]
+name = "rustc-std-workspace-std"
+version = "1.99.0"
+dependencies = [
+ "std",
+]
+
+[[package]]
+name = "rustc-workspace-hack"
+version = "1.0.0"
+dependencies = [
+ "bstr",
+ "byteorder",
+ "crossbeam-utils",
+ "libc",
+ "libz-sys",
+ "proc-macro2",
+ "quote",
+ "rand_core 0.5.1",
+ "serde",
+ "serde_json",
+ "smallvec",
+ "syn",
+ "url 2.2.2",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_apfloat"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "smallvec",
+]
+
+[[package]]
+name = "rustc_arena"
+version = "0.0.0"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "rustc_ast"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "rustc_data_structures",
+ "rustc_index",
+ "rustc_lexer",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_ast_lowering"
+version = "0.0.0"
+dependencies = [
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_query_system",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_ast_passes"
+version = "0.0.0"
+dependencies = [
+ "itertools",
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_parse",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_ast_pretty"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_span",
+]
+
+[[package]]
+name = "rustc_attr"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_lexer",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+]
+
+[[package]]
+name = "rustc_borrowck"
+version = "0.0.0"
+dependencies = [
+ "either",
+ "itertools",
+ "polonius-engine",
+ "rustc_const_eval",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_graphviz",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_lexer",
+ "rustc_middle",
+ "rustc_mir_dataflow",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "rustc_traits",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_builtin_macros"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_expand",
+ "rustc_feature",
+ "rustc_lexer",
+ "rustc_lint_defs",
+ "rustc_parse",
+ "rustc_parse_format",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_codegen_llvm"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "cstr",
+ "libc",
+ "libloading",
+ "measureme 10.0.0",
+ "rustc-demangle",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_codegen_ssa",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_fs_util",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_llvm",
+ "rustc_macros",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_query_system",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_codegen_ssa"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "cc",
+ "itertools",
+ "jobserver",
+ "libc",
+ "object 0.28.4",
+ "pathdiff",
+ "regex",
+ "rustc_apfloat",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_fs_util",
+ "rustc_hir",
+ "rustc_incremental",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_query_system",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_symbol_mangling",
+ "rustc_target",
+ "smallvec",
+ "snap",
+ "tempfile",
+ "thorin-dwp",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_const_eval"
+version = "0.0.0"
+dependencies = [
+ "rustc_apfloat",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_mir_dataflow",
+ "rustc_query_system",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_data_structures"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg-if 0.1.10",
+ "ena",
+ "indexmap",
+ "jobserver",
+ "libc",
+ "measureme 10.0.0",
+ "memmap2",
+ "parking_lot",
+ "rustc-hash",
+ "rustc-rayon",
+ "rustc-rayon-core",
+ "rustc_graphviz",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+ "smallvec",
+ "stable_deref_trait",
+ "stacker",
+ "tempfile",
+ "tracing",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_driver"
+version = "0.0.0"
+dependencies = [
+ "libc",
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_codegen_ssa",
+ "rustc_const_eval",
+ "rustc_data_structures",
+ "rustc_error_codes",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_hir_pretty",
+ "rustc_interface",
+ "rustc_lint",
+ "rustc_log",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_parse",
+ "rustc_plugin_impl",
+ "rustc_save_analysis",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_typeck",
+ "tracing",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_error_codes"
+version = "0.0.0"
+
+[[package]]
+name = "rustc_error_messages"
+version = "0.0.0"
+dependencies = [
+ "fluent-bundle",
+ "fluent-syntax",
+ "intl-memoizer",
+ "rustc_data_structures",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "tracing",
+ "unic-langid",
+]
+
+[[package]]
+name = "rustc_errors"
+version = "0.0.0"
+dependencies = [
+ "annotate-snippets",
+ "atty",
+ "rustc_data_structures",
+ "rustc_error_messages",
+ "rustc_lint_defs",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "termcolor",
+ "termize",
+ "tracing",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_expand"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_passes",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_lexer",
+ "rustc_lint_defs",
+ "rustc_macros",
+ "rustc_parse",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_feature"
+version = "0.0.0"
+dependencies = [
+ "rustc_data_structures",
+ "rustc_span",
+]
+
+[[package]]
+name = "rustc_fs_util"
+version = "0.0.0"
+
+[[package]]
+name = "rustc_graphviz"
+version = "0.0.0"
+
+[[package]]
+name = "rustc_hir"
+version = "0.0.0"
+dependencies = [
+ "odht",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_error_messages",
+ "rustc_feature",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_hir_pretty"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_hir",
+ "rustc_span",
+ "rustc_target",
+]
+
+[[package]]
+name = "rustc_incremental"
+version = "0.0.0"
+dependencies = [
+ "rand 0.8.5",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_fs_util",
+ "rustc_graphviz",
+ "rustc_hir",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_index"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "rustc_macros",
+ "rustc_serialize",
+ "smallvec",
+]
+
+[[package]]
+name = "rustc_infer"
+version = "0.0.0"
+dependencies = [
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_interface"
+version = "0.0.0"
+dependencies = [
+ "libc",
+ "libloading",
+ "rustc-rayon",
+ "rustc-rayon-core",
+ "rustc_ast",
+ "rustc_ast_lowering",
+ "rustc_ast_passes",
+ "rustc_attr",
+ "rustc_borrowck",
+ "rustc_builtin_macros",
+ "rustc_codegen_llvm",
+ "rustc_codegen_ssa",
+ "rustc_const_eval",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_expand",
+ "rustc_hir",
+ "rustc_incremental",
+ "rustc_lint",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_mir_build",
+ "rustc_mir_transform",
+ "rustc_monomorphize",
+ "rustc_parse",
+ "rustc_passes",
+ "rustc_plugin_impl",
+ "rustc_privacy",
+ "rustc_query_impl",
+ "rustc_resolve",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_symbol_mangling",
+ "rustc_target",
+ "rustc_trait_selection",
+ "rustc_traits",
+ "rustc_ty_utils",
+ "rustc_typeck",
+ "smallvec",
+ "tempfile",
+ "tracing",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_lexer"
+version = "0.1.0"
+dependencies = [
+ "expect-test",
+ "unic-emoji-char",
+ "unicode-xid",
+]
+
+[[package]]
+name = "rustc_lint"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_middle",
+ "rustc_parse_format",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "tracing",
+ "unicode-security",
+]
+
+[[package]]
+name = "rustc_lint_defs"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_error_messages",
+ "rustc_hir",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "rustc_target",
+]
+
+[[package]]
+name = "rustc_llvm"
+version = "0.0.0"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "rustc_log"
+version = "0.0.0"
+dependencies = [
+ "atty",
+ "rustc_span",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+]
+
+[[package]]
+name = "rustc_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "rustc_metadata"
+version = "0.0.0"
+dependencies = [
+ "libloading",
+ "odht",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_expand",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_hir_pretty",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "snap",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_middle"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "chalk-ir",
+ "either",
+ "gsgdt",
+ "polonius-engine",
+ "rand 0.8.5",
+ "rand_xoshiro 0.6.0",
+ "rustc-rayon",
+ "rustc-rayon-core",
+ "rustc_apfloat",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_graphviz",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_query_system",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_type_ir",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_mir_build"
+version = "0.0.0"
+dependencies = [
+ "rustc_apfloat",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_mir_dataflow"
+version = "0.0.0"
+dependencies = [
+ "polonius-engine",
+ "regex",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_graphviz",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_mir_transform"
+version = "0.0.0"
+dependencies = [
+ "coverage_test_macros",
+ "itertools",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_const_eval",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_middle",
+ "rustc_mir_dataflow",
+ "rustc_query_system",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_monomorphize"
+version = "0.0.0"
+dependencies = [
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_middle",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_parse"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_lexer",
+ "rustc_macros",
+ "rustc_session",
+ "rustc_span",
+ "tracing",
+ "unicode-normalization",
+ "unicode-width",
+]
+
+[[package]]
+name = "rustc_parse_format"
+version = "0.0.0"
+dependencies = [
+ "rustc_lexer",
+]
+
+[[package]]
+name = "rustc_passes"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_expand",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_lexer",
+ "rustc_middle",
+ "rustc_parse",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_plugin_impl"
+version = "0.0.0"
+dependencies = [
+ "libloading",
+ "rustc_ast",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_lint",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_session",
+ "rustc_span",
+]
+
+[[package]]
+name = "rustc_privacy"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_middle",
+ "rustc_session",
+ "rustc_span",
+ "rustc_trait_selection",
+ "rustc_typeck",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_query_impl"
+version = "0.0.0"
+dependencies = [
+ "measureme 10.0.0",
+ "rustc-rayon-core",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_query_system",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_query_system"
+version = "0.0.0"
+dependencies = [
+ "parking_lot",
+ "rustc-rayon-core",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_resolve"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_ast_lowering",
+ "rustc_ast_pretty",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_expand",
+ "rustc_feature",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_metadata",
+ "rustc_middle",
+ "rustc_query_system",
+ "rustc_session",
+ "rustc_span",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_save_analysis"
+version = "0.0.0"
+dependencies = [
+ "rls-data",
+ "rls-span",
+ "rustc_ast",
+ "rustc_ast_pretty",
+ "rustc_data_structures",
+ "rustc_hir",
+ "rustc_hir_pretty",
+ "rustc_lexer",
+ "rustc_middle",
+ "rustc_session",
+ "rustc_span",
+ "serde_json",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_serialize"
+version = "0.0.0"
+dependencies = [
+ "indexmap",
+ "rustc_macros",
+ "smallvec",
+]
+
+[[package]]
+name = "rustc_session"
+version = "0.0.0"
+dependencies = [
+ "getopts",
+ "rustc_ast",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_feature",
+ "rustc_fs_util",
+ "rustc_hir",
+ "rustc_lint_defs",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "rustc_target",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_span"
+version = "0.0.0"
+dependencies = [
+ "cfg-if 0.1.10",
+ "md-5",
+ "rustc_arena",
+ "rustc_data_structures",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+ "scoped-tls",
+ "sha-1 0.10.0",
+ "sha2",
+ "tracing",
+ "unicode-width",
+]
+
+[[package]]
+name = "rustc_symbol_mangling"
+version = "0.0.0"
+dependencies = [
+ "punycode",
+ "rustc-demangle",
+ "rustc_data_structures",
+ "rustc_hir",
+ "rustc_middle",
+ "rustc_query_system",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_target"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "rustc_data_structures",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_tools_util"
+version = "0.2.0"
+
+[[package]]
+name = "rustc_tools_util"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b725dadae9fabc488df69a287f5a99c5eaf5d10853842a8a3dfac52476f544ee"
+
+[[package]]
+name = "rustc_trait_selection"
+version = "0.0.0"
+dependencies = [
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_lint_defs",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_parse_format",
+ "rustc_query_system",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_traits"
+version = "0.0.0"
+dependencies = [
+ "chalk-engine",
+ "chalk-ir",
+ "chalk-solve",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_hir",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_middle",
+ "rustc_span",
+ "rustc_trait_selection",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_ty_utils"
+version = "0.0.0"
+dependencies = [
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_hir",
+ "rustc_infer",
+ "rustc_middle",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_type_ir"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "rustc_data_structures",
+ "rustc_index",
+ "rustc_macros",
+ "rustc_serialize",
+]
+
+[[package]]
+name = "rustc_typeck"
+version = "0.0.0"
+dependencies = [
+ "rustc_arena",
+ "rustc_ast",
+ "rustc_attr",
+ "rustc_data_structures",
+ "rustc_errors",
+ "rustc_graphviz",
+ "rustc_hir",
+ "rustc_hir_pretty",
+ "rustc_index",
+ "rustc_infer",
+ "rustc_lint",
+ "rustc_macros",
+ "rustc_middle",
+ "rustc_serialize",
+ "rustc_session",
+ "rustc_span",
+ "rustc_target",
+ "rustc_trait_selection",
+ "rustc_ty_utils",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustdoc"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "askama",
+ "atty",
+ "expect-test",
+ "itertools",
+ "minifier",
+ "once_cell",
+ "pulldown-cmark",
+ "rayon",
+ "regex",
+ "rustdoc-json-types",
+ "serde",
+ "serde_json",
+ "smallvec",
+ "tempfile",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+]
+
+[[package]]
+name = "rustdoc-json-types"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustdoc-themes"
+version = "0.1.0"
+
+[[package]]
+name = "rustdoc-tool"
+version = "0.0.0"
+dependencies = [
+ "rustdoc",
+]
+
+[[package]]
+name = "rustfix"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2c50b74badcddeb8f7652fa8323ce440b95286f8e4b64ebfd871c609672704e"
+dependencies = [
+ "anyhow",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustfix"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0be05fc0675ef4f47119dc39cfc46636bb77d4fc4ef1bd851b9c3f7697f32a"
+dependencies = [
+ "anyhow",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustfmt-config_proc_macro"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+]
+
+[[package]]
+name = "rustfmt-nightly"
+version = "1.4.38"
+dependencies = [
+ "annotate-snippets",
+ "anyhow",
+ "bytecount",
+ "cargo_metadata",
+ "derive-new",
+ "diff",
+ "dirs",
+ "env_logger 0.8.4",
+ "getopts",
+ "ignore",
+ "itertools",
+ "lazy_static",
+ "log",
+ "regex",
+ "rustc-workspace-hack",
+ "rustfmt-config_proc_macro",
+ "serde",
+ "serde_json",
+ "structopt",
+ "term 0.6.1",
+ "thiserror",
+ "toml",
+ "unicode-segmentation",
+ "unicode-width",
+ "unicode_categories",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "security-framework"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "self_cell"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
+
+[[package]]
+name = "semver"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_ignored"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c2c7d39d14f2f2ea82239de71594782f186fd03501ac81f0ce08e674819ff2f"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.10.2",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.10.2",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shell-escape"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
+
+[[package]]
+name = "shlex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "similar"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
+
+[[package]]
+name = "siphasher"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
+
+[[package]]
+name = "sized-chunks"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65e65d6a9f13cd78f361ea5a2cf53a45d67cdda421ba0316b9be101560f3d207"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "smallvec"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+
+[[package]]
+name = "snap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da73c8f77aebc0e40c300b93f0a5f1bece7a248a36eee287d4e095f35c7b7d6e"
+
+[[package]]
+name = "snapbox"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1f212b806d6f56d19838e36a0aaa7e79a0bc9ca177e873fb87651ad92f983e2"
+dependencies = [
+ "concolor",
+ "content_inspector",
+ "dunce",
+ "filetime",
+ "normalize-line-endings",
+ "similar",
+ "snapbox-macros",
+ "tempfile",
+ "walkdir",
+ "yansi",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c01dea7e04cbb27ef4c86e9922184608185f7cd95c1763bc30d727cda4a5e930"
+
+[[package]]
+name = "socket2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "stacker"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90939d5171a4420b3ff5fbc8954d641e7377335454c259dcb80786f3f21dc9b4"
+dependencies = [
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "psm",
+ "winapi",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "std"
+version = "0.0.0"
+dependencies = [
+ "addr2line",
+ "alloc",
+ "cfg-if 0.1.10",
+ "compiler_builtins",
+ "core",
+ "dlmalloc",
+ "fortanix-sgx-abi",
+ "hashbrown 0.12.0",
+ "hermit-abi 0.2.0",
+ "libc",
+ "miniz_oxide",
+ "object 0.26.2",
+ "panic_abort",
+ "panic_unwind",
+ "profiler_builtins",
+ "rand 0.7.3",
+ "rustc-demangle",
+ "std_detect",
+ "unwind",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "std_detect"
+version = "0.1.5"
+dependencies = [
+ "cfg-if 0.1.10",
+ "compiler_builtins",
+ "libc",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "string_cache"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26"
+dependencies = [
+ "lazy_static",
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d63676e2abafa709460982ddc02a3bb586b6d15a49b75c212e06edd3933acee"
+dependencies = [
+ "vte",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "structopt"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
+dependencies = [
+ "clap 2.34.0",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "strum"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
+
+[[package]]
+name = "strum_macros"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand 0.8.5",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "term"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5"
+dependencies = [
+ "dirs",
+ "winapi",
+]
+
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "termize"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1706be6b564323ce7092f5f7e6b118a14c8ef7ed0e69c8c5329c914a9f101295"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "test"
+version = "0.0.0"
+dependencies = [
+ "cfg-if 0.1.10",
+ "core",
+ "getopts",
+ "libc",
+ "panic_abort",
+ "panic_unwind",
+ "proc_macro",
+ "std",
+]
+
+[[package]]
+name = "tester"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0639d10d8f4615f223a57275cf40f9bdb7cfbb806bcb7f7cc56e3beb55a576eb"
+dependencies = [
+ "cfg-if 1.0.0",
+ "getopts",
+ "libc",
+ "num_cpus",
+ "term 0.7.0",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thorin-dwp"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd95b4559c196987c8451b4e14d08a4c796c2844f9adf4d2a2dbc9b3142843be"
+dependencies = [
+ "gimli 0.26.1",
+ "hashbrown 0.11.2",
+ "object 0.28.4",
+ "tracing",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tidy"
+version = "0.1.0"
+dependencies = [
+ "cargo_metadata",
+ "crossbeam-utils",
+ "lazy_static",
+ "regex",
+ "walkdir",
+]
+
+[[package]]
+name = "tier-check"
+version = "0.1.0"
+
+[[package]]
+name = "tikv-jemalloc-sys"
+version = "0.4.1+5.2.1-patched"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a26331b05179d4cb505c8d6814a7e18d298972f0a551b0e3cefccff927f86d3"
+dependencies = [
+ "cc",
+ "fs_extra",
+ "libc",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1"
+
+[[package]]
+name = "tinyvec"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
+
+[[package]]
+name = "tokio"
+version = "1.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50dae83881bc9b0403dd5b44ea9deed3e939856cc8722d5be37f0d6e5c6d53dd"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba98375fd631b83696f87c64e4ed8e29e6a1f3404d6aed95fa95163bad38e705"
+dependencies = [
+ "combine",
+ "indexmap",
+ "itertools",
+ "kstring",
+ "serde",
+]
+
+[[package]]
+name = "topological-sort"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+dependencies = [
+ "cfg-if 1.0.0",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3"
+dependencies = [
+ "ansi_term",
+ "lazy_static",
+ "matchers",
+ "parking_lot",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tracing-tree"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ce989c9962c7f61fe084dd4a230eec784649dfc2392467c790007c3a6e134e7"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "type-map"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
+dependencies = [
+ "rustc-hash",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "ucd-parse"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5269f8d35df6b8b60758343a6d742ecf09e4bca13faee32af5503aebd1e11b7c"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-emoji-char"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-langid"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5"
+dependencies = [
+ "unic-langid-impl",
+ "unic-langid-macros",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d"
+dependencies = [
+ "tinystr",
+]
+
+[[package]]
+name = "unic-langid-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5"
+dependencies = [
+ "proc-macro-hack",
+ "tinystr",
+ "unic-langid-impl",
+ "unic-langid-macros-impl",
+]
+
+[[package]]
+name = "unic-langid-macros-impl"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f"
+dependencies = [
+ "proc-macro-hack",
+ "quote",
+ "syn",
+ "unic-langid-impl",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bdd"
+version = "0.1.0"
+dependencies = [
+ "ucd-parse",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-script"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "098ec66172ce21cd55f8bcc786ee209dd20e04eff70acfca30cb79924d173ae9"
+
+[[package]]
+name = "unicode-security"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d87c28edc5b263377e448d6cdcb935c06b95413d8013ba6fae470558ccab18f"
+dependencies = [
+ "unicode-normalization",
+ "unicode-script",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+ "rustc-std-workspace-std",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
+[[package]]
+name = "unified-diff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "496a3d395ed0c30f411ceace4a91f7d93b148fb5a9b383d5d4cff7850f048d5f"
+dependencies = [
+ "diff",
+]
+
+[[package]]
+name = "unindent"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
+
+[[package]]
+name = "unstable-book-gen"
+version = "0.1.0"
+dependencies = [
+ "num-traits",
+ "tidy",
+]
+
+[[package]]
+name = "unwind"
+version = "0.0.0"
+dependencies = [
+ "cc",
+ "cfg-if 0.1.10",
+ "compiler_builtins",
+ "core",
+ "libc",
+]
+
+[[package]]
+name = "url"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
+dependencies = [
+ "idna 0.1.5",
+ "matches",
+ "percent-encoding 1.0.1",
+]
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.2.0",
+ "matches",
+ "percent-encoding 2.1.0",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+
+[[package]]
+name = "utf8parse"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "vergen"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbc87f9a7a9d61b15d51d1d3547284f67b6b4f1494ce3fc5814c101f35a5183"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "enum-iterator",
+ "getset",
+ "git2",
+ "rustversion",
+ "thiserror",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "vte"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xattr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "xz2"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c179869f34fc7c01830d3ce7ea2086bc3a07e0d35289b667d0a8bf910258926c"
+dependencies = [
+ "lzma-sys",
+]
+
+[[package]]
+name = "yaml-merge-keys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd236a7dc9bb598f349fe4a8754f49181fee50284daa15cd1ba652d722280004"
+dependencies = [
+ "lazy_static",
+ "thiserror",
+ "yaml-rust 0.4.4",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "yansi"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+
+[[package]]
+name = "yansi-term"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
+dependencies = [
+ "winapi",
+]
--- /dev/null
+#!/bin/bash
+
+set -ex
+
+echo "Removing the current docs for master"
+rm -rf out/master/ || exit 0
+
+echo "Making the docs for master"
+mkdir out/master/
+cp util/gh-pages/index.html out/master
++cp util/gh-pages/script.js out/master
+cp util/gh-pages/lints.json out/master
+
+if [[ -n $TAG_NAME ]]; then
+ echo "Save the doc for the current tag ($TAG_NAME) and point stable/ to it"
+ cp -Tr out/master "out/$TAG_NAME"
+ rm -f out/stable
+ ln -s "$TAG_NAME" out/stable
+fi
+
+if [[ $BETA = "true" ]]; then
+ echo "Update documentation for the beta release"
+ cp -r out/master/* out/beta
+fi
+
+# Generate version index that is shown as root index page
+cp util/gh-pages/versions.html out/index.html
+
+echo "Making the versions.json file"
+python3 ./util/versions.py out
+
+# Now let's go have some fun with the cloned repo
+cd out
+git config user.name "GHA CI"
+git config user.email "gha@ci.invalid"
+
+if [[ -n $TAG_NAME ]]; then
+ # track files, so that the following check works
+ git add --intent-to-add "$TAG_NAME"
+ if git diff --exit-code --quiet -- $TAG_NAME/; then
+ echo "No changes to the output on this push; exiting."
+ exit 0
+ fi
+ # Add the new dir
+ git add "$TAG_NAME"
+ # Update the symlink
+ git add stable
+ # Update versions file
+ git add versions.json
+ git commit -m "Add documentation for ${TAG_NAME} release: ${SHA}"
+elif [[ $BETA = "true" ]]; then
+ if git diff --exit-code --quiet -- beta/; then
+ echo "No changes to the output on this push; exiting."
+ exit 0
+ fi
+ git add beta
+ git commit -m "Automatic deploy to GitHub Pages (beta): ${SHA}"
+else
+ if git diff --exit-code --quiet; then
+ echo "No changes to the output on this push; exiting."
+ exit 0
+ fi
+ git add .
+ git commit -m "Automatic deploy to GitHub Pages: ${SHA}"
+fi
+
+git push "$SSH_REPO" "$TARGET_BRANCH"
--- /dev/null
- ## Rust 1.61 (beta)
+# Changelog
+
+All notable changes to this project will be documented in this file.
+See [Changelog Update](doc/changelog_update.md) if you want to update this
+document.
+
+## Unreleased / In Rust Nightly
+
+[d0cf3481...master](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...master)
+
- Current beta, released 2022-05-19
++## Rust 1.61
+
- Current stable, released 2022-04-07
++Current stable, released 2022-05-19
+
+[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
+
+### New Lints
+
+* [`only_used_in_recursion`]
+ [#8422](https://github.com/rust-lang/rust-clippy/pull/8422)
+* [`cast_enum_truncation`]
+ [#8381](https://github.com/rust-lang/rust-clippy/pull/8381)
+* [`missing_spin_loop`]
+ [#8174](https://github.com/rust-lang/rust-clippy/pull/8174)
+* [`deref_by_slicing`]
+ [#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
+* [`needless_match`]
+ [#8471](https://github.com/rust-lang/rust-clippy/pull/8471)
+* [`allow_attributes_without_reason`] (Requires `#![feature(lint_reasons)]`)
+ [#8504](https://github.com/rust-lang/rust-clippy/pull/8504)
+* [`print_in_format_impl`]
+ [#8253](https://github.com/rust-lang/rust-clippy/pull/8253)
+* [`unnecessary_find_map`]
+ [#8489](https://github.com/rust-lang/rust-clippy/pull/8489)
+* [`or_then_unwrap`]
+ [#8561](https://github.com/rust-lang/rust-clippy/pull/8561)
+* [`unnecessary_join`]
+ [#8579](https://github.com/rust-lang/rust-clippy/pull/8579)
+* [`iter_with_drain`]
+ [#8483](https://github.com/rust-lang/rust-clippy/pull/8483)
+* [`cast_enum_constructor`]
+ [#8562](https://github.com/rust-lang/rust-clippy/pull/8562)
+* [`cast_slice_different_sizes`]
+ [#8445](https://github.com/rust-lang/rust-clippy/pull/8445)
+
+### Moves and Deprecations
+
+* Moved [`transmute_undefined_repr`] to `nursery` (now allow-by-default)
+ [#8432](https://github.com/rust-lang/rust-clippy/pull/8432)
+* Moved [`try_err`] to `restriction`
+ [#8544](https://github.com/rust-lang/rust-clippy/pull/8544)
+* Move [`iter_with_drain`] to `nursery`
+ [#8541](https://github.com/rust-lang/rust-clippy/pull/8541)
+* Renamed `to_string_in_display` to [`recursive_format_impl`]
+ [#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
+
+### Enhancements
+
+* [`dbg_macro`]: The lint level can now be set with crate attributes and works inside macros
+ [#8411](https://github.com/rust-lang/rust-clippy/pull/8411)
+* [`ptr_as_ptr`]: Now works inside macros
+ [#8442](https://github.com/rust-lang/rust-clippy/pull/8442)
+* [`use_self`]: Now works for variants in match expressions
+ [#8456](https://github.com/rust-lang/rust-clippy/pull/8456)
+* [`await_holding_lock`]: Now lints for `parking_lot::{Mutex, RwLock}`
+ [#8419](https://github.com/rust-lang/rust-clippy/pull/8419)
+* [`recursive_format_impl`]: Now checks for format calls on `self`
+ [#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
+
+### False Positive Fixes
+
+* [`new_without_default`]: No longer lints for `new()` methods with `#[doc(hidden)]`
+ [#8472](https://github.com/rust-lang/rust-clippy/pull/8472)
+* [`transmute_undefined_repr`]: No longer lints for single field structs with `#[repr(C)]`,
+ generic parameters, wide pointers, unions, tuples and allow several forms of type erasure
+ [#8425](https://github.com/rust-lang/rust-clippy/pull/8425)
+ [#8553](https://github.com/rust-lang/rust-clippy/pull/8553)
+ [#8440](https://github.com/rust-lang/rust-clippy/pull/8440)
+ [#8547](https://github.com/rust-lang/rust-clippy/pull/8547)
+* [`match_single_binding`], [`match_same_arms`], [`match_as_ref`], [`match_bool`]: No longer
+ lint `match` expressions with `cfg`ed arms
+ [#8443](https://github.com/rust-lang/rust-clippy/pull/8443)
+* [`single_component_path_imports`]: No longer lint on macros
+ [#8537](https://github.com/rust-lang/rust-clippy/pull/8537)
+* [`ptr_arg`]: Allow `&mut` arguments for `Cow<_>`
+ [#8552](https://github.com/rust-lang/rust-clippy/pull/8552)
+* [`needless_borrow`]: No longer lints for method calls
+ [#8441](https://github.com/rust-lang/rust-clippy/pull/8441)
+* [`match_same_arms`]: Now ensures that interposing arm patterns don't overlap
+ [#8232](https://github.com/rust-lang/rust-clippy/pull/8232)
+* [`default_trait_access`]: Now allows `Default::default` in update expressions
+ [#8433](https://github.com/rust-lang/rust-clippy/pull/8433)
+
+### Suggestion Fixes/Improvements
+
+* [`redundant_slicing`]: Fixed suggestion for a method calls
+ [#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
+* [`map_flatten`]: Long suggestions will now be split up into two help messages
+ [#8520](https://github.com/rust-lang/rust-clippy/pull/8520)
+* [`unnecessary_lazy_evaluations`]: Now shows suggestions for longer code snippets
+ [#8543](https://github.com/rust-lang/rust-clippy/pull/8543)
+* [`unnecessary_sort_by`]: Now suggests `Reverse` including the path
+ [#8462](https://github.com/rust-lang/rust-clippy/pull/8462)
+* [`search_is_some`]: More suggestions are now `MachineApplicable`
+ [#8536](https://github.com/rust-lang/rust-clippy/pull/8536)
+
+### Documentation Improvements
+
+* [`new_without_default`]: Document `pub` requirement for the struct and fields
+ [#8429](https://github.com/rust-lang/rust-clippy/pull/8429)
+
+## Rust 1.60
+
++Released 2022-04-07
+
+[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
+
+### New Lints
+
+* [`single_char_lifetime_names`]
+ [#8236](https://github.com/rust-lang/rust-clippy/pull/8236)
+* [`iter_overeager_cloned`]
+ [#8203](https://github.com/rust-lang/rust-clippy/pull/8203)
+* [`transmute_undefined_repr`]
+ [#8398](https://github.com/rust-lang/rust-clippy/pull/8398)
+* [`default_union_representation`]
+ [#8289](https://github.com/rust-lang/rust-clippy/pull/8289)
+* [`manual_bits`]
+ [#8213](https://github.com/rust-lang/rust-clippy/pull/8213)
+* [`borrow_as_ptr`]
+ [#8210](https://github.com/rust-lang/rust-clippy/pull/8210)
+
+### Moves and Deprecations
+
+* Moved [`disallowed_methods`] and [`disallowed_types`] to `style` (now warn-by-default)
+ [#8261](https://github.com/rust-lang/rust-clippy/pull/8261)
+* Rename `ref_in_deref` to [`needless_borrow`]
+ [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* Moved [`mutex_atomic`] to `nursery` (now allow-by-default)
+ [#8260](https://github.com/rust-lang/rust-clippy/pull/8260)
+
+### Enhancements
+
+* [`ptr_arg`]: Now takes the argument usage into account and lints for mutable references
+ [#8271](https://github.com/rust-lang/rust-clippy/pull/8271)
+* [`unused_io_amount`]: Now supports async read and write traits
+ [#8179](https://github.com/rust-lang/rust-clippy/pull/8179)
+* [`while_let_on_iterator`]: Improved detection to catch more cases
+ [#8221](https://github.com/rust-lang/rust-clippy/pull/8221)
+* [`trait_duplication_in_bounds`]: Now covers trait functions with `Self` bounds
+ [#8252](https://github.com/rust-lang/rust-clippy/pull/8252)
+* [`unwrap_used`]: Now works for `.get(i).unwrap()` and `.get_mut(i).unwrap()`
+ [#8372](https://github.com/rust-lang/rust-clippy/pull/8372)
+* [`map_clone`]: The suggestion takes `msrv` into account
+ [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`manual_bits`] and [`borrow_as_ptr`]: Now track the `clippy::msrv` attribute
+ [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`disallowed_methods`]: Now works for methods on primitive types
+ [#8112](https://github.com/rust-lang/rust-clippy/pull/8112)
+* [`not_unsafe_ptr_arg_deref`]: Now works for type aliases
+ [#8273](https://github.com/rust-lang/rust-clippy/pull/8273)
+* [`needless_question_mark`]: Now works for async functions
+ [#8311](https://github.com/rust-lang/rust-clippy/pull/8311)
+* [`iter_not_returning_iterator`]: Now handles type projections
+ [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`wrong_self_convention`]: Now detects wrong `self` references in more cases
+ [#8208](https://github.com/rust-lang/rust-clippy/pull/8208)
+* [`single_match`]: Now works for `match` statements with tuples
+ [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+
+### False Positive Fixes
+
+* [`erasing_op`]: No longer triggers if the output type changes
+ [#8204](https://github.com/rust-lang/rust-clippy/pull/8204)
+* [`if_same_then_else`]: No longer triggers for `if let` statements
+ [#8297](https://github.com/rust-lang/rust-clippy/pull/8297)
+* [`manual_memcpy`]: No longer lints on `VecDeque`
+ [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`trait_duplication_in_bounds`]: Now takes path segments into account
+ [#8315](https://github.com/rust-lang/rust-clippy/pull/8315)
+* [`deref_addrof`]: No longer lints when the dereference or borrow occurs in different a context
+ [#8268](https://github.com/rust-lang/rust-clippy/pull/8268)
+* [`type_repetition_in_bounds`]: Now checks for full equality to prevent false positives
+ [#8224](https://github.com/rust-lang/rust-clippy/pull/8224)
+* [`ptr_arg`]: No longer lint for mutable references in traits
+ [#8369](https://github.com/rust-lang/rust-clippy/pull/8369)
+* [`implicit_clone`]: No longer lints for double references
+ [#8231](https://github.com/rust-lang/rust-clippy/pull/8231)
+* [`needless_lifetimes`]: No longer lints lifetimes for explicit `self` types
+ [#8278](https://github.com/rust-lang/rust-clippy/pull/8278)
+* [`op_ref`]: No longer lints in `BinOp` impl if that can cause recursion
+ [#8298](https://github.com/rust-lang/rust-clippy/pull/8298)
+* [`enum_variant_names`]: No longer triggers for empty variant names
+ [#8329](https://github.com/rust-lang/rust-clippy/pull/8329)
+* [`redundant_closure`]: No longer lints for `Arc<T>` or `Rc<T>`
+ [#8193](https://github.com/rust-lang/rust-clippy/pull/8193)
+* [`iter_not_returning_iterator`]: No longer lints on trait implementations but therefore on trait definitions
+ [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`single_match`]: No longer lints on exhaustive enum patterns without a wildcard
+ [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+* [`manual_swap`]: No longer lints on cases that involve automatic dereferences
+ [#8220](https://github.com/rust-lang/rust-clippy/pull/8220)
+* [`useless_format`]: Now works for implicit named arguments
+ [#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
+ [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* [`chars_next_cmp`]: Correctly excapes the suggestion
+ [#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
+* [`explicit_write`]: Add suggestions for `write!`s with format arguments
+ [#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
+* [`manual_memcpy`]: Suggests `copy_from_slice` when applicable
+ [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`or_fun_call`]: Improved suggestion display for long arguments
+ [#8292](https://github.com/rust-lang/rust-clippy/pull/8292)
+* [`unnecessary_cast`]: Now correctly includes the sign
+ [#8350](https://github.com/rust-lang/rust-clippy/pull/8350)
+* [`cmp_owned`]: No longer flips the comparison order
+ [#8299](https://github.com/rust-lang/rust-clippy/pull/8299)
+* [`explicit_counter_loop`]: Now correctly suggests `iter()` on references
+ [#8382](https://github.com/rust-lang/rust-clippy/pull/8382)
+
+### ICE Fixes
+
+* [`manual_split_once`]
+ [#8250](https://github.com/rust-lang/rust-clippy/pull/8250)
+
+### Documentation Improvements
+
+* [`map_flatten`]: Add documentation for the `Option` type
+ [#8354](https://github.com/rust-lang/rust-clippy/pull/8354)
+* Document that Clippy's driver might use a different code generation than rustc
+ [#8037](https://github.com/rust-lang/rust-clippy/pull/8037)
+* Clippy's lint list will now automatically focus the search box
+ [#8343](https://github.com/rust-lang/rust-clippy/pull/8343)
+
+### Others
+
+* Clippy now warns if we find multiple Clippy config files exist
+ [#8326](https://github.com/rust-lang/rust-clippy/pull/8326)
+
+## Rust 1.59
+
+Released 2022-02-24
+
+[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
+
+### New Lints
+
+* [`index_refutable_slice`]
+ [#7643](https://github.com/rust-lang/rust-clippy/pull/7643)
+* [`needless_splitn`]
+ [#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
+* [`unnecessary_to_owned`]
+ [#7978](https://github.com/rust-lang/rust-clippy/pull/7978)
+* [`needless_late_init`]
+ [#7995](https://github.com/rust-lang/rust-clippy/pull/7995)
+* [`octal_escapes`] [#8007](https://github.com/rust-lang/rust-clippy/pull/8007)
+* [`return_self_not_must_use`]
+ [#8071](https://github.com/rust-lang/rust-clippy/pull/8071)
+* [`init_numbered_fields`]
+ [#8170](https://github.com/rust-lang/rust-clippy/pull/8170)
+
+### Moves and Deprecations
+
+* Move `if_then_panic` to `pedantic` and rename to [`manual_assert`] (now
+ allow-by-default) [#7810](https://github.com/rust-lang/rust-clippy/pull/7810)
+* Rename `disallow_type` to [`disallowed_types`] and `disallowed_method` to
+ [`disallowed_methods`]
+ [#7984](https://github.com/rust-lang/rust-clippy/pull/7984)
+* Move [`map_flatten`] to `complexity` (now warn-by-default)
+ [#8054](https://github.com/rust-lang/rust-clippy/pull/8054)
+
+### Enhancements
+
+* [`match_overlapping_arm`]: Fix false negative where after included ranges,
+ overlapping ranges weren't linted anymore
+ [#7909](https://github.com/rust-lang/rust-clippy/pull/7909)
+* [`deprecated_cfg_attr`]: Now takes the specified MSRV into account
+ [#7944](https://github.com/rust-lang/rust-clippy/pull/7944)
+* [`cast_lossless`]: Now also lints for `bool` to integer casts
+ [#7948](https://github.com/rust-lang/rust-clippy/pull/7948)
+* [`let_underscore_lock`]: Also emit lints for the `parking_lot` crate
+ [#7957](https://github.com/rust-lang/rust-clippy/pull/7957)
+* [`needless_borrow`]
+ [#7977](https://github.com/rust-lang/rust-clippy/pull/7977)
+ * Lint when a borrow is auto-dereffed more than once
+ * Lint in the trailing expression of a block for a match arm
+* [`strlen_on_c_strings`]
+ [8001](https://github.com/rust-lang/rust-clippy/pull/8001)
+ * Lint when used without a fully-qualified path
+ * Suggest removing the surrounding unsafe block when possible
+* [`non_ascii_literal`]: Now also lints on `char`s, not just `string`s
+ [#8034](https://github.com/rust-lang/rust-clippy/pull/8034)
+* [`single_char_pattern`]: Now also lints on `split_inclusive`, `split_once`,
+ `rsplit_once`, `replace`, and `replacen`
+ [#8077](https://github.com/rust-lang/rust-clippy/pull/8077)
+* [`unwrap_or_else_default`]: Now also lints on `std` constructors like
+ `Vec::new`, `HashSet::new`, and `HashMap::new`
+ [#8163](https://github.com/rust-lang/rust-clippy/pull/8163)
+* [`shadow_reuse`]: Now also lints on shadowed `if let` bindings, instead of
+ [`shadow_unrelated`]
+ [#8165](https://github.com/rust-lang/rust-clippy/pull/8165)
+
+### False Positive Fixes
+
+* [`or_fun_call`], [`unnecessary_lazy_evaluations`]: Improve heuristics, so that
+ cheap functions (e.g. calling `.len()` on a `Vec`) won't get linted anymore
+ [#7639](https://github.com/rust-lang/rust-clippy/pull/7639)
+* [`manual_split_once`]: No longer suggests code changing the original behavior
+ [#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
+* Don't show [`no_effect`] or [`unnecessary_operation`] warning for unit struct
+ implementing `FnOnce`
+ [#7898](https://github.com/rust-lang/rust-clippy/pull/7898)
+* [`semicolon_if_nothing_returned`]: Fixed a bug, where the lint wrongly
+ triggered on `let-else` statements
+ [#7955](https://github.com/rust-lang/rust-clippy/pull/7955)
+* [`if_then_some_else_none`]: No longer lints if there is an early return
+ [#7980](https://github.com/rust-lang/rust-clippy/pull/7980)
+* [`needless_collect`]: No longer suggests removal of `collect` when removal
+ would create code requiring mutably borrowing a value multiple times
+ [#7982](https://github.com/rust-lang/rust-clippy/pull/7982)
+* [`shadow_same`]: Fix false positive for `async` function's params
+ [#7997](https://github.com/rust-lang/rust-clippy/pull/7997)
+* [`suboptimal_flops`]: No longer triggers in constant functions
+ [#8009](https://github.com/rust-lang/rust-clippy/pull/8009)
+* [`type_complexity`]: No longer lints on associated types in traits
+ [#8030](https://github.com/rust-lang/rust-clippy/pull/8030)
+* [`question_mark`]: No longer lints if returned object is not local
+ [#8080](https://github.com/rust-lang/rust-clippy/pull/8080)
+* [`option_if_let_else`]: No longer lint on complex sub-patterns
+ [#8086](https://github.com/rust-lang/rust-clippy/pull/8086)
+* [`blocks_in_if_conditions`]: No longer lints on empty closures
+ [#8100](https://github.com/rust-lang/rust-clippy/pull/8100)
+* [`enum_variant_names`]: No longer lint when first prefix is only a substring
+ of a camel-case word
+ [#8127](https://github.com/rust-lang/rust-clippy/pull/8127)
+* [`identity_op`]: Only lint on integral operands
+ [#8183](https://github.com/rust-lang/rust-clippy/pull/8183)
+
+### Suggestion Fixes/Improvements
+
+* [`search_is_some`]: Fix suggestion for `any()` not taking item by reference
+ [#7463](https://github.com/rust-lang/rust-clippy/pull/7463)
+* [`almost_swapped`]: Now detects if there is a `no_std` or `no_core` attribute
+ and adapts the suggestion accordingly
+ [#7877](https://github.com/rust-lang/rust-clippy/pull/7877)
+* [`redundant_pattern_matching`]: Fix suggestion for deref expressions
+ [#7949](https://github.com/rust-lang/rust-clippy/pull/7949)
+* [`explicit_counter_loop`]: Now also produces a suggestion for non-`usize`
+ types [#7950](https://github.com/rust-lang/rust-clippy/pull/7950)
+* [`manual_map`]: Fix suggestion when used with unsafe functions and blocks
+ [#7968](https://github.com/rust-lang/rust-clippy/pull/7968)
+* [`option_map_or_none`]: Suggest `map` over `and_then` when possible
+ [#7971](https://github.com/rust-lang/rust-clippy/pull/7971)
+* [`option_if_let_else`]: No longer expands macros in the suggestion
+ [#7974](https://github.com/rust-lang/rust-clippy/pull/7974)
+* [`iter_cloned_collect`]: Suggest `copied` over `cloned` when possible
+ [#8006](https://github.com/rust-lang/rust-clippy/pull/8006)
+* [`doc_markdown`]: No longer uses inline hints to improve readability of
+ suggestion [#8011](https://github.com/rust-lang/rust-clippy/pull/8011)
+* [`needless_question_mark`]: Now better explains the suggestion
+ [#8028](https://github.com/rust-lang/rust-clippy/pull/8028)
+* [`single_char_pattern`]: Escape backslash `\` in suggestion
+ [#8067](https://github.com/rust-lang/rust-clippy/pull/8067)
+* [`needless_bool`]: Suggest `a != b` over `!(a == b)`
+ [#8117](https://github.com/rust-lang/rust-clippy/pull/8117)
+* [`iter_skip_next`]: Suggest to add a `mut` if it is necessary in order to
+ apply this lints suggestion
+ [#8133](https://github.com/rust-lang/rust-clippy/pull/8133)
+* [`neg_multiply`]: Now produces a suggestion
+ [#8144](https://github.com/rust-lang/rust-clippy/pull/8144)
+* [`needless_return`]: Now suggests the unit type `()` over an empty block `{}`
+ in match arms [#8185](https://github.com/rust-lang/rust-clippy/pull/8185)
+* [`suboptimal_flops`]: Now gives a syntactically correct suggestion for
+ `to_radians` and `to_degrees`
+ [#8187](https://github.com/rust-lang/rust-clippy/pull/8187)
+
+### ICE Fixes
+
+* [`undocumented_unsafe_blocks`]
+ [#7945](https://github.com/rust-lang/rust-clippy/pull/7945)
+ [#7988](https://github.com/rust-lang/rust-clippy/pull/7988)
+* [`unnecessary_cast`]
+ [#8167](https://github.com/rust-lang/rust-clippy/pull/8167)
+
+### Documentation Improvements
+
+* [`print_stdout`], [`print_stderr`], [`dbg_macro`]: Document how the lint level
+ can be changed crate-wide
+ [#8040](https://github.com/rust-lang/rust-clippy/pull/8040)
+* Added a note to the `README` that config changes don't apply to already
+ compiled code [#8175](https://github.com/rust-lang/rust-clippy/pull/8175)
+
+### Others
+
+* [Clippy's lint
+ list](https://rust-lang.github.io/rust-clippy/master/index.html) now displays
+ the version a lint was added. :tada:
+ [#7813](https://github.com/rust-lang/rust-clippy/pull/7813)
+* New and improved issue templates
+ [#8032](https://github.com/rust-lang/rust-clippy/pull/8032)
+* _Dev:_ Add `cargo dev lint` command, to run your modified Clippy version on a
+ file [#7917](https://github.com/rust-lang/rust-clippy/pull/7917)
+
+## Rust 1.58
+
+Released 2022-01-13
+
+[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
+
+### Rust 1.58.1
+
+* Move [`non_send_fields_in_send_ty`] to `nursery` (now allow-by-default)
+ [#8075](https://github.com/rust-lang/rust-clippy/pull/8075)
+* [`useless_format`]: Handle implicit named arguments
+ [#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
+
+### New lints
+
+* [`transmute_num_to_bytes`]
+ [#7805](https://github.com/rust-lang/rust-clippy/pull/7805)
+* [`match_str_case_mismatch`]
+ [#7806](https://github.com/rust-lang/rust-clippy/pull/7806)
+* [`format_in_format_args`], [`to_string_in_format_args`]
+ [#7743](https://github.com/rust-lang/rust-clippy/pull/7743)
+* [`uninit_vec`]
+ [#7682](https://github.com/rust-lang/rust-clippy/pull/7682)
+* [`fn_to_numeric_cast_any`]
+ [#7705](https://github.com/rust-lang/rust-clippy/pull/7705)
+* [`undocumented_unsafe_blocks`]
+ [#7748](https://github.com/rust-lang/rust-clippy/pull/7748)
+* [`trailing_empty_array`]
+ [#7838](https://github.com/rust-lang/rust-clippy/pull/7838)
+* [`string_slice`]
+ [#7878](https://github.com/rust-lang/rust-clippy/pull/7878)
+
+### Moves or deprecations of lints
+
+* Move [`non_send_fields_in_send_ty`] to `suspicious`
+ [#7874](https://github.com/rust-lang/rust-clippy/pull/7874)
+* Move [`non_ascii_literal`] to `restriction`
+ [#7907](https://github.com/rust-lang/rust-clippy/pull/7907)
+
+### Changes that expand what code existing lints cover
+
+* [`question_mark`] now covers `Result`
+ [#7840](https://github.com/rust-lang/rust-clippy/pull/7840)
+* Make [`useless_format`] recognize bare `format!("")`
+ [#7801](https://github.com/rust-lang/rust-clippy/pull/7801)
+* Lint on underscored variables with no side effects in [`no_effect`]
+ [#7775](https://github.com/rust-lang/rust-clippy/pull/7775)
+* Expand [`match_ref_pats`] to check for multiple reference patterns
+ [#7800](https://github.com/rust-lang/rust-clippy/pull/7800)
+
+### False positive fixes
+
+* Fix false positive of [`implicit_saturating_sub`] with `else` clause
+ [#7832](https://github.com/rust-lang/rust-clippy/pull/7832)
+* Fix [`question_mark`] when there is call in conditional predicate
+ [#7860](https://github.com/rust-lang/rust-clippy/pull/7860)
+* [`mut_mut`] no longer lints when type is defined in external macros
+ [#7795](https://github.com/rust-lang/rust-clippy/pull/7795)
+* Avoid [`eq_op`] in test functions
+ [#7811](https://github.com/rust-lang/rust-clippy/pull/7811)
+* [`cast_possible_truncation`] no longer lints when cast is coming from `signum`
+ method call [#7850](https://github.com/rust-lang/rust-clippy/pull/7850)
+* [`match_str_case_mismatch`] no longer lints on uncased characters
+ [#7865](https://github.com/rust-lang/rust-clippy/pull/7865)
+* [`ptr_arg`] no longer lints references to type aliases
+ [#7890](https://github.com/rust-lang/rust-clippy/pull/7890)
+* [`missing_safety_doc`] now also accepts "implementation safety" headers
+ [#7856](https://github.com/rust-lang/rust-clippy/pull/7856)
+* [`missing_safety_doc`] no longer lints if any parent has `#[doc(hidden)]`
+ attribute [#7849](https://github.com/rust-lang/rust-clippy/pull/7849)
+* [`if_not_else`] now ignores else-if statements
+ [#7895](https://github.com/rust-lang/rust-clippy/pull/7895)
+* Avoid linting [`cast_possible_truncation`] on bit-reducing operations
+ [#7819](https://github.com/rust-lang/rust-clippy/pull/7819)
+* Avoid linting [`field_reassign_with_default`] when `Drop` and `Copy` are
+ involved [#7794](https://github.com/rust-lang/rust-clippy/pull/7794)
+* [`unnecessary_sort_by`] now checks if argument implements `Ord` trait
+ [#7824](https://github.com/rust-lang/rust-clippy/pull/7824)
+* Fix false positive in [`match_overlapping_arm`]
+ [#7847](https://github.com/rust-lang/rust-clippy/pull/7847)
+* Prevent [`needless_lifetimes`] false positive in `async` function definition
+ [#7901](https://github.com/rust-lang/rust-clippy/pull/7901)
+
+### Suggestion fixes/improvements
+
+* Keep an initial `::` when [`doc_markdown`] suggests to use ticks
+ [#7916](https://github.com/rust-lang/rust-clippy/pull/7916)
+* Add a machine applicable suggestion for the [`doc_markdown`] missing backticks
+ lint [#7904](https://github.com/rust-lang/rust-clippy/pull/7904)
+* [`equatable_if_let`] no longer expands macros in the suggestion
+ [#7788](https://github.com/rust-lang/rust-clippy/pull/7788)
+* Make [`shadow_reuse`] suggestion less verbose
+ [#7782](https://github.com/rust-lang/rust-clippy/pull/7782)
+
+### ICE fixes
+
+* Fix ICE in [`enum_variant_names`]
+ [#7873](https://github.com/rust-lang/rust-clippy/pull/7873)
+* Fix ICE in [`undocumented_unsafe_blocks`]
+ [#7891](https://github.com/rust-lang/rust-clippy/pull/7891)
+
+### Documentation improvements
+
+* Fixed naive doc formatting for `#[must_use]` lints ([`must_use_unit`],
+ [`double_must_use`], [`must_use_candidate`], [`let_underscore_must_use`])
+ [#7827](https://github.com/rust-lang/rust-clippy/pull/7827)
+* Fix typo in example for [`match_result_ok`]
+ [#7815](https://github.com/rust-lang/rust-clippy/pull/7815)
+
+### Others
+
+* Allow giving reasons for [`disallowed_types`]
+ [#7791](https://github.com/rust-lang/rust-clippy/pull/7791)
+* Fix [`manual_assert`] and [`match_wild_err_arm`] for `#![no_std]` and Rust
+ 2021. [#7851](https://github.com/rust-lang/rust-clippy/pull/7851)
+* Fix regression in [`semicolon_if_nothing_returned`] on macros containing while
+ loops [#7789](https://github.com/rust-lang/rust-clippy/pull/7789)
+* Added a new configuration `literal-suffix-style` to enforce a certain style
+ writing [`unseparated_literal_suffix`]
+ [#7726](https://github.com/rust-lang/rust-clippy/pull/7726)
+
+## Rust 1.57
+
+Released 2021-12-02
+
+[7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa)
+
+### New Lints
+
+* [`negative_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`redundant_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`mod_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`self_named_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`manual_split_once`]
+ [#7565](https://github.com/rust-lang/rust-clippy/pull/7565)
+* [`derivable_impls`]
+ [#7570](https://github.com/rust-lang/rust-clippy/pull/7570)
+* [`needless_option_as_deref`]
+ [#7596](https://github.com/rust-lang/rust-clippy/pull/7596)
+* [`iter_not_returning_iterator`]
+ [#7610](https://github.com/rust-lang/rust-clippy/pull/7610)
+* [`same_name_method`]
+ [#7653](https://github.com/rust-lang/rust-clippy/pull/7653)
+* [`manual_assert`] [#7669](https://github.com/rust-lang/rust-clippy/pull/7669)
+* [`non_send_fields_in_send_ty`]
+ [#7709](https://github.com/rust-lang/rust-clippy/pull/7709)
+* [`equatable_if_let`]
+ [#7762](https://github.com/rust-lang/rust-clippy/pull/7762)
+
+### Moves and Deprecations
+
+* Move [`shadow_unrelated`] to `restriction`
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* Move [`option_if_let_else`] to `nursery`
+ [#7568](https://github.com/rust-lang/rust-clippy/pull/7568)
+* Move [`branches_sharing_code`] to `nursery`
+ [#7595](https://github.com/rust-lang/rust-clippy/pull/7595)
+* Rename `if_let_some_result` to [`match_result_ok`] which now also handles
+ `while let` cases [#7608](https://github.com/rust-lang/rust-clippy/pull/7608)
+* Move [`many_single_char_names`] to `pedantic`
+ [#7671](https://github.com/rust-lang/rust-clippy/pull/7671)
+* Move [`float_cmp`] to `pedantic`
+ [#7692](https://github.com/rust-lang/rust-clippy/pull/7692)
+* Rename `box_vec` to [`box_collection`] and lint on more general cases
+ [#7693](https://github.com/rust-lang/rust-clippy/pull/7693)
+* Uplift `invalid_atomic_ordering` to rustc
+ [rust-lang/rust#84039](https://github.com/rust-lang/rust/pull/84039)
+
+### Enhancements
+
+* Rewrite the `shadow*` lints, so that they find a lot more shadows and are not
+ limited to certain patterns
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* The `avoid-breaking-exported-api` configuration now also works for
+ [`box_collection`], [`redundant_allocation`], [`rc_buffer`], [`vec_box`],
+ [`option_option`], [`linkedlist`], [`rc_mutex`]
+ [#7560](https://github.com/rust-lang/rust-clippy/pull/7560)
+* [`unnecessary_unwrap`]: Now also checks for `expect`s
+ [#7584](https://github.com/rust-lang/rust-clippy/pull/7584)
+* [`disallowed_methods`]: Allow adding a reason that will be displayed with the
+ lint message
+ [#7621](https://github.com/rust-lang/rust-clippy/pull/7621)
+* [`approx_constant`]: Now checks the MSRV for `LOG10_2` and `LOG2_10`
+ [#7629](https://github.com/rust-lang/rust-clippy/pull/7629)
+* [`approx_constant`]: Add `TAU`
+ [#7642](https://github.com/rust-lang/rust-clippy/pull/7642)
+* [`needless_borrow`]: Now also lints on needless mutable borrows
+ [#7657](https://github.com/rust-lang/rust-clippy/pull/7657)
+* [`missing_safety_doc`]: Now also lints on unsafe traits
+ [#7734](https://github.com/rust-lang/rust-clippy/pull/7734)
+
+### False Positive Fixes
+
+* [`manual_map`]: No longer lints when the option is borrowed in the match and
+ also consumed in the arm
+ [#7531](https://github.com/rust-lang/rust-clippy/pull/7531)
+* [`filter_next`]: No longer lints if `filter` method is not the
+ `Iterator::filter` method
+ [#7562](https://github.com/rust-lang/rust-clippy/pull/7562)
+* [`manual_flatten`]: No longer lints if expression is used after `if let`
+ [#7566](https://github.com/rust-lang/rust-clippy/pull/7566)
+* [`option_if_let_else`]: Multiple fixes
+ [#7573](https://github.com/rust-lang/rust-clippy/pull/7573)
+ * `break` and `continue` statements local to the would-be closure are
+ allowed
+ * Don't lint in const contexts
+ * Don't lint when yield expressions are used
+ * Don't lint when the captures made by the would-be closure conflict with
+ the other branch
+ * Don't lint when a field of a local is used when the type could be
+ potentially moved from
+ * In some cases, don't lint when scrutinee expression conflicts with the
+ captures of the would-be closure
+* [`redundant_allocation`]: No longer lints on `Box<Box<dyn T>>` which replaces
+ wide pointers with thin pointers
+ [#7592](https://github.com/rust-lang/rust-clippy/pull/7592)
+* [`bool_assert_comparison`]: No longer lints on types that do not implement the
+ `Not` trait with `Output = bool`
+ [#7605](https://github.com/rust-lang/rust-clippy/pull/7605)
+* [`mut_range_bound`]: No longer lints on range bound mutations, that are
+ immediately followed by a `break;`
+ [#7607](https://github.com/rust-lang/rust-clippy/pull/7607)
+* [`mutable_key_type`]: Improve accuracy and document remaining false positives
+ and false negatives
+ [#7640](https://github.com/rust-lang/rust-clippy/pull/7640)
+* [`redundant_closure`]: Rewrite the lint to fix various false positives and
+ false negatives [#7661](https://github.com/rust-lang/rust-clippy/pull/7661)
+* [`large_enum_variant`]: No longer wrongly identifies the second largest
+ variant [#7677](https://github.com/rust-lang/rust-clippy/pull/7677)
+* [`needless_return`]: No longer lints on let-else expressions
+ [#7685](https://github.com/rust-lang/rust-clippy/pull/7685)
+* [`suspicious_else_formatting`]: No longer lints in proc-macros
+ [#7707](https://github.com/rust-lang/rust-clippy/pull/7707)
+* [`excessive_precision`]: No longer lints when in some cases the float was
+ already written in the shortest form
+ [#7722](https://github.com/rust-lang/rust-clippy/pull/7722)
+* [`doc_markdown`]: No longer lints on intra-doc links
+ [#7772](https://github.com/rust-lang/rust-clippy/pull/7772)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_operation`]: Recommend using an `assert!` instead of using a
+ function call in an indexing operation
+ [#7453](https://github.com/rust-lang/rust-clippy/pull/7453)
+* [`manual_split_once`]: Produce semantically equivalent suggestion when
+ `rsplitn` is used [#7663](https://github.com/rust-lang/rust-clippy/pull/7663)
+* [`while_let_on_iterator`]: Produce correct suggestion when using `&mut`
+ [#7690](https://github.com/rust-lang/rust-clippy/pull/7690)
+* [`manual_assert`]: No better handles complex conditions
+ [#7741](https://github.com/rust-lang/rust-clippy/pull/7741)
+* Correctly handle signs in exponents in numeric literals lints
+ [#7747](https://github.com/rust-lang/rust-clippy/pull/7747)
+* [`suspicious_map`]: Now also suggests to use `inspect` as an alternative
+ [#7770](https://github.com/rust-lang/rust-clippy/pull/7770)
+* Drop exponent from suggestion if it is 0 in numeric literals lints
+ [#7774](https://github.com/rust-lang/rust-clippy/pull/7774)
+
+### ICE Fixes
+
+* [`implicit_hasher`]
+ [#7761](https://github.com/rust-lang/rust-clippy/pull/7761)
+
+### Others
+
+* Clippy now uses the 2021
+ [Edition!](https://www.youtube.com/watch?v=q0aNduqb2Ro)
+ [#7664](https://github.com/rust-lang/rust-clippy/pull/7664)
+
+## Rust 1.56
+
+Released 2021-10-21
+
+[74d1561...7bfc26e](https://github.com/rust-lang/rust-clippy/compare/74d1561...7bfc26e)
+
+### New Lints
+
+* [`unwrap_or_else_default`]
+ [#7516](https://github.com/rust-lang/rust-clippy/pull/7516)
+
+### Enhancements
+
+* [`needless_continue`]: Now also lints in `loop { continue; }` case
+ [#7477](https://github.com/rust-lang/rust-clippy/pull/7477)
+* [`disallowed_types`]: Now also primitive types can be disallowed
+ [#7488](https://github.com/rust-lang/rust-clippy/pull/7488)
+* [`manual_swap`]: Now also lints on xor swaps
+ [#7506](https://github.com/rust-lang/rust-clippy/pull/7506)
+* [`map_flatten`]: Now also lints on the `Result` type
+ [#7522](https://github.com/rust-lang/rust-clippy/pull/7522)
+* [`no_effect`]: Now also lints on inclusive ranges
+ [#7556](https://github.com/rust-lang/rust-clippy/pull/7556)
+
+### False Positive Fixes
+
+* [`nonstandard_macro_braces`]: No longer lints on similar named nested macros
+ [#7478](https://github.com/rust-lang/rust-clippy/pull/7478)
+* [`too_many_lines`]: No longer lints in closures to avoid duplicated diagnostics
+ [#7534](https://github.com/rust-lang/rust-clippy/pull/7534)
+* [`similar_names`]: No longer complains about `iter` and `item` being too
+ similar [#7546](https://github.com/rust-lang/rust-clippy/pull/7546)
+
+### Suggestion Fixes/Improvements
+
+* [`similar_names`]: No longer suggests to insert or add an underscore as a fix
+ [#7221](https://github.com/rust-lang/rust-clippy/pull/7221)
+* [`new_without_default`]: No longer shows the full qualified type path when
+ suggesting adding a `Default` implementation
+ [#7493](https://github.com/rust-lang/rust-clippy/pull/7493)
+* [`while_let_on_iterator`]: Now suggests re-borrowing mutable references
+ [#7520](https://github.com/rust-lang/rust-clippy/pull/7520)
+* [`extend_with_drain`]: Improve code suggestion for mutable and immutable
+ references [#7533](https://github.com/rust-lang/rust-clippy/pull/7533)
+* [`trivially_copy_pass_by_ref`]: Now properly handles `Self` type
+ [#7535](https://github.com/rust-lang/rust-clippy/pull/7535)
+* [`never_loop`]: Now suggests using `if let` instead of a `for` loop when
+ applicable [#7541](https://github.com/rust-lang/rust-clippy/pull/7541)
+
+### Documentation Improvements
+
+* Clippy now uses a lint to generate its lint documentation. [Lints all the way
+ down](https://en.wikipedia.org/wiki/Turtles_all_the_way_down).
+ [#7502](https://github.com/rust-lang/rust-clippy/pull/7502)
+* Reworked Clippy's website:
+ [#7172](https://github.com/rust-lang/rust-clippy/issues/7172)
+ [#7279](https://github.com/rust-lang/rust-clippy/pull/7279)
+ * Added applicability information about lints
+ * Added a link to jump into the implementation
+ * Improved loading times
+ * Adapted some styling
+* `cargo clippy --help` now also explains the `--fix` and `--no-deps` flag
+ [#7492](https://github.com/rust-lang/rust-clippy/pull/7492)
+* [`unnested_or_patterns`]: Removed `or_patterns` feature gate in the code
+ example [#7507](https://github.com/rust-lang/rust-clippy/pull/7507)
+
+## Rust 1.55
+
+Released 2021-09-09
+
+[3ae8faf...74d1561](https://github.com/rust-lang/rust-clippy/compare/3ae8faf...74d1561)
+
+### Important Changes
+
+* Stabilized `cargo clippy --fix` :tada:
+ [#7405](https://github.com/rust-lang/rust-clippy/pull/7405)
+
+### New Lints
+
+* [`rc_mutex`]
+ [#7316](https://github.com/rust-lang/rust-clippy/pull/7316)
+* [`nonstandard_macro_braces`]
+ [#7299](https://github.com/rust-lang/rust-clippy/pull/7299)
+* [`strlen_on_c_strings`]
+ [#7243](https://github.com/rust-lang/rust-clippy/pull/7243)
+* [`self_named_constructors`]
+ [#7403](https://github.com/rust-lang/rust-clippy/pull/7403)
+* [`disallowed_script_idents`]
+ [#7400](https://github.com/rust-lang/rust-clippy/pull/7400)
+* [`disallowed_types`]
+ [#7315](https://github.com/rust-lang/rust-clippy/pull/7315)
+* [`missing_enforced_import_renames`]
+ [#7300](https://github.com/rust-lang/rust-clippy/pull/7300)
+* [`extend_with_drain`]
+ [#7270](https://github.com/rust-lang/rust-clippy/pull/7270)
+
+### Moves and Deprecations
+
+* Moved [`from_iter_instead_of_collect`] to `pedantic`
+ [#7375](https://github.com/rust-lang/rust-clippy/pull/7375)
+* Added `suspicious` as a new lint group for *code that is most likely wrong or useless*
+ [#7350](https://github.com/rust-lang/rust-clippy/pull/7350)
+ * Moved [`blanket_clippy_restriction_lints`] to `suspicious`
+ * Moved [`empty_loop`] to `suspicious`
+ * Moved [`eval_order_dependence`] to `suspicious`
+ * Moved [`float_equality_without_abs`] to `suspicious`
+ * Moved [`for_loops_over_fallibles`] to `suspicious`
+ * Moved [`misrefactored_assign_op`] to `suspicious`
+ * Moved [`mut_range_bound`] to `suspicious`
+ * Moved [`mutable_key_type`] to `suspicious`
+ * Moved [`suspicious_arithmetic_impl`] to `suspicious`
+ * Moved [`suspicious_assignment_formatting`] to `suspicious`
+ * Moved [`suspicious_else_formatting`] to `suspicious`
+ * Moved [`suspicious_map`] to `suspicious`
+ * Moved [`suspicious_op_assign_impl`] to `suspicious`
+ * Moved [`suspicious_unary_op_formatting`] to `suspicious`
+
+### Enhancements
+
+* [`while_let_on_iterator`]: Now suggests `&mut iter` inside closures
+ [#7262](https://github.com/rust-lang/rust-clippy/pull/7262)
+* [`doc_markdown`]:
+ * Now detects unbalanced ticks
+ [#7357](https://github.com/rust-lang/rust-clippy/pull/7357)
+ * Add `FreeBSD` to the default configuration as an allowed identifier
+ [#7334](https://github.com/rust-lang/rust-clippy/pull/7334)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]: Now allows wildcards for enums with unstable
+ or hidden variants
+ [#7407](https://github.com/rust-lang/rust-clippy/pull/7407)
+* [`redundant_allocation`]: Now additionally supports the `Arc<>` type
+ [#7308](https://github.com/rust-lang/rust-clippy/pull/7308)
+* [`blacklisted_name`]: Now allows blacklisted names in test code
+ [#7379](https://github.com/rust-lang/rust-clippy/pull/7379)
+* [`redundant_closure`]: Suggests `&mut` for `FnMut`
+ [#7437](https://github.com/rust-lang/rust-clippy/pull/7437)
+* [`disallowed_methods`], [`disallowed_types`]: The configuration values `disallowed-method` and `disallowed-type`
+ no longer require fully qualified paths
+ [#7345](https://github.com/rust-lang/rust-clippy/pull/7345)
+* [`zst_offset`]: Fixed lint invocation after it was accidentally suppressed
+ [#7396](https://github.com/rust-lang/rust-clippy/pull/7396)
+
+### False Positive Fixes
+
+* [`default_numeric_fallback`]: No longer lints on float literals as function arguments
+ [#7446](https://github.com/rust-lang/rust-clippy/pull/7446)
+* [`use_self`]: No longer lints on type parameters
+ [#7288](https://github.com/rust-lang/rust-clippy/pull/7288)
+* [`unimplemented`]: Now ignores the `assert` and `debug_assert` macros
+ [#7439](https://github.com/rust-lang/rust-clippy/pull/7439)
+* [`branches_sharing_code`]: Now always checks for block expressions
+ [#7462](https://github.com/rust-lang/rust-clippy/pull/7462)
+* [`field_reassign_with_default`]: No longer triggers in macros
+ [#7160](https://github.com/rust-lang/rust-clippy/pull/7160)
+* [`redundant_clone`]: No longer lints on required clones for borrowed data
+ [#7346](https://github.com/rust-lang/rust-clippy/pull/7346)
+* [`default_numeric_fallback`]: No longer triggers in external macros
+ [#7325](https://github.com/rust-lang/rust-clippy/pull/7325)
+* [`needless_bool`]: No longer lints in macros
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`useless_format`]: No longer triggers when additional text is being appended
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`assertions_on_constants`]: `cfg!(...)` is no longer considered to be a constant
+ [#7319](https://github.com/rust-lang/rust-clippy/pull/7319)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_collect`]: Now show correct lint messages for shadowed values
+ [#7289](https://github.com/rust-lang/rust-clippy/pull/7289)
+* [`wrong_pub_self_convention`]: The deprecated message now suggest the correct configuration value
+ [#7382](https://github.com/rust-lang/rust-clippy/pull/7382)
+* [`semicolon_if_nothing_returned`]: Allow missing semicolon in blocks with only one expression
+ [#7326](https://github.com/rust-lang/rust-clippy/pull/7326)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#7470](https://github.com/rust-lang/rust-clippy/pull/7470)
+* [`redundant_pattern_matching`]
+ [#7471](https://github.com/rust-lang/rust-clippy/pull/7471)
+* [`modulo_one`]
+ [#7473](https://github.com/rust-lang/rust-clippy/pull/7473)
+* [`use_self`]
+ [#7428](https://github.com/rust-lang/rust-clippy/pull/7428)
+
+## Rust 1.54
+
+Released 2021-07-29
+
+[7c7683c...3ae8faf](https://github.com/rust-lang/rust-clippy/compare/7c7683c...3ae8faf)
+
+### New Lints
+
+- [`ref_binding_to_reference`]
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`needless_bitwise_bool`]
+ [#7133](https://github.com/rust-lang/rust-clippy/pull/7133)
+- [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225)
+- [`manual_str_repeat`]
+ [#7265](https://github.com/rust-lang/rust-clippy/pull/7265)
+- [`suspicious_splitn`]
+ [#7292](https://github.com/rust-lang/rust-clippy/pull/7292)
+
+### Moves and Deprecations
+
+- Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of
+ the new `avoid-breaking-exported-api` config option (see
+ [Enhancements](#1-54-enhancements))
+ [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- Move [`inconsistent_struct_constructor`] to `pedantic`
+ [#7193](https://github.com/rust-lang/rust-clippy/pull/7193)
+- Move [`needless_borrow`] to `style` (now warn-by-default)
+ [#7254](https://github.com/rust-lang/rust-clippy/pull/7254)
+- Move [`suspicious_operation_groupings`] to `nursery`
+ [#7266](https://github.com/rust-lang/rust-clippy/pull/7266)
+- Move [`semicolon_if_nothing_returned`] to `pedantic`
+ [#7268](https://github.com/rust-lang/rust-clippy/pull/7268)
+
+### Enhancements <a name="1-54-enhancements"></a>
+
+- [`while_let_on_iterator`]: Now also lints in nested loops
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix`
+ [#7156](https://github.com/rust-lang/rust-clippy/pull/7156)
+- [`needless_collect`]: Now also lints on assignments with type annotations
+ [#7163](https://github.com/rust-lang/rust-clippy/pull/7163)
+- [`if_then_some_else_none`]: Now works with the MSRV config
+ [#7177](https://github.com/rust-lang/rust-clippy/pull/7177)
+- Add `avoid-breaking-exported-api` config option for the lints
+ [`enum_variant_names`], [`large_types_passed_by_value`],
+ [`trivially_copy_pass_by_ref`], [`unnecessary_wraps`],
+ [`upper_case_acronyms`], and [`wrong_self_convention`]. We recommend to set
+ this configuration option to `false` before a major release (1.0/2.0/...) to
+ clean up the API [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- [`needless_collect`]: Now lints on even more data structures
+ [#7188](https://github.com/rust-lang/rust-clippy/pull/7188)
+- [`missing_docs_in_private_items`]: No longer sees `#[<name> = "<value>"]` like
+ attributes as sufficient documentation
+ [#7281](https://github.com/rust-lang/rust-clippy/pull/7281)
+- [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]:
+ Now work as expected when used with `allow`
+ [#7282](https://github.com/rust-lang/rust-clippy/pull/7282)
+
+### False Positive Fixes
+
+- [`implicit_return`]: Now takes all diverging functions in account to avoid
+ false positives [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+- [`while_let_on_iterator`]: No longer lints when the iterator is a struct field
+ and the struct is used in the loop
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`multiple_inherent_impl`]: No longer lints with generic arguments
+ [#7089](https://github.com/rust-lang/rust-clippy/pull/7089)
+- [`comparison_chain`]: No longer lints in a `const` context
+ [#7118](https://github.com/rust-lang/rust-clippy/pull/7118)
+- [`while_immutable_condition`]: Fix false positive where mutation in the loop
+ variable wasn't picked up
+ [#7144](https://github.com/rust-lang/rust-clippy/pull/7144)
+- [`default_trait_access`]: No longer lints in macros
+ [#7150](https://github.com/rust-lang/rust-clippy/pull/7150)
+- [`needless_question_mark`]: No longer lints when the inner value is implicitly
+ dereferenced [#7165](https://github.com/rust-lang/rust-clippy/pull/7165)
+- [`unused_unit`]: No longer lints when multiple macro contexts are involved
+ [#7167](https://github.com/rust-lang/rust-clippy/pull/7167)
+- [`eval_order_dependence`]: Fix false positive in async context
+ [#7174](https://github.com/rust-lang/rust-clippy/pull/7174)
+- [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the
+ type [#7175](https://github.com/rust-lang/rust-clippy/pull/7175)
+- [`wrong_self_convention`]: No longer lints in trait implementations of
+ non-`Copy` types [#7182](https://github.com/rust-lang/rust-clippy/pull/7182)
+- [`suboptimal_flops`]: No longer lints on `powi(2)`
+ [#7201](https://github.com/rust-lang/rust-clippy/pull/7201)
+- [`wrong_self_convention`]: No longer lints if there is no implicit `self`
+ [#7215](https://github.com/rust-lang/rust-clippy/pull/7215)
+- [`option_if_let_else`]: No longer lints on `else if let` pattern
+ [#7216](https://github.com/rust-lang/rust-clippy/pull/7216)
+- [`use_self`], [`useless_conversion`]: Fix false positives when generic
+ arguments are involved
+ [#7223](https://github.com/rust-lang/rust-clippy/pull/7223)
+- [`manual_unwrap_or`]: Fix false positive with deref coercion
+ [#7233](https://github.com/rust-lang/rust-clippy/pull/7233)
+- [`similar_names`]: No longer lints on `wparam`/`lparam`
+ [#7255](https://github.com/rust-lang/rust-clippy/pull/7255)
+- [`redundant_closure`]: No longer lints on using the `vec![]` macro in a
+ closure [#7263](https://github.com/rust-lang/rust-clippy/pull/7263)
+
+### Suggestion Fixes/Improvements
+
+- [`implicit_return`]
+ [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+ - Fix suggestion for async functions
+ - Improve suggestion with macros
+ - Suggest to change `break` to `return` when appropriate
+- [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`match_single_binding`]: Improve suggestion when match scrutinee has side
+ effects [#7095](https://github.com/rust-lang/rust-clippy/pull/7095)
+- [`needless_borrow`]: Now suggests to also change usage sites as needed
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`write_with_newline`]: Improve suggestion when only `\n` is written to the
+ buffer [#7183](https://github.com/rust-lang/rust-clippy/pull/7183)
+- [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also
+ when a `<_ as Trait>::_` is involved
+ [#7264](https://github.com/rust-lang/rust-clippy/pull/7264)
+- [`not_unsafe_ptr_arg_deref`]: Improved error message
+ [#7294](https://github.com/rust-lang/rust-clippy/pull/7294)
+
+### ICE Fixes
+
+- Fix ICE when running Clippy on `libstd`
+ [#7140](https://github.com/rust-lang/rust-clippy/pull/7140)
+- [`implicit_return`]
+ [#7242](https://github.com/rust-lang/rust-clippy/pull/7242)
+
+## Rust 1.53
+
+Released 2021-06-17
+
+[6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c)
+
+### New Lints
+
+* [`option_filter_map`]
+ [#6342](https://github.com/rust-lang/rust-clippy/pull/6342)
+* [`branches_sharing_code`]
+ [#6463](https://github.com/rust-lang/rust-clippy/pull/6463)
+* [`needless_for_each`]
+ [#6706](https://github.com/rust-lang/rust-clippy/pull/6706)
+* [`if_then_some_else_none`]
+ [#6859](https://github.com/rust-lang/rust-clippy/pull/6859)
+* [`non_octal_unix_permissions`]
+ [#7001](https://github.com/rust-lang/rust-clippy/pull/7001)
+* [`unnecessary_self_imports`]
+ [#7072](https://github.com/rust-lang/rust-clippy/pull/7072)
+* [`bool_assert_comparison`]
+ [#7083](https://github.com/rust-lang/rust-clippy/pull/7083)
+* [`cloned_instead_of_copied`]
+ [#7098](https://github.com/rust-lang/rust-clippy/pull/7098)
+* [`flat_map_option`]
+ [#7101](https://github.com/rust-lang/rust-clippy/pull/7101)
+
+### Moves and Deprecations
+
+* Deprecate [`filter_map`] lint
+ [#7059](https://github.com/rust-lang/rust-clippy/pull/7059)
+* Move [`transmute_ptr_to_ptr`] to `pedantic`
+ [#7102](https://github.com/rust-lang/rust-clippy/pull/7102)
+
+### Enhancements
+
+* [`mem_replace_with_default`]: Also lint on common std constructors
+ [#6820](https://github.com/rust-lang/rust-clippy/pull/6820)
+* [`wrong_self_convention`]: Also lint on `to_*_mut` methods
+ [#6828](https://github.com/rust-lang/rust-clippy/pull/6828)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]:
+ [#6863](https://github.com/rust-lang/rust-clippy/pull/6863)
+ * Attempt to find a common path prefix in suggestion
+ * Don't lint on `Option` and `Result`
+ * Consider `Self` prefix
+* [`explicit_deref_methods`]: Also lint on chained `deref` calls
+ [#6865](https://github.com/rust-lang/rust-clippy/pull/6865)
+* [`or_fun_call`]: Also lint on `unsafe` blocks
+ [#6928](https://github.com/rust-lang/rust-clippy/pull/6928)
+* [`vec_box`], [`linkedlist`], [`option_option`]: Also lint in `const` and
+ `static` items [#6938](https://github.com/rust-lang/rust-clippy/pull/6938)
+* [`search_is_some`]: Also check for `is_none`
+ [#6942](https://github.com/rust-lang/rust-clippy/pull/6942)
+* [`string_lit_as_bytes`]: Also lint on `into_bytes`
+ [#6959](https://github.com/rust-lang/rust-clippy/pull/6959)
+* [`len_without_is_empty`]: Also lint if function signatures of `len` and
+ `is_empty` don't match
+ [#6980](https://github.com/rust-lang/rust-clippy/pull/6980)
+* [`redundant_pattern_matching`]: Also lint if the pattern is a `&` pattern
+ [#6991](https://github.com/rust-lang/rust-clippy/pull/6991)
+* [`clone_on_copy`]: Also lint on chained method calls taking `self` by value
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`missing_panics_doc`]: Also lint on `assert_eq!` and `assert_ne!`
+ [#7029](https://github.com/rust-lang/rust-clippy/pull/7029)
+* [`needless_return`]: Also lint in `async` functions
+ [#7067](https://github.com/rust-lang/rust-clippy/pull/7067)
+* [`unused_io_amount`]: Also lint on expressions like `_.read().ok()?`
+ [#7100](https://github.com/rust-lang/rust-clippy/pull/7100)
+* [`iter_cloned_collect`]: Also lint on large arrays, since const-generics are
+ now stable [#7138](https://github.com/rust-lang/rust-clippy/pull/7138)
+
+### False Positive Fixes
+
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6805](https://github.com/rust-lang/rust-clippy/pull/6805)
+* [`suspicious_map`]: No longer lints when side effects may occur inside the
+ `map` call [#6831](https://github.com/rust-lang/rust-clippy/pull/6831)
+* [`manual_map`], [`manual_unwrap_or`]: No longer lints in `const` functions
+ [#6917](https://github.com/rust-lang/rust-clippy/pull/6917)
+* [`wrong_self_convention`]: Now respects `Copy` types
+ [#6924](https://github.com/rust-lang/rust-clippy/pull/6924)
+* [`needless_question_mark`]: No longer lints if the `?` and the `Some(..)` come
+ from different macro contexts [#6935](https://github.com/rust-lang/rust-clippy/pull/6935)
+* [`map_entry`]: Better detect if the entry API can be used
+ [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`or_fun_call`]: No longer lints on some `len` function calls
+ [#6950](https://github.com/rust-lang/rust-clippy/pull/6950)
+* [`new_ret_no_self`]: No longer lints when `Self` is returned with different
+ generic arguments [#6952](https://github.com/rust-lang/rust-clippy/pull/6952)
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6981](https://github.com/rust-lang/rust-clippy/pull/6981)
+* [`explicit_into_iter_loop`]: Only lint when `into_iter` is an implementation
+ of `IntoIterator` [#6982](https://github.com/rust-lang/rust-clippy/pull/6982)
+* [`expl_impl_clone_on_copy`]: Take generic constraints into account before
+ suggesting to use `derive` instead
+ [#6993](https://github.com/rust-lang/rust-clippy/pull/6993)
+* [`missing_panics_doc`]: No longer lints when only debug-assertions are used
+ [#6996](https://github.com/rust-lang/rust-clippy/pull/6996)
+* [`clone_on_copy`]: Only lint when using the `Clone` trait
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`wrong_self_convention`]: No longer lints inside a trait implementation
+ [#7002](https://github.com/rust-lang/rust-clippy/pull/7002)
+* [`redundant_clone`]: No longer lints when the cloned value is modified while
+ the clone is in use
+ [#7011](https://github.com/rust-lang/rust-clippy/pull/7011)
+* [`same_item_push`]: No longer lints if the `Vec` is used in the loop body
+ [#7018](https://github.com/rust-lang/rust-clippy/pull/7018)
+* [`cargo_common_metadata`]: Remove author requirement
+ [#7026](https://github.com/rust-lang/rust-clippy/pull/7026)
+* [`panic_in_result_fn`]: No longer lints on `debug_assert` family
+ [#7060](https://github.com/rust-lang/rust-clippy/pull/7060)
+* [`panic`]: No longer wrongfully lints on `debug_assert` with message
+ [#7063](https://github.com/rust-lang/rust-clippy/pull/7063)
+* [`wrong_self_convention`]: No longer lints in trait implementations where no
+ `self` is involved [#7064](https://github.com/rust-lang/rust-clippy/pull/7064)
+* [`missing_const_for_fn`]: No longer lints when unstable `const` function is
+ involved [#7076](https://github.com/rust-lang/rust-clippy/pull/7076)
+* [`suspicious_else_formatting`]: Allow Allman style braces
+ [#7087](https://github.com/rust-lang/rust-clippy/pull/7087)
+* [`inconsistent_struct_constructor`]: No longer lints in macros
+ [#7097](https://github.com/rust-lang/rust-clippy/pull/7097)
+* [`single_component_path_imports`]: No longer lints on macro re-exports
+ [#7120](https://github.com/rust-lang/rust-clippy/pull/7120)
+
+### Suggestion Fixes/Improvements
+
+* [`redundant_pattern_matching`]: Add a note when applying this lint would
+ change the drop order
+ [#6568](https://github.com/rust-lang/rust-clippy/pull/6568)
+* [`write_literal`], [`print_literal`]: Add auto-applicable suggestion
+ [#6821](https://github.com/rust-lang/rust-clippy/pull/6821)
+* [`manual_map`]: Fix suggestion for complex `if let ... else` chains
+ [#6856](https://github.com/rust-lang/rust-clippy/pull/6856)
+* [`inconsistent_struct_constructor`]: Make lint description and message clearer
+ [#6892](https://github.com/rust-lang/rust-clippy/pull/6892)
+* [`map_entry`]: Now suggests `or_insert`, `insert_with` or `match _.entry(_)`
+ as appropriate [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`manual_flatten`]: Suggest to insert `copied` if necessary
+ [#6962](https://github.com/rust-lang/rust-clippy/pull/6962)
+* [`redundant_slicing`]: Fix suggestion when a re-borrow might be required or
+ when the value is from a macro call
+ [#6975](https://github.com/rust-lang/rust-clippy/pull/6975)
+* [`match_wildcard_for_single_variants`]: Fix suggestion for hidden variant
+ [#6988](https://github.com/rust-lang/rust-clippy/pull/6988)
+* [`clone_on_copy`]: Correct suggestion when the cloned value is a macro call
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`manual_map`]: Fix suggestion at the end of an if chain
+ [#7004](https://github.com/rust-lang/rust-clippy/pull/7004)
+* Fix needless parenthesis output in multiple lint suggestions
+ [#7013](https://github.com/rust-lang/rust-clippy/pull/7013)
+* [`needless_collect`]: Better explanation in the lint message
+ [#7020](https://github.com/rust-lang/rust-clippy/pull/7020)
+* [`useless_vec`]: Now considers mutability
+ [#7036](https://github.com/rust-lang/rust-clippy/pull/7036)
+* [`useless_format`]: Wrap the content in braces if necessary
+ [#7092](https://github.com/rust-lang/rust-clippy/pull/7092)
+* [`single_match`]: Don't suggest an equality check for types which don't
+ implement `PartialEq`
+ [#7093](https://github.com/rust-lang/rust-clippy/pull/7093)
+* [`from_over_into`]: Mention type in help message
+ [#7099](https://github.com/rust-lang/rust-clippy/pull/7099)
+* [`manual_unwrap_or`]: Fix invalid code suggestion due to a macro call
+ [#7136](https://github.com/rust-lang/rust-clippy/pull/7136)
+
+### ICE Fixes
+
+* [`macro_use_imports`]
+ [#7022](https://github.com/rust-lang/rust-clippy/pull/7022)
+* [`missing_panics_doc`]
+ [#7034](https://github.com/rust-lang/rust-clippy/pull/7034)
+* [`tabs_in_doc_comments`]
+ [#7039](https://github.com/rust-lang/rust-clippy/pull/7039)
+* [`missing_const_for_fn`]
+ [#7128](https://github.com/rust-lang/rust-clippy/pull/7128)
+
+### Others
+
+* [Clippy's lint
+ list](https://rust-lang.github.io/rust-clippy/master/index.html) now supports
+ themes [#7030](https://github.com/rust-lang/rust-clippy/pull/7030)
+* Lints that were uplifted to `rustc` now mention the new `rustc` name in the
+ deprecation warning
+ [#7056](https://github.com/rust-lang/rust-clippy/pull/7056)
+
+## Rust 1.52
+
+Released 2021-05-06
+
+[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
+
+### New Lints
+
+* [`from_str_radix_10`]
+ [#6717](https://github.com/rust-lang/rust-clippy/pull/6717)
+* [`implicit_clone`]
+ [#6730](https://github.com/rust-lang/rust-clippy/pull/6730)
+* [`semicolon_if_nothing_returned`]
+ [#6681](https://github.com/rust-lang/rust-clippy/pull/6681)
+* [`manual_flatten`]
+ [#6646](https://github.com/rust-lang/rust-clippy/pull/6646)
+* [`inconsistent_struct_constructor`]
+ [#6769](https://github.com/rust-lang/rust-clippy/pull/6769)
+* [`iter_count`]
+ [#6791](https://github.com/rust-lang/rust-clippy/pull/6791)
+* [`default_numeric_fallback`]
+ [#6662](https://github.com/rust-lang/rust-clippy/pull/6662)
+* [`bytes_nth`]
+ [#6695](https://github.com/rust-lang/rust-clippy/pull/6695)
+* [`filter_map_identity`]
+ [#6685](https://github.com/rust-lang/rust-clippy/pull/6685)
+* [`manual_map`]
+ [#6573](https://github.com/rust-lang/rust-clippy/pull/6573)
+
+### Moves and Deprecations
+
+* Moved [`upper_case_acronyms`] to `pedantic`
+ [#6775](https://github.com/rust-lang/rust-clippy/pull/6775)
+* Moved [`manual_map`] to `nursery`
+ [#6796](https://github.com/rust-lang/rust-clippy/pull/6796)
+* Moved [`unnecessary_wraps`] to `pedantic`
+ [#6765](https://github.com/rust-lang/rust-clippy/pull/6765)
+* Moved [`trivial_regex`] to `nursery`
+ [#6696](https://github.com/rust-lang/rust-clippy/pull/6696)
+* Moved [`naive_bytecount`] to `pedantic`
+ [#6825](https://github.com/rust-lang/rust-clippy/pull/6825)
+* Moved [`upper_case_acronyms`] to `style`
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* Moved [`manual_map`] to `style`
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+
+### Enhancements
+
+* [`disallowed_methods`]: Now supports functions in addition to methods
+ [#6674](https://github.com/rust-lang/rust-clippy/pull/6674)
+* [`upper_case_acronyms`]: Added a new configuration `upper-case-acronyms-aggressive` to
+ trigger the lint if there is more than one uppercase character next to each other
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* [`collapsible_match`]: Now supports block comparison with different value names
+ [#6754](https://github.com/rust-lang/rust-clippy/pull/6754)
+* [`unnecessary_wraps`]: Will now suggest removing unnecessary wrapped return unit type, like `Option<()>`
+ [#6665](https://github.com/rust-lang/rust-clippy/pull/6665)
+* Improved value usage detection in closures
+ [#6698](https://github.com/rust-lang/rust-clippy/pull/6698)
+
+### False Positive Fixes
+
+* [`use_self`]: No longer lints in macros
+ [#6833](https://github.com/rust-lang/rust-clippy/pull/6833)
+* [`use_self`]: Fixed multiple false positives for: generics, associated types and derive implementations
+ [#6179](https://github.com/rust-lang/rust-clippy/pull/6179)
+* [`missing_inline_in_public_items`]: No longer lints for procedural macros
+ [#6814](https://github.com/rust-lang/rust-clippy/pull/6814)
+* [`inherent_to_string`]: No longer lints on functions with function generics
+ [#6771](https://github.com/rust-lang/rust-clippy/pull/6771)
+* [`doc_markdown`]: Add `OpenDNS` to the default configuration as an allowed identifier
+ [#6783](https://github.com/rust-lang/rust-clippy/pull/6783)
+* [`missing_panics_doc`]: No longer lints on [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html)
+ [#6700](https://github.com/rust-lang/rust-clippy/pull/6700)
+* [`collapsible_if`]: No longer lints on if statements with attributes
+ [#6701](https://github.com/rust-lang/rust-clippy/pull/6701)
+* [`match_same_arms`]: Only considers empty blocks as equal if the tokens contained are the same
+ [#6843](https://github.com/rust-lang/rust-clippy/pull/6843)
+* [`redundant_closure`]: Now ignores macros
+ [#6871](https://github.com/rust-lang/rust-clippy/pull/6871)
+* [`manual_map`]: Fixed false positives when control flow statements like `return`, `break` etc. are used
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* [`vec_init_then_push`]: Fixed false positives for loops and if statements
+ [#6697](https://github.com/rust-lang/rust-clippy/pull/6697)
+* [`len_without_is_empty`]: Will now consider multiple impl blocks and `#[allow]` on
+ the `len` method as well as the type definition.
+ [#6853](https://github.com/rust-lang/rust-clippy/pull/6853)
+* [`let_underscore_drop`]: Only lints on types which implement `Drop`
+ [#6682](https://github.com/rust-lang/rust-clippy/pull/6682)
+* [`unit_arg`]: No longer lints on unit arguments when they come from a path expression.
+ [#6601](https://github.com/rust-lang/rust-clippy/pull/6601)
+* [`cargo_common_metadata`]: No longer lints if
+ [`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)
+ is defined in the manifest
+ [#6650](https://github.com/rust-lang/rust-clippy/pull/6650)
+
+### Suggestion Fixes/Improvements
+
+* [`collapsible_match`]: Fixed lint message capitalization
+ [#6766](https://github.com/rust-lang/rust-clippy/pull/6766)
+* [`or_fun_call`]: Improved suggestions for `or_insert(vec![])`
+ [#6790](https://github.com/rust-lang/rust-clippy/pull/6790)
+* [`manual_map`]: No longer expands macros in the suggestions
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* Aligned Clippy's lint messages with the rustc dev guide
+ [#6787](https://github.com/rust-lang/rust-clippy/pull/6787)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#6866](https://github.com/rust-lang/rust-clippy/pull/6866)
+
+### Documentation Improvements
+
+* [`useless_format`]: Improved the documentation example
+ [#6854](https://github.com/rust-lang/rust-clippy/pull/6854)
+* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper
+ [#6782](https://github.com/rust-lang/rust-clippy/pull/6782)
+
+### Others
+* Running `cargo clippy` after `cargo check` now works as expected
+ (`cargo clippy` and `cargo check` no longer shares the same build cache)
+ [#6687](https://github.com/rust-lang/rust-clippy/pull/6687)
+* Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed.
+ [#6834](https://github.com/rust-lang/rust-clippy/pull/6834)
+* Extracted Clippy's `utils` module into the new `clippy_utils` crate
+ [#6756](https://github.com/rust-lang/rust-clippy/pull/6756)
+* Clippy lintcheck tool improvements
+ [#6800](https://github.com/rust-lang/rust-clippy/pull/6800)
+ [#6735](https://github.com/rust-lang/rust-clippy/pull/6735)
+ [#6764](https://github.com/rust-lang/rust-clippy/pull/6764)
+ [#6708](https://github.com/rust-lang/rust-clippy/pull/6708)
+ [#6780](https://github.com/rust-lang/rust-clippy/pull/6780)
+ [#6686](https://github.com/rust-lang/rust-clippy/pull/6686)
+
+## Rust 1.51
+
+Released 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
+
+Released 2021-02-11
+
+[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
+
+### New Lints
+
+* [`suspicious_operation_groupings`] [#6086](https://github.com/rust-lang/rust-clippy/pull/6086)
+* [`size_of_in_element_count`] [#6394](https://github.com/rust-lang/rust-clippy/pull/6394)
+* [`unnecessary_wraps`] [#6070](https://github.com/rust-lang/rust-clippy/pull/6070)
+* [`let_underscore_drop`] [#6305](https://github.com/rust-lang/rust-clippy/pull/6305)
+* [`collapsible_match`] [#6402](https://github.com/rust-lang/rust-clippy/pull/6402)
+* [`redundant_else`] [#6330](https://github.com/rust-lang/rust-clippy/pull/6330)
+* [`zero_sized_map_values`] [#6218](https://github.com/rust-lang/rust-clippy/pull/6218)
+* [`print_stderr`] [#6367](https://github.com/rust-lang/rust-clippy/pull/6367)
+* [`string_from_utf8_as_bytes`] [#6134](https://github.com/rust-lang/rust-clippy/pull/6134)
+
+### Moves and Deprecations
+
+* Previously deprecated [`str_to_string`] and [`string_to_string`] have been un-deprecated
+ as `restriction` lints [#6333](https://github.com/rust-lang/rust-clippy/pull/6333)
+* Deprecate `panic_params` lint. This is now available in rustc as `non_fmt_panics`
+ [#6351](https://github.com/rust-lang/rust-clippy/pull/6351)
+* Move [`map_err_ignore`] to `restriction`
+ [#6416](https://github.com/rust-lang/rust-clippy/pull/6416)
+* Move [`await_holding_refcell_ref`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+* Move [`await_holding_lock`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+
+### Enhancements
+
+* Add the `unreadable-literal-lint-fractions` configuration to disable
+ the `unreadable_literal` lint for fractions
+ [#6421](https://github.com/rust-lang/rust-clippy/pull/6421)
+* [`clone_on_copy`]: Now shows the type in the lint message
+ [#6443](https://github.com/rust-lang/rust-clippy/pull/6443)
+* [`redundant_pattern_matching`]: Now also lints on `std::task::Poll`
+ [#6339](https://github.com/rust-lang/rust-clippy/pull/6339)
+* [`redundant_pattern_matching`]: Additionally also lints on `std::net::IpAddr`
+ [#6377](https://github.com/rust-lang/rust-clippy/pull/6377)
+* [`search_is_some`]: Now suggests `contains` instead of `find(foo).is_some()`
+ [#6119](https://github.com/rust-lang/rust-clippy/pull/6119)
+* [`clone_double_ref`]: Now prints the reference type in the lint message
+ [#6442](https://github.com/rust-lang/rust-clippy/pull/6442)
+* [`modulo_one`]: Now also lints on -1.
+ [#6360](https://github.com/rust-lang/rust-clippy/pull/6360)
+* [`empty_loop`]: Now lints no_std crates, too
+ [#6205](https://github.com/rust-lang/rust-clippy/pull/6205)
+* [`or_fun_call`]: Now also lints when indexing `HashMap` or `BTreeMap`
+ [#6267](https://github.com/rust-lang/rust-clippy/pull/6267)
+* [`wrong_self_convention`]: Now also lints in trait definitions
+ [#6316](https://github.com/rust-lang/rust-clippy/pull/6316)
+* [`needless_borrow`]: Print the type in the lint message
+ [#6449](https://github.com/rust-lang/rust-clippy/pull/6449)
+
+[msrv_readme]: https://github.com/rust-lang/rust-clippy#specifying-the-minimum-supported-rust-version
+
+### False Positive Fixes
+
+* [`manual_range_contains`]: No longer lints in `const fn`
+ [#6382](https://github.com/rust-lang/rust-clippy/pull/6382)
+* [`unnecessary_lazy_evaluations`]: No longer lints if closure argument is used
+ [#6370](https://github.com/rust-lang/rust-clippy/pull/6370)
+* [`match_single_binding`]: Now ignores cases with `#[cfg()]` macros
+ [#6435](https://github.com/rust-lang/rust-clippy/pull/6435)
+* [`match_like_matches_macro`]: No longer lints on arms with attributes
+ [#6290](https://github.com/rust-lang/rust-clippy/pull/6290)
+* [`map_clone`]: No longer lints with deref and clone
+ [#6269](https://github.com/rust-lang/rust-clippy/pull/6269)
+* [`map_clone`]: No longer lints in the case of &mut
+ [#6301](https://github.com/rust-lang/rust-clippy/pull/6301)
+* [`needless_update`]: Now ignores `non_exhaustive` structs
+ [#6464](https://github.com/rust-lang/rust-clippy/pull/6464)
+* [`needless_collect`]: No longer lints when a collect is needed multiple times
+ [#6313](https://github.com/rust-lang/rust-clippy/pull/6313)
+* [`unnecessary_cast`] No longer lints cfg-dependent types
+ [#6369](https://github.com/rust-lang/rust-clippy/pull/6369)
+* [`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
+
+* [`vec_box`]: Provide correct type scope suggestion
+ [#6271](https://github.com/rust-lang/rust-clippy/pull/6271)
+* [`manual_range_contains`]: Give correct suggestion when using floats
+ [#6320](https://github.com/rust-lang/rust-clippy/pull/6320)
+* [`unnecessary_lazy_evaluations`]: Don't always mark suggestion as MachineApplicable
+ [#6272](https://github.com/rust-lang/rust-clippy/pull/6272)
+* [`manual_async_fn`]: Improve suggestion formatting
+ [#6294](https://github.com/rust-lang/rust-clippy/pull/6294)
+* [`unnecessary_cast`]: Fix incorrectly formatted float literal suggestion
+ [#6362](https://github.com/rust-lang/rust-clippy/pull/6362)
+
+### ICE Fixes
+
+* Fix a crash in [`from_iter_instead_of_collect`]
+ [#6304](https://github.com/rust-lang/rust-clippy/pull/6304)
+* Fix a silent crash when parsing doc comments in [`needless_doctest_main`]
+ [#6458](https://github.com/rust-lang/rust-clippy/pull/6458)
+
+### Documentation Improvements
+
+* The lint website search has been improved ([#6477](https://github.com/rust-lang/rust-clippy/pull/6477)):
+ * Searching for lints with dashes and spaces is possible now. For example
+ `missing-errors-doc` and `missing errors doc` are now valid aliases for lint names
+ * Improved fuzzy search in lint descriptions
+* Various README improvements
+ [#6287](https://github.com/rust-lang/rust-clippy/pull/6287)
+* Add known problems to [`comparison_chain`] documentation
+ [#6390](https://github.com/rust-lang/rust-clippy/pull/6390)
+* Fix example used in [`cargo_common_metadata`]
+ [#6293](https://github.com/rust-lang/rust-clippy/pull/6293)
+* Improve [`map_clone`] documentation
+ [#6340](https://github.com/rust-lang/rust-clippy/pull/6340)
+
+### Others
+
+* You can now tell Clippy about the MSRV your project supports. Please refer to
+ the specific README section to learn more about MSRV support [here][msrv_readme]
+ [#6201](https://github.com/rust-lang/rust-clippy/pull/6201)
+* Add `--no-deps` option to avoid running on path dependencies in workspaces
+ [#6188](https://github.com/rust-lang/rust-clippy/pull/6188)
+
+## Rust 1.49
+
+Released 2020-12-31
+
+[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1)
+
+### New Lints
+
+* [`field_reassign_with_default`] [#5911](https://github.com/rust-lang/rust-clippy/pull/5911)
+* [`await_holding_refcell_ref`] [#6029](https://github.com/rust-lang/rust-clippy/pull/6029)
+* [`disallowed_methods`] [#6081](https://github.com/rust-lang/rust-clippy/pull/6081)
+* [`inline_asm_x86_att_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`inline_asm_x86_intel_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`from_iter_instead_of_collect`] [#6101](https://github.com/rust-lang/rust-clippy/pull/6101)
+* [`mut_mutex_lock`] [#6103](https://github.com/rust-lang/rust-clippy/pull/6103)
+* [`single_element_loop`] [#6109](https://github.com/rust-lang/rust-clippy/pull/6109)
+* [`manual_unwrap_or`] [#6123](https://github.com/rust-lang/rust-clippy/pull/6123)
+* [`large_types_passed_by_value`] [#6135](https://github.com/rust-lang/rust-clippy/pull/6135)
+* [`result_unit_err`] [#6157](https://github.com/rust-lang/rust-clippy/pull/6157)
+* [`ref_option_ref`] [#6165](https://github.com/rust-lang/rust-clippy/pull/6165)
+* [`manual_range_contains`] [#6177](https://github.com/rust-lang/rust-clippy/pull/6177)
+* [`unusual_byte_groupings`] [#6183](https://github.com/rust-lang/rust-clippy/pull/6183)
+* [`comparison_to_empty`] [#6226](https://github.com/rust-lang/rust-clippy/pull/6226)
+* [`map_collect_result_unit`] [#6227](https://github.com/rust-lang/rust-clippy/pull/6227)
+* [`manual_ok_or`] [#6233](https://github.com/rust-lang/rust-clippy/pull/6233)
+
+### Moves and Deprecations
+
+* Rename `single_char_push_str` to [`single_char_add_str`]
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* Rename `zero_width_space` to [`invisible_characters`]
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* Deprecate `drop_bounds` (uplifted)
+ [#6111](https://github.com/rust-lang/rust-clippy/pull/6111)
+* Move [`string_lit_as_bytes`] to `nursery`
+ [#6117](https://github.com/rust-lang/rust-clippy/pull/6117)
+* Move [`rc_buffer`] to `restriction`
+ [#6128](https://github.com/rust-lang/rust-clippy/pull/6128)
+
+### Enhancements
+
+* [`manual_memcpy`]: Also lint when there are loop counters (and produce a
+ reliable suggestion)
+ [#5727](https://github.com/rust-lang/rust-clippy/pull/5727)
+* [`single_char_add_str`]: Also lint on `String::insert_str`
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* [`invisible_characters`]: Also lint the characters `\u{AD}` and `\u{2060}`
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* [`eq_op`]: Also lint on the `assert_*!` macro family
+ [#6167](https://github.com/rust-lang/rust-clippy/pull/6167)
+* [`items_after_statements`]: Also lint in local macro expansions
+ [#6176](https://github.com/rust-lang/rust-clippy/pull/6176)
+* [`unnecessary_cast`]: Also lint casts on integer and float literals
+ [#6187](https://github.com/rust-lang/rust-clippy/pull/6187)
+* [`manual_unwrap_or`]: Also lint `Result::unwrap_or`
+ [#6190](https://github.com/rust-lang/rust-clippy/pull/6190)
+* [`match_like_matches_macro`]: Also lint when `match` has more than two arms
+ [#6216](https://github.com/rust-lang/rust-clippy/pull/6216)
+* [`integer_arithmetic`]: Better handle `/` an `%` operators
+ [#6229](https://github.com/rust-lang/rust-clippy/pull/6229)
+
+### False Positive Fixes
+
+* [`needless_lifetimes`]: Bail out if the function has a `where` clause with the
+ lifetime [#5978](https://github.com/rust-lang/rust-clippy/pull/5978)
+* [`explicit_counter_loop`]: No longer lints, when loop counter is used after it
+ is incremented [#6076](https://github.com/rust-lang/rust-clippy/pull/6076)
+* [`or_fun_call`]: Revert changes addressing the handling of `const fn`
+ [#6077](https://github.com/rust-lang/rust-clippy/pull/6077)
+* [`needless_range_loop`]: No longer lints, when the iterable is used in the
+ range [#6102](https://github.com/rust-lang/rust-clippy/pull/6102)
+* [`inconsistent_digit_grouping`]: Fix bug when using floating point exponent
+ [#6104](https://github.com/rust-lang/rust-clippy/pull/6104)
+* [`mistyped_literal_suffixes`]: No longer lints on the fractional part of a
+ float (e.g. `713.32_64`)
+ [#6114](https://github.com/rust-lang/rust-clippy/pull/6114)
+* [`invalid_regex`]: No longer lint on unicode characters within `bytes::Regex`
+ [#6132](https://github.com/rust-lang/rust-clippy/pull/6132)
+* [`boxed_local`]: No longer lints on `extern fn` arguments
+ [#6133](https://github.com/rust-lang/rust-clippy/pull/6133)
+* [`needless_lifetimes`]: Fix regression, where lifetime is used in `where`
+ clause [#6198](https://github.com/rust-lang/rust-clippy/pull/6198)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_sort_by`]: Avoid dereferencing the suggested closure parameter
+ [#6078](https://github.com/rust-lang/rust-clippy/pull/6078)
+* [`needless_arbitrary_self_type`]: Correctly handle expanded code
+ [#6093](https://github.com/rust-lang/rust-clippy/pull/6093)
+* [`useless_format`]: Preserve raw strings in suggestion
+ [#6151](https://github.com/rust-lang/rust-clippy/pull/6151)
+* [`empty_loop`]: Suggest alternatives
+ [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`borrowed_box`]: Correctly add parentheses in suggestion
+ [#6200](https://github.com/rust-lang/rust-clippy/pull/6200)
+* [`unused_unit`]: Improve suggestion formatting
+ [#6247](https://github.com/rust-lang/rust-clippy/pull/6247)
+
+### Documentation Improvements
+
+* Some doc improvements:
+ * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090)
+ * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`doc_markdown`]: Document problematic link text style
+ [#6107](https://github.com/rust-lang/rust-clippy/pull/6107)
+
+## Rust 1.48
+
+Released 2020-11-19
+
+[09bd400...e636b88](https://github.com/rust-lang/rust-clippy/compare/09bd400...e636b88)
+
+### New lints
+
+* [`self_assignment`] [#5894](https://github.com/rust-lang/rust-clippy/pull/5894)
+* [`unnecessary_lazy_evaluations`] [#5720](https://github.com/rust-lang/rust-clippy/pull/5720)
+* [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038)
+* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998)
+* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044)
+* `to_string_in_display` [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
+* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
+
+### Moves and Deprecations
+
+* Downgrade [`verbose_bit_mask`] to pedantic
+ [#6036](https://github.com/rust-lang/rust-clippy/pull/6036)
+
+### Enhancements
+
+* Extend [`precedence`] to handle chains of methods combined with unary negation
+ [#5928](https://github.com/rust-lang/rust-clippy/pull/5928)
+* [`useless_vec`]: add a configuration value for the maximum allowed size on the stack
+ [#5907](https://github.com/rust-lang/rust-clippy/pull/5907)
+* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr`
+ [#5884](https://github.com/rust-lang/rust-clippy/pull/5884)
+* `invalid_atomic_ordering`: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
+ [#6025](https://github.com/rust-lang/rust-clippy/pull/6025)
+* Avoid [`redundant_pattern_matching`] triggering in macros
+ [#6069](https://github.com/rust-lang/rust-clippy/pull/6069)
+* [`option_if_let_else`]: distinguish pure from impure `else` expressions
+ [#5937](https://github.com/rust-lang/rust-clippy/pull/5937)
+* [`needless_doctest_main`]: parse doctests instead of using textual search
+ [#5912](https://github.com/rust-lang/rust-clippy/pull/5912)
+* [`wildcard_imports`]: allow `prelude` to appear in any segment of an import
+ [#5929](https://github.com/rust-lang/rust-clippy/pull/5929)
+* Re-enable [`len_zero`] for ranges now that `range_is_empty` is stable
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+* [`option_as_ref_deref`]: catch fully-qualified calls to `Deref::deref` and `DerefMut::deref_mut`
+ [#5933](https://github.com/rust-lang/rust-clippy/pull/5933)
+
+### False Positive Fixes
+
+* [`useless_attribute`]: permit allowing [`wildcard_imports`] and [`enum_glob_use`]
+ [#5994](https://github.com/rust-lang/rust-clippy/pull/5994)
+* [`transmute_ptr_to_ptr`]: avoid suggesting dereferencing raw pointers in const contexts
+ [#5999](https://github.com/rust-lang/rust-clippy/pull/5999)
+* [`redundant_closure_call`]: take into account usages of the closure in nested functions and closures
+ [#5920](https://github.com/rust-lang/rust-clippy/pull/5920)
+* Fix false positive in [`borrow_interior_mutable_const`] when referencing a field behind a pointer
+ [#5949](https://github.com/rust-lang/rust-clippy/pull/5949)
+* [`doc_markdown`]: allow using "GraphQL" without backticks
+ [#5996](https://github.com/rust-lang/rust-clippy/pull/5996)
+* `to_string_in_display`: avoid linting when calling `to_string()` on anything that is not `self`
+ [#5971](https://github.com/rust-lang/rust-clippy/pull/5971)
+* [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays
+ [#6034](https://github.com/rust-lang/rust-clippy/pull/6034)
+* [`should_implement_trait`]: ignore methods with lifetime parameters
+ [#5725](https://github.com/rust-lang/rust-clippy/pull/5725)
+* [`needless_return`]: avoid linting if a temporary borrows a local variable
+ [#5903](https://github.com/rust-lang/rust-clippy/pull/5903)
+* Restrict [`unnecessary_sort_by`] to non-reference, Copy types
+ [#6006](https://github.com/rust-lang/rust-clippy/pull/6006)
+* Avoid suggesting `from_bits`/`to_bits` in const contexts in [`transmute_int_to_float`]
+ [#5919](https://github.com/rust-lang/rust-clippy/pull/5919)
+* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: improve detection of interior mutable types
+ [#6046](https://github.com/rust-lang/rust-clippy/pull/6046)
+
+### Suggestion Fixes/Improvements
+
+* [`let_and_return`]: add a cast to the suggestion when the return expression has adjustments
+ [#5946](https://github.com/rust-lang/rust-clippy/pull/5946)
+* [`useless_conversion`]: show the type in the error message
+ [#6035](https://github.com/rust-lang/rust-clippy/pull/6035)
+* [`unnecessary_mut_passed`]: discriminate between functions and methods in the error message
+ [#5892](https://github.com/rust-lang/rust-clippy/pull/5892)
+* [`float_cmp`] and [`float_cmp_const`]: change wording to make margin of error less ambiguous
+ [#6043](https://github.com/rust-lang/rust-clippy/pull/6043)
+* [`default_trait_access`]: do not use unnecessary type parameters in the suggestion
+ [#5993](https://github.com/rust-lang/rust-clippy/pull/5993)
+* [`collapsible_if`]: don't use expanded code in the suggestion
+ [#5992](https://github.com/rust-lang/rust-clippy/pull/5992)
+* Do not suggest empty format strings in [`print_with_newline`] and [`write_with_newline`]
+ [#6042](https://github.com/rust-lang/rust-clippy/pull/6042)
+* [`unit_arg`]: improve the readability of the suggestion
+ [#5931](https://github.com/rust-lang/rust-clippy/pull/5931)
+* [`stable_sort_primitive`]: print the type that is being sorted in the lint message
+ [#5935](https://github.com/rust-lang/rust-clippy/pull/5935)
+* Show line count and max lines in [`too_many_lines`] lint message
+ [#6009](https://github.com/rust-lang/rust-clippy/pull/6009)
+* Keep parentheses in the suggestion of [`useless_conversion`] where applicable
+ [#5900](https://github.com/rust-lang/rust-clippy/pull/5900)
+* [`option_map_unit_fn`] and [`result_map_unit_fn`]: print the unit type `()` explicitly
+ [#6024](https://github.com/rust-lang/rust-clippy/pull/6024)
+* [`redundant_allocation`]: suggest replacing `Rc<Box<T>>` with `Rc<T>`
+ [#5899](https://github.com/rust-lang/rust-clippy/pull/5899)
+* Make lint messages adhere to rustc dev guide conventions
+ [#5893](https://github.com/rust-lang/rust-clippy/pull/5893)
+
+### ICE Fixes
+
+* Fix ICE in [`repeat_once`]
+ [#5948](https://github.com/rust-lang/rust-clippy/pull/5948)
+
+### Documentation Improvements
+
+* [`mutable_key_type`]: explain potential for false positives when the interior mutable type is not accessed in the `Hash` implementation
+ [#6019](https://github.com/rust-lang/rust-clippy/pull/6019)
+* [`unnecessary_mut_passed`]: fix typo
+ [#5913](https://github.com/rust-lang/rust-clippy/pull/5913)
+* Add example of false positive to [`ptr_arg`] docs.
+ [#5885](https://github.com/rust-lang/rust-clippy/pull/5885)
+* [`box_vec`](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection), [`vec_box`] and [`borrowed_box`]: add link to the documentation of `Box`
+ [#6023](https://github.com/rust-lang/rust-clippy/pull/6023)
+
+## Rust 1.47
+
+Released 2020-10-08
+
+[c2c07fa...09bd400](https://github.com/rust-lang/rust-clippy/compare/c2c07fa...09bd400)
+
+### New lints
+
+* [`derive_ord_xor_partial_ord`] [#5848](https://github.com/rust-lang/rust-clippy/pull/5848)
+* [`trait_duplication_in_bounds`] [#5852](https://github.com/rust-lang/rust-clippy/pull/5852)
+* [`map_identity`] [#5694](https://github.com/rust-lang/rust-clippy/pull/5694)
+* [`unit_return_expecting_ord`] [#5737](https://github.com/rust-lang/rust-clippy/pull/5737)
+* [`pattern_type_mismatch`] [#4841](https://github.com/rust-lang/rust-clippy/pull/4841)
+* [`repeat_once`] [#5773](https://github.com/rust-lang/rust-clippy/pull/5773)
+* [`same_item_push`] [#5825](https://github.com/rust-lang/rust-clippy/pull/5825)
+* [`needless_arbitrary_self_type`] [#5869](https://github.com/rust-lang/rust-clippy/pull/5869)
+* [`match_like_matches_macro`] [#5769](https://github.com/rust-lang/rust-clippy/pull/5769)
+* [`stable_sort_primitive`] [#5809](https://github.com/rust-lang/rust-clippy/pull/5809)
+* [`blanket_clippy_restriction_lints`] [#5750](https://github.com/rust-lang/rust-clippy/pull/5750)
+* [`option_if_let_else`] [#5301](https://github.com/rust-lang/rust-clippy/pull/5301)
+
+### Moves and Deprecations
+
+* Deprecate [`regex_macro`] lint
+ [#5760](https://github.com/rust-lang/rust-clippy/pull/5760)
+* Move [`range_minus_one`] to `pedantic`
+ [#5752](https://github.com/rust-lang/rust-clippy/pull/5752)
+
+### Enhancements
+
+* Improve [`needless_collect`] by catching `collect` calls followed by `iter` or `into_iter` calls
+ [#5837](https://github.com/rust-lang/rust-clippy/pull/5837)
+* [`panic`], [`todo`], [`unimplemented`] and [`unreachable`] now detect calls with formatting
+ [#5811](https://github.com/rust-lang/rust-clippy/pull/5811)
+* Detect more cases of [`suboptimal_flops`] and [`imprecise_flops`]
+ [#5443](https://github.com/rust-lang/rust-clippy/pull/5443)
+* Handle asymmetrical implementations of `PartialEq` in [`cmp_owned`]
+ [#5701](https://github.com/rust-lang/rust-clippy/pull/5701)
+* Make it possible to allow [`unsafe_derive_deserialize`]
+ [#5870](https://github.com/rust-lang/rust-clippy/pull/5870)
+* Catch `ord.min(a).max(b)` where a < b in [`min_max`]
+ [#5871](https://github.com/rust-lang/rust-clippy/pull/5871)
+* Make [`clone_on_copy`] suggestion machine applicable
+ [#5745](https://github.com/rust-lang/rust-clippy/pull/5745)
+* Enable [`len_zero`] on ranges now that `is_empty` is stable on them
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+
+### False Positive Fixes
+
+* Avoid triggering [`or_fun_call`] with const fns that take no arguments
+ [#5889](https://github.com/rust-lang/rust-clippy/pull/5889)
+* Fix [`redundant_closure_call`] false positive for closures that have multiple calls
+ [#5800](https://github.com/rust-lang/rust-clippy/pull/5800)
+* Don't lint cases involving `ManuallyDrop` in [`redundant_clone`]
+ [#5824](https://github.com/rust-lang/rust-clippy/pull/5824)
+* Treat a single expression the same as a single statement in the 2nd arm of a match in [`single_match_else`]
+ [#5771](https://github.com/rust-lang/rust-clippy/pull/5771)
+* Don't trigger [`unnested_or_patterns`] if the feature `or_patterns` is not enabled
+ [#5758](https://github.com/rust-lang/rust-clippy/pull/5758)
+* Avoid linting if key borrows in [`unnecessary_sort_by`]
+ [#5756](https://github.com/rust-lang/rust-clippy/pull/5756)
+* Consider `Try` impl for `Poll` when generating suggestions in [`try_err`]
+ [#5857](https://github.com/rust-lang/rust-clippy/pull/5857)
+* Take input lifetimes into account in `manual_async_fn`
+ [#5859](https://github.com/rust-lang/rust-clippy/pull/5859)
+* Fix multiple false positives in [`type_repetition_in_bounds`] and add a configuration option
+ [#5761](https://github.com/rust-lang/rust-clippy/pull/5761)
+* Limit the [`suspicious_arithmetic_impl`] lint to one binary operation
+ [#5820](https://github.com/rust-lang/rust-clippy/pull/5820)
+
+### Suggestion Fixes/Improvements
+
+* Improve readability of [`shadow_unrelated`] suggestion by truncating the RHS snippet
+ [#5788](https://github.com/rust-lang/rust-clippy/pull/5788)
+* Suggest `filter_map` instead of `flat_map` when mapping to `Option` in [`map_flatten`]
+ [#5846](https://github.com/rust-lang/rust-clippy/pull/5846)
+* Ensure suggestion is shown correctly for long method call chains in [`iter_nth_zero`]
+ [#5793](https://github.com/rust-lang/rust-clippy/pull/5793)
+* Drop borrow operator in suggestions of [`redundant_pattern_matching`]
+ [#5815](https://github.com/rust-lang/rust-clippy/pull/5815)
+* Add suggestion for [`iter_skip_next`]
+ [#5843](https://github.com/rust-lang/rust-clippy/pull/5843)
+* Improve [`collapsible_if`] fix suggestion
+ [#5732](https://github.com/rust-lang/rust-clippy/pull/5732)
+
+### ICE Fixes
+
+* Fix ICE caused by [`needless_collect`]
+ [#5877](https://github.com/rust-lang/rust-clippy/pull/5877)
+* Fix ICE caused by [`unnested_or_patterns`]
+ [#5784](https://github.com/rust-lang/rust-clippy/pull/5784)
+
+### Documentation Improvements
+
+* Fix grammar of [`await_holding_lock`] documentation
+ [#5748](https://github.com/rust-lang/rust-clippy/pull/5748)
+
+### Others
+
+* Make lints adhere to the rustc dev guide
+ [#5888](https://github.com/rust-lang/rust-clippy/pull/5888)
+
+## Rust 1.46
+
+Released 2020-08-27
+
+[7ea7cd1...c2c07fa](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...c2c07fa)
+
+### New lints
+
+* [`unnested_or_patterns`] [#5378](https://github.com/rust-lang/rust-clippy/pull/5378)
+* [`iter_next_slice`] [#5597](https://github.com/rust-lang/rust-clippy/pull/5597)
+* [`unnecessary_sort_by`] [#5623](https://github.com/rust-lang/rust-clippy/pull/5623)
+* [`vec_resize_to_zero`] [#5637](https://github.com/rust-lang/rust-clippy/pull/5637)
+
+### Moves and Deprecations
+
+* Move [`cast_ptr_alignment`] to pedantic [#5667](https://github.com/rust-lang/rust-clippy/pull/5667)
+
+### Enhancements
+
+* Improve [`mem_replace_with_uninit`] lint [#5695](https://github.com/rust-lang/rust-clippy/pull/5695)
+
+### False Positive Fixes
+
+* [`len_zero`]: Avoid linting ranges when the `range_is_empty` feature is not enabled
+ [#5656](https://github.com/rust-lang/rust-clippy/pull/5656)
+* [`let_and_return`]: Don't lint if a temporary borrow is involved
+ [#5680](https://github.com/rust-lang/rust-clippy/pull/5680)
+* [`reversed_empty_ranges`]: Avoid linting `N..N` in for loop arguments in
+ [#5692](https://github.com/rust-lang/rust-clippy/pull/5692)
+* [`if_same_then_else`]: Don't assume multiplication is always commutative
+ [#5702](https://github.com/rust-lang/rust-clippy/pull/5702)
+* [`blacklisted_name`]: Remove `bar` from the default configuration
+ [#5712](https://github.com/rust-lang/rust-clippy/pull/5712)
+* [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts
+ [#5724](https://github.com/rust-lang/rust-clippy/pull/5724)
+
+### Suggestion Fixes/Improvements
+
+* Fix suggestion of [`unit_arg`] lint, so that it suggest semantic equivalent code
+ [#4455](https://github.com/rust-lang/rust-clippy/pull/4455)
+* Add auto applicable suggestion to [`macro_use_imports`]
+ [#5279](https://github.com/rust-lang/rust-clippy/pull/5279)
+
+### ICE Fixes
+
+* Fix ICE in the `consts` module of Clippy [#5709](https://github.com/rust-lang/rust-clippy/pull/5709)
+
+### Documentation Improvements
+
+* Improve code examples across multiple lints [#5664](https://github.com/rust-lang/rust-clippy/pull/5664)
+
+### Others
+
+* Introduce a `--rustc` flag to `clippy-driver`, which turns `clippy-driver`
+ into `rustc` and passes all the given arguments to `rustc`. This is especially
+ useful for tools that need the `rustc` version Clippy was compiled with,
+ instead of the Clippy version. E.g. `clippy-driver --rustc --version` will
+ print the output of `rustc --version`.
+ [#5178](https://github.com/rust-lang/rust-clippy/pull/5178)
+* New issue templates now make it easier to complain if Clippy is too annoying
+ or not annoying enough! [#5735](https://github.com/rust-lang/rust-clippy/pull/5735)
+
+## Rust 1.45
+
+Released 2020-07-16
+
+[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1)
+
+### New lints
+
+* [`match_wildcard_for_single_variants`] [#5582](https://github.com/rust-lang/rust-clippy/pull/5582)
+* [`unsafe_derive_deserialize`] [#5493](https://github.com/rust-lang/rust-clippy/pull/5493)
+* [`if_let_mutex`] [#5332](https://github.com/rust-lang/rust-clippy/pull/5332)
+* [`mismatched_target_os`] [#5506](https://github.com/rust-lang/rust-clippy/pull/5506)
+* [`await_holding_lock`] [#5439](https://github.com/rust-lang/rust-clippy/pull/5439)
+* [`match_on_vec_items`] [#5522](https://github.com/rust-lang/rust-clippy/pull/5522)
+* [`manual_async_fn`] [#5576](https://github.com/rust-lang/rust-clippy/pull/5576)
+* [`reversed_empty_ranges`] [#5583](https://github.com/rust-lang/rust-clippy/pull/5583)
+* [`manual_non_exhaustive`] [#5550](https://github.com/rust-lang/rust-clippy/pull/5550)
+
+### Moves and Deprecations
+
+* Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408)
+* Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622)
+* Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599)
+* Generalize `option_and_then_some` and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529)
+* Rename `identity_conversion` to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568)
+* Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` into [`blocks_in_if_conditions`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` into [`map_unwrap_or`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_unwrap_used` and `result_unwrap_used` into [`unwrap_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_expect_used` and `result_expect_used` into [`expect_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `for_loop_over_option` and `for_loop_over_result` into [`for_loops_over_fallibles`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+
+### Enhancements
+
+* Avoid running cargo lints when not enabled to improve performance. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505)
+* Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631)
+* Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592)
+* Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616)
+
+### False Positive Fixes
+
+* [`while_let_on_iterator`] [#5525](https://github.com/rust-lang/rust-clippy/pull/5525)
+* [`empty_line_after_outer_attr`] [#5609](https://github.com/rust-lang/rust-clippy/pull/5609)
+* [`unnecessary_unwrap`] [#5558](https://github.com/rust-lang/rust-clippy/pull/5558)
+* [`comparison_chain`] [#5596](https://github.com/rust-lang/rust-clippy/pull/5596)
+* Don't trigger [`used_underscore_binding`] in await desugaring. [#5535](https://github.com/rust-lang/rust-clippy/pull/5535)
+* Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491)
+* Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602)
+* Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564)
+* Whitelist more words in [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611)
+* Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636)
+* Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647)
+* Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`]
+and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651)
+* Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429)
+
+### Suggestion Improvements
+
+* Simplify suggestions in [`manual_memcpy`]. [#5536](https://github.com/rust-lang/rust-clippy/pull/5536)
+* Fix suggestion in [`redundant_pattern_matching`] for macros. [#5511](https://github.com/rust-lang/rust-clippy/pull/5511)
+* Avoid suggesting `copied()` for mutable references in [`map_clone`]. [#5530](https://github.com/rust-lang/rust-clippy/pull/5530)
+* Improve help message for [`clone_double_ref`]. [#5547](https://github.com/rust-lang/rust-clippy/pull/5547)
+
+### ICE Fixes
+
+* Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590)
+* Fix ICE on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499)
+
+### Documentation
+
+* Clarify the documentation of [`unnecessary_mut_passed`]. [#5639](https://github.com/rust-lang/rust-clippy/pull/5639)
+* Extend example for [`unneeded_field_pattern`]. [#5541](https://github.com/rust-lang/rust-clippy/pull/5541)
+
+## Rust 1.44
+
+Released 2020-06-04
+
+[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8)
+
+### New lints
+
+* [`explicit_deref_methods`] [#5226](https://github.com/rust-lang/rust-clippy/pull/5226)
+* [`implicit_saturating_sub`] [#5427](https://github.com/rust-lang/rust-clippy/pull/5427)
+* [`macro_use_imports`] [#5230](https://github.com/rust-lang/rust-clippy/pull/5230)
+* [`verbose_file_reads`] [#5272](https://github.com/rust-lang/rust-clippy/pull/5272)
+* [`future_not_send`] [#5423](https://github.com/rust-lang/rust-clippy/pull/5423)
+* [`redundant_pub_crate`] [#5319](https://github.com/rust-lang/rust-clippy/pull/5319)
+* [`large_const_arrays`] [#5248](https://github.com/rust-lang/rust-clippy/pull/5248)
+* [`result_map_or_into_option`] [#5415](https://github.com/rust-lang/rust-clippy/pull/5415)
+* [`redundant_allocation`] [#5349](https://github.com/rust-lang/rust-clippy/pull/5349)
+* [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+* [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+
+
+### Moves and Deprecations
+
+* Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380)
+* Move [`cognitive_complexity`] to nursery [#5428](https://github.com/rust-lang/rust-clippy/pull/5428)
+* Move [`useless_transmute`] to nursery [#5364](https://github.com/rust-lang/rust-clippy/pull/5364)
+* Downgrade [`inefficient_to_string`] to pedantic [#5412](https://github.com/rust-lang/rust-clippy/pull/5412)
+* Downgrade [`option_option`] to pedantic [#5401](https://github.com/rust-lang/rust-clippy/pull/5401)
+* Downgrade [`unreadable_literal`] to pedantic [#5419](https://github.com/rust-lang/rust-clippy/pull/5419)
+* Downgrade [`let_unit_value`] to pedantic [#5409](https://github.com/rust-lang/rust-clippy/pull/5409)
+* Downgrade [`trivially_copy_pass_by_ref`] to pedantic [#5410](https://github.com/rust-lang/rust-clippy/pull/5410)
+* Downgrade [`implicit_hasher`] to pedantic [#5411](https://github.com/rust-lang/rust-clippy/pull/5411)
+
+### Enhancements
+
+* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to
+ auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363)
+* Make [`redundant_clone`] also trigger on cases where the cloned value is not
+ consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304)
+* Expand [`integer_arithmetic`] to also disallow bit-shifting [#5430](https://github.com/rust-lang/rust-clippy/pull/5430)
+* [`option_as_ref_deref`] now detects more deref cases [#5425](https://github.com/rust-lang/rust-clippy/pull/5425)
+* [`large_enum_variant`] now report the sizes of the largest and second-largest variants [#5466](https://github.com/rust-lang/rust-clippy/pull/5466)
+* [`bool_comparison`] now also checks for inequality comparisons that can be
+ written more concisely [#5365](https://github.com/rust-lang/rust-clippy/pull/5365)
+* Expand [`clone_on_copy`] to work in method call arguments as well [#5441](https://github.com/rust-lang/rust-clippy/pull/5441)
+* [`redundant_pattern_matching`] now also handles `while let` [#5483](https://github.com/rust-lang/rust-clippy/pull/5483)
+* [`integer_arithmetic`] now also lints references of integers [#5329](https://github.com/rust-lang/rust-clippy/pull/5329)
+* Expand [`float_cmp_const`] to also work on arrays [#5345](https://github.com/rust-lang/rust-clippy/pull/5345)
+* Trigger [`map_flatten`] when map is called on an `Option` [#5473](https://github.com/rust-lang/rust-clippy/pull/5473)
+
+### False Positive Fixes
+
+* [`many_single_char_names`] [#5468](https://github.com/rust-lang/rust-clippy/pull/5468)
+* [`should_implement_trait`] [#5437](https://github.com/rust-lang/rust-clippy/pull/5437)
+* [`unused_self`] [#5387](https://github.com/rust-lang/rust-clippy/pull/5387)
+* [`redundant_clone`] [#5453](https://github.com/rust-lang/rust-clippy/pull/5453)
+* [`precedence`] [#5445](https://github.com/rust-lang/rust-clippy/pull/5445)
+* [`suspicious_op_assign_impl`] [#5424](https://github.com/rust-lang/rust-clippy/pull/5424)
+* [`needless_lifetimes`] [#5293](https://github.com/rust-lang/rust-clippy/pull/5293)
+* [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287)
+* [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451)
+
+
+### Suggestion Improvements
+
+* Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481)
+* Improve the suggested placeholder in [`option_map_unit_fn`] [#5292](https://github.com/rust-lang/rust-clippy/pull/5292)
+* Improve suggestion for [`match_single_binding`] when triggered inside a closure [#5350](https://github.com/rust-lang/rust-clippy/pull/5350)
+
+### ICE Fixes
+
+* Handle the unstable `trivial_bounds` feature [#5296](https://github.com/rust-lang/rust-clippy/pull/5296)
+* `shadow_*` lints [#5297](https://github.com/rust-lang/rust-clippy/pull/5297)
+
+### Documentation
+
+* Fix documentation generation for configurable lints [#5353](https://github.com/rust-lang/rust-clippy/pull/5353)
+* Update documentation for [`new_ret_no_self`] [#5448](https://github.com/rust-lang/rust-clippy/pull/5448)
+* The documentation for [`option_option`] now suggest using a tri-state enum [#5403](https://github.com/rust-lang/rust-clippy/pull/5403)
+* Fix bit mask example in [`verbose_bit_mask`] documentation [#5454](https://github.com/rust-lang/rust-clippy/pull/5454)
+* [`wildcard_imports`] documentation now mentions that `use ...::prelude::*` is
+ not linted [#5312](https://github.com/rust-lang/rust-clippy/pull/5312)
+
+## Rust 1.43
+
+Released 2020-04-23
+
+[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b)
+
+### New lints
+
+* [`imprecise_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`suboptimal_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`wildcard_imports`] [#5029](https://github.com/rust-lang/rust-clippy/pull/5029)
+* [`single_component_path_imports`] [#5058](https://github.com/rust-lang/rust-clippy/pull/5058)
+* [`match_single_binding`] [#5061](https://github.com/rust-lang/rust-clippy/pull/5061)
+* [`let_underscore_lock`] [#5101](https://github.com/rust-lang/rust-clippy/pull/5101)
+* [`struct_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`fn_params_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`option_env_unwrap`] [#5148](https://github.com/rust-lang/rust-clippy/pull/5148)
+* [`lossy_float_literal`] [#5202](https://github.com/rust-lang/rust-clippy/pull/5202)
+* [`rest_pat_in_fully_bound_structs`] [#5258](https://github.com/rust-lang/rust-clippy/pull/5258)
+
+### Moves and Deprecations
+
+* Move [`unneeded_field_pattern`] to pedantic group [#5200](https://github.com/rust-lang/rust-clippy/pull/5200)
+
+### Enhancements
+
+* Make [`missing_errors_doc`] lint also trigger on `async` functions
+ [#5181](https://github.com/rust-lang/rust-clippy/pull/5181)
+* Add more constants to [`approx_constant`] [#5193](https://github.com/rust-lang/rust-clippy/pull/5193)
+* Extend [`question_mark`] lint [#5266](https://github.com/rust-lang/rust-clippy/pull/5266)
+
+### False Positive Fixes
+
+* [`use_debug`] [#5047](https://github.com/rust-lang/rust-clippy/pull/5047)
+* [`unnecessary_unwrap`] [#5132](https://github.com/rust-lang/rust-clippy/pull/5132)
+* [`zero_prefixed_literal`] [#5170](https://github.com/rust-lang/rust-clippy/pull/5170)
+* [`missing_const_for_fn`] [#5216](https://github.com/rust-lang/rust-clippy/pull/5216)
+
+### Suggestion Improvements
+
+* Improve suggestion when blocks of code are suggested [#5134](https://github.com/rust-lang/rust-clippy/pull/5134)
+
+### ICE Fixes
+
+* `misc_early` lints [#5129](https://github.com/rust-lang/rust-clippy/pull/5129)
+* [`missing_errors_doc`] [#5213](https://github.com/rust-lang/rust-clippy/pull/5213)
+* Fix ICE when evaluating `usize`s [#5256](https://github.com/rust-lang/rust-clippy/pull/5256)
+
+### Documentation
+
+* Improve documentation of [`iter_nth_zero`]
+* Add documentation pages for stable releases [#5171](https://github.com/rust-lang/rust-clippy/pull/5171)
+
+### Others
+
+* Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190)
+
+
+## Rust 1.42
+
+Released 2020-03-12
+
+[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206)
+
+### New lints
+
+* [`filetype_is_file`] [#4543](https://github.com/rust-lang/rust-clippy/pull/4543)
+* [`let_underscore_must_use`] [#4823](https://github.com/rust-lang/rust-clippy/pull/4823)
+* [`modulo_arithmetic`] [#4867](https://github.com/rust-lang/rust-clippy/pull/4867)
+* [`mem_replace_with_default`] [#4881](https://github.com/rust-lang/rust-clippy/pull/4881)
+* [`mutable_key_type`] [#4885](https://github.com/rust-lang/rust-clippy/pull/4885)
+* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945)
+* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960)
+* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966)
+* `invalid_atomic_ordering` [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
+* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067)
+
+### Moves and Deprecations
+
+* Move [`transmute_float_to_int`] from nursery to complexity group
+ [#5015](https://github.com/rust-lang/rust-clippy/pull/5015)
+* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057)
+* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Deprecate `unused_label` [#4930](https://github.com/rust-lang/rust-clippy/pull/4930)
+
+### Enhancements
+
+* Lint vectored IO in [`unused_io_amount`] [#5027](https://github.com/rust-lang/rust-clippy/pull/5027)
+* Make [`vec_box`] configurable by adding a size threshold [#5081](https://github.com/rust-lang/rust-clippy/pull/5081)
+* Also lint constants in [`cmp_nan`] [#4910](https://github.com/rust-lang/rust-clippy/pull/4910)
+* Fix false negative in [`expect_fun_call`] [#4915](https://github.com/rust-lang/rust-clippy/pull/4915)
+* Fix false negative in [`redundant_clone`] [#5017](https://github.com/rust-lang/rust-clippy/pull/5017)
+
+### False Positive Fixes
+
+* [`map_clone`] [#4937](https://github.com/rust-lang/rust-clippy/pull/4937)
+* [`replace_consts`] [#4977](https://github.com/rust-lang/rust-clippy/pull/4977)
+* [`let_and_return`] [#5008](https://github.com/rust-lang/rust-clippy/pull/5008)
+* [`eq_op`] [#5079](https://github.com/rust-lang/rust-clippy/pull/5079)
+* [`possible_missing_comma`] [#5083](https://github.com/rust-lang/rust-clippy/pull/5083)
+* [`debug_assert_with_mut_call`] [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Don't trigger [`let_underscore_must_use`] in external macros
+ [#5082](https://github.com/rust-lang/rust-clippy/pull/5082)
+* Don't trigger [`empty_loop`] in `no_std` crates [#5086](https://github.com/rust-lang/rust-clippy/pull/5086)
+
+### Suggestion Improvements
+
+* `option_map_unwrap_or` [#4634](https://github.com/rust-lang/rust-clippy/pull/4634)
+* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934)
+* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935)
+* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956)
+* `unknown_clippy_lints` [#4963](https://github.com/rust-lang/rust-clippy/pull/4963)
+* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978)
+* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022)
+* `if_let_some_result` [#5032](https://github.com/rust-lang/rust-clippy/pull/5032)
+
+### ICE fixes
+
+* [`unsound_collection_transmute`] [#4975](https://github.com/rust-lang/rust-clippy/pull/4975)
+
+### Documentation
+
+* Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`]
+
+
+## Rust 1.41
+
+Released 2020-01-30
+
+[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7)
+
+* New Lints:
+ * [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697)
+ * [`to_digit_is_some`] [#4801](https://github.com/rust-lang/rust-clippy/pull/4801)
+ * [`tabs_in_doc_comments`] [#4806](https://github.com/rust-lang/rust-clippy/pull/4806)
+ * [`large_stack_arrays`] [#4807](https://github.com/rust-lang/rust-clippy/pull/4807)
+ * [`same_functions_in_if_condition`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814)
+ * [`zst_offset`] [#4816](https://github.com/rust-lang/rust-clippy/pull/4816)
+ * [`as_conversions`] [#4821](https://github.com/rust-lang/rust-clippy/pull/4821)
+ * [`missing_errors_doc`] [#4884](https://github.com/rust-lang/rust-clippy/pull/4884)
+ * [`transmute_float_to_int`] [#4889](https://github.com/rust-lang/rust-clippy/pull/4889)
+* Remove plugin interface, see
+ [Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for
+ details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714)
+* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863)
+* Deprecate `into_iter_on_array` [#4788](https://github.com/rust-lang/rust-clippy/pull/4788)
+* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes
+ [#4808](https://github.com/rust-lang/rust-clippy/pull/4808)
+* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842)
+* Fix false positive in `while_immutable_condition` [#4730](https://github.com/rust-lang/rust-clippy/pull/4730)
+* Fix false positive in `explicit_counter_loop` [#4803](https://github.com/rust-lang/rust-clippy/pull/4803)
+* Fix false positive in `must_use_candidate` [#4794](https://github.com/rust-lang/rust-clippy/pull/4794)
+* Fix false positive in `print_with_newline` and `write_with_newline`
+ [#4769](https://github.com/rust-lang/rust-clippy/pull/4769)
+* Fix false positive in `derive_hash_xor_eq` [#4766](https://github.com/rust-lang/rust-clippy/pull/4766)
+* Fix false positive in `missing_inline_in_public_items` [#4870](https://github.com/rust-lang/rust-clippy/pull/4870)
+* Fix false positive in `string_add` [#4880](https://github.com/rust-lang/rust-clippy/pull/4880)
+* Fix false positive in `float_arithmetic` [#4851](https://github.com/rust-lang/rust-clippy/pull/4851)
+* Fix false positive in `cast_sign_loss` [#4883](https://github.com/rust-lang/rust-clippy/pull/4883)
+* Fix false positive in `manual_swap` [#4877](https://github.com/rust-lang/rust-clippy/pull/4877)
+* Fix ICEs occurring while checking some block expressions [#4772](https://github.com/rust-lang/rust-clippy/pull/4772)
+* Fix ICE in `use_self` [#4776](https://github.com/rust-lang/rust-clippy/pull/4776)
+* Fix ICEs related to `const_generics` [#4780](https://github.com/rust-lang/rust-clippy/pull/4780)
+* Display help when running `clippy-driver` without arguments, instead of ICEing
+ [#4810](https://github.com/rust-lang/rust-clippy/pull/4810)
+* Clippy has its own ICE message now [#4588](https://github.com/rust-lang/rust-clippy/pull/4588)
+* Show deprecated lints in the documentation again [#4757](https://github.com/rust-lang/rust-clippy/pull/4757)
+* Improve Documentation by adding positive examples to some lints
+ [#4832](https://github.com/rust-lang/rust-clippy/pull/4832)
+
+## Rust 1.40
+
+Released 2019-12-19
+
+[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb)
+
+* New Lints:
+ * [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537)
+ * [`needless_doctest_main`] [#4603](https://github.com/rust-lang/rust-clippy/pull/4603)
+ * [`suspicious_unary_op_formatting`] [#4615](https://github.com/rust-lang/rust-clippy/pull/4615)
+ * [`debug_assert_with_mut_call`] [#4680](https://github.com/rust-lang/rust-clippy/pull/4680)
+ * [`unused_self`] [#4619](https://github.com/rust-lang/rust-clippy/pull/4619)
+ * [`inefficient_to_string`] [#4683](https://github.com/rust-lang/rust-clippy/pull/4683)
+ * [`must_use_unit`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`must_use_candidate`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`double_must_use`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`comparison_chain`] [#4569](https://github.com/rust-lang/rust-clippy/pull/4569)
+ * [`unsound_collection_transmute`] [#4592](https://github.com/rust-lang/rust-clippy/pull/4592)
+ * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `option_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `result_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+* Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736)
+* Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613)
+* Expand `integer_arithmetic` to also detect mutating arithmetic like `+=` [#4585](https://github.com/rust-lang/rust-clippy/pull/4585)
+* Fix false positive in `nonminimal_bool` [#4568](https://github.com/rust-lang/rust-clippy/pull/4568)
+* Fix false positive in `missing_safety_doc` [#4611](https://github.com/rust-lang/rust-clippy/pull/4611)
+* Fix false positive in `cast_sign_loss` [#4614](https://github.com/rust-lang/rust-clippy/pull/4614)
+* Fix false positive in `redundant_clone` [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Fix false positive in `try_err` [#4721](https://github.com/rust-lang/rust-clippy/pull/4721)
+* Fix false positive in `toplevel_ref_arg` [#4570](https://github.com/rust-lang/rust-clippy/pull/4570)
+* Fix false positive in `multiple_inherent_impl` [#4593](https://github.com/rust-lang/rust-clippy/pull/4593)
+* Improve more suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4575](https://github.com/rust-lang/rust-clippy/pull/4575)
+* Improve suggestion for `zero_ptr` [#4599](https://github.com/rust-lang/rust-clippy/pull/4599)
+* Improve suggestion for `explicit_counter_loop` [#4691](https://github.com/rust-lang/rust-clippy/pull/4691)
+* Improve suggestion for `mul_add` [#4602](https://github.com/rust-lang/rust-clippy/pull/4602)
+* Improve suggestion for `assertions_on_constants` [#4635](https://github.com/rust-lang/rust-clippy/pull/4635)
+* Fix ICE in `use_self` [#4671](https://github.com/rust-lang/rust-clippy/pull/4671)
+* Fix ICE when encountering const casts [#4590](https://github.com/rust-lang/rust-clippy/pull/4590)
+
+## Rust 1.39
+
+Released 2019-11-07
+
+[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b)
+
+* New Lints:
+ * [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479)
+ * [`flat_map_identity`] [#4231](https://github.com/rust-lang/rust-clippy/pull/4231)
+ * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535)
+ * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511)
+ * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394)
+ * `option_and_then_some` [#4386](https://github.com/rust-lang/rust-clippy/pull/4386)
+ * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498)
+* Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348)
+* Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403)
+* Move `cast_lossless` to pedantic group [#4539](https://github.com/rust-lang/rust-clippy/pull/4539)
+* `temporary_cstring_as_ptr` now catches more cases [#4425](https://github.com/rust-lang/rust-clippy/pull/4425)
+* `use_self` now works in constructors, too [#4525](https://github.com/rust-lang/rust-clippy/pull/4525)
+* `cargo_common_metadata` now checks for license files [#4518](https://github.com/rust-lang/rust-clippy/pull/4518)
+* `cognitive_complexity` now includes the measured complexity in the warning message [#4469](https://github.com/rust-lang/rust-clippy/pull/4469)
+* Fix false positives in `block_in_if_*` lints [#4458](https://github.com/rust-lang/rust-clippy/pull/4458)
+* Fix false positive in `cast_lossless` [#4473](https://github.com/rust-lang/rust-clippy/pull/4473)
+* Fix false positive in `clone_on_copy` [#4411](https://github.com/rust-lang/rust-clippy/pull/4411)
+* Fix false positive in `deref_addrof` [#4487](https://github.com/rust-lang/rust-clippy/pull/4487)
+* Fix false positive in `too_many_lines` [#4490](https://github.com/rust-lang/rust-clippy/pull/4490)
+* Fix false positive in `new_ret_no_self` [#4365](https://github.com/rust-lang/rust-clippy/pull/4365)
+* Fix false positive in `manual_swap` [#4478](https://github.com/rust-lang/rust-clippy/pull/4478)
+* Fix false positive in `missing_const_for_fn` [#4450](https://github.com/rust-lang/rust-clippy/pull/4450)
+* Fix false positive in `extra_unused_lifetimes` [#4477](https://github.com/rust-lang/rust-clippy/pull/4477)
+* Fix false positive in `inherent_to_string` [#4460](https://github.com/rust-lang/rust-clippy/pull/4460)
+* Fix false positive in `map_entry` [#4495](https://github.com/rust-lang/rust-clippy/pull/4495)
+* Fix false positive in `unused_unit` [#4445](https://github.com/rust-lang/rust-clippy/pull/4445)
+* Fix false positive in `redundant_pattern` [#4489](https://github.com/rust-lang/rust-clippy/pull/4489)
+* Fix false positive in `wrong_self_convention` [#4369](https://github.com/rust-lang/rust-clippy/pull/4369)
+* Improve various suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4558](https://github.com/rust-lang/rust-clippy/pull/4558)
+* Improve suggestions for `redundant_pattern_matching` [#4352](https://github.com/rust-lang/rust-clippy/pull/4352)
+* Improve suggestions for `explicit_write` [#4544](https://github.com/rust-lang/rust-clippy/pull/4544)
+* Improve suggestion for `or_fun_call` [#4522](https://github.com/rust-lang/rust-clippy/pull/4522)
+* Improve suggestion for `match_as_ref` [#4446](https://github.com/rust-lang/rust-clippy/pull/4446)
+* Improve suggestion for `unnecessary_fold_span` [#4382](https://github.com/rust-lang/rust-clippy/pull/4382)
+* Add suggestions for `unseparated_literal_suffix` [#4401](https://github.com/rust-lang/rust-clippy/pull/4401)
+* Add suggestions for `char_lit_as_u8` [#4418](https://github.com/rust-lang/rust-clippy/pull/4418)
+
+## Rust 1.38
+
+Released 2019-09-26
+
+[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860)
+
+* New Lints:
+ * [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203)
+ * [`inherent_to_string`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766)
+ * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222)
+* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307)
+* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308)
+* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262)
+* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337)
+* Fix false positive in `pub_enum_variant_names` and `enum_variant_names` [#4345](https://github.com/rust-lang/rust-clippy/pull/4345)
+* Fix false positive in `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Fix false positive in `string_lit_as_bytes` [#4233](https://github.com/rust-lang/rust-clippy/pull/4233)
+* Fix false positive in `needless_lifetimes` [#4266](https://github.com/rust-lang/rust-clippy/pull/4266)
+* Fix false positive in `float_cmp` [#4275](https://github.com/rust-lang/rust-clippy/pull/4275)
+* Fix false positives in `needless_return` [#4274](https://github.com/rust-lang/rust-clippy/pull/4274)
+* Fix false negative in `match_same_arms` [#4246](https://github.com/rust-lang/rust-clippy/pull/4246)
+* Fix incorrect suggestion for `needless_bool` [#4335](https://github.com/rust-lang/rust-clippy/pull/4335)
+* Improve suggestion for `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Improve suggestion for `single_char_literal` [#4361](https://github.com/rust-lang/rust-clippy/pull/4361)
+* Improve suggestion for `len_zero` [#4314](https://github.com/rust-lang/rust-clippy/pull/4314)
+* Fix ICE in `implicit_hasher` [#4268](https://github.com/rust-lang/rust-clippy/pull/4268)
+* Fix allow bug in `trivially_copy_pass_by_ref` [#4250](https://github.com/rust-lang/rust-clippy/pull/4250)
+
+## Rust 1.37
+
+Released 2019-08-15
+
+[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e)
+
+* New Lints:
+ * [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088)
+ * [`get_last_with_len`] [#3832](https://github.com/rust-lang/rust-clippy/pull/3832)
+ * [`integer_division`] [#4195](https://github.com/rust-lang/rust-clippy/pull/4195)
+* Renamed Lint: `const_static_lifetime` is now called [`redundant_static_lifetimes`].
+ The lint now covers statics in addition to consts [#4162](https://github.com/rust-lang/rust-clippy/pull/4162)
+* [`match_same_arms`] now warns for all identical arms, instead of only the first one [#4102](https://github.com/rust-lang/rust-clippy/pull/4102)
+* [`needless_return`] now works with void functions [#4220](https://github.com/rust-lang/rust-clippy/pull/4220)
+* Fix false positive in [`redundant_closure`] [#4190](https://github.com/rust-lang/rust-clippy/pull/4190)
+* Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107)
+* Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214)
+* Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136)
+* Improve suggestions for `option_map_unwrap_or_else` and `result_map_unwrap_or_else` [#4164](https://github.com/rust-lang/rust-clippy/pull/4164)
+* Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119)
+* Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137)
+* Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071)
+* Add macro check for [`unreadable_literal`] [#4099](https://github.com/rust-lang/rust-clippy/pull/4099)
+
+## Rust 1.36
+
+Released 2019-07-04
+
+[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7)
+
+* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039)
+* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954)
+* Move `path_buf_push_overwrite` to the nursery [#4013](https://github.com/rust-lang/rust-clippy/pull/4013)
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Allow allowing of [`toplevel_ref_arg`] lint [#4007](https://github.com/rust-lang/rust-clippy/pull/4007)
+* Fix false negative in [`or_fun_call`] pertaining to nested constructors [#4084](https://github.com/rust-lang/rust-clippy/pull/4084)
+* Fix false positive in [`or_fun_call`] pertaining to enum variant constructors [#4018](https://github.com/rust-lang/rust-clippy/pull/4018)
+* Fix false positive in [`useless_let_if_seq`] pertaining to interior mutability [#4035](https://github.com/rust-lang/rust-clippy/pull/4035)
+* Fix false positive in [`redundant_closure`] pertaining to non-function types [#4008](https://github.com/rust-lang/rust-clippy/pull/4008)
+* Fix false positive in [`let_and_return`] pertaining to attributes on `let`s [#4024](https://github.com/rust-lang/rust-clippy/pull/4024)
+* Fix false positive in [`module_name_repetitions`] lint pertaining to attributes [#4006](https://github.com/rust-lang/rust-clippy/pull/4006)
+* Fix false positive on [`assertions_on_constants`] pertaining to `debug_assert!` [#3989](https://github.com/rust-lang/rust-clippy/pull/3989)
+* Improve suggestion in [`map_clone`] to suggest `.copied()` where applicable [#3970](https://github.com/rust-lang/rust-clippy/pull/3970) [#4043](https://github.com/rust-lang/rust-clippy/pull/4043)
+* Improve suggestion for [`search_is_some`] [#4049](https://github.com/rust-lang/rust-clippy/pull/4049)
+* Improve suggestion applicability for [`naive_bytecount`] [#3984](https://github.com/rust-lang/rust-clippy/pull/3984)
+* Improve suggestion applicability for [`while_let_loop`] [#3975](https://github.com/rust-lang/rust-clippy/pull/3975)
+* Improve diagnostics for [`too_many_arguments`] [#4053](https://github.com/rust-lang/rust-clippy/pull/4053)
+* Improve diagnostics for [`cast_lossless`] [#4021](https://github.com/rust-lang/rust-clippy/pull/4021)
+* Deal with macro checks in desugarings better [#4082](https://github.com/rust-lang/rust-clippy/pull/4082)
+* Add macro check for [`unnecessary_cast`] [#4026](https://github.com/rust-lang/rust-clippy/pull/4026)
+* Remove [`approx_constant`]'s documentation's "Known problems" section. [#4027](https://github.com/rust-lang/rust-clippy/pull/4027)
+* Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960)
+* Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931)
+
+
+## Rust 1.35
+
+Released 2019-05-20
+
+[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e)
+
+* New lint: `drop_bounds` to detect `T: Drop` bounds
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code
+* Move [`get_unwrap`] to the restriction category
+* Improve suggestions for [`iter_cloned_collect`]
+* Improve suggestions for [`cast_lossless`] to suggest suffixed literals
+* Fix false positives in [`print_with_newline`] and [`write_with_newline`] pertaining to raw strings
+* Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()`
+* Fix false positive in [`bool_comparison`] pertaining to non-bool types
+* Fix false positive in [`redundant_closure`] pertaining to differences in borrows
+* Fix false positive in `option_map_unwrap_or` on non-copy types
+* Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls
+* Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros
+* Fix false positive in [`needless_continue`] pertaining to loop labels
+* Fix false positive for [`boxed_local`] pertaining to arguments moved into closures
+* Fix false positive for [`use_self`] in nested functions
+* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846)
+* Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables
+* Fix suggestion for [`single_char_pattern`] to correctly escape single quotes
+* Avoid triggering [`redundant_closure`] in macros
+* ICE fixes: [#3805](https://github.com/rust-lang/rust-clippy/pull/3805), [#3772](https://github.com/rust-lang/rust-clippy/pull/3772), [#3741](https://github.com/rust-lang/rust-clippy/pull/3741)
+
+## Rust 1.34
+
+Released 2019-04-10
+
+[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380)
+
+* New lint: [`assertions_on_constants`] to detect for example `assert!(true)`
+* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro
+* New lint: [`missing_const_for_fn`] that can suggest functions to be made `const`
+* New lint: [`too_many_lines`] to detect functions with excessive LOC. It can be
+ configured using the `too-many-lines-threshold` configuration.
+* New lint: [`wildcard_enum_match_arm`] to check for wildcard enum matches using `_`
+* Expand `redundant_closure` to also work for methods (not only functions)
+* Fix ICEs in `vec_box`, `needless_pass_by_value` and `implicit_hasher`
+* Fix false positive in `cast_sign_loss`
+* Fix false positive in `integer_arithmetic`
+* Fix false positive in `unit_arg`
+* Fix false positives in `implicit_return`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `cast_lossless`
+* Fix incorrect suggestion for `expect_fun_call`
+* Fix incorrect suggestion for `needless_bool`
+* Fix incorrect suggestion for `needless_range_loop`
+* Fix incorrect suggestion for `use_self`
+* Fix incorrect suggestion for `while_let_on_iterator`
+* Clippy is now slightly easier to invoke in non-cargo contexts. See
+ [#3665][pull3665] for more details.
+* We now have [improved documentation][adding_lints] on how to add new lints
+
+## Rust 1.33
+
+Released 2019-02-26
+
+[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724)
+
+* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`]
+* The `rust-clippy` repository is now part of the `rust-lang` org.
+* Rename `stutter` to `module_name_repetitions`
+* Merge `new_without_default_derive` into `new_without_default` lint
+* Move `large_digit_groups` from `style` group to `pedantic`
+* Expand `bool_comparison` to check for `<`, `<=`, `>`, `>=`, and `!=`
+ comparisons against booleans
+* Expand `no_effect` to detect writes to constants such as `A_CONST.field = 2`
+* Expand `redundant_clone` to work on struct fields
+* Expand `suspicious_else_formatting` to detect `if .. {..} {..}`
+* Expand `use_self` to work on tuple structs and also in local macros
+* Fix ICE in `result_map_unit_fn` and `option_map_unit_fn`
+* Fix false positives in `implicit_return`
+* Fix false positives in `use_self`
+* Fix false negative in `clone_on_copy`
+* Fix false positive in `doc_markdown`
+* Fix false positive in `empty_loop`
+* Fix false positive in `if_same_then_else`
+* Fix false positive in `infinite_iter`
+* Fix false positive in `question_mark`
+* Fix false positive in `useless_asref`
+* Fix false positive in `wildcard_dependencies`
+* Fix false positive in `write_with_newline`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `get_unwrap`
+
+## Rust 1.32
+
+Released 2019-01-17
+
+[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be)
+
+* New lints: [`slow_vector_initialization`], `mem_discriminant_non_enum`,
+ [`redundant_clone`], [`wildcard_dependencies`],
+ [`into_iter_on_ref`], `into_iter_on_array`, [`deprecated_cfg_attr`],
+ [`cargo_common_metadata`]
+* Add support for `u128` and `i128` to integer related lints
+* Add float support to `mistyped_literal_suffixes`
+* Fix false positives in `use_self`
+* Fix false positives in `missing_comma`
+* Fix false positives in `new_ret_no_self`
+* Fix false positives in `possible_missing_comma`
+* Fix false positive in `integer_arithmetic` in constant items
+* Fix false positive in `needless_borrow`
+* Fix false positive in `out_of_bounds_indexing`
+* Fix false positive in `new_without_default_derive`
+* Fix false positive in `string_lit_as_bytes`
+* Fix false negative in `out_of_bounds_indexing`
+* Fix false negative in `use_self`. It will now also check existential types
+* Fix incorrect suggestion for `redundant_closure_call`
+* Fix various suggestions that contained expanded macros
+* Fix `bool_comparison` triggering 3 times on on on the same code
+* Expand `trivially_copy_pass_by_ref` to work on trait methods
+* Improve suggestion for `needless_range_loop`
+* Move `needless_pass_by_value` from `pedantic` group to `style`
+
+## Rust 1.31
+
+Released 2018-12-06
+
+[125907ad..2e26fdc2](https://github.com/rust-lang/rust-clippy/compare/125907ad..2e26fdc2)
+
+* Clippy has been relicensed under a dual MIT / Apache license.
+ See [#3093](https://github.com/rust-lang/rust-clippy/issues/3093) for more
+ information.
+* With Rust 1.31, Clippy is no longer available via crates.io. The recommended
+ installation method is via `rustup component add clippy`.
+* New lints: [`redundant_pattern_matching`], [`unnecessary_filter_map`],
+ [`unused_unit`], [`map_flatten`], [`mem_replace_option_with_none`]
+* Fix ICE in `if_let_redundant_pattern_matching`
+* Fix ICE in `needless_pass_by_value` when encountering a generic function
+ argument with a lifetime parameter
+* Fix ICE in `needless_range_loop`
+* Fix ICE in `single_char_pattern` when encountering a constant value
+* Fix false positive in `assign_op_pattern`
+* Fix false positive in `boxed_local` on trait implementations
+* Fix false positive in `cmp_owned`
+* Fix false positive in `collapsible_if` when conditionals have comments
+* Fix false positive in `double_parens`
+* Fix false positive in `excessive_precision`
+* Fix false positive in `explicit_counter_loop`
+* Fix false positive in `fn_to_numeric_cast_with_truncation`
+* Fix false positive in `map_clone`
+* Fix false positive in `new_ret_no_self`
+* Fix false positive in `new_without_default` when `new` is unsafe
+* Fix false positive in `type_complexity` when using extern types
+* Fix false positive in `useless_format`
+* Fix false positive in `wrong_self_convention`
+* Fix incorrect suggestion for `excessive_precision`
+* Fix incorrect suggestion for `expect_fun_call`
+* Fix incorrect suggestion for `get_unwrap`
+* Fix incorrect suggestion for `useless_format`
+* `fn_to_numeric_cast_with_truncation` lint can be disabled again
+* Improve suggestions for `manual_memcpy`
+* Improve help message for `needless_lifetimes`
+
+## Rust 1.30
+
+Released 2018-10-25
+
+[14207503...125907ad](https://github.com/rust-lang/rust-clippy/compare/14207503...125907ad)
+
+* Deprecate `assign_ops` lint
+* New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`],
+ [`needless_collect`], [`copy_iterator`]
+* `cargo clippy -V` now includes the Clippy commit hash of the Rust
+ Clippy component
+* Fix ICE in `implicit_hasher`
+* Fix ICE when encountering `println!("{}" a);`
+* Fix ICE when encountering a macro call in match statements
+* Fix false positive in `default_trait_access`
+* Fix false positive in `trivially_copy_pass_by_ref`
+* Fix false positive in `similar_names`
+* Fix false positive in `redundant_field_name`
+* Fix false positive in `expect_fun_call`
+* Fix false negative in `identity_conversion`
+* Fix false negative in `explicit_counter_loop`
+* Fix `range_plus_one` suggestion and false negative
+* `print_with_newline` / `write_with_newline`: don't warn about string with several `\n`s in them
+* Fix `useless_attribute` to also whitelist `unused_extern_crates`
+* Fix incorrect suggestion for `single_char_pattern`
+* Improve suggestion for `identity_conversion` lint
+* Move `explicit_iter_loop` and `explicit_into_iter_loop` from `style` group to `pedantic`
+* Move `range_plus_one` and `range_minus_one` from `nursery` group to `complexity`
+* Move `shadow_unrelated` from `restriction` group to `pedantic`
+* Move `indexing_slicing` from `pedantic` group to `restriction`
+
+## Rust 1.29
+
+Released 2018-09-13
+
+[v0.0.212...14207503](https://github.com/rust-lang/rust-clippy/compare/v0.0.212...14207503)
+
+* :tada: :tada: **Rust 1.29 is the first stable Rust that includes a bundled Clippy** :tada:
+ :tada:
+ You can now run `rustup component add clippy-preview` and then `cargo
+ clippy` to run Clippy. This should put an end to the continuous nightly
+ upgrades for Clippy users.
+* Clippy now follows the Rust versioning scheme instead of its own
+* Fix ICE when encountering a `while let (..) = x.iter()` construct
+* Fix false positives in `use_self`
+* Fix false positive in `trivially_copy_pass_by_ref`
+* Fix false positive in `useless_attribute` lint
+* Fix false positive in `print_literal`
+* Fix `use_self` regressions
+* Improve lint message for `neg_cmp_op_on_partial_ord`
+* Improve suggestion highlight for `single_char_pattern`
+* Improve suggestions for various print/write macro lints
+* Improve website header
+
+## 0.0.212 (2018-07-10)
+* Rustup to *rustc 1.29.0-nightly (e06c87544 2018-07-06)*
+
+## 0.0.211
+* Rustup to *rustc 1.28.0-nightly (e3bf634e0 2018-06-28)*
+
+## 0.0.210
+* Rustup to *rustc 1.28.0-nightly (01cc982e9 2018-06-24)*
+
+## 0.0.209
+* Rustup to *rustc 1.28.0-nightly (523097979 2018-06-18)*
+
+## 0.0.208
+* Rustup to *rustc 1.28.0-nightly (86a8f1a63 2018-06-17)*
+
+## 0.0.207
+* Rustup to *rustc 1.28.0-nightly (2a0062974 2018-06-09)*
+
+## 0.0.206
+* Rustup to *rustc 1.28.0-nightly (5bf68db6e 2018-05-28)*
+
+## 0.0.205
+* Rustup to *rustc 1.28.0-nightly (990d8aa74 2018-05-25)*
+* Rename `unused_lifetimes` to `extra_unused_lifetimes` because of naming conflict with new rustc lint
+
+## 0.0.204
+* Rustup to *rustc 1.28.0-nightly (71e87be38 2018-05-22)*
+
+## 0.0.203
+* Rustup to *rustc 1.28.0-nightly (a3085756e 2018-05-19)*
+* Clippy attributes are now of the form `clippy::cyclomatic_complexity` instead of `clippy(cyclomatic_complexity)`
+
+## 0.0.202
+* Rustup to *rustc 1.28.0-nightly (952f344cd 2018-05-18)*
+
+## 0.0.201
+* Rustup to *rustc 1.27.0-nightly (2f2a11dfc 2018-05-16)*
+
+## 0.0.200
+* Rustup to *rustc 1.27.0-nightly (9fae15374 2018-05-13)*
+
+## 0.0.199
+* Rustup to *rustc 1.27.0-nightly (ff2ac35db 2018-05-12)*
+
+## 0.0.198
+* Rustup to *rustc 1.27.0-nightly (acd3871ba 2018-05-10)*
+
+## 0.0.197
+* Rustup to *rustc 1.27.0-nightly (428ea5f6b 2018-05-06)*
+
+## 0.0.196
+* Rustup to *rustc 1.27.0-nightly (e82261dfb 2018-05-03)*
+
+## 0.0.195
+* Rustup to *rustc 1.27.0-nightly (ac3c2288f 2018-04-18)*
+
+## 0.0.194
+* Rustup to *rustc 1.27.0-nightly (bd40cbbe1 2018-04-14)*
+* New lints: [`cast_ptr_alignment`], [`transmute_ptr_to_ptr`], [`write_literal`], [`write_with_newline`], [`writeln_empty_string`]
+
+## 0.0.193
+* Rustup to *rustc 1.27.0-nightly (eeea94c11 2018-04-06)*
+
+## 0.0.192
+* Rustup to *rustc 1.27.0-nightly (fb44b4c0e 2018-04-04)*
+* New lint: [`print_literal`]
+
+## 0.0.191
+* Rustup to *rustc 1.26.0-nightly (ae544ee1c 2018-03-29)*
+* Lint audit; categorize lints as style, correctness, complexity, pedantic, nursery, restriction.
+
+## 0.0.190
+* Fix a bunch of intermittent cargo bugs
+
+## 0.0.189
+* Rustup to *rustc 1.26.0-nightly (5508b2714 2018-03-18)*
+
+## 0.0.188
+* Rustup to *rustc 1.26.0-nightly (392645394 2018-03-15)*
+* New lint: [`while_immutable_condition`]
+
+## 0.0.187
+* Rustup to *rustc 1.26.0-nightly (322d7f7b9 2018-02-25)*
+* New lints: [`redundant_field_names`], [`suspicious_arithmetic_impl`], [`suspicious_op_assign_impl`]
+
+## 0.0.186
+* Rustup to *rustc 1.25.0-nightly (0c6091fbd 2018-02-04)*
+* Various false positive fixes
+
+## 0.0.185
+* Rustup to *rustc 1.25.0-nightly (56733bc9f 2018-02-01)*
+* New lint: [`question_mark`]
+
+## 0.0.184
+* Rustup to *rustc 1.25.0-nightly (90eb44a58 2018-01-29)*
+* New lints: [`double_comparisons`], [`empty_line_after_outer_attr`]
+
+## 0.0.183
+* Rustup to *rustc 1.25.0-nightly (21882aad7 2018-01-28)*
+* New lint: [`misaligned_transmute`]
+
+## 0.0.182
+* Rustup to *rustc 1.25.0-nightly (a0dcecff9 2018-01-24)*
+* New lint: [`decimal_literal_representation`]
+
+## 0.0.181
+* Rustup to *rustc 1.25.0-nightly (97520ccb1 2018-01-21)*
+* New lints: [`else_if_without_else`], [`option_option`], [`unit_arg`], [`unnecessary_fold`]
+* Removed `unit_expr`
+* Various false positive fixes for [`needless_pass_by_value`]
+
+## 0.0.180
+* Rustup to *rustc 1.25.0-nightly (3f92e8d89 2018-01-14)*
+
+## 0.0.179
+* Rustup to *rustc 1.25.0-nightly (61452e506 2018-01-09)*
+
+## 0.0.178
+* Rustup to *rustc 1.25.0-nightly (ee220daca 2018-01-07)*
+
+## 0.0.177
+* Rustup to *rustc 1.24.0-nightly (250b49205 2017-12-21)*
+* New lint: [`match_as_ref`]
+
+## 0.0.176
+* Rustup to *rustc 1.24.0-nightly (0077d128d 2017-12-14)*
+
+## 0.0.175
+* Rustup to *rustc 1.24.0-nightly (bb42071f6 2017-12-01)*
+
+## 0.0.174
+* Rustup to *rustc 1.23.0-nightly (63739ab7b 2017-11-21)*
+
+## 0.0.173
+* Rustup to *rustc 1.23.0-nightly (33374fa9d 2017-11-20)*
+
+## 0.0.172
+* Rustup to *rustc 1.23.0-nightly (d0f8e2913 2017-11-16)*
+
+## 0.0.171
+* Rustup to *rustc 1.23.0-nightly (ff0f5de3b 2017-11-14)*
+
+## 0.0.170
+* Rustup to *rustc 1.23.0-nightly (d6b06c63a 2017-11-09)*
+
+## 0.0.169
+* Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)*
+* New lints: [`just_underscores_and_digits`], `result_map_unwrap_or_else`, [`transmute_bytes_to_str`]
+
+## 0.0.168
+* Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)*
+
+## 0.0.167
+* Rustup to *rustc 1.23.0-nightly (90ef3372e 2017-10-29)*
+* New lints: `const_static_lifetime`, [`erasing_op`], [`fallible_impl_from`], [`println_empty_string`], [`useless_asref`]
+
+## 0.0.166
+* Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)*
+* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], `invalid_ref`, [`option_map_or_none`],
+ [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`],
+ [`transmute_int_to_float`]
+
+## 0.0.165
+* Rust upgrade to rustc 1.22.0-nightly (0e6f4cf51 2017-09-27)
+* New lint: [`mut_range_bound`]
+
+## 0.0.164
+* Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)*
+* New lint: [`int_plus_one`]
+
+## 0.0.163
+* Update to *rustc 1.22.0-nightly (14039a42a 2017-09-22)*
+
+## 0.0.162
+* Update to *rustc 1.22.0-nightly (0701b37d9 2017-09-18)*
+* New lint: [`chars_last_cmp`]
+* Improved suggestions for [`needless_borrow`], [`ptr_arg`],
+
+## 0.0.161
+* Update to *rustc 1.22.0-nightly (539f2083d 2017-09-13)*
+
+## 0.0.160
+* Update to *rustc 1.22.0-nightly (dd08c3070 2017-09-12)*
+
+## 0.0.159
+* Update to *rustc 1.22.0-nightly (eba374fb2 2017-09-11)*
+* New lint: [`clone_on_ref_ptr`]
+
+## 0.0.158
+* New lint: [`manual_memcpy`]
+* [`cast_lossless`] no longer has redundant parentheses in its suggestions
+* Update to *rustc 1.22.0-nightly (dead08cb3 2017-09-08)*
+
+## 0.0.157 - 2017-09-04
+* Update to *rustc 1.22.0-nightly (981ce7d8d 2017-09-03)*
+* New lint: `unit_expr`
+
+## 0.0.156 - 2017-09-03
+* Update to *rustc 1.22.0-nightly (744dd6c1d 2017-09-02)*
+
+## 0.0.155
+* Update to *rustc 1.21.0-nightly (c11f689d2 2017-08-29)*
+* New lint: [`infinite_iter`], [`maybe_infinite_iter`], [`cast_lossless`]
+
+## 0.0.154
+* Update to *rustc 1.21.0-nightly (2c0558f63 2017-08-24)*
+* Fix [`use_self`] triggering inside derives
+* Add support for linting an entire workspace with `cargo clippy --all`
+* New lint: [`naive_bytecount`]
+
+## 0.0.153
+* Update to *rustc 1.21.0-nightly (8c303ed87 2017-08-20)*
+* New lint: [`use_self`]
+
+## 0.0.152
+* Update to *rustc 1.21.0-nightly (df511d554 2017-08-14)*
+
+## 0.0.151
+* Update to *rustc 1.21.0-nightly (13d94d5fa 2017-08-10)*
+
+## 0.0.150
+* Update to *rustc 1.21.0-nightly (215e0b10e 2017-08-08)*
+
+## 0.0.148
+* Update to *rustc 1.21.0-nightly (37c7d0ebb 2017-07-31)*
+* New lints: [`unreadable_literal`], [`inconsistent_digit_grouping`], [`large_digit_groups`]
+
+## 0.0.147
+* Update to *rustc 1.21.0-nightly (aac223f4f 2017-07-30)*
+
+## 0.0.146
+* Update to *rustc 1.21.0-nightly (52a330969 2017-07-27)*
+* Fixes false positives in `inline_always`
+* Fixes false negatives in `panic_params`
+
+## 0.0.145
+* Update to *rustc 1.20.0-nightly (afe145d22 2017-07-23)*
+
+## 0.0.144
+* Update to *rustc 1.20.0-nightly (086eaa78e 2017-07-15)*
+
+## 0.0.143
+* Update to *rustc 1.20.0-nightly (d84693b93 2017-07-09)*
+* Fix `cargo clippy` crashing on `dylib` projects
+* Fix false positives around `nested_while_let` and `never_loop`
+
+## 0.0.142
+* Update to *rustc 1.20.0-nightly (067971139 2017-07-02)*
+
+## 0.0.141
+* Rewrite of the `doc_markdown` lint.
+* Deprecated [`range_step_by_zero`]
+* New lint: [`iterator_step_by_zero`]
+* New lint: [`needless_borrowed_reference`]
+* Update to *rustc 1.20.0-nightly (69c65d296 2017-06-28)*
+
+## 0.0.140 - 2017-06-16
+* Update to *rustc 1.19.0-nightly (258ae6dd9 2017-06-15)*
+
+## 0.0.139 — 2017-06-10
+* Update to *rustc 1.19.0-nightly (4bf5c99af 2017-06-10)*
+* Fix bugs with for loop desugaring
+* Check for [`AsRef`]/[`AsMut`] arguments in [`wrong_self_convention`]
+
+## 0.0.138 — 2017-06-05
+* Update to *rustc 1.19.0-nightly (0418fa9d3 2017-06-04)*
+
+## 0.0.137 — 2017-06-05
+* Update to *rustc 1.19.0-nightly (6684d176c 2017-06-03)*
+
+## 0.0.136 — 2017—05—26
+* Update to *rustc 1.19.0-nightly (557967766 2017-05-26)*
+
+## 0.0.135 — 2017—05—24
+* Update to *rustc 1.19.0-nightly (5b13bff52 2017-05-23)*
+
+## 0.0.134 — 2017—05—19
+* Update to *rustc 1.19.0-nightly (0ed1ec9f9 2017-05-18)*
+
+## 0.0.133 — 2017—05—14
+* Update to *rustc 1.19.0-nightly (826d8f385 2017-05-13)*
+
+## 0.0.132 — 2017—05—05
+* Fix various bugs and some ices
+
+## 0.0.131 — 2017—05—04
+* Update to *rustc 1.19.0-nightly (2d4ed8e0c 2017-05-03)*
+
+## 0.0.130 — 2017—05—03
+* Update to *rustc 1.19.0-nightly (6a5fc9eec 2017-05-02)*
+
+## 0.0.129 — 2017-05-01
+* Update to *rustc 1.19.0-nightly (06fb4d256 2017-04-30)*
+
+## 0.0.128 — 2017-04-28
+* Update to *rustc 1.18.0-nightly (94e884b63 2017-04-27)*
+
+## 0.0.127 — 2017-04-27
+* Update to *rustc 1.18.0-nightly (036983201 2017-04-26)*
+* New lint: [`needless_continue`]
+
+## 0.0.126 — 2017-04-24
+* Update to *rustc 1.18.0-nightly (2bd4b5c6d 2017-04-23)*
+
+## 0.0.125 — 2017-04-19
+* Update to *rustc 1.18.0-nightly (9f2abadca 2017-04-18)*
+
+## 0.0.124 — 2017-04-16
+* Update to *rustc 1.18.0-nightly (d5cf1cb64 2017-04-15)*
+
+## 0.0.123 — 2017-04-07
+* Fix various false positives
+
+## 0.0.122 — 2017-04-07
+* Rustup to *rustc 1.18.0-nightly (91ae22a01 2017-04-05)*
+* New lint: [`op_ref`]
+
+## 0.0.121 — 2017-03-21
+* Rustup to *rustc 1.17.0-nightly (134c4a0f0 2017-03-20)*
+
+## 0.0.120 — 2017-03-17
+* Rustup to *rustc 1.17.0-nightly (0aeb9c129 2017-03-15)*
+
+## 0.0.119 — 2017-03-13
+* Rustup to *rustc 1.17.0-nightly (824c9ebbd 2017-03-12)*
+
+## 0.0.118 — 2017-03-05
+* Rustup to *rustc 1.17.0-nightly (b1e31766d 2017-03-03)*
+
+## 0.0.117 — 2017-03-01
+* Rustup to *rustc 1.17.0-nightly (be760566c 2017-02-28)*
+
+## 0.0.116 — 2017-02-28
+* Fix `cargo clippy` on 64 bit windows systems
+
+## 0.0.115 — 2017-02-27
+* Rustup to *rustc 1.17.0-nightly (60a0edc6c 2017-02-26)*
+* New lints: [`zero_ptr`], [`never_loop`], [`mut_from_ref`]
+
+## 0.0.114 — 2017-02-08
+* Rustup to *rustc 1.17.0-nightly (c49d10207 2017-02-07)*
+* Tests are now ui tests (testing the exact output of rustc)
+
+## 0.0.113 — 2017-02-04
+* Rustup to *rustc 1.16.0-nightly (eedaa94e3 2017-02-02)*
+* New lint: [`large_enum_variant`]
+* `explicit_into_iter_loop` provides suggestions
+
+## 0.0.112 — 2017-01-27
+* Rustup to *rustc 1.16.0-nightly (df8debf6d 2017-01-25)*
+
+## 0.0.111 — 2017-01-21
+* Rustup to *rustc 1.16.0-nightly (a52da95ce 2017-01-20)*
+
+## 0.0.110 — 2017-01-20
+* Add badges and categories to `Cargo.toml`
+
+## 0.0.109 — 2017-01-19
+* Update to *rustc 1.16.0-nightly (c07a6ae77 2017-01-17)*
+
+## 0.0.108 — 2017-01-12
+* Update to *rustc 1.16.0-nightly (2782e8f8f 2017-01-12)*
+
+## 0.0.107 — 2017-01-11
+* Update regex dependency
+* Fix FP when matching `&&mut` by `&ref`
+* Reintroduce `for (_, x) in &mut hash_map` -> `for x in hash_map.values_mut()`
+* New lints: [`unused_io_amount`], [`forget_ref`], [`short_circuit_statement`]
+
+## 0.0.106 — 2017-01-04
+* Fix FP introduced by rustup in [`wrong_self_convention`]
+
+## 0.0.105 — 2017-01-04
+* Update to *rustc 1.16.0-nightly (468227129 2017-01-03)*
+* New lints: [`deref_addrof`], [`double_parens`], [`pub_enum_variant_names`]
+* Fix suggestion in [`new_without_default`]
+* FP fix in [`absurd_extreme_comparisons`]
+
+## 0.0.104 — 2016-12-15
+* Update to *rustc 1.15.0-nightly (8f02c429a 2016-12-15)*
+
+## 0.0.103 — 2016-11-25
+* Update to *rustc 1.15.0-nightly (d5814b03e 2016-11-23)*
+
+## 0.0.102 — 2016-11-24
+* Update to *rustc 1.15.0-nightly (3bf2be9ce 2016-11-22)*
+
+## 0.0.101 — 2016-11-23
+* Update to *rustc 1.15.0-nightly (7b3eeea22 2016-11-21)*
+* New lint: [`string_extend_chars`]
+
+## 0.0.100 — 2016-11-20
+* Update to *rustc 1.15.0-nightly (ac635aa95 2016-11-18)*
+
+## 0.0.99 — 2016-11-18
+* Update to rustc 1.15.0-nightly (0ed951993 2016-11-14)
+* New lint: [`get_unwrap`]
+
+## 0.0.98 — 2016-11-08
+* Fixes an issue due to a change in how cargo handles `--sysroot`, which broke `cargo clippy`
+
+## 0.0.97 — 2016-11-03
+* For convenience, `cargo clippy` defines a `cargo-clippy` feature. This was
+ previously added for a short time under the name `clippy` but removed for
+ compatibility.
+* `cargo clippy --help` is more helping (and less helpful :smile:)
+* Rustup to *rustc 1.14.0-nightly (5665bdf3e 2016-11-02)*
+* New lints: [`if_let_redundant_pattern_matching`], [`partialeq_ne_impl`]
+
+## 0.0.96 — 2016-10-22
+* Rustup to *rustc 1.14.0-nightly (f09420685 2016-10-20)*
+* New lint: [`iter_skip_next`]
+
+## 0.0.95 — 2016-10-06
+* Rustup to *rustc 1.14.0-nightly (3210fd5c2 2016-10-05)*
+
+## 0.0.94 — 2016-10-04
+* Fixes bustage on Windows due to forbidden directory name
+
+## 0.0.93 — 2016-10-03
+* Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)*
+* `option_map_unwrap_or` and `option_map_unwrap_or_else` are now
+ allowed by default.
+* New lint: [`explicit_into_iter_loop`]
+
+## 0.0.92 — 2016-09-30
+* Rustup to *rustc 1.14.0-nightly (289f3a4ca 2016-09-29)*
+
+## 0.0.91 — 2016-09-28
+* Rustup to *rustc 1.13.0-nightly (d0623cf7b 2016-09-26)*
+
+## 0.0.90 — 2016-09-09
+* Rustup to *rustc 1.13.0-nightly (f1f40f850 2016-09-09)*
+
+## 0.0.89 — 2016-09-06
+* Rustup to *rustc 1.13.0-nightly (cbe4de78e 2016-09-05)*
+
+## 0.0.88 — 2016-09-04
+* Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)*
+* The following lints are not new but were only usable through the `clippy`
+ lint groups: [`filter_next`], `for_loop_over_option`,
+ `for_loop_over_result` and [`match_overlapping_arm`]. You should now be
+ able to `#[allow/deny]` them individually and they are available directly
+ through `cargo clippy`.
+
+## 0.0.87 — 2016-08-31
+* Rustup to *rustc 1.13.0-nightly (eac41469d 2016-08-30)*
+* New lints: [`builtin_type_shadow`]
+* Fix FP in [`zero_prefixed_literal`] and `0b`/`0o`
+
+## 0.0.86 — 2016-08-28
+* Rustup to *rustc 1.13.0-nightly (a23064af5 2016-08-27)*
+* New lints: [`missing_docs_in_private_items`], [`zero_prefixed_literal`]
+
+## 0.0.85 — 2016-08-19
+* Fix ICE with [`useless_attribute`]
+* [`useless_attribute`] ignores `unused_imports` on `use` statements
+
+## 0.0.84 — 2016-08-18
+* Rustup to *rustc 1.13.0-nightly (aef6971ca 2016-08-17)*
+
+## 0.0.83 — 2016-08-17
+* Rustup to *rustc 1.12.0-nightly (1bf5fa326 2016-08-16)*
+* New lints: [`print_with_newline`], [`useless_attribute`]
+
+## 0.0.82 — 2016-08-17
+* Rustup to *rustc 1.12.0-nightly (197be89f3 2016-08-15)*
+* New lint: [`module_inception`]
+
+## 0.0.81 — 2016-08-14
+* Rustup to *rustc 1.12.0-nightly (1deb02ea6 2016-08-12)*
+* New lints: [`eval_order_dependence`], [`mixed_case_hex_literals`], [`unseparated_literal_suffix`]
+* False positive fix in [`too_many_arguments`]
+* Addition of functionality to [`needless_borrow`]
+* Suggestions for [`clone_on_copy`]
+* Bug fix in [`wrong_self_convention`]
+* Doc improvements
+
+## 0.0.80 — 2016-07-31
+* Rustup to *rustc 1.12.0-nightly (1225e122f 2016-07-30)*
+* New lints: [`misrefactored_assign_op`], [`serde_api_misuse`]
+
+## 0.0.79 — 2016-07-10
+* Rustup to *rustc 1.12.0-nightly (f93aaf84c 2016-07-09)*
+* Major suggestions refactoring
+
+## 0.0.78 — 2016-07-02
+* Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)*
+* New lints: [`wrong_transmute`], [`double_neg`], [`filter_map`]
+* For compatibility, `cargo clippy` does not defines the `clippy` feature
+ introduced in 0.0.76 anymore
+* [`collapsible_if`] now considers `if let`
+
+## 0.0.77 — 2016-06-21
+* Rustup to *rustc 1.11.0-nightly (5522e678b 2016-06-20)*
+* New lints: `stutter` and [`iter_nth`]
+
+## 0.0.76 — 2016-06-10
+* Rustup to *rustc 1.11.0-nightly (7d2f75a95 2016-06-09)*
+* `cargo clippy` now automatically defines the `clippy` feature
+* New lint: [`not_unsafe_ptr_arg_deref`]
+
+## 0.0.75 — 2016-06-08
+* Rustup to *rustc 1.11.0-nightly (763f9234b 2016-06-06)*
+
+## 0.0.74 — 2016-06-07
+* Fix bug with `cargo-clippy` JSON parsing
+* Add the `CLIPPY_DISABLE_DOCS_LINKS` environment variable to deactivate the
+ “for further information visit *lint-link*” message.
+
+## 0.0.73 — 2016-06-05
+* Fix false positives in [`useless_let_if_seq`]
+
+## 0.0.72 — 2016-06-04
+* Fix false positives in [`useless_let_if_seq`]
+
+## 0.0.71 — 2016-05-31
+* Rustup to *rustc 1.11.0-nightly (a967611d8 2016-05-30)*
+* New lint: [`useless_let_if_seq`]
+
+## 0.0.70 — 2016-05-28
+* Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)*
+* [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`,
+ `RegexBuilder::new` and byte regexes
+
+## 0.0.69 — 2016-05-20
+* Rustup to *rustc 1.10.0-nightly (476fe6eef 2016-05-21)*
+* [`used_underscore_binding`] has been made `Allow` temporarily
+
+## 0.0.68 — 2016-05-17
+* Rustup to *rustc 1.10.0-nightly (cd6a40017 2016-05-16)*
+* New lint: [`unnecessary_operation`]
+
+## 0.0.67 — 2016-05-12
+* Rustup to *rustc 1.10.0-nightly (22ac88f1a 2016-05-11)*
+
+## 0.0.66 — 2016-05-11
+* New `cargo clippy` subcommand
+* New lints: [`assign_op_pattern`], [`assign_ops`], [`needless_borrow`]
+
+## 0.0.65 — 2016-05-08
+* Rustup to *rustc 1.10.0-nightly (62e2b2fb7 2016-05-06)*
+* New lints: [`float_arithmetic`], [`integer_arithmetic`]
+
+## 0.0.64 — 2016-04-26
+* Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)*
+* New lints: `temporary_cstring_as_ptr`, [`unsafe_removed_from_name`], and [`mem_forget`]
+
+## 0.0.63 — 2016-04-08
+* Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)*
+
+## 0.0.62 — 2016-04-07
+* Rustup to *rustc 1.9.0-nightly (bf5da36f1 2016-04-06)*
+
+## 0.0.61 — 2016-04-03
+* Rustup to *rustc 1.9.0-nightly (5ab11d72c 2016-04-02)*
+* New lint: [`invalid_upcast_comparisons`]
+
+## 0.0.60 — 2016-04-01
+* Rustup to *rustc 1.9.0-nightly (e1195c24b 2016-03-31)*
+
+## 0.0.59 — 2016-03-31
+* Rustup to *rustc 1.9.0-nightly (30a3849f2 2016-03-30)*
+* New lints: [`logic_bug`], [`nonminimal_bool`]
+* Fixed: [`match_same_arms`] now ignores arms with guards
+* Improved: [`useless_vec`] now warns on `for … in vec![…]`
+
+## 0.0.58 — 2016-03-27
+* Rustup to *rustc 1.9.0-nightly (d5a91e695 2016-03-26)*
+* New lint: [`doc_markdown`]
+
+## 0.0.57 — 2016-03-27
+* Update to *rustc 1.9.0-nightly (a1e29daf1 2016-03-25)*
+* Deprecated lints: [`str_to_string`], [`string_to_string`], [`unstable_as_slice`], [`unstable_as_mut_slice`]
+* New lint: [`crosspointer_transmute`]
+
+## 0.0.56 — 2016-03-23
+* Update to *rustc 1.9.0-nightly (0dcc413e4 2016-03-22)*
+* New lints: [`many_single_char_names`] and [`similar_names`]
+
+## 0.0.55 — 2016-03-21
+* Update to *rustc 1.9.0-nightly (02310fd31 2016-03-19)*
+
+## 0.0.54 — 2016-03-16
+* Update to *rustc 1.9.0-nightly (c66d2380a 2016-03-15)*
+
+## 0.0.53 — 2016-03-15
+* Add a [configuration file]
+
+## ~~0.0.52~~
+
+## 0.0.51 — 2016-03-13
+* Add `str` to types considered by [`len_zero`]
+* New lints: [`indexing_slicing`]
+
+## 0.0.50 — 2016-03-11
+* Update to *rustc 1.9.0-nightly (c9629d61c 2016-03-10)*
+
+## 0.0.49 — 2016-03-09
+* Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)*
+* New lints: [`overflow_check_conditional`], `unused_label`, [`new_without_default`]
+
+## 0.0.48 — 2016-03-07
+* Fixed: ICE in [`needless_range_loop`] with globals
+
+## 0.0.47 — 2016-03-07
+* Update to *rustc 1.9.0-nightly (998a6720b 2016-03-07)*
+* New lint: [`redundant_closure_call`]
+
+[`AsMut`]: https://doc.rust-lang.org/std/convert/trait.AsMut.html
+[`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
+[configuration file]: ./rust-clippy#configuration
+[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665
+[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
+[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md
+
+<!-- lint disable no-unused-definitions -->
+<!-- begin autogenerated links to lint list -->
+[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
+[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
+[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
+[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
+[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
+[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
+[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
+[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
+[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
+[`await_holding_invalid_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type
+[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
+[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
+[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
+[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
+[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
+[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
++[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr
++[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt
+[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
+[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
+[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
+[`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr
+[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
+[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
+[`box_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection
++[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec
+[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
+[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
+[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
+[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
+[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
+[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
+[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
+[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
+[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
+[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
+[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
+[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
+[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
+[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
+[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
+[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
+[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
+[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
+[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
+[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
+[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
+[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
+[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
+[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
+[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
+[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
+[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
+[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
+[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
+[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
+[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if
+[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
+[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
+[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
+[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
++[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
+[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
+[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
+[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
+[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
++[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
+[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
+[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
+[`decimal_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
+[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
+[`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
+[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
+[`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing
+[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
+[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
+[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
++[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
++[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
+[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
+[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
++[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
+[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
+[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
+[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
+[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
+[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
+[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
+[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
++[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
+[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
+[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
+[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
++[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
+[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
+[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
+[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
+[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
+[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
+[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
+[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
+[`empty_structs_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_structs_with_brackets
+[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
+[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
+[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
+[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
+[`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let
+[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
+[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
+[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
+[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
+[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
+[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
+[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit
+[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
+[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
+[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
+[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
+[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
+[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
+[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop
+[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write
+[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice
+[`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain
+[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes
+[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from
+[`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default
+[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file
+[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
+[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
+[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
+[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
+[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
+[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
+[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
+[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
+[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
+[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
+[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
+[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
+[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
+[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
+[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
+[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
++[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option
++[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
+[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
+[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
+[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
+[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
+[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
+[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
+[`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
++[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
+[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
+[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
+[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching
++[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result
+[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else
+[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
+[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
+[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
+[`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone
+[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
+[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
+[`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
+[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
+[`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
+[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
+[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
+[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
+[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
+[`init_numbered_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#init_numbered_fields
+[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
+[`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax
+[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
+[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
+[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
+[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
+[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
+[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
++[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
+[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
++[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
+[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
++[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
+[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
+[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
+[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
+[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
+[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
+[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
+[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
+[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
+[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
+[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
+[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
+[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
+[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
+[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
+[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
+[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
+[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
+[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
+[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
+[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
+[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
+[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
+[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
+[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
+[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
+[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
+[`let_underscore_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop
+[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
+[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use
+[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
+[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
+[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
+[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
+[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
+[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
+[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
+[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
+[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
+[`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
+[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
+[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
+[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
+[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
+[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
+[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
+[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
+[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
+[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
+[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
+[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
+[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore
+[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
+[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
+[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
+[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
+[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool
+[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
+[`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items
+[`match_overlapping_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_overlapping_arm
+[`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats
+[`match_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_result_ok
+[`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms
+[`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding
+[`match_str_case_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_str_case_mismatch
+[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
+[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
+[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
++[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
+[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
+[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
+[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default
+[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit
+[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
+[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
+[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
+[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
+[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
+[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
+[`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames
+[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
+[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
+[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
+[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
+[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
+[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
+[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
++[`mixed_read_write_in_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_read_write_in_expression
+[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
+[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
+[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
+[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
+[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
+[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
+[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
+[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
+[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit
+[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
+[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
+[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock
+[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
+[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
+[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
+[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
+[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
+[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
+[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
+[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
+[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
+[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
+[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
+[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
+[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
+[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
+[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
+[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
+[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
+[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
+[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
+[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
+[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
+[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
+[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
+[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
+[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
+[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
+[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
+[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
+[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
+[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
+[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
++[`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive
+[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
+[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
+[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
+[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
+[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
+[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
+[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
+[`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces
+[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
+[`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
+[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
+[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
+[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
++[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
+[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
+[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
++[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
+[`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
+[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
+[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
+[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
++[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or
++[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else
+[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
++[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used
+[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
+[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
+[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
+[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
+[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
+[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
++[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
+[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
+[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
+[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
+[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
+[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
+[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
+[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
+[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
+[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
+[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
+[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline
+[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string
+[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
+[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr
+[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
+[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
+[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
+[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
+[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
+[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
+[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
+[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
+[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
+[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
++[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
+[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
+[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
+[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
+[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
+[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
+[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
+[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
+[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
+[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
+[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
+[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
+[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
+[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
+[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
+[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
++[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
+[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
+[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
+[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
+[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
+[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
++[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
+[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
+[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
++[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else
+[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err
++[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
+[`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use
+[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
+[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
+[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
+[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
+[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
+[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
+[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
+[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
+[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
+[`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix
+[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
+[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
+[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
+[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated
+[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
+[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
+[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
++[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
+[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
+[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
+[`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names
+[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
++[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
+[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
+[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
+[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
+[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
+[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
+[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
+[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
+[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
+[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
+[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
+[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
+[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
+[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
+[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
+[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
+[`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice
+[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
+[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
+[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
++[`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter
+[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
+[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
+[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
+[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
+[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
+[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
+[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
+[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
+[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
+[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
+[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
++[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
+[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
++[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
+[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
+[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
+[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
+[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
+[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
+[`trailing_empty_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#trailing_empty_array
+[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds
+[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
+[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int
+[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
+[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
+[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
+[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
+[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
+[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
+[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
+[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
+[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
+[`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
+[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
+[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
+[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
+[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
+[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
+[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
+[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
+[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
+[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
+[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
+[`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
+[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
+[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
+[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
+[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
++[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
+[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
+[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
+[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
+[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
+[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
+[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
+[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
+[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
+[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
+[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
+[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
+[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
+[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
+[`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
+[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern
+[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
+[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns
+[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
+[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
+[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize
+[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
+[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
+[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix
+[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute
+[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
+[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
+[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
+[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
+[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
++[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
+[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
+[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
+[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
+[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
+[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
+[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
+[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
+[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
+[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
+[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding
+[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
+[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
+[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
+[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
+[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq
+[`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute
+[`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec
+[`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box
+[`vec_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_init_then_push
+[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero
+[`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask
+[`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads
+[`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
+[`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition
+[`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop
+[`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator
+[`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies
+[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm
+[`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports
+[`wildcard_in_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_in_or_patterns
+[`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal
+[`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline
+[`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string
+[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention
+[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention
+[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute
+[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
+[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
+[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
+[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
++[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
+[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
+<!-- end autogenerated links to lint list -->
--- /dev/null
- `rustc -Z ast-json` on an example of the structure and compare with the [nodes in the AST docs].
+# Contributing to Clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+**First**: if you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyway. You won't be
+yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change
+something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.
+
+Clippy welcomes contributions from everyone. There are many ways to contribute to Clippy and the following document
+explains how you can contribute and how to get started. If you have any questions about contributing or need help with
+anything, feel free to ask questions on issues or visit the `#clippy` on [Zulip].
+
+All contributors are expected to follow the [Rust Code of Conduct].
+
+- [Contributing to Clippy](#contributing-to-clippy)
+ - [Getting started](#getting-started)
+ - [High level approach](#high-level-approach)
+ - [Finding something to fix/improve](#finding-something-to-fiximprove)
+ - [Writing code](#writing-code)
+ - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
+ - [IntelliJ Rust](#intellij-rust)
+ - [Rust Analyzer](#rust-analyzer)
+ - [How Clippy works](#how-clippy-works)
+ - [Syncing changes between Clippy and `rust-lang/rust`](#syncing-changes-between-clippy-and-rust-langrust)
+ - [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos)
+ - [Performing the sync from `rust-lang/rust` to Clippy](#performing-the-sync-from-rust-langrust-to-clippy)
+ - [Performing the sync from Clippy to `rust-lang/rust`](#performing-the-sync-from-clippy-to-rust-langrust)
+ - [Defining remotes](#defining-remotes)
+ - [Issue and PR triage](#issue-and-pr-triage)
+ - [Bors and Homu](#bors-and-homu)
+ - [Contributions](#contributions)
+
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
+[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
+
+## Getting started
+
+**Note: If this is your first time contributing to Clippy, you should
+first read the [Basics docs](doc/basics.md).**
+
+### High level approach
+
+1. Find something to fix/improve
+2. Change code (likely some file in `clippy_lints/src/`)
+3. Follow the instructions in the [Basics docs](doc/basics.md) to get set up
+4. Run `cargo test` in the root directory and wiggle code until it passes
+5. Open a PR (also can be done after 2. if you run into problems)
+
+### Finding something to fix/improve
+
+All issues on Clippy are mentored, if you want help simply ask @Manishearth, @flip1995, @phansch
+or @llogiq directly by mentioning them in the issue or over on [Zulip]. This list may be out of date.
+All currently active mentors can be found [here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3)
+
+Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy
+issues. You can use `@rustbot claim` to assign the issue to yourself.
+
+There are also some abandoned PRs, marked with [`S-inactive-closed`].
+Pretty often these PRs are nearly completed and just need some extra steps
+(formatting, addressing review comments, ...) to be merged. If you want to
+complete such a PR, please leave a comment in the PR and open a new one based
+on it.
+
+Issues marked [`T-AST`] involve simple matching of the syntax tree structure,
+and are generally easier than [`T-middle`] issues, which involve types
+and resolved paths.
+
+[`T-AST`] issues will generally need you to match against a predefined syntax structure.
+To figure out how this syntax structure is encoded in the AST, it is recommended to run
- But we can make it nest-less by using [if_chain] macro, [like this][nest-less].
++`rustc -Z unpretty=ast-tree` on an example of the structure and compare with the [nodes in the AST docs].
+Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
- [deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/mem_forget.rs#L29-L43
- [if_chain]: https://docs.rs/if_chain/*/if_chain
- [nest-less]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/bit_mask.rs#L124-L150
++But we can make it nest-less by using [let chains], [like this][nest-less].
+
+[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good-first-issue`]
+first. Sometimes they are only somewhat involved code wise, but not difficult per-se.
+Note that [`E-medium`] issues may require some knowledge of Clippy internals or some
+debugging to find the actual problem behind the issue.
+
+[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+[`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
+[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
+[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST
+[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle
+[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium
+[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty
+[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/
++[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/mem_forget.rs#L31-L45
++[let chains]: https://github.com/rust-lang/rust/pull/94927
++[nest-less]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/bit_mask.rs#L133-L159
+
+## Writing code
+
+Have a look at the [docs for writing lints][adding_lints] for more details.
+
+If you want to add a new lint or change existing ones apart from bugfixing, it's
+also a good idea to give the [stability guarantees][rfc_stability] and
+[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a
+quick read.
+
+[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
+[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md
+[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees
+[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories
+
+## Getting code-completion for rustc internals to work
+
+### IntelliJ Rust
+Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals
+using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not
+available via a `rustup` component at the time of writing.
+To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via
+`git clone https://github.com/rust-lang/rust/`.
+Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies
+which `IntelliJ Rust` will be able to understand.
+Run `cargo dev setup intellij --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
+you just cloned.
+The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
+Clippy's `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
+Just make sure to remove the dependencies again before finally making a pull request!
+
+[rustc_repo]: https://github.com/rust-lang/rust/
+[IntelliJ_rust_homepage]: https://intellij-rust.github.io/
+
+### Rust Analyzer
+As of [#6869][6869], [`rust-analyzer`][ra_homepage] can understand that Clippy uses compiler-internals
+using `extern crate` when `package.metadata.rust-analyzer.rustc_private` is set to `true` in Clippy's `Cargo.toml.`
+You will require a `nightly` toolchain with the `rustc-dev` component installed.
+Make sure that in the `rust-analyzer` configuration, you set
+```
+{ "rust-analyzer.rustcSource": "discover" }
+```
+and
+```
+{ "rust-analyzer.updates.channel": "nightly" }
+```
+You should be able to see information on things like `Expr` or `EarlyContext` now if you hover them, also
+a lot more type hints.
+This will work with `rust-analyzer 2021-03-15` shipped in nightly `1.52.0-nightly (107896c32 2021-03-15)` or later.
+
+[ra_homepage]: https://rust-analyzer.github.io/
+[6869]: https://github.com/rust-lang/rust-clippy/pull/6869
+
+## How Clippy works
+
+[`clippy_lints/src/lib.rs`][lint_crate_entry] imports all the different lint modules and registers in the [`LintStore`].
+For example, the [`else_if_without_else`][else_if_without_else] lint is registered like this:
+
+```rust
+// ./clippy_lints/src/lib.rs
+
+// ...
+pub mod else_if_without_else;
+// ...
+
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // ...
+ store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
+ // ...
+
+ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ // ...
+ LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ // ...
+ ]);
+}
+```
+
+The [`rustc_lint::LintStore`][`LintStore`] provides two methods to register lints:
+[register_early_pass][reg_early_pass] and [register_late_pass][reg_late_pass]. Both take an object
+that implements an [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass] respectively. This is done in
+every single lint. It's worth noting that the majority of `clippy_lints/src/lib.rs` is autogenerated by `cargo dev
+update_lints`. When you are writing your own lint, you can use that script to save you some time.
+
+```rust
+// ./clippy_lints/src/else_if_without_else.rs
+
+use rustc_lint::{EarlyLintPass, EarlyContext};
+
+// ...
+
+pub struct ElseIfWithoutElse;
+
+// ...
+
+impl EarlyLintPass for ElseIfWithoutElse {
+ // ... the functions needed, to make the lint work
+}
+```
+
+The difference between `EarlyLintPass` and `LateLintPass` is that the methods of the `EarlyLintPass` trait only provide
+AST information. The methods of the `LateLintPass` trait are executed after type checking and contain type information
+via the `LateContext` parameter.
+
+That's why the `else_if_without_else` example uses the `register_early_pass` function. Because the
+[actual lint logic][else_if_without_else] does not depend on any type information.
+
+[lint_crate_entry]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs
+[else_if_without_else]: https://github.com/rust-lang/rust-clippy/blob/4253aa7137cb7378acc96133c787e49a345c2b3c/clippy_lints/src/else_if_without_else.rs
+[`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html
+[reg_early_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_early_pass
+[reg_late_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_late_pass
+[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
+[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
+
+## Syncing changes between Clippy and [`rust-lang/rust`]
+
+Clippy currently gets built with a pinned nightly version.
+
+In the `rust-lang/rust` repository, where rustc resides, there's a copy of Clippy
+that compiler hackers modify from time to time to adapt to changes in the unstable
+API of the compiler.
+
+We need to sync these changes back to this repository periodically, and the changes
+made to this repository in the meantime also need to be synced to the `rust-lang/rust` repository.
+
+To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is done
+in a bi-weekly basis if there's no urgent changes. This is done starting on the day of
+the Rust stable release and then every other week. That way we guarantee that we keep
+this repo up to date with the latest compiler API, and every feature in Clippy is available
+for 2 weeks in nightly, before it can get to beta. For reference, the first sync
+following this cadence was performed the 2020-08-27.
+
+This process is described in detail in the following sections. For general information
+about `subtree`s in the Rust repository see [Rust's `CONTRIBUTING.md`][subtree].
+
+### Patching git-subtree to work with big repos
+
+Currently, there's a bug in `git-subtree` that prevents it from working properly
+with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's stale.
+Before continuing with the following steps, we need to manually apply that fix to
+our local copy of `git-subtree`.
+
+You can get the patched version of `git-subtree` from [here][gitgitgadget-pr].
+Put this file under `/usr/lib/git-core` (taking a backup of the previous file)
+and make sure it has the proper permissions:
+
+```bash
+sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree
+sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree
+sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree
+```
+
+_Note:_ The first time running `git subtree push` a cache has to be built. This
+involves going through the complete Clippy history once. For this you have to
+increase the stack limit though, which you can do with `ulimit -s 60000`.
+Make sure to run the `ulimit` command from the same session you call git subtree.
+
+_Note:_ If you are a Debian user, `dash` is the shell used by default for scripts instead of `sh`.
+This shell has a hardcoded recursion limit set to 1000. In order to make this process work,
+you need to force the script to run `bash` instead. You can do this by editing the first
+line of the `git-subtree` script and changing `sh` to `bash`.
+
+### Performing the sync from [`rust-lang/rust`] to Clippy
+
+Here is a TL;DR version of the sync process (all of the following commands have
+to be run inside the `rust` directory):
+
+1. Clone the [`rust-lang/rust`] repository or make sure it is up to date.
+2. Checkout the commit from the latest available nightly. You can get it using `rustup check`.
+3. Sync the changes to the rust-copy of Clippy to your Clippy fork:
+ ```bash
+ # Make sure to change `your-github-name` to your github name in the following command. Also be
+ # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand
+ # because changes cannot be fast forwarded
+ git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust
+ ```
+
+ _Note:_ This will directly push to the remote repository. You can also push
+ to your local copy by replacing the remote address with `/path/to/rust-clippy`
+ directory.
+
+ _Note:_ Most of the time you have to create a merge commit in the
+ `rust-clippy` repo (this has to be done in the Clippy repo, not in the
+ rust-copy of Clippy):
+ ```bash
+ git fetch origin && git fetch upstream
+ git checkout sync-from-rust
+ git merge upstream/master
+ ```
+4. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to
+ accelerate the process ping the `@rust-lang/clippy` team in your PR and/or
+ ~~annoy~~ ask them in the [Zulip] stream.)
+
+### Performing the sync from Clippy to [`rust-lang/rust`]
+
+All of the following commands have to be run inside the `rust` directory.
+
+1. Make sure Clippy itself is up-to-date by following the steps outlined in the previous
+section if necessary.
+
+2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy:
+ ```bash
+ git checkout -b sync-from-clippy
+ git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master
+ ```
+3. Open a PR to [`rust-lang/rust`]
+
+### Defining remotes
+
+You may want to define remotes, so you don't have to type out the remote
+addresses on every sync. You can do this with the following commands (these
+commands still have to be run inside the `rust` directory):
+
+```bash
+# Set clippy-upstream remote for pulls
+$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy
+# Make sure to not push to the upstream repo
+$ git remote set-url --push clippy-upstream DISABLED
+# Set clippy-origin remote to your fork for pushes
+$ git remote add clippy-origin git@github.com:your-github-name/rust-clippy
+# Set a local remote
+$ git remote add clippy-local /path/to/rust-clippy
+```
+
+You can then sync with the remote names from above, e.g.:
+
+```bash
+$ git subtree push -P src/tools/clippy clippy-local sync-from-rust
+```
+
+[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
+[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree
+[`rust-lang/rust`]: https://github.com/rust-lang/rust
+
+## Issue and PR triage
+
+Clippy is following the [Rust triage procedure][triage] for issues and pull
+requests.
+
+However, we are a smaller project with all contributors being volunteers
+currently. Between writing new lints, fixing issues, reviewing pull requests and
+responding to issues there may not always be enough time to stay on top of it
+all.
+
+Our highest priority is fixing [crashes][l-crash] and [bugs][l-bug], for example
+an ICE in a popular crate that many other crates depend on. We don't
+want Clippy to crash on your code and we want it to be as reliable as the
+suggestions from Rust compiler errors.
+
+We have prioritization labels and a sync-blocker label, which are described below.
+- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent.
+- [P-medium][p-medium]: Should be addressed by a team member until the next sync.
+- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport.
+- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync.
+Or rather: before the sync this should be addressed,
+e.g. by removing a lint again, so it doesn't hit beta/stable.
+
+## Bors and Homu
+
+We use a bot powered by [Homu][homu] to help automate testing and landing of pull
+requests in Clippy. The bot's username is @bors.
+
+You can find the Clippy bors queue [here][homu_queue].
+
+If you have @bors permissions, you can find an overview of the available
+commands [here][homu_instructions].
+
+[triage]: https://forge.rust-lang.org/release/triage-procedure.html
+[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
+[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
+[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low
+[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
+[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
+[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
+[homu]: https://github.com/rust-lang/homu
+[homu_instructions]: https://bors.rust-lang.org/
+[homu_queue]: https://bors.rust-lang.org/queue/clippy
+
+## Contributions
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
+All code in this repository is under the [Apache-2.0] or the [MIT] license.
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
+
+[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
+[MIT]: https://opensource.org/licenses/MIT
--- /dev/null
- version = "0.1.62"
+[package]
+name = "clippy"
++version = "0.1.63"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+categories = ["development-tools", "development-tools::cargo-plugins"]
+build = "build.rs"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "cargo-clippy"
+test = false
+path = "src/main.rs"
+
+[[bin]]
+name = "clippy-driver"
+path = "src/driver.rs"
+
+[dependencies]
+clippy_lints = { path = "clippy_lints" }
+semver = "1.0"
+rustc_tools_util = { path = "rustc_tools_util" }
+tempfile = { version = "3.2", optional = true }
++termize = "0.1"
+
+[dev-dependencies]
+compiletest_rs = { version = "0.7.1", features = ["tmp"] }
+tester = "0.9"
+regex = "1.5"
+# This is used by the `collect-metadata` alias.
+filetime = "0.2"
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0"
+
+# UI test dependencies
+clippy_utils = { path = "clippy_utils" }
+derive-new = "0.5"
+if_chain = "1.0"
+itertools = "0.10.1"
+quote = "1.0"
+serde = { version = "1.0.125", features = ["derive"] }
+syn = { version = "1.0", features = ["full"] }
+futures = "0.3"
+parking_lot = "0.11.2"
+tokio = { version = "1", features = ["io-util"] }
+rustc-semver = "1.1"
+
+[build-dependencies]
+rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
+
+[features]
+deny-warnings = ["clippy_lints/deny-warnings"]
+integration = ["tempfile"]
+internal = ["clippy_lints/internal"]
+
+[package.metadata.rust-analyzer]
+# This package uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- pub fn run(path: &str) {
+use crate::cargo_clippy_path;
+use std::process::{self, Command, ExitStatus};
+use std::{fs, io};
+
+fn exit_if_err(status: io::Result<ExitStatus>) {
+ match status.expect("failed to run command").code() {
+ Some(0) => {},
+ Some(n) => process::exit(n),
+ None => {
+ eprintln!("Killed by signal");
+ process::exit(1);
+ },
+ }
+}
+
++pub fn run<'a>(path: &str, args: impl Iterator<Item = &'a str>) {
+ let is_file = match fs::metadata(path) {
+ Ok(metadata) => metadata.is_file(),
+ Err(e) => {
+ eprintln!("Failed to read {path}: {e:?}");
+ process::exit(1);
+ },
+ };
+
+ if is_file {
+ exit_if_err(
+ Command::new("cargo")
+ .args(["run", "--bin", "clippy-driver", "--"])
+ .args(["-L", "./target/debug"])
+ .args(["-Z", "no-codegen"])
+ .args(["--edition", "2021"])
+ .arg(path)
++ .args(args)
+ .status(),
+ );
+ } else {
+ exit_if_err(Command::new("cargo").arg("build").status());
+
+ // Run in a tempdir as changes to clippy do not retrigger linting
+ let target = tempfile::Builder::new()
+ .prefix("clippy")
+ .tempdir()
+ .expect("failed to create tempdir");
+
+ let status = Command::new(cargo_clippy_path())
++ .arg("clippy")
++ .args(args)
+ .current_dir(path)
+ .env("CARGO_TARGET_DIR", target.as_ref())
+ .status();
+
+ target.close().expect("failed to remove tempdir");
+ exit_if_err(status);
+ }
+}
--- /dev/null
- lint::run(path);
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
+use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
+use indoc::indoc;
+fn main() {
+ let matches = get_clap_config();
+
+ match matches.subcommand() {
+ ("bless", Some(matches)) => {
+ bless::bless(matches.is_present("ignore-timestamp"));
+ },
+ ("fmt", Some(matches)) => {
+ fmt::run(matches.is_present("check"), matches.is_present("verbose"));
+ },
+ ("update_lints", Some(matches)) => {
+ if matches.is_present("print-only") {
+ update_lints::print_lints();
+ } else if matches.is_present("check") {
+ update_lints::update(update_lints::UpdateMode::Check);
+ } else {
+ update_lints::update(update_lints::UpdateMode::Change);
+ }
+ },
+ ("new_lint", Some(matches)) => {
+ match new_lint::create(
+ matches.value_of("pass"),
+ matches.value_of("name"),
+ matches.value_of("category"),
+ matches.is_present("msrv"),
+ ) {
+ Ok(_) => update_lints::update(update_lints::UpdateMode::Change),
+ Err(e) => eprintln!("Unable to create lint: {}", e),
+ }
+ },
+ ("setup", Some(sub_command)) => match sub_command.subcommand() {
+ ("intellij", Some(matches)) => {
+ if matches.is_present("remove") {
+ setup::intellij::remove_rustc_src();
+ } else {
+ setup::intellij::setup_rustc_src(
+ matches
+ .value_of("rustc-repo-path")
+ .expect("this field is mandatory and therefore always valid"),
+ );
+ }
+ },
+ ("git-hook", Some(matches)) => {
+ if matches.is_present("remove") {
+ setup::git_hook::remove_hook();
+ } else {
+ setup::git_hook::install_hook(matches.is_present("force-override"));
+ }
+ },
+ ("vscode-tasks", Some(matches)) => {
+ if matches.is_present("remove") {
+ setup::vscode::remove_tasks();
+ } else {
+ setup::vscode::install_tasks(matches.is_present("force-override"));
+ }
+ },
+ _ => {},
+ },
+ ("remove", Some(sub_command)) => match sub_command.subcommand() {
+ ("git-hook", Some(_)) => setup::git_hook::remove_hook(),
+ ("intellij", Some(_)) => setup::intellij::remove_rustc_src(),
+ ("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(),
+ _ => {},
+ },
+ ("serve", Some(matches)) => {
+ let port = matches.value_of("port").unwrap().parse().unwrap();
+ let lint = matches.value_of("lint");
+ serve::run(port, lint);
+ },
+ ("lint", Some(matches)) => {
+ let path = matches.value_of("path").unwrap();
- * lint modules in `clippy_lints/*` are visible in `src/lifb.rs` via `pub mod`\n \
++ let args = matches.values_of("args").into_iter().flatten();
++ lint::run(path, args);
+ },
+ ("rename_lint", Some(matches)) => {
+ let old_name = matches.value_of("old_name").unwrap();
+ let new_name = matches.value_of("new_name").unwrap_or(old_name);
+ let uplift = matches.is_present("uplift");
+ update_lints::rename(old_name, new_name, uplift);
+ },
+ _ => {},
+ }
+}
+
+fn get_clap_config<'a>() -> ArgMatches<'a> {
+ App::new("Clippy developer tooling")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(
+ SubCommand::with_name("bless")
+ .about("bless the test output changes")
+ .arg(
+ Arg::with_name("ignore-timestamp")
+ .long("ignore-timestamp")
+ .help("Include files updated before clippy was built"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("fmt")
+ .about("Run rustfmt on all projects and tests")
+ .arg(
+ Arg::with_name("check")
+ .long("check")
+ .help("Use the rustfmt --check option"),
+ )
+ .arg(
+ Arg::with_name("verbose")
+ .short("v")
+ .long("verbose")
+ .help("Echo commands run"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("update_lints")
+ .about("Updates lint registration and information from the source code")
+ .long_about(
+ "Makes sure that:\n \
+ * the lint count in README.md is correct\n \
+ * the changelog contains markdown link references at the bottom\n \
+ * all lint groups include the correct lints\n \
++ * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \
+ * all lints are registered in the lint store",
+ )
+ .arg(Arg::with_name("print-only").long("print-only").help(
+ "Print a table of lints to STDOUT. \
+ This does not include deprecated and internal lints. \
+ (Does not modify any files)",
+ ))
+ .arg(
+ Arg::with_name("check")
+ .long("check")
+ .help("Checks that `cargo dev update_lints` has been run. Used on CI."),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("new_lint")
+ .about("Create new lint and run `cargo dev update_lints`")
+ .arg(
+ Arg::with_name("pass")
+ .short("p")
+ .long("pass")
+ .help("Specify whether the lint runs during the early or late pass")
+ .takes_value(true)
+ .possible_values(&["early", "late"])
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("name")
+ .short("n")
+ .long("name")
+ .help("Name of the new lint in snake case, ex: fn_too_long")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("category")
+ .short("c")
+ .long("category")
+ .help("What category the lint belongs to")
+ .default_value("nursery")
+ .possible_values(&[
+ "style",
+ "correctness",
+ "suspicious",
+ "complexity",
+ "perf",
+ "pedantic",
+ "restriction",
+ "cargo",
+ "nursery",
+ "internal",
+ "internal_warn",
+ ])
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("msrv")
+ .long("msrv")
+ .help("Add MSRV config code to the lint"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("setup")
+ .about("Support for setting up your personal development environment")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(
+ SubCommand::with_name("intellij")
+ .about("Alter dependencies so Intellij Rust can find rustc internals")
+ .arg(
+ Arg::with_name("remove")
+ .long("remove")
+ .help("Remove the dependencies added with 'cargo dev setup intellij'")
+ .required(false),
+ )
+ .arg(
+ Arg::with_name("rustc-repo-path")
+ .long("repo-path")
+ .short("r")
+ .help("The path to a rustc repo that will be used for setting the dependencies")
+ .takes_value(true)
+ .value_name("path")
+ .conflicts_with("remove")
+ .required(true),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("git-hook")
+ .about("Add a pre-commit git hook that formats your code to make it look pretty")
+ .arg(
+ Arg::with_name("remove")
+ .long("remove")
+ .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'")
+ .required(false),
+ )
+ .arg(
+ Arg::with_name("force-override")
+ .long("force-override")
+ .short("f")
+ .help("Forces the override of an existing git pre-commit hook")
+ .required(false),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("vscode-tasks")
+ .about("Add several tasks to vscode for formatting, validation and testing")
+ .arg(
+ Arg::with_name("remove")
+ .long("remove")
+ .help("Remove the tasks added with 'cargo dev setup vscode-tasks'")
+ .required(false),
+ )
+ .arg(
+ Arg::with_name("force-override")
+ .long("force-override")
+ .short("f")
+ .help("Forces the override of existing vscode tasks")
+ .required(false),
+ ),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("remove")
+ .about("Support for undoing changes done by the setup command")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook"))
+ .subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks"))
+ .subcommand(
+ SubCommand::with_name("intellij")
+ .about("Removes rustc source paths added via `cargo dev setup intellij`"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("serve")
+ .about("Launch a local 'ALL the Clippy Lints' website in a browser")
+ .arg(
+ Arg::with_name("port")
+ .long("port")
+ .short("p")
+ .help("Local port for the http server")
+ .default_value("8000")
+ .validator_os(serve::validate_port),
+ )
+ .arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")),
+ )
+ .subcommand(
+ SubCommand::with_name("lint")
+ .about("Manually run clippy on a file or package")
+ .after_help(indoc! {"
+ EXAMPLES
+ Lint a single file:
+ cargo dev lint tests/ui/attrs.rs
+
+ Lint a package directory:
+ cargo dev lint tests/ui-cargo/wildcard_dependencies/fail
+ cargo dev lint ~/my-project
++
++ Run rustfix:
++ cargo dev lint ~/my-project -- --fix
++
++ Set lint levels:
++ cargo dev lint file.rs -- -W clippy::pedantic
++ cargo dev lint ~/my-project -- -- -W clippy::pedantic
+ "})
+ .arg(
+ Arg::with_name("path")
+ .required(true)
+ .help("The path to a file or package directory to lint"),
++ )
++ .arg(
++ Arg::with_name("args")
++ .multiple(true)
++ .help("Pass extra arguments to cargo/clippy-driver"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("rename_lint")
+ .about("Renames the given lint")
+ .arg(
+ Arg::with_name("old_name")
+ .index(1)
+ .required(true)
+ .help("The name of the lint to rename"),
+ )
+ .arg(
+ Arg::with_name("new_name")
+ .index(2)
+ .required_unless("uplift")
+ .help("The new name of the lint"),
+ )
+ .arg(
+ Arg::with_name("uplift")
+ .long("uplift")
+ .help("This lint will be uplifted into rustc"),
+ ),
+ )
+ .get_matches()
+}
--- /dev/null
- fn get_stabilisation_version() -> String {
+use crate::clippy_project_root;
+use indoc::indoc;
+use std::fmt::Write as _;
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+
+struct LintData<'a> {
+ pass: &'a str,
+ name: &'a str,
+ category: &'a str,
+ project_root: PathBuf,
+}
+
+trait Context {
+ fn context<C: AsRef<str>>(self, text: C) -> Self;
+}
+
+impl<T> Context for io::Result<T> {
+ fn context<C: AsRef<str>>(self, text: C) -> Self {
+ match self {
+ Ok(t) => Ok(t),
+ Err(e) => {
+ let message = format!("{}: {}", text.as_ref(), e);
+ Err(io::Error::new(ErrorKind::Other, message))
+ },
+ }
+ }
+}
+
+/// Creates the files required to implement and test a new lint and runs `update_lints`.
+///
+/// # Errors
+///
+/// This function errors out if the files couldn't be created or written to.
+pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
+ let lint = LintData {
+ pass: pass.expect("`pass` argument is validated by clap"),
+ name: lint_name.expect("`name` argument is validated by clap"),
+ category: category.expect("`category` argument is validated by clap"),
+ project_root: clippy_project_root(),
+ };
+
+ create_lint(&lint, msrv).context("Unable to create lint implementation")?;
+ create_test(&lint).context("Unable to create a test for the new lint")?;
+ add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")
+}
+
+fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ let lint_contents = get_lint_file_contents(lint, enable_msrv);
+
+ let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
+ write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
+}
+
+fn create_test(lint: &LintData<'_>) -> io::Result<()> {
+ fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
+ let mut path = location.into().join(case);
+ fs::create_dir(&path)?;
+ write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
+
+ path.push("src");
+ fs::create_dir(&path)?;
+ let header = format!("// compile-flags: --crate-name={}", lint_name);
+ write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
+
+ Ok(())
+ }
+
+ if lint.category == "cargo" {
+ let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
+ let test_dir = lint.project_root.join(relative_test_dir);
+ fs::create_dir(&test_dir)?;
+
+ create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
+ create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
+ } else {
+ let test_path = format!("tests/ui/{}.rs", lint.name);
+ let test_contents = get_test_file_contents(lint.name, None);
+ write_file(lint.project_root.join(test_path), test_contents)
+ }
+}
+
+fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ let path = "clippy_lints/src/lib.rs";
+ let mut lib_rs = fs::read_to_string(path).context("reading")?;
+
+ let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
+
+ let new_lint = if enable_msrv {
+ format!(
+ "store.register_{lint_pass}_pass(move || Box::new({module_name}::{camel_name}::new(msrv)));\n ",
+ lint_pass = lint.pass,
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ } else {
+ format!(
+ "store.register_{lint_pass}_pass(|| Box::new({module_name}::{camel_name}));\n ",
+ lint_pass = lint.pass,
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ };
+
+ lib_rs.insert_str(comment_start, &new_lint);
+
+ fs::write(path, lib_rs).context("writing")
+}
+
+fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
+ fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
+ OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(path)?
+ .write_all(contents)
+ }
+
+ inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
+}
+
+fn to_camel_case(name: &str) -> String {
+ name.split('_')
+ .map(|s| {
+ if s.is_empty() {
+ String::from("")
+ } else {
+ [&s[0..1].to_uppercase(), &s[1..]].concat()
+ }
+ })
+ .collect()
+}
+
- let version = get_stabilisation_version();
++fn get_stabilization_version() -> String {
+ fn parse_manifest(contents: &str) -> Option<String> {
+ let version = contents
+ .lines()
+ .filter_map(|l| l.split_once('='))
+ .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
+ let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
+ return None;
+ };
+ let (minor, patch) = version.split_once('.')?;
+ Some(format!(
+ "{}.{}.0",
+ minor.parse::<u32>().ok()?,
+ patch.parse::<u32>().ok()?
+ ))
+ }
+ let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
+ parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
+}
+
+fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
+ let mut contents = format!(
+ indoc! {"
+ #![warn(clippy::{})]
+
+ fn main() {{
+ // test code goes here
+ }}
+ "},
+ lint_name
+ );
+
+ if let Some(header) = header_commands {
+ contents = format!("{}\n{}", header, contents);
+ }
+
+ contents
+}
+
+fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
+ format!(
+ indoc! {r#"
+ # {}
+
+ [package]
+ name = "{}"
+ version = "0.1.0"
+ publish = false
+
+ [workspace]
+ "#},
+ hint, lint_name
+ )
+}
+
+fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
+ let mut result = String::new();
+
+ let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
+ "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
+ "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
+ _ => {
+ unreachable!("`pass_type` should only ever be `early` or `late`!");
+ },
+ };
+
++ let version = get_stabilization_version();
+ let lint_name = lint.name;
+ let category = lint.category;
+ let name_camel = to_camel_case(lint.name);
+ let name_upper = lint_name.to_uppercase();
+
+ result.push_str(&if enable_msrv {
+ format!(
+ indoc! {"
+ use clippy_utils::msrvs;
+ {pass_import}
+ use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
+ use rustc_semver::RustcVersion;
+ use rustc_session::{{declare_tool_lint, impl_lint_pass}};
+
+ "},
+ pass_type = pass_type,
+ pass_import = pass_import,
+ context_import = context_import,
+ )
+ } else {
+ format!(
+ indoc! {"
+ {pass_import}
+ use rustc_lint::{{{context_import}, {pass_type}}};
+ use rustc_session::{{declare_lint_pass, declare_tool_lint}};
+
+ "},
+ pass_import = pass_import,
+ pass_type = pass_type,
+ context_import = context_import
+ )
+ });
+
+ let _ = write!(
+ result,
+ indoc! {r#"
+ declare_clippy_lint! {{
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// ```
+ #[clippy::version = "{version}"]
+ pub {name_upper},
+ {category},
+ "default lint description"
+ }}
+ "#},
+ version = version,
+ name_upper = name_upper,
+ category = category,
+ );
+
+ result.push_str(&if enable_msrv {
+ format!(
+ indoc! {"
+ pub struct {name_camel} {{
+ msrv: Option<RustcVersion>,
+ }}
+
+ impl {name_camel} {{
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {{
+ Self {{ msrv }}
+ }}
+ }}
+
+ impl_lint_pass!({name_camel} => [{name_upper}]);
+
+ impl {pass_type}{pass_lifetimes} for {name_camel} {{
+ extract_msrv_attr!({context_import});
+ }}
+
+ // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
+ // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
+ // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
+ "},
+ pass_type = pass_type,
+ pass_lifetimes = pass_lifetimes,
+ name_upper = name_upper,
+ name_camel = name_camel,
+ context_import = context_import,
+ )
+ } else {
+ format!(
+ indoc! {"
+ declare_lint_pass!({name_camel} => [{name_upper}]);
+
+ impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
+ "},
+ pass_type = pass_type,
+ pass_lifetimes = pass_lifetimes,
+ name_upper = name_upper,
+ name_camel = name_camel,
+ )
+ });
+
+ result
+}
+
+#[test]
+fn test_camel_case() {
+ let s = "a_lint";
+ let s2 = to_camel_case(s);
+ assert_eq!(s2, "ALint");
+
+ let name = "a_really_long_new_lint";
+ let name2 = to_camel_case(name);
+ assert_eq!(name2, "AReallyLongNewLint");
+
+ let name3 = "lint__name";
+ let name4 = to_camel_case(name3);
+ assert_eq!(name4, "LintName");
+}
--- /dev/null
- #[derive(Clone, Copy, PartialEq)]
+use aho_corasick::AhoCorasickBuilder;
+use core::fmt::Write as _;
+use itertools::Itertools;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
+use std::collections::{HashMap, HashSet};
+use std::ffi::OsStr;
+use std::fs;
+use std::io::{self, Read as _, Seek as _, Write as _};
+use std::path::{Path, PathBuf};
+use walkdir::{DirEntry, WalkDir};
+
+use crate::clippy_project_root;
+
+const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
+ // Use that command to update this file and do not edit by hand.\n\
+ // Manual edits will be overwritten.\n\n";
+
+const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
+
- .map(|l| &l.name)
- .chain(deprecated_lints.iter().map(|l| &l.name))
++#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum UpdateMode {
+ Check,
+ Change,
+}
+
+/// Runs the `update_lints` command.
+///
+/// This updates various generated values from the lint source code.
+///
+/// `update_mode` indicates if the files should be updated or if updates should be checked for.
+///
+/// # Panics
+///
+/// Panics if a file path could not read from or then written to
+pub fn update(update_mode: UpdateMode) {
+ let (lints, deprecated_lints, renamed_lints) = gather_all();
+ generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints);
+}
+
+fn generate_lint_files(
+ update_mode: UpdateMode,
+ lints: &[Lint],
+ deprecated_lints: &[DeprecatedLint],
+ renamed_lints: &[RenamedLint],
+) {
+ let internal_lints = Lint::internal_lints(lints);
+ let usable_lints = Lint::usable_lints(lints);
+ let mut sorted_usable_lints = usable_lints.clone();
+ sorted_usable_lints.sort_by_key(|lint| lint.name.clone());
+
+ replace_region_in_file(
+ update_mode,
+ Path::new("README.md"),
+ "[There are over ",
+ " lints included in this crate!]",
+ |res| {
+ write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
+ },
+ );
+
+ replace_region_in_file(
+ update_mode,
+ Path::new("CHANGELOG.md"),
+ "<!-- begin autogenerated links to lint list -->\n",
+ "<!-- end autogenerated links to lint list -->",
+ |res| {
+ for lint in usable_lints
+ .iter()
- #[derive(Clone, PartialEq, Debug)]
++ .map(|l| &*l.name)
++ .chain(deprecated_lints.iter().map(|l| &*l.name))
++ .chain(
++ renamed_lints
++ .iter()
++ .map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)),
++ )
+ .sorted()
+ {
+ writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap();
+ }
+ },
+ );
+
+ // This has to be in lib.rs, otherwise rustfmt doesn't work
+ replace_region_in_file(
+ update_mode,
+ Path::new("clippy_lints/src/lib.rs"),
+ "// begin lints modules, do not remove this comment, it’s used in `update_lints`\n",
+ "// end lints modules, do not remove this comment, it’s used in `update_lints`",
+ |res| {
+ for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
+ writeln!(res, "mod {};", lint_mod).unwrap();
+ }
+ },
+ );
+
+ process_file(
+ "clippy_lints/src/lib.register_lints.rs",
+ update_mode,
+ &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
+ );
+ process_file(
+ "clippy_lints/src/lib.deprecated.rs",
+ update_mode,
+ &gen_deprecated(deprecated_lints),
+ );
+
+ let all_group_lints = usable_lints.iter().filter(|l| {
+ matches!(
+ &*l.group,
+ "correctness" | "suspicious" | "style" | "complexity" | "perf"
+ )
+ });
+ let content = gen_lint_group_list("all", all_group_lints);
+ process_file("clippy_lints/src/lib.register_all.rs", update_mode, &content);
+
+ for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
+ let content = gen_lint_group_list(&lint_group, lints.iter());
+ process_file(
+ &format!("clippy_lints/src/lib.register_{}.rs", lint_group),
+ update_mode,
+ &content,
+ );
+ }
+
+ let content = gen_deprecated_lints_test(deprecated_lints);
+ process_file("tests/ui/deprecated.rs", update_mode, &content);
+
+ let content = gen_renamed_lints_test(renamed_lints);
+ process_file("tests/ui/rename.rs", update_mode, &content);
+}
+
+pub fn print_lints() {
+ let (lint_list, _, _) = gather_all();
+ let usable_lints = Lint::usable_lints(&lint_list);
+ let usable_lint_count = usable_lints.len();
+ let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
+
+ for (lint_group, mut lints) in grouped_by_lint_group {
+ println!("\n## {}", lint_group);
+
+ lints.sort_by_key(|l| l.name.clone());
+
+ for lint in lints {
+ println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc);
+ }
+ }
+
+ println!("there are {} lints", usable_lint_count);
+}
+
+/// Runs the `rename_lint` command.
+///
+/// This does the following:
+/// * Adds an entry to `renamed_lints.rs`.
+/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
+/// * Renames the lint struct to the new name.
+/// * Renames the module containing the lint struct to the new name if it shares a name with the
+/// lint.
+///
+/// # Panics
+/// Panics for the following conditions:
+/// * If a file path could not read from or then written to
+/// * If either lint name has a prefix
+/// * If `old_name` doesn't name an existing lint.
+/// * If `old_name` names a deprecated or renamed lint.
+#[allow(clippy::too_many_lines)]
+pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
+ if let Some((prefix, _)) = old_name.split_once("::") {
+ panic!("`{}` should not contain the `{}` prefix", old_name, prefix);
+ }
+ if let Some((prefix, _)) = new_name.split_once("::") {
+ panic!("`{}` should not contain the `{}` prefix", new_name, prefix);
+ }
+
+ let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
+ let mut old_lint_index = None;
+ let mut found_new_name = false;
+ for (i, lint) in lints.iter().enumerate() {
+ if lint.name == old_name {
+ old_lint_index = Some(i);
+ } else if lint.name == new_name {
+ found_new_name = true;
+ }
+ }
+ let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name));
+
+ let lint = RenamedLint {
+ old_name: format!("clippy::{}", old_name),
+ new_name: if uplift {
+ new_name.into()
+ } else {
+ format!("clippy::{}", new_name)
+ },
+ };
+
+ // Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
+ // case.
+ assert!(
+ !renamed_lints.iter().any(|l| lint.old_name == l.old_name),
+ "`{}` has already been renamed",
+ old_name
+ );
+ assert!(
+ !deprecated_lints.iter().any(|l| lint.old_name == l.name),
+ "`{}` has already been deprecated",
+ old_name
+ );
+
+ // Update all lint level attributes. (`clippy::lint_name`)
+ for file in WalkDir::new(clippy_project_root())
+ .into_iter()
+ .map(Result::unwrap)
+ .filter(|f| {
+ let name = f.path().file_name();
+ let ext = f.path().extension();
+ (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
+ && name != Some(OsStr::new("rename.rs"))
+ && name != Some(OsStr::new("renamed_lints.rs"))
+ })
+ {
+ rewrite_file(file.path(), |s| {
+ replace_ident_like(s, &[(&lint.old_name, &lint.new_name)])
+ });
+ }
+
+ renamed_lints.push(lint);
+ renamed_lints.sort_by(|lhs, rhs| {
+ lhs.new_name
+ .starts_with("clippy::")
+ .cmp(&rhs.new_name.starts_with("clippy::"))
+ .reverse()
+ .then_with(|| lhs.old_name.cmp(&rhs.old_name))
+ });
+
+ write_file(
+ Path::new("clippy_lints/src/renamed_lints.rs"),
+ &gen_renamed_lints_list(&renamed_lints),
+ );
+
+ if uplift {
+ write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
+ println!(
+ "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.",
+ old_name
+ );
+ } else if found_new_name {
+ write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
+ println!(
+ "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.",
+ new_name
+ );
+ } else {
+ // Rename the lint struct and source files sharing a name with the lint.
+ let lint = &mut lints[old_lint_index];
+ let old_name_upper = old_name.to_uppercase();
+ let new_name_upper = new_name.to_uppercase();
+ lint.name = new_name.into();
+
+ // Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
+ if try_rename_file(
+ Path::new(&format!("tests/ui/{}.rs", old_name)),
+ Path::new(&format!("tests/ui/{}.rs", new_name)),
+ ) {
+ try_rename_file(
+ Path::new(&format!("tests/ui/{}.stderr", old_name)),
+ Path::new(&format!("tests/ui/{}.stderr", new_name)),
+ );
+ try_rename_file(
+ Path::new(&format!("tests/ui/{}.fixed", old_name)),
+ Path::new(&format!("tests/ui/{}.fixed", new_name)),
+ );
+ }
+
+ // Try to rename the file containing the lint if the file name matches the lint's name.
+ let replacements;
+ let replacements = if lint.module == old_name
+ && try_rename_file(
+ Path::new(&format!("clippy_lints/src/{}.rs", old_name)),
+ Path::new(&format!("clippy_lints/src/{}.rs", new_name)),
+ ) {
+ // Edit the module name in the lint list. Note there could be multiple lints.
+ for lint in lints.iter_mut().filter(|l| l.module == old_name) {
+ lint.module = new_name.into();
+ }
+ replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
+ replacements.as_slice()
+ } else if !lint.module.contains("::")
+ // Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
+ && try_rename_file(
+ Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)),
+ Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)),
+ )
+ {
+ // Edit the module name in the lint list. Note there could be multiple lints, or none.
+ let renamed_mod = format!("{}::{}", lint.module, old_name);
+ for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
+ lint.module = format!("{}::{}", lint.module, new_name);
+ }
+ replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
+ replacements.as_slice()
+ } else {
+ replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
+ &replacements[0..1]
+ };
+
+ // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
+ // renamed.
+ for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) {
+ rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
+ }
+
+ generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
+ println!("{} has been successfully renamed", old_name);
+ }
+
+ println!("note: `cargo uitest` still needs to be run to update the test results");
+}
+
+/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
+/// were no replacements.
+fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
+ fn is_ident_char(c: u8) -> bool {
+ matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
+ }
+
+ let searcher = AhoCorasickBuilder::new()
+ .dfa(true)
+ .match_kind(aho_corasick::MatchKind::LeftmostLongest)
+ .build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
+ .unwrap();
+
+ let mut result = String::with_capacity(contents.len() + 1024);
+ let mut pos = 0;
+ let mut edited = false;
+ for m in searcher.find_iter(contents) {
+ let (old, new) = replacements[m.pattern()];
+ result.push_str(&contents[pos..m.start()]);
+ result.push_str(
+ if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
+ && !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0))
+ {
+ edited = true;
+ new
+ } else {
+ old
+ },
+ );
+ pos = m.end();
+ }
+ result.push_str(&contents[pos..]);
+ edited.then(|| result)
+}
+
+fn round_to_fifty(count: usize) -> usize {
+ count / 50 * 50
+}
+
+fn process_file(path: impl AsRef<Path>, update_mode: UpdateMode, content: &str) {
+ if update_mode == UpdateMode::Check {
+ let old_content =
+ fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.as_ref().display(), e));
+ if content != old_content {
+ exit_with_failure();
+ }
+ } else {
+ fs::write(&path, content.as_bytes())
+ .unwrap_or_else(|e| panic!("Cannot write to {}: {}", path.as_ref().display(), e));
+ }
+}
+
+fn exit_with_failure() {
+ println!(
+ "Not all lints defined properly. \
+ Please run `cargo dev update_lints` to make sure all lints are defined properly."
+ );
+ std::process::exit(1);
+}
+
+/// Lint data parsed from the Clippy source code.
- #[derive(Clone, PartialEq, Debug)]
++#[derive(Clone, PartialEq, Eq, Debug)]
+struct Lint {
+ name: String,
+ group: String,
+ desc: String,
+ module: String,
+}
+
+impl Lint {
+ #[must_use]
+ fn new(name: &str, group: &str, desc: &str, module: &str) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ group: group.into(),
+ desc: remove_line_splices(desc),
+ module: module.into(),
+ }
+ }
+
+ /// Returns all non-deprecated lints and non-internal lints
+ #[must_use]
+ fn usable_lints(lints: &[Self]) -> Vec<Self> {
+ lints
+ .iter()
+ .filter(|l| !l.group.starts_with("internal"))
+ .cloned()
+ .collect()
+ }
+
+ /// Returns all internal lints (not `internal_warn` lints)
+ #[must_use]
+ fn internal_lints(lints: &[Self]) -> Vec<Self> {
+ lints.iter().filter(|l| l.group == "internal").cloned().collect()
+ }
+
+ /// Returns the lints in a `HashMap`, grouped by the different lint groups
+ #[must_use]
+ fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
+ lints.map(|lint| (lint.group.to_string(), lint)).into_group_map()
+ }
+}
+
++#[derive(Clone, PartialEq, Eq, Debug)]
+struct DeprecatedLint {
+ name: String,
+ reason: String,
+}
+impl DeprecatedLint {
+ fn new(name: &str, reason: &str) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ reason: remove_line_splices(reason),
+ }
+ }
+}
+
+struct RenamedLint {
+ old_name: String,
+ new_name: String,
+}
+impl RenamedLint {
+ fn new(old_name: &str, new_name: &str) -> Self {
+ Self {
+ old_name: remove_line_splices(old_name),
+ new_name: remove_line_splices(new_name),
+ }
+ }
+}
+
+/// Generates the code for registering a group
+fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
+ let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
+ details.sort_unstable();
+
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+
+ let _ = writeln!(
+ output,
+ "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![",
+ group_name
+ );
+ for (module, name) in details {
+ let _ = writeln!(output, " LintId::of({}::{}),", module, name);
+ }
+ output.push_str("])\n");
+
+ output
+}
+
+/// Generates the `register_removed` code
+#[must_use]
+fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+ output.push_str("{\n");
+ for lint in lints {
+ let _ = write!(
+ output,
+ concat!(
+ " store.register_removed(\n",
+ " \"clippy::{}\",\n",
+ " \"{}\",\n",
+ " );\n"
+ ),
+ lint.name, lint.reason,
+ );
+ }
+ output.push_str("}\n");
+
+ output
+}
+
+/// Generates the code for registering lints
+#[must_use]
+fn gen_register_lint_list<'a>(
+ internal_lints: impl Iterator<Item = &'a Lint>,
+ usable_lints: impl Iterator<Item = &'a Lint>,
+) -> String {
+ let mut details: Vec<_> = internal_lints
+ .map(|l| (false, &l.module, l.name.to_uppercase()))
+ .chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
+ .collect();
+ details.sort_unstable();
+
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+ output.push_str("store.register_lints(&[\n");
+
+ for (is_public, module_name, lint_name) in details {
+ if !is_public {
+ output.push_str(" #[cfg(feature = \"internal\")]\n");
+ }
+ let _ = writeln!(output, " {}::{},", module_name, lint_name);
+ }
+ output.push_str("])\n");
+
+ output
+}
+
+fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
+ let mut res: String = GENERATED_FILE_COMMENT.into();
+ for lint in lints {
+ writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap();
+ }
+ res.push_str("\nfn main() {}\n");
+ res
+}
+
+fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
+ let mut seen_lints = HashSet::new();
+ let mut res: String = GENERATED_FILE_COMMENT.into();
+ res.push_str("// run-rustfix\n\n");
+ for lint in lints {
+ if seen_lints.insert(&lint.new_name) {
+ writeln!(res, "#![allow({})]", lint.new_name).unwrap();
+ }
+ }
+ seen_lints.clear();
+ for lint in lints {
+ if seen_lints.insert(&lint.old_name) {
+ writeln!(res, "#![warn({})]", lint.old_name).unwrap();
+ }
+ }
+ res.push_str("\nfn main() {}\n");
+ res
+}
+
+fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
+ const HEADER: &str = "\
+ // This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
+ #[rustfmt::skip]\n\
+ pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
+
+ let mut res = String::from(HEADER);
+ for lint in lints {
+ writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
+ }
+ res.push_str("];\n");
+ res
+}
+
+/// Gathers all lints defined in `clippy_lints/src`
+fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
+ let mut lints = Vec::with_capacity(1000);
+ let mut deprecated_lints = Vec::with_capacity(50);
+ let mut renamed_lints = Vec::with_capacity(50);
+
+ for (rel_path, file) in clippy_lints_src_files() {
+ let path = file.path();
+ let contents =
+ fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
+ let module = rel_path
+ .components()
+ .map(|c| c.as_os_str().to_str().unwrap())
+ .collect::<Vec<_>>()
+ .join("::");
+
+ // If the lints are stored in mod.rs, we get the module name from
+ // the containing directory:
+ let module = if let Some(module) = module.strip_suffix("::mod.rs") {
+ module
+ } else {
+ module.strip_suffix(".rs").unwrap_or(&module)
+ };
+
+ match module {
+ "deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints),
+ "renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints),
+ _ => parse_contents(&contents, module, &mut lints),
+ }
+ }
+ (lints, deprecated_lints, renamed_lints)
+}
+
+fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
+ let root_path = clippy_project_root().join("clippy_lints/src");
+ let iter = WalkDir::new(&root_path).into_iter();
+ iter.map(Result::unwrap)
+ .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
+ .map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
+}
+
+macro_rules! match_tokens {
+ ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
+ {
+ $($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() {
+ _x
+ } else {
+ continue;
+ };)*
+ #[allow(clippy::unused_unit)]
+ { ($($($capture,)?)*) }
+ }
+ }
+}
+
+/// Parse a source file looking for `declare_clippy_lint` macro invocations.
+fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
+ let mut offset = 0usize;
+ let mut iter = tokenize(contents).map(|t| {
+ let range = offset..offset + t.len;
+ offset = range.end;
+ (t.kind, &contents[range])
+ });
+
+ while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") {
+ let mut iter = iter
+ .by_ref()
+ .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
+ // matches `!{`
+ match_tokens!(iter, Bang OpenBrace);
+ match iter.next() {
+ // #[clippy::version = "version"] pub
+ Some((TokenKind::Pound, _)) => {
+ match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
+ },
+ // pub
+ Some((TokenKind::Ident, _)) => (),
+ _ => continue,
+ }
+ let (name, group, desc) = match_tokens!(
+ iter,
+ // LINT_NAME
+ Ident(name) Comma
+ // group,
+ Ident(group) Comma
+ // "description" }
+ Literal{..}(desc) CloseBrace
+ );
+ lints.push(Lint::new(name, group, desc, module));
+ }
+}
+
+/// Parse a source file looking for `declare_deprecated_lint` macro invocations.
+fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
+ let mut offset = 0usize;
+ let mut iter = tokenize(contents).map(|t| {
+ let range = offset..offset + t.len;
+ offset = range.end;
+ (t.kind, &contents[range])
+ });
+ while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
+ let mut iter = iter
+ .by_ref()
+ .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
+ let (name, reason) = match_tokens!(
+ iter,
+ // !{
+ Bang OpenBrace
+ // #[clippy::version = "version"]
+ Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket
+ // pub LINT_NAME,
+ Ident Ident(name) Comma
+ // "description"
+ Literal{kind: LiteralKind::Str{..},..}(reason)
+ // }
+ CloseBrace
+ );
+ lints.push(DeprecatedLint::new(name, reason));
+ }
+}
+
+fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
+ for line in contents.lines() {
+ let mut offset = 0usize;
+ let mut iter = tokenize(line).map(|t| {
+ let range = offset..offset + t.len;
+ offset = range.end;
+ (t.kind, &line[range])
+ });
+ let (old_name, new_name) = match_tokens!(
+ iter,
+ // ("old_name",
+ Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma
+ // "new_name"),
+ Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
+ );
+ lints.push(RenamedLint::new(old_name, new_name));
+ }
+}
+
+/// Removes the line splices and surrounding quotes from a string literal
+fn remove_line_splices(s: &str) -> String {
+ let s = s
+ .strip_prefix('r')
+ .unwrap_or(s)
+ .trim_matches('#')
+ .strip_prefix('"')
+ .and_then(|s| s.strip_suffix('"'))
+ .unwrap_or_else(|| panic!("expected quoted string, found `{}`", s));
+ let mut res = String::with_capacity(s.len());
+ unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, _| res.push_str(&s[range]));
+ res
+}
+
+/// Replaces a region in a file delimited by two lines matching regexes.
+///
+/// `path` is the relative path to the file on which you want to perform the replacement.
+///
+/// See `replace_region_in_text` for documentation of the other options.
+///
+/// # Panics
+///
+/// Panics if the path could not read or then written
+fn replace_region_in_file(
+ update_mode: UpdateMode,
+ path: &Path,
+ start: &str,
+ end: &str,
+ write_replacement: impl FnMut(&mut String),
+) {
+ let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
+ let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) {
+ Ok(x) => x,
+ Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()),
+ };
+
+ match update_mode {
+ UpdateMode::Check if contents != new_contents => exit_with_failure(),
+ UpdateMode::Check => (),
+ UpdateMode::Change => {
+ if let Err(e) = fs::write(path, new_contents.as_bytes()) {
+ panic!("Cannot write to `{}`: {}", path.display(), e);
+ }
+ },
+ }
+}
+
+/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
+/// were found, or the missing delimiter if not.
+fn replace_region_in_text<'a>(
+ text: &str,
+ start: &'a str,
+ end: &'a str,
+ mut write_replacement: impl FnMut(&mut String),
+) -> Result<String, &'a str> {
+ let (text_start, rest) = text.split_once(start).ok_or(start)?;
+ let (_, text_end) = rest.split_once(end).ok_or(end)?;
+
+ let mut res = String::with_capacity(text.len() + 4096);
+ res.push_str(text_start);
+ res.push_str(start);
+ write_replacement(&mut res);
+ res.push_str(end);
+ res.push_str(text_end);
+
+ Ok(res)
+}
+
+fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
+ match fs::OpenOptions::new().create_new(true).write(true).open(new_name) {
+ Ok(file) => drop(file),
+ Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
+ Err(e) => panic_file(e, new_name, "create"),
+ };
+ match fs::rename(old_name, new_name) {
+ Ok(()) => true,
+ Err(e) => {
+ drop(fs::remove_file(new_name));
+ if e.kind() == io::ErrorKind::NotFound {
+ false
+ } else {
+ panic_file(e, old_name, "rename");
+ }
+ },
+ }
+}
+
+#[allow(clippy::needless_pass_by_value)]
+fn panic_file(error: io::Error, name: &Path, action: &str) -> ! {
+ panic!("failed to {} file `{}`: {}", action, name.display(), error)
+}
+
+fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
+ let mut file = fs::OpenOptions::new()
+ .write(true)
+ .read(true)
+ .open(path)
+ .unwrap_or_else(|e| panic_file(e, path, "open"));
+ let mut buf = String::new();
+ file.read_to_string(&mut buf)
+ .unwrap_or_else(|e| panic_file(e, path, "read"));
+ if let Some(new_contents) = f(&buf) {
+ file.rewind().unwrap_or_else(|e| panic_file(e, path, "write"));
+ file.write_all(new_contents.as_bytes())
+ .unwrap_or_else(|e| panic_file(e, path, "write"));
+ file.set_len(new_contents.len() as u64)
+ .unwrap_or_else(|e| panic_file(e, path, "write"));
+ }
+}
+
+fn write_file(path: &Path, contents: &str) {
+ fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write"));
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_contents() {
+ static CONTENTS: &str = r#"
+ declare_clippy_lint! {
+ #[clippy::version = "Hello Clippy!"]
+ pub PTR_ARG,
+ style,
+ "really long \
+ text"
+ }
+
+ declare_clippy_lint!{
+ #[clippy::version = "Test version"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "single line"
+ }
+ "#;
+ let mut result = Vec::new();
+ parse_contents(CONTENTS, "module_name", &mut result);
+
+ let expected = vec![
+ Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"),
+ Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
+ ];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_parse_deprecated_contents() {
+ static DEPRECATED_CONTENTS: &str = r#"
+ /// some doc comment
+ declare_deprecated_lint! {
+ #[clippy::version = "I'm a version"]
+ pub SHOULD_ASSERT_EQ,
+ "`assert!()` will be more flexible with RFC 2011"
+ }
+ "#;
+
+ let mut result = Vec::new();
+ parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
+
+ let expected = vec![DeprecatedLint::new(
+ "should_assert_eq",
+ "\"`assert!()` will be more flexible with RFC 2011\"",
+ )];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_usable_lints() {
+ let lints = vec![
+ Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
+ Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
+ Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
+ ];
+ let expected = vec![Lint::new(
+ "should_assert_eq2",
+ "Not Deprecated",
+ "\"abc\"",
+ "module_name",
+ )];
+ assert_eq!(expected, Lint::usable_lints(&lints));
+ }
+
+ #[test]
+ fn test_by_lint_group() {
+ let lints = vec![
+ Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
+ Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
+ Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
+ ];
+ let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
+ expected.insert(
+ "group1".to_string(),
+ vec![
+ Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
+ Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
+ ],
+ );
+ expected.insert(
+ "group2".to_string(),
+ vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
+ );
+ assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
+ }
+
+ #[test]
+ fn test_gen_deprecated() {
+ let lints = vec![
+ DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
+ DeprecatedLint::new("another_deprecated", "\"will be removed\""),
+ ];
+
+ let expected = GENERATED_FILE_COMMENT.to_string()
+ + &[
+ "{",
+ " store.register_removed(",
+ " \"clippy::should_assert_eq\",",
+ " \"has been superseded by should_assert_eq2\",",
+ " );",
+ " store.register_removed(",
+ " \"clippy::another_deprecated\",",
+ " \"will be removed\",",
+ " );",
+ "}",
+ ]
+ .join("\n")
+ + "\n";
+
+ assert_eq!(expected, gen_deprecated(&lints));
+ }
+
+ #[test]
+ fn test_gen_lint_group_list() {
+ let lints = vec![
+ Lint::new("abc", "group1", "\"abc\"", "module_name"),
+ Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
+ Lint::new("internal", "internal_style", "\"abc\"", "module_name"),
+ ];
+ let expected = GENERATED_FILE_COMMENT.to_string()
+ + &[
+ "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![",
+ " LintId::of(module_name::ABC),",
+ " LintId::of(module_name::INTERNAL),",
+ " LintId::of(module_name::SHOULD_ASSERT_EQ),",
+ "])",
+ ]
+ .join("\n")
+ + "\n";
+
+ let result = gen_lint_group_list("group1", lints.iter());
+
+ assert_eq!(expected, result);
+ }
+}
--- /dev/null
- version = "0.1.62"
+[package]
+name = "clippy_lints"
++version = "0.1.63"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+edition = "2021"
+
+[dependencies]
+cargo_metadata = "0.14"
+clippy_utils = { path = "../clippy_utils" }
+if_chain = "1.0"
+itertools = "0.10.1"
+pulldown-cmark = { version = "0.9", default-features = false }
+quine-mc_cluskey = "0.2"
+regex-syntax = "0.6"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = { version = "1.0", optional = true }
+toml = "0.5"
+unicode-normalization = "0.1"
+unicode-script = { version = "0.5", default-features = false }
+semver = "1.0"
+rustc-semver = "1.1"
+# NOTE: cargo requires serde feat in its url dep
+# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
+url = { version = "2.2", features = ["serde"] }
+
+[features]
+deny-warnings = ["clippy_utils/deny-warnings"]
+# build clippy with internal lints enabled, off by default
+internal = ["clippy_utils/internal", "serde_json"]
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- if is_approx_const(constant, s, min_digits)
- && msrv.as_ref().map_or(true, |msrv| meets_msrv(self.msrv.as_ref(), msrv))
- {
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol;
+use std::f64::consts as f64;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for floating point literals that approximate
+ /// constants which are defined in
+ /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
+ /// or
+ /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
+ /// respectively, suggesting to use the predefined constant.
+ ///
+ /// ### Why is this bad?
+ /// Usually, the definition in the standard library is more
+ /// precise than what people come up with. If you find that your definition is
+ /// actually more precise, please [file a Rust
+ /// issue](https://github.com/rust-lang/rust/issues).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 3.14;
+ /// let y = 1_f64 / x;
+ /// ```
+ /// Use predefined constants instead:
+ /// ```rust
+ /// let x = std::f32::consts::PI;
+ /// let y = std::f64::consts::FRAC_1_PI;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub APPROX_CONSTANT,
+ correctness,
+ "the approximate of a known float constant (in `std::fXX::consts`)"
+}
+
+// Tuples are of the form (constant, name, min_digits, msrv)
+const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
+ (f64::E, "E", 4, None),
+ (f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
+ (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
+ (f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
+ (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
+ (f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
+ (f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
+ (f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
+ (f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
+ (f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
+ (f64::LN_2, "LN_2", 5, None),
+ (f64::LN_10, "LN_10", 5, None),
+ (f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
+ (f64::LOG2_E, "LOG2_E", 5, None),
+ (f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
+ (f64::LOG10_E, "LOG10_E", 5, None),
+ (f64::PI, "PI", 3, None),
+ (f64::SQRT_2, "SQRT_2", 5, None),
+ (f64::TAU, "TAU", 3, Some(msrvs::TAU)),
+];
+
+pub struct ApproxConstant {
+ msrv: Option<RustcVersion>,
+}
+
+impl ApproxConstant {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+
+ fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
+ match *lit {
+ LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
+ FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"),
+ FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"),
+ },
+ LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"),
+ _ => (),
+ }
+ }
+
+ fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
+ let s = s.as_str();
+ if s.parse::<f64>().is_ok() {
+ for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
++ if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| meets_msrv(self.msrv, msrv)) {
+ span_lint_and_help(
+ cx,
+ APPROX_CONSTANT,
+ e.span,
+ &format!("approximate value of `{}::consts::{}` found", module, &name),
+ None,
+ "consider using the constant directly",
+ );
+ return;
+ }
+ }
+ }
+ }
+}
+
+impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
+
+impl<'tcx> LateLintPass<'tcx> for ApproxConstant {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Lit(lit) = &e.kind {
+ self.check_lit(cx, &lit.node, e);
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns `false` if the number of significant figures in `value` are
+/// less than `min_digits`; otherwise, returns true if `value` is equal
+/// to `constant`, rounded to the number of digits present in `value`.
+#[must_use]
+fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
+ if value.len() <= min_digits {
+ false
+ } else if constant.to_string().starts_with(value) {
+ // The value is a truncated constant
+ true
+ } else {
+ let round_const = format!("{:.*}", value.len() - 2, constant);
+ value == round_const
+ }
+}
--- /dev/null
- /// If `a op= a op b` is really the correct behaviour it should be
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{binop_traits, sugg};
+use clippy_utils::{eq_expr_value, trait_ref_of_method};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a = a op b` or `a = b commutative_op a`
+ /// patterns.
+ ///
+ /// ### Why is this bad?
+ /// These can be written as the shorter `a op= b`.
+ ///
+ /// ### Known problems
+ /// While forbidden by the spec, `OpAssign` traits may have
+ /// implementations that differ from the regular `Op` impl.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 0;
+ /// // ...
+ /// // Bad
+ /// a = a + b;
+ ///
+ /// // Good
+ /// a += b;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ASSIGN_OP_PATTERN,
+ style,
+ "assigning the result of an operation on a variable to that same variable"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a op= a op b` or `a op= b op a` patterns.
+ ///
+ /// ### Why is this bad?
+ /// Most likely these are bugs where one meant to write `a
+ /// op= b`.
+ ///
+ /// ### Known problems
+ /// Clippy cannot know for sure if `a op= a op b` should have
+ /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
++ /// If `a op= a op b` is really the correct behavior it should be
+ /// written as `a = a op a op b` as it's less confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 2;
+ /// // ...
+ /// a += a + b;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MISREFACTORED_ASSIGN_OP,
+ suspicious,
+ "having a variable on both sides of an assign op"
+}
+
+declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]);
+
+impl<'tcx> LateLintPass<'tcx> for AssignOps {
+ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ match &expr.kind {
+ hir::ExprKind::AssignOp(op, lhs, rhs) => {
+ if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
+ if op.node != binop.node {
+ return;
+ }
+ // lhs op= l op r
+ if eq_expr_value(cx, lhs, l) {
+ lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
+ }
+ // lhs op= l commutative_op r
+ if is_commutative(op.node) && eq_expr_value(cx, lhs, r) {
+ lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
+ }
+ }
+ },
+ hir::ExprKind::Assign(assignee, e, _) => {
+ if let hir::ExprKind::Binary(op, l, r) = &e.kind {
+ let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
+ let ty = cx.typeck_results().expr_ty(assignee);
+ let rty = cx.typeck_results().expr_ty(rhs);
+ if_chain! {
+ if let Some((_, lang_item)) = binop_traits(op.node);
+ if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
+ let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id);
+ if trait_ref_of_method(cx, parent_fn)
+ .map_or(true, |t| t.path.res.def_id() != trait_id);
+ if implements_trait(cx, ty, trait_id, &[rty.into()]);
+ then {
+ span_lint_and_then(
+ cx,
+ ASSIGN_OP_PATTERN,
+ expr.span,
+ "manual implementation of an assign operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) =
+ (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
+ {
+ diag.span_suggestion(
+ expr.span,
+ "replace it with",
+ format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+ }
+ };
+
+ let mut visitor = ExprVisitor {
+ assignee,
+ counter: 0,
+ cx,
+ };
+
+ walk_expr(&mut visitor, e);
+
+ if visitor.counter == 1 {
+ // a = a op b
+ if eq_expr_value(cx, assignee, l) {
+ lint(assignee, r);
+ }
+ // a = b commutative_op a
+ // Limited to primitive type as these ops are know to be commutative
+ if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
+ match op.node {
+ hir::BinOpKind::Add
+ | hir::BinOpKind::Mul
+ | hir::BinOpKind::And
+ | hir::BinOpKind::Or
+ | hir::BinOpKind::BitXor
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr => {
+ lint(assignee, l);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn lint_misrefactored_assign_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ op: hir::BinOp,
+ rhs: &hir::Expr<'_>,
+ assignee: &hir::Expr<'_>,
+ rhs_other: &hir::Expr<'_>,
+) {
+ span_lint_and_then(
+ cx,
+ MISREFACTORED_ASSIGN_OP,
+ expr.span,
+ "variable appears on both sides of an assignment operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
+ let a = &sugg::Sugg::hir(cx, assignee, "..");
+ let r = &sugg::Sugg::hir(cx, rhs, "..");
+ let long = format!("{} = {}", snip_a, sugg::make_binop(op.node.into(), a, r));
+ diag.span_suggestion(
+ expr.span,
+ &format!(
+ "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
+ snip_a,
+ snip_a,
+ op.node.as_str(),
+ snip_r,
+ long
+ ),
+ format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or",
+ long,
+ Applicability::MaybeIncorrect, // snippet
+ );
+ }
+ },
+ );
+}
+
+#[must_use]
+fn is_commutative(op: hir::BinOpKind) -> bool {
+ use rustc_hir::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ match op {
+ Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
+ Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
+ }
+}
+
+struct ExprVisitor<'a, 'tcx> {
+ assignee: &'a hir::Expr<'a>,
+ counter: u8,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if eq_expr_value(self.cx, self.assignee, expr) {
+ self.counter += 1;
+ }
+
+ walk_expr(self, expr);
+ }
+}
--- /dev/null
- use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MacArgs, MacArgsEq, MetaItemKind, NestedMetaItem};
+//! checks for attributes
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::macros::{is_panic, macro_backtrace};
+use clippy_utils::msrvs;
+use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
+use clippy_utils::{extract_msrv_attr, meets_msrv};
+use if_chain::if_chain;
- let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind {
- attr
- } else {
- return;
- };
-
- if attr.style == AttrStyle::Outer {
- if let MacArgs::Eq(_, MacArgsEq::Ast(expr)) = &attr_item.args
- && !matches!(expr.kind, rustc_ast::ExprKind::Lit(..)) {
- return;
- }
- if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
- return;
- }
-
++use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
+};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::Symbol;
+use semver::Version;
+
+static UNIX_SYSTEMS: &[&str] = &[
+ "android",
+ "dragonfly",
+ "emscripten",
+ "freebsd",
+ "fuchsia",
+ "haiku",
+ "illumos",
+ "ios",
+ "l4re",
+ "linux",
+ "macos",
+ "netbsd",
+ "openbsd",
+ "redox",
+ "solaris",
+ "vxworks",
+];
+
+// NOTE: windows is excluded from the list because it's also a valid target family.
+static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items annotated with `#[inline(always)]`,
+ /// unless the annotated function is empty or simply panics.
+ ///
+ /// ### Why is this bad?
+ /// While there are valid uses of this annotation (and once
+ /// you know when to use it, by all means `allow` this lint), it's a common
+ /// newbie-mistake to pepper one's code with it.
+ ///
+ /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
+ /// measure if that additional function call really affects your runtime profile
+ /// sufficiently to make up for the increase in compile time.
+ ///
+ /// ### Known problems
+ /// False positives, big time. This lint is meant to be
+ /// deactivated by everyone doing serious performance work. This means having
+ /// done the measurement.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[inline(always)]
+ /// fn not_quite_hot_code(..) { ... }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INLINE_ALWAYS,
+ pedantic,
+ "use of `#[inline(always)]`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `extern crate` and `use` items annotated with
+ /// lint attributes.
+ ///
+ /// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
+ /// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
+ /// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
+ /// `extern crate` items with a `#[macro_use]` attribute.
+ ///
+ /// ### Why is this bad?
+ /// Lint attributes have no effect on crate imports. Most
+ /// likely a `!` was forgotten.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// #[deny(dead_code)]
+ /// extern crate foo;
+ /// #[forbid(dead_code)]
+ /// use foo::bar;
+ ///
+ /// // Ok
+ /// #[allow(unused_imports)]
+ /// use foo::baz;
+ /// #[allow(unused_imports)]
+ /// #[macro_use]
+ /// extern crate baz;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_ATTRIBUTE,
+ correctness,
+ "use of lint attributes on `extern crate` items"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[deprecated]` annotations with a `since`
+ /// field that is not a valid semantic version.
+ ///
+ /// ### Why is this bad?
+ /// For checking the version of the deprecation, it must be
+ /// a valid semver. Failing that, the contained information is useless.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[deprecated(since = "forever")]
+ /// fn something_else() { /* ... */ }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DEPRECATED_SEMVER,
+ correctness,
+ "use of `#[deprecated(since = \"x\")]` where x is not semver"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty lines after outer attributes
+ ///
+ /// ### Why is this bad?
+ /// Most likely the attribute was meant to be an inner attribute using a '!'.
+ /// If it was meant to be an outer attribute, then the following item
+ /// should not be separated by empty lines.
+ ///
+ /// ### Known problems
+ /// Can cause false positives.
+ ///
+ /// From the clippy side it's difficult to detect empty lines between an attributes and the
+ /// following item because empty lines and comments are not part of the AST. The parsing
+ /// currently works for basic cases but is not perfect.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Good (as inner attribute)
+ /// #![allow(dead_code)]
+ ///
+ /// fn this_is_fine() { }
+ ///
+ /// // Bad
+ /// #[allow(dead_code)]
+ ///
+ /// fn not_quite_good_code() { }
+ ///
+ /// // Good (as outer attribute)
+ /// #[allow(dead_code)]
+ /// fn this_is_fine_too() { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LINE_AFTER_OUTER_ATTR,
+ nursery,
+ "empty line after outer attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
+ ///
+ /// ### Why is this bad?
+ /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
+ /// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// #![deny(clippy::restriction)]
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #![deny(clippy::as_conversions)]
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub BLANKET_CLIPPY_RESTRICTION_LINTS,
+ suspicious,
+ "enabling the complete restriction group"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
+ /// with `#[rustfmt::skip]`.
+ ///
+ /// ### Why is this bad?
+ /// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
+ /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
+ ///
+ /// ### Known problems
+ /// This lint doesn't detect crate level inner attributes, because they get
+ /// processed before the PreExpansionPass lints get executed. See
+ /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// #[cfg_attr(rustfmt, rustfmt_skip)]
+ /// fn main() { }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #[rustfmt::skip]
+ /// fn main() { }
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub DEPRECATED_CFG_ATTR,
+ complexity,
+ "usage of `cfg_attr(rustfmt)` instead of tool attributes"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cfg attributes having operating systems used in target family position.
+ ///
+ /// ### Why is this bad?
+ /// The configuration option will not be recognised and the related item will not be included
+ /// by the conditional compilation engine.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// #[cfg(linux)]
+ /// fn conditional() { }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #[cfg(target_os = "linux")]
+ /// fn conditional() { }
+ /// ```
+ ///
+ /// Or:
+ /// ```rust
+ /// #[cfg(unix)]
+ /// fn conditional() { }
+ /// ```
+ /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
+ #[clippy::version = "1.45.0"]
+ pub MISMATCHED_TARGET_OS,
+ correctness,
+ "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for attributes that allow lints without a reason.
+ ///
+ /// (This requires the `lint_reasons` feature)
+ ///
+ /// ### Why is this bad?
+ /// Allowing a lint should always have a reason. This reason should be documented to
+ /// ensure that others understand the reasoning
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// #![feature(lint_reasons)]
+ ///
+ /// #![allow(clippy::some_lint)]
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #![feature(lint_reasons)]
+ ///
+ /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ restriction,
+ "ensures that all `allow` and `expect` attributes have a reason"
+}
+
+declare_lint_pass!(Attributes => [
+ ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ INLINE_ALWAYS,
+ DEPRECATED_SEMVER,
+ USELESS_ATTRIBUTE,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Attributes {
+ fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
+ if let Some(items) = &attr.meta_item_list() {
+ if let Some(ident) = attr.ident() {
+ if is_lint_level(ident.name) {
+ check_clippy_lint_names(cx, ident.name, items);
+ }
+ if matches!(ident.name, sym::allow | sym::expect) {
+ check_lint_reason(cx, ident.name, items, attr);
+ }
+ if items.is_empty() || !attr.has_name(sym::deprecated) {
+ return;
+ }
+ for item in items {
+ if_chain! {
+ if let NestedMetaItem::MetaItem(mi) = &item;
+ if let MetaItemKind::NameValue(lit) = &mi.kind;
+ if mi.has_name(sym::since);
+ then {
+ check_semver(cx, item.span(), lit);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if is_relevant_item(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, attrs);
+ }
+ match item.kind {
+ ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
+ let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
+
+ for attr in attrs {
+ if in_external_macro(cx.sess(), attr.span) {
+ return;
+ }
+ if let Some(lint_list) = &attr.meta_item_list() {
+ if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
+ for lint in lint_list {
+ match item.kind {
+ ItemKind::Use(..) => {
+ if is_word(lint, sym!(unused_imports))
+ || is_word(lint, sym::deprecated)
+ || is_word(lint, sym!(unreachable_pub))
+ || is_word(lint, sym!(unused))
+ || extract_clippy_lint(lint).map_or(false, |s| {
+ matches!(
+ s.as_str(),
+ "wildcard_imports" | "enum_glob_use" | "redundant_pub_crate",
+ )
+ })
+ {
+ return;
+ }
+ },
+ ItemKind::ExternCrate(..) => {
+ if is_word(lint, sym!(unused_imports)) && skip_unused_imports {
+ return;
+ }
+ if is_word(lint, sym!(unused_extern_crates)) {
+ return;
+ }
+ },
+ _ => {},
+ }
+ }
+ let line_span = first_line_of_span(cx, attr.span);
+
+ if let Some(mut sugg) = snippet_opt(cx, line_span) {
+ if sugg.contains("#[") {
+ span_lint_and_then(
+ cx,
+ USELESS_ATTRIBUTE,
+ line_span,
+ "useless lint attribute",
+ |diag| {
+ sugg = sugg.replacen("#[", "#![", 1);
+ diag.span_suggestion(
+ line_span,
+ "if you just forgot a `!`, use",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if is_relevant_impl(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if is_relevant_trait(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+}
+
+/// Returns the lint name if it is clippy lint.
+fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
+ if_chain! {
+ if let Some(meta_item) = lint.meta_item();
+ if meta_item.path.segments.len() > 1;
+ if let tool_name = meta_item.path.segments[0].ident;
+ if tool_name.name == sym::clippy;
+ then {
+ let lint_name = meta_item.path.segments.last().unwrap().ident.name;
+ return Some(lint_name);
+ }
+ }
+ None
+}
+
+fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
+ for lint in items {
+ if let Some(lint_name) = extract_clippy_lint(lint) {
+ if lint_name.as_str() == "restriction" && name != sym::allow {
+ span_lint_and_help(
+ cx,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+ lint.span(),
+ "restriction lints are not meant to be all enabled",
+ None,
+ "try enabling only the lints you really need",
+ );
+ }
+ }
+ }
+}
+
+fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
+ // Check for the feature
+ if !cx.tcx.sess.features_untracked().lint_reasons {
+ return;
+ }
+
+ // Check if the reason is present
+ if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
+ && let MetaItemKind::NameValue(_) = &item.kind
+ && item.path == sym::reason
+ {
+ return;
+ }
+
+ span_lint_and_help(
+ cx,
+ ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ attr.span,
+ &format!("`{}` attribute without specifying a reason", name.as_str()),
+ None,
+ "try adding a reason at the end with `, reason = \"..\"`",
+ );
+}
+
+fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Fn(_, _, eid) = item.kind {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
+ } else {
+ true
+ }
+}
+
+fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
+ match item.kind {
+ ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value),
+ _ => false,
+ }
+}
+
+fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
+ match item.kind {
+ TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
+ TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
+ },
+ _ => false,
+ }
+}
+
+fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
+ block.stmts.first().map_or(
+ block
+ .expr
+ .as_ref()
+ .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
+ |stmt| match &stmt.kind {
+ StmtKind::Local(_) => true,
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
+ StmtKind::Item(_) => false,
+ },
+ )
+}
+
+fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
+ if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
+ is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
+ }) {
+ return false;
+ }
+ match &expr.kind {
+ ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
+ ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
+ ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
+ _ => true,
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
+ if span.from_expansion() {
+ return;
+ }
+
+ for attr in attrs {
+ if let Some(values) = attr.meta_item_list() {
+ if values.len() != 1 || !attr.has_name(sym::inline) {
+ continue;
+ }
+ if is_word(&values[0], sym::always) {
+ span_lint(
+ cx,
+ INLINE_ALWAYS,
+ attr.span,
+ &format!(
+ "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
+ name
+ ),
+ );
+ }
+ }
+ }
+}
+
+fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
+ if let LitKind::Str(is, _) = lit.kind {
+ if Version::parse(is.as_str()).is_ok() {
+ return;
+ }
+ }
+ span_lint(
+ cx,
+ DEPRECATED_SEMVER,
+ span,
+ "the since field must contain a semver-compliant version",
+ );
+}
+
+fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
+ if let NestedMetaItem::MetaItem(mi) = &nmi {
+ mi.is_word() && mi.has_name(expected)
+ } else {
+ false
+ }
+}
+
+pub struct EarlyAttributes {
+ pub msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(EarlyAttributes => [
+ DEPRECATED_CFG_ATTR,
+ MISMATCHED_TARGET_OS,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+]);
+
+impl EarlyLintPass for EarlyAttributes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ check_empty_line_after_outer_attr(cx, item);
+ }
+
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
+ check_deprecated_cfg_attr(cx, attr, self.msrv);
+ check_mismatched_target_os(cx, attr);
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ for attr in &item.attrs {
- if meets_msrv(msrv.as_ref(), &msrvs::TOOL_ATTRIBUTES);
++ if matches!(attr.kind, AttrKind::Normal(..))
++ && attr.style == AttrStyle::Outer
++ && is_present_in_source(cx, attr.span)
++ {
+ let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
+ let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt(), item.span.parent());
+
+ if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
+ let lines = snippet.split('\n').collect::<Vec<_>>();
+ let lines = without_block_comments(lines);
+
+ if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
+ span_lint(
+ cx,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+ begin_of_attr_to_item,
+ "found an empty line after an outer attribute. \
+ Perhaps you forgot to add a `!` to make it an inner attribute?",
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option<RustcVersion>) {
+ if_chain! {
++ if meets_msrv(msrv, msrvs::TOOL_ATTRIBUTES);
+ // check cfg_attr
+ if attr.has_name(sym::cfg_attr);
+ if let Some(items) = attr.meta_item_list();
+ if items.len() == 2;
+ // check for `rustfmt`
+ if let Some(feature_item) = items[0].meta_item();
+ if feature_item.has_name(sym::rustfmt);
+ // check for `rustfmt_skip` and `rustfmt::skip`
+ if let Some(skip_item) = &items[1].meta_item();
+ if skip_item.has_name(sym!(rustfmt_skip)) ||
+ skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip;
+ // Only lint outer attributes, because custom inner attributes are unstable
+ // Tracking issue: https://github.com/rust-lang/rust/issues/54726
+ if attr.style == AttrStyle::Outer;
+ then {
+ span_lint_and_sugg(
+ cx,
+ DEPRECATED_CFG_ATTR,
+ attr.span,
+ "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
+ "use",
+ "#[rustfmt::skip]".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
+ fn find_os(name: &str) -> Option<&'static str> {
+ UNIX_SYSTEMS
+ .iter()
+ .chain(NON_UNIX_SYSTEMS.iter())
+ .find(|&&os| os == name)
+ .copied()
+ }
+
+ fn is_unix(name: &str) -> bool {
+ UNIX_SYSTEMS.iter().any(|&os| os == name)
+ }
+
+ fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
+ let mut mismatched = Vec::new();
+
+ for item in items {
+ if let NestedMetaItem::MetaItem(meta) = item {
+ match &meta.kind {
+ MetaItemKind::List(list) => {
+ mismatched.extend(find_mismatched_target_os(list));
+ },
+ MetaItemKind::Word => {
+ if_chain! {
+ if let Some(ident) = meta.ident();
+ if let Some(os) = find_os(ident.name.as_str());
+ then {
+ mismatched.push((os, ident.span));
+ }
+ }
+ },
+ MetaItemKind::NameValue(..) => {},
+ }
+ }
+ }
+
+ mismatched
+ }
+
+ if_chain! {
+ if attr.has_name(sym::cfg);
+ if let Some(list) = attr.meta_item_list();
+ let mismatched = find_mismatched_target_os(&list);
+ if !mismatched.is_empty();
+ then {
+ let mess = "operating system used in target family position";
+
+ span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
+ // Avoid showing the unix suggestion multiple times in case
+ // we have more than one mismatch for unix-like systems
+ let mut unix_suggested = false;
+
+ for (os, span) in mismatched {
+ let sugg = format!("target_os = \"{}\"", os);
+ diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
+
+ if !unix_suggested && is_unix(os) {
+ diag.help("did you mean `unix`?");
+ unix_suggested = true;
+ }
+ }
+ });
+ }
+ }
+}
+
+fn is_lint_level(symbol: Symbol) -> bool {
+ matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
+}
--- /dev/null
- use if_chain::if_chain;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::sugg::Sugg;
- if_chain! {
- if let ExprKind::Binary(op, left, right) = &e.kind;
- if BinOpKind::Eq == op.node;
- if let ExprKind::Binary(op1, left1, right1) = &left.kind;
- if BinOpKind::BitAnd == op1.node;
- if let ExprKind::Lit(lit) = &right1.kind;
- if let LitKind::Int(n, _) = lit.node;
- if let ExprKind::Lit(lit1) = &right.kind;
- if let LitKind::Int(0, _) = lit1.node;
- if n.leading_zeros() == n.count_zeros();
- if n > u128::from(self.verbose_bit_mask_threshold);
- then {
- span_lint_and_then(cx,
- VERBOSE_BIT_MASK,
- e.span,
- "bit mask could be simplified with a call to `trailing_zeros`",
- |diag| {
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for incompatible bit masks in comparisons.
+ ///
+ /// The formula for detecting if an expression of the type `_ <bit_op> m
+ /// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
+ /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
+ /// table:
+ ///
+ /// |Comparison |Bit Op|Example |is always|Formula |
+ /// |------------|------|-------------|---------|----------------------|
+ /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` |
+ /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
+ /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
+ /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` |
+ /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` |
+ /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` |
+ ///
+ /// ### Why is this bad?
+ /// If the bits that the comparison cares about are always
+ /// set to zero or one by the bit mask, the comparison is constant `true` or
+ /// `false` (depending on mask, compared value, and operators).
+ ///
+ /// So the code is actively misleading, and the only reason someone would write
+ /// this intentionally is to win an underhanded Rust contest or create a
+ /// test-case for this lint.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x & 1 == 2) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BAD_BIT_MASK,
+ correctness,
+ "expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks in comparisons which can be removed
+ /// without changing the outcome. The basic structure can be seen in the
+ /// following table:
+ ///
+ /// |Comparison| Bit Op |Example |equals |
+ /// |----------|----------|------------|-------|
+ /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`|
+ /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`|
+ ///
+ /// ### Why is this bad?
+ /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
+ /// but still a bit misleading, because the bit mask is ineffective.
+ ///
+ /// ### Known problems
+ /// False negatives: This lint will only match instances
+ /// where we have figured out the math (which is for a power-of-two compared
+ /// value). This means things like `x | 1 >= 7` (which would be better written
+ /// as `x >= 6`) will not be reported (but bit masks like this are fairly
+ /// uncommon).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x | 1 > 3) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INEFFECTIVE_BIT_MASK,
+ correctness,
+ "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks that can be replaced by a call
+ /// to `trailing_zeros`
+ ///
+ /// ### Why is this bad?
+ /// `x.trailing_zeros() > 4` is much clearer than `x & 15
+ /// == 0`
+ ///
+ /// ### Known problems
+ /// llvm generates better code for `x & 15 == 0` on x86
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x & 0b1111 == 0 { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub VERBOSE_BIT_MASK,
+ pedantic,
+ "expressions where a bit mask is less readable than the corresponding method call"
+}
+
+#[derive(Copy, Clone)]
+pub struct BitMask {
+ verbose_bit_mask_threshold: u64,
+}
+
+impl BitMask {
+ #[must_use]
+ pub fn new(verbose_bit_mask_threshold: u64) -> Self {
+ Self {
+ verbose_bit_mask_threshold,
+ }
+ }
+}
+
+impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]);
+
+impl<'tcx> LateLintPass<'tcx> for BitMask {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(cmp, left, right) = &e.kind {
+ if cmp.node.is_comparison() {
+ if let Some(cmp_opt) = fetch_int_literal(cx, right) {
+ check_compare(cx, left, cmp.node, cmp_opt, e.span);
+ } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
+ check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span);
+ }
+ }
+ }
- });
- }
++
++ if let ExprKind::Binary(op, left, right) = &e.kind
++ && BinOpKind::Eq == op.node
++ && let ExprKind::Binary(op1, left1, right1) = &left.kind
++ && BinOpKind::BitAnd == op1.node
++ && let ExprKind::Lit(lit) = &right1.kind
++ && let LitKind::Int(n, _) = lit.node
++ && let ExprKind::Lit(lit1) = &right.kind
++ && let LitKind::Int(0, _) = lit1.node
++ && n.leading_zeros() == n.count_zeros()
++ && n > u128::from(self.verbose_bit_mask_threshold)
++ {
++ span_lint_and_then(
++ cx,
++ VERBOSE_BIT_MASK,
++ e.span,
++ "bit mask could be simplified with a call to `trailing_zeros`",
++ |diag| {
+ let sugg = Sugg::hir(cx, left1, "...").maybe_par();
+ diag.span_suggestion(
+ e.span,
+ "try",
+ format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
+ Applicability::MaybeIncorrect,
+ );
++ },
++ );
+ }
+ }
+}
+
+#[must_use]
+fn invert_cmp(cmp: BinOpKind) -> BinOpKind {
+ match cmp {
+ BinOpKind::Eq => BinOpKind::Eq,
+ BinOpKind::Ne => BinOpKind::Ne,
+ BinOpKind::Lt => BinOpKind::Gt,
+ BinOpKind::Gt => BinOpKind::Lt,
+ BinOpKind::Le => BinOpKind::Ge,
+ BinOpKind::Ge => BinOpKind::Le,
+ _ => BinOpKind::Or, // Dummy
+ }
+}
+
+fn check_compare(cx: &LateContext<'_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp_value: u128, span: Span) {
+ if let ExprKind::Binary(op, left, right) = &bit_op.kind {
+ if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr {
+ return;
+ }
+ fetch_int_literal(cx, right)
+ .or_else(|| fetch_int_literal(cx, left))
+ .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span));
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_bit_mask(
+ cx: &LateContext<'_>,
+ bit_op: BinOpKind,
+ cmp_op: BinOpKind,
+ mask_value: u128,
+ cmp_value: u128,
+ span: Span,
+) {
+ match cmp_op {
+ BinOpKind::Eq | BinOpKind::Ne => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value & cmp_value != cmp_value {
+ if cmp_value != 0 {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` can never be equal to `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ }
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value | cmp_value != cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` can never be equal to `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ }
+ },
+ _ => (),
+ },
+ BinOpKind::Lt | BinOpKind::Ge => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value < cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` will always be lower than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value >= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` will never be lower than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else {
+ check_ineffective_lt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ BinOpKind::Le | BinOpKind::Gt => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value <= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` will never be higher than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value > cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` will always be higher than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else {
+ check_ineffective_gt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ _ => (),
+ }
+}
+
+fn check_ineffective_lt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if c.is_power_of_two() && m < c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
+ &format!(
+ "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
+ op, m, c
+ ),
+ );
+ }
+}
+
+fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if (c + 1).is_power_of_two() && m <= c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
+ &format!(
+ "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
+ op, m, c
+ ),
+ );
+ }
+}
+
+fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option<u128> {
+ match constant(cx, cx.typeck_results(), lit)?.0 {
+ Constant::Int(n) => Some(n),
+ _ => None,
+ }
+}
--- /dev/null
- #[allow(clippy::cast_possible_truncation)]
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that can be written more
+ /// concisely.
+ ///
+ /// ### Why is this bad?
+ /// Readability of boolean expressions suffers from
+ /// unnecessary duplication.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior of `||` and
+ /// `&&`. Ignores `|`, `&` and `^`.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a && true // should be: if a
+ /// if !(a == b) // should be: if a != b
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NONMINIMAL_BOOL,
+ complexity,
+ "boolean expressions that can be written more concisely"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that contain terminals that
+ /// can be eliminated.
+ ///
+ /// ### Why is this bad?
+ /// This is most likely a logic bug.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a && b || a { ... }
+ /// ```
+ /// The `b` is unnecessary, the expression is equivalent to `if a`.
+ #[clippy::version = "pre 1.29.0"]
+ pub LOGIC_BUG,
+ correctness,
+ "boolean expressions that contain terminals which can be eliminated"
+}
+
+// For each pairs, both orders are considered.
+const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")];
+
+declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, LOGIC_BUG]);
+
+impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ NonminimalBoolVisitor { cx }.visit_body(body);
+ }
+}
+
+struct NonminimalBoolVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+use quine_mc_cluskey::Bool;
+struct Hir2Qmm<'a, 'tcx, 'v> {
+ terminals: Vec<&'v Expr<'v>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
+ fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
+ for a in a {
+ if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
+ if binop.node == op {
+ v = self.extract(op, &[lhs, rhs], v)?;
+ continue;
+ }
+ }
+ v.push(self.run(a)?);
+ }
+ Ok(v)
+ }
+
+ fn run(&mut self, e: &'v Expr<'_>) -> Result<Bool, String> {
+ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
+ match bin_op_kind {
+ BinOpKind::Eq => Some(BinOpKind::Ne),
+ BinOpKind::Ne => Some(BinOpKind::Eq),
+ BinOpKind::Gt => Some(BinOpKind::Le),
+ BinOpKind::Ge => Some(BinOpKind::Lt),
+ BinOpKind::Lt => Some(BinOpKind::Ge),
+ BinOpKind::Le => Some(BinOpKind::Gt),
+ _ => None,
+ }
+ }
+
+ // prevent folding of `cfg!` macros and the like
+ if !e.span.from_expansion() {
+ match &e.kind {
+ ExprKind::Unary(UnOp::Not, inner) => return Ok(Bool::Not(Box::new(self.run(inner)?))),
+ ExprKind::Binary(binop, lhs, rhs) => match &binop.node {
+ BinOpKind::Or => {
+ return Ok(Bool::Or(self.extract(BinOpKind::Or, &[lhs, rhs], Vec::new())?));
+ },
+ BinOpKind::And => {
+ return Ok(Bool::And(self.extract(BinOpKind::And, &[lhs, rhs], Vec::new())?));
+ },
+ _ => (),
+ },
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(true) => return Ok(Bool::True),
+ LitKind::Bool(false) => return Ok(Bool::False),
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+ for (n, expr) in self.terminals.iter().enumerate() {
+ if eq_expr_value(self.cx, e, expr) {
- #[allow(clippy::cast_possible_truncation)]
++ #[expect(clippy::cast_possible_truncation)]
+ return Ok(Bool::Term(n as u8));
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
+ if implements_ord(self.cx, e_lhs);
+ if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
+ if negate(e_binop.node) == Some(expr_binop.node);
+ if eq_expr_value(self.cx, e_lhs, expr_lhs);
+ if eq_expr_value(self.cx, e_rhs, expr_rhs);
+ then {
- #[allow(clippy::cast_possible_truncation)]
++ #[expect(clippy::cast_possible_truncation)]
+ return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
+ }
+ }
+ }
+ let n = self.terminals.len();
+ self.terminals.push(e);
+ if n < 32 {
++ #[expect(clippy::cast_possible_truncation)]
+ Ok(Bool::Term(n as u8))
+ } else {
+ Err("too many literals".to_owned())
+ }
+ }
+}
+
+struct SuggestContext<'a, 'tcx, 'v> {
+ terminals: &'v [&'v Expr<'v>],
+ cx: &'a LateContext<'tcx>,
+ output: String,
+}
+
+impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
+ fn recurse(&mut self, suggestion: &Bool) -> Option<()> {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match suggestion {
+ True => {
+ self.output.push_str("true");
+ },
+ False => {
+ self.output.push_str("false");
+ },
+ Not(inner) => match **inner {
+ And(_) | Or(_) => {
+ self.output.push('!');
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ },
+ Term(n) => {
+ let terminal = self.terminals[n as usize];
+ if let Some(str) = simplify_not(self.cx, terminal) {
+ self.output.push_str(&str);
+ } else {
+ self.output.push('!');
+ let snip = snippet_opt(self.cx, terminal.span)?;
+ self.output.push_str(&snip);
+ }
+ },
+ True | False | Not(_) => {
+ self.output.push('!');
+ self.recurse(inner)?;
+ },
+ },
+ And(v) => {
+ for (index, inner) in v.iter().enumerate() {
+ if index > 0 {
+ self.output.push_str(" && ");
+ }
+ if let Or(_) = *inner {
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ } else {
+ self.recurse(inner);
+ }
+ }
+ },
+ Or(v) => {
+ for (index, inner) in v.iter().rev().enumerate() {
+ if index > 0 {
+ self.output.push_str(" || ");
+ }
+ self.recurse(inner);
+ }
+ },
+ &Term(n) => {
+ let snip = snippet_opt(self.cx, self.terminals[n as usize].span)?;
+ self.output.push_str(&snip);
+ },
+ }
+ Some(())
+ }
+}
+
+fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ match &expr.kind {
+ ExprKind::Binary(binop, lhs, rhs) => {
+ if !implements_ord(cx, lhs) {
+ return None;
+ }
+
+ match binop.node {
+ BinOpKind::Eq => Some(" != "),
+ BinOpKind::Ne => Some(" == "),
+ BinOpKind::Lt => Some(" >= "),
+ BinOpKind::Gt => Some(" <= "),
+ BinOpKind::Le => Some(" > "),
+ BinOpKind::Ge => Some(" < "),
+ _ => None,
+ }
+ .and_then(|op| {
+ Some(format!(
+ "{}{}{}",
+ snippet_opt(cx, lhs.span)?,
+ op,
+ snippet_opt(cx, rhs.span)?
+ ))
+ })
+ },
+ ExprKind::MethodCall(path, args, _) if args.len() == 1 => {
+ let type_of_receiver = cx.typeck_results().expr_ty(&args[0]);
+ if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option)
+ && !is_type_diagnostic_item(cx, type_of_receiver, sym::Result)
+ {
+ return None;
+ }
+ METHODS_WITH_NEGATION
+ .iter()
+ .copied()
+ .flat_map(|(a, b)| vec![(a, b), (b, a)])
+ .find(|&(a, _)| {
+ let path: &str = path.ident.name.as_str();
+ a == path
+ })
+ .and_then(|(_, neg_method)| Some(format!("{}.{}()", snippet_opt(cx, args[0].span)?, neg_method)))
+ },
+ _ => None,
+ }
+}
+
+fn suggest(cx: &LateContext<'_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
+ let mut suggest_context = SuggestContext {
+ terminals,
+ cx,
+ output: String::new(),
+ };
+ suggest_context.recurse(suggestion);
+ suggest_context.output
+}
+
+fn simple_negate(b: Bool) -> Bool {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match b {
+ True => False,
+ False => True,
+ t @ Term(_) => Not(Box::new(t)),
+ And(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ Or(v)
+ },
+ Or(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ And(v)
+ },
+ Not(inner) => *inner,
+ }
+}
+
+#[derive(Default)]
+struct Stats {
+ terminals: [usize; 32],
+ negations: usize,
+ ops: usize,
+}
+
+fn terminal_stats(b: &Bool) -> Stats {
+ fn recurse(b: &Bool, stats: &mut Stats) {
+ match b {
+ True | False => stats.ops += 1,
+ Not(inner) => {
+ match **inner {
+ And(_) | Or(_) => stats.ops += 1, // brackets are also operations
+ _ => stats.negations += 1,
+ }
+ recurse(inner, stats);
+ },
+ And(v) | Or(v) => {
+ stats.ops += v.len() - 1;
+ for inner in v {
+ recurse(inner, stats);
+ }
+ },
+ &Term(n) => stats.terminals[n as usize] += 1,
+ }
+ }
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ let mut stats = Stats::default();
+ recurse(b, &mut stats);
+ stats
+}
+
+impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
+ fn bool_expr(&self, e: &'tcx Expr<'_>) {
+ let mut h2q = Hir2Qmm {
+ terminals: Vec::new(),
+ cx: self.cx,
+ };
+ if let Ok(expr) = h2q.run(e) {
+ if h2q.terminals.len() > 8 {
+ // QMC has exponentially slow behavior as the number of terminals increases
+ // 8 is reasonable, it takes approximately 0.2 seconds.
+ // See #825
+ return;
+ }
+
+ let stats = terminal_stats(&expr);
+ let mut simplified = expr.simplify();
+ for simple in Bool::Not(Box::new(expr)).simplify() {
+ match simple {
+ Bool::Not(_) | Bool::True | Bool::False => {},
+ _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
+ }
+ let simple_negated = simple_negate(simple);
+ if simplified.iter().any(|s| *s == simple_negated) {
+ continue;
+ }
+ simplified.push(simple_negated);
+ }
+ let mut improvements = Vec::with_capacity(simplified.len());
+ 'simplified: for suggestion in &simplified {
+ let simplified_stats = terminal_stats(suggestion);
+ let mut improvement = false;
+ for i in 0..32 {
+ // ignore any "simplifications" that end up requiring a terminal more often
+ // than in the original expression
+ if stats.terminals[i] < simplified_stats.terminals[i] {
+ continue 'simplified;
+ }
+ if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
+ span_lint_and_then(
+ self.cx,
+ LOGIC_BUG,
+ e.span,
+ "this boolean expression contains a logic bug",
+ |diag| {
+ diag.span_help(
+ h2q.terminals[i].span,
+ "this expression can be optimized out by applying boolean operations to the \
+ outer expression",
+ );
+ diag.span_suggestion(
+ e.span,
+ "it would look like the following",
+ suggest(self.cx, suggestion, &h2q.terminals),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ // don't also lint `NONMINIMAL_BOOL`
+ return;
+ }
+ // if the number of occurrences of a terminal decreases or any of the stats
+ // decreases while none increases
+ improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
+ || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
+ || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
+ }
+ if improvement {
+ improvements.push(suggestion);
+ }
+ }
+ let nonminimal_bool_lint = |suggestions: Vec<_>| {
+ span_lint_and_then(
+ self.cx,
+ NONMINIMAL_BOOL,
+ e.span,
+ "this boolean expression can be simplified",
+ |diag| {
+ diag.span_suggestions(
+ e.span,
+ "try",
+ suggestions.into_iter(),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ };
+ if improvements.is_empty() {
+ let mut visitor = NotSimplificationVisitor { cx: self.cx };
+ visitor.visit_expr(e);
+ } else {
+ nonminimal_bool_lint(
+ improvements
+ .into_iter()
+ .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
+ .collect(),
+ );
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !e.span.from_expansion() {
+ match &e.kind {
+ ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
+ self.bool_expr(e);
+ },
+ ExprKind::Unary(UnOp::Not, inner) => {
+ if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() {
+ self.bool_expr(e);
+ }
+ },
+ _ => {},
+ }
+ }
+ walk_expr(self, e);
+ }
+}
+
+fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]))
+}
+
+struct NotSimplificationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind {
+ if let Some(suggestion) = simplify_not(self.cx, inner) {
+ span_lint_and_sugg(
+ self.cx,
+ NONMINIMAL_BOOL,
+ expr.span,
+ "this boolean expression can be simplified",
+ "try",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::BORROW_AS_PTR) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_no_std_crate;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `&expr as *const T` or
+ /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or
+ /// `ptr::addr_of_mut` instead.
+ ///
+ /// ### Why is this bad?
+ /// This would improve readability and avoid creating a reference
+ /// that points to an uninitialized value or unaligned place.
+ /// Read the `ptr::addr_of` docs for more information.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let val = 1;
+ /// let p = &val as *const i32;
+ ///
+ /// let mut val_mut = 1;
+ /// let p_mut = &mut val_mut as *mut i32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let val = 1;
+ /// let p = std::ptr::addr_of!(val);
+ ///
+ /// let mut val_mut = 1;
+ /// let p_mut = std::ptr::addr_of_mut!(val_mut);
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub BORROW_AS_PTR,
+ pedantic,
+ "borrowing just to cast to a raw pointer"
+}
+
+impl_lint_pass!(BorrowAsPtr => [BORROW_AS_PTR]);
+
+pub struct BorrowAsPtr {
+ msrv: Option<RustcVersion>,
+}
+
+impl BorrowAsPtr {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for BorrowAsPtr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if !meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) {
+ return;
+ }
+
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Cast(left_expr, ty) = &expr.kind;
+ if let TyKind::Ptr(_) = ty.kind;
+ if let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = &left_expr.kind;
+
+ then {
+ let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
+ let macro_name = match mutability {
+ Mutability::Not => "addr_of",
+ Mutability::Mut => "addr_of_mut",
+ };
+
+ span_lint_and_sugg(
+ cx,
+ BORROW_AS_PTR,
+ expr.span,
+ "borrow as raw pointer",
+ "try",
+ format!(
+ "{}::ptr::{}!({})",
+ core_or_std,
+ macro_name,
+ snippet_opt(cx, e.span).unwrap()
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- msrv: &Option<RustcVersion>,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_semver::RustcVersion;
+
+use super::CAST_ABS_TO_UNSIGNED;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_expr: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
- if meets_msrv(msrv.as_ref(), &msrvs::UNSIGNED_ABS);
++ msrv: Option<RustcVersion>,
+) {
+ if_chain! {
++ if meets_msrv(msrv, msrvs::UNSIGNED_ABS);
+ if cast_from.is_integral();
+ if cast_to.is_integral();
+ if cast_from.is_signed();
+ if !cast_to.is_signed();
+ if let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind;
+ if let method_name = method_path.ident.name.as_str();
+ if method_name == "abs";
+ then {
+ span_lint_and_sugg(
+ cx,
+ CAST_ABS_TO_UNSIGNED,
+ expr.span,
+ &format!("casting the result of `{}::{}()` to {}", cast_from, method_name, cast_to),
+ "replace with",
+ format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- msrv: &Option<RustcVersion>,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_isize_or_usize;
+use clippy_utils::{in_constant, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_semver::RustcVersion;
+
+use super::{utils, CAST_LOSSLESS};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_op: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
- msrv: &Option<RustcVersion>,
++ msrv: Option<RustcVersion>,
+) {
+ if !should_lint(cx, expr, cast_from, cast_to, msrv) {
+ return;
+ }
+
+ // The suggestion is to use a function call, so if the original expression
+ // has parens on the outside, they are no longer needed.
+ let mut applicability = Applicability::MachineApplicable;
+ let opt = snippet_opt(cx, cast_op.span);
+ let sugg = opt.as_ref().map_or_else(
+ || {
+ applicability = Applicability::HasPlaceholders;
+ ".."
+ },
+ |snip| {
+ if should_strip_parens(cast_op, snip) {
+ &snip[1..snip.len() - 1]
+ } else {
+ snip.as_str()
+ }
+ },
+ );
+
+ let message = if cast_from.is_bool() {
+ format!(
+ "casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
+ cast_from, cast_to
+ )
+ } else {
+ format!(
+ "casting `{}` to `{}` may become silently lossy if you later change the type",
+ cast_from, cast_to
+ )
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CAST_LOSSLESS,
+ expr.span,
+ &message,
+ "try",
+ format!("{}::from({})", cast_to, sugg),
+ applicability,
+ );
+}
+
+fn should_lint(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
- from_nbits < to_nbits
++ msrv: Option<RustcVersion>,
+) -> bool {
+ // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
+ if in_constant(cx, expr.hir_id) {
+ return false;
+ }
+
+ match (cast_from.is_integral(), cast_to.is_integral()) {
+ (true, true) => {
+ let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+ !is_isize_or_usize(cast_from)
+ && !is_isize_or_usize(cast_to)
+ && from_nbits < to_nbits
+ && !cast_signed_to_unsigned
+ },
+
+ (true, false) => {
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
+ 32
+ } else {
+ 64
+ };
- (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true,
++ !is_isize_or_usize(cast_from) && from_nbits < to_nbits
+ },
++ (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv, msrvs::FROM_BOOL) => true,
+ (_, _) => {
+ matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
+ },
+ }
+}
+
+fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
+ if let ExprKind::Binary(_, _, _) = cast_expr.kind {
+ if snip.starts_with('(') && snip.ends_with(')') {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Option<RustcVersion>) {
+use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
+use if_chain::if_chain;
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, Node};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::CAST_SLICE_DIFFERENT_SIZES;
+
- if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
++pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Option<RustcVersion>) {
+ // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
- // call `expr_cast_chain_tys` and update the end type with the final tartet type.
++ if !meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS) {
+ return;
+ }
+
+ // if this cast is the child of another cast expression then don't emit something for it, the full
+ // chain will be analyzed
+ if is_child_of_cast(cx, expr) {
+ return;
+ }
+
+ if let Some(CastChainInfo {
+ left_cast,
+ start_ty,
+ end_ty,
+ }) = expr_cast_chain_tys(cx, expr)
+ {
+ if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
+ let from_size = from_layout.size.bytes();
+ let to_size = to_layout.size.bytes();
+ if from_size != to_size && from_size != 0 && to_size != 0 {
+ span_lint_and_then(
+ cx,
+ CAST_SLICE_DIFFERENT_SIZES,
+ expr.span,
+ &format!(
+ "casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
+ start_ty.ty, from_size, end_ty.ty, to_size,
+ ),
+ |diag| {
+ let ptr_snippet = source::snippet(cx, left_cast.span, "..");
+
+ let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
+ Mutability::Mut => ("_mut", "mut"),
+ Mutability::Not => ("", "const"),
+ };
+ let sugg = format!(
+ "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
+ // get just the ty from the TypeAndMut so that the printed type isn't something like `mut
+ // T`, extract just the `T`
+ end_ty.ty
+ );
+
+ diag.span_suggestion(
+ expr.span,
+ &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
+ sugg,
+ rustc_errors::Applicability::HasPlaceholders,
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let map = cx.tcx.hir();
+ if_chain! {
+ if let Some(parent_id) = map.find_parent_node(expr.hir_id);
+ if let Some(parent) = map.find(parent_id);
+ then {
+ let expr = match parent {
+ Node::Block(block) => {
+ if let Some(parent_expr) = block.expr {
+ parent_expr
+ } else {
+ return false;
+ }
+ },
+ Node::Expr(expr) => expr,
+ _ => return false,
+ };
+
+ matches!(expr.kind, ExprKind::Cast(..))
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
+/// the type is one of those slices
+fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
+ match ty.kind() {
+ ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
+ ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+struct CastChainInfo<'tcx> {
+ /// The left most part of the cast chain, or in other words, the first cast in the chain
+ /// Used for diagnostics
+ left_cast: &'tcx Expr<'tcx>,
+ /// The starting type of the cast chain
+ start_ty: TypeAndMut<'tcx>,
+ /// The final type of the cast chain
+ end_ty: TypeAndMut<'tcx>,
+}
+
+/// Returns a `CastChainInfo` with the left-most cast in the chain and the original ptr T and final
+/// ptr U if the expression is composed of casts.
+/// Returns None if the expr is not a Cast
+fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<CastChainInfo<'tcx>> {
+ if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
+ let cast_to = cx.typeck_results().expr_ty(expr);
+ let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
+
+ // If the expression that makes up the source of this cast is itself a cast, recursively
++ // call `expr_cast_chain_tys` and update the end type with the final target type.
+ // Otherwise, this cast is not immediately nested, just construct the info for this cast
+ if let Some(prev_info) = expr_cast_chain_tys(cx, cast_expr) {
+ Some(CastChainInfo {
+ end_ty: to_slice_ty,
+ ..prev_info
+ })
+ } else {
+ let cast_from = cx.typeck_results().expr_ty(cast_expr);
+ let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
+ Some(CastChainInfo {
+ left_cast: cast_expr,
+ start_ty: from_slice_ty,
+ end_ty: to_slice_ty,
+ })
+ }
+ } else {
+ None
+ }
+}
--- /dev/null
- /// It’s basically guaranteed to be undefined behaviour.
+mod cast_abs_to_unsigned;
+mod cast_enum_constructor;
+mod cast_lossless;
+mod cast_possible_truncation;
+mod cast_possible_wrap;
+mod cast_precision_loss;
+mod cast_ptr_alignment;
+mod cast_ref_to_mut;
+mod cast_sign_loss;
+mod cast_slice_different_sizes;
+mod char_lit_as_u8;
+mod fn_to_numeric_cast;
+mod fn_to_numeric_cast_any;
+mod fn_to_numeric_cast_with_truncation;
+mod ptr_as_ptr;
+mod unnecessary_cast;
+mod utils;
+
+use clippy_utils::is_hir_ty_cfg_dependant;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from any numerical to a float type where
+ /// the receiving type cannot store all values from the original type without
+ /// rounding errors. This possible rounding is to be expected, so this lint is
+ /// `Allow` by default.
+ ///
+ /// Basically, this warns on casting any integer with 32 or more bits to `f32`
+ /// or any 64-bit integer to `f64`.
+ ///
+ /// ### Why is this bad?
+ /// It's not bad at all. But in some applications it can be
+ /// helpful to know where precision loss can take place. This lint can help find
+ /// those places in the code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = u64::MAX;
+ /// x as f64;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_PRECISION_LOSS,
+ pedantic,
+ "casts that cause loss of precision, e.g., `x as f32` where `x: u64`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from a signed to an unsigned numerical
+ /// type. In this case, negative values wrap around to large positive values,
+ /// which can be quite surprising in practice. However, as the cast works as
+ /// defined, this lint is `Allow` by default.
+ ///
+ /// ### Why is this bad?
+ /// Possibly surprising results. You can activate this lint
+ /// as a one-time check to see where numerical wrapping can arise.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let y: i8 = -1;
+ /// y as u128; // will return 18446744073709551615
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_SIGN_LOSS,
+ pedantic,
+ "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts between numerical types that may
+ /// truncate large values. This is expected behavior, so the cast is `Allow` by
+ /// default.
+ ///
+ /// ### Why is this bad?
+ /// In some problem domains, it is good practice to avoid
+ /// truncation. This lint can be activated to help assess where additional
+ /// checks could be beneficial.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn as_u8(x: u64) -> u8 {
+ /// x as u8
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_POSSIBLE_TRUNCATION,
+ pedantic,
+ "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an unsigned type to a signed type of
+ /// the same size. Performing such a cast is a 'no-op' for the compiler,
+ /// i.e., nothing is changed at the bit level, and the binary representation of
+ /// the value is reinterpreted. This can cause wrapping if the value is too big
+ /// for the target signed type. However, the cast works as defined, so this lint
+ /// is `Allow` by default.
+ ///
+ /// ### Why is this bad?
+ /// While such a cast is not bad in itself, the results can
+ /// be surprising when this is not the intended behavior, as demonstrated by the
+ /// example below.
+ ///
+ /// ### Example
+ /// ```rust
+ /// u32::MAX as i32; // will yield a value of `-1`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_POSSIBLE_WRAP,
+ pedantic,
+ "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts between numerical types that may
+ /// be replaced by safe conversion functions.
+ ///
+ /// ### Why is this bad?
+ /// Rust's `as` keyword will perform many kinds of
+ /// conversions, including silently lossy conversions. Conversion functions such
+ /// as `i32::from` will only perform lossless conversions. Using the conversion
+ /// functions prevents conversions from turning into silent lossy conversions if
+ /// the types of the input expressions ever change, and make it easier for
+ /// people reading the code to know that the conversion is lossless.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// x as u64
+ /// }
+ /// ```
+ ///
+ /// Using `::from` would look like this:
+ ///
+ /// ```rust
+ /// fn as_u64(x: u8) -> u64 {
+ /// u64::from(x)
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_LOSSLESS,
+ pedantic,
+ "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts to the same type, casts of int literals to integer types
+ /// and casts of float literals to float types.
+ ///
+ /// ### Why is this bad?
+ /// It's just unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = 2i32 as i32;
+ /// let _ = 0.5 as f32;
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// let _ = 2_i32;
+ /// let _ = 0.5_f32;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_CAST,
+ complexity,
+ "cast to the same type, e.g., `x as i32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts, using `as` or `pointer::cast`,
+ /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing the resulting pointer may be undefined
+ /// behavior.
+ ///
+ /// ### Known problems
+ /// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
+ /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like
+ /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (&1u8 as *const u8) as *const u16;
+ /// let _ = (&mut 1u8 as *mut u8) as *mut u16;
+ ///
+ /// (&1u8 as *const u8).cast::<u16>();
+ /// (&mut 1u8 as *mut u8).cast::<u16>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CAST_PTR_ALIGNMENT,
+ pedantic,
+ "cast from a pointer to a more-strictly-aligned pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of function pointers to something other than usize
+ ///
+ /// ### Why is this bad?
+ /// Casting a function pointer to anything other than usize/isize is not portable across
+ /// architectures, because you end up losing bits if the target type is too small or end up with a
+ /// bunch of extra bits that waste space and add more instructions to the final binary than
+ /// strictly necessary for the problem
+ ///
+ /// Casting to isize also doesn't make sense since there are no signed addresses.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// fn fun() -> i32 { 1 }
+ /// let a = fun as i64;
+ ///
+ /// // Good
+ /// fn fun2() -> i32 { 1 }
+ /// let a = fun2 as usize;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FN_TO_NUMERIC_CAST,
+ style,
+ "casting a function pointer to a numeric type other than usize"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of a function pointer to a numeric type not wide enough to
+ /// store address.
+ ///
+ /// ### Why is this bad?
+ /// Such a cast discards some bits of the function's address. If this is intended, it would be more
+ /// clearly expressed by casting to usize first, then casting the usize to the intended type (with
+ /// a comment) to perform the truncation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// fn fn1() -> i16 {
+ /// 1
+ /// };
+ /// let _ = fn1 as i32;
+ ///
+ /// // Better: Cast to usize first, then comment with the reason for the truncation
+ /// fn fn2() -> i16 {
+ /// 1
+ /// };
+ /// let fn_ptr = fn2 as usize;
+ /// let fn_ptr_truncated = fn_ptr as i32;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ style,
+ "casting a function pointer to a numeric type not wide enough to store the address"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of a function pointer to any integer type.
+ ///
+ /// ### Why is this bad?
+ /// Casting a function pointer to an integer can have surprising results and can occur
+ /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
+ /// low-level with function pointers then you can opt-out of casting functions to integers in
+ /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
+ /// pointer casts in your code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad: fn1 is cast as `usize`
+ /// fn fn1() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn1 as usize;
+ ///
+ /// // Good: maybe you intended to call the function?
+ /// fn fn2() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn2() as usize;
+ ///
+ /// // Good: maybe you intended to cast it to a function type?
+ /// fn fn3() -> u16 {
+ /// 1
+ /// }
+ /// let _ = fn3 as fn() -> u16;
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub FN_TO_NUMERIC_CAST_ANY,
+ restriction,
+ "casting a function pointer to any integer type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of `&T` to `&mut T` anywhere in the code.
+ ///
+ /// ### Why is this bad?
- ptr_as_ptr::check(cx, expr, &self.msrv);
++ /// It’s basically guaranteed to be undefined behavior.
+ /// `UnsafeCell` is the only way to obtain aliasable data that is considered
+ /// mutable.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn x(r: &i32) {
+ /// unsafe {
+ /// *(r as *const _ as *mut _) += 1;
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Instead consider using interior mutability types.
+ ///
+ /// ```rust
+ /// use std::cell::UnsafeCell;
+ ///
+ /// fn x(r: &UnsafeCell<i32>) {
+ /// unsafe {
+ /// *r.get() += 1;
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub CAST_REF_TO_MUT,
+ correctness,
+ "a cast of reference to a mutable pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions where a character literal is cast
+ /// to `u8` and suggests using a byte literal instead.
+ ///
+ /// ### Why is this bad?
+ /// In general, casting values to smaller types is
+ /// error-prone and should be avoided where possible. In the particular case of
+ /// converting a character literal to u8, it is easy to avoid by just using a
+ /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
+ /// than `'a' as u8`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// 'x' as u8
+ /// ```
+ ///
+ /// A better version, using the byte literal:
+ ///
+ /// ```rust,ignore
+ /// b'x'
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHAR_LIT_AS_U8,
+ complexity,
+ "casting a character literal to `u8` truncates"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `as` casts between raw pointers without changing its mutability,
+ /// namely `*const T` to `*const U` and `*mut T` to `*mut U`.
+ ///
+ /// ### Why is this bad?
+ /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because
+ /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr as *const i32;
+ /// let _ = mut_ptr as *mut i32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr.cast::<i32>();
+ /// let _ = mut_ptr.cast::<i32>();
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub PTR_AS_PTR,
+ pedantic,
+ "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an enum type to an integral type which will definitely truncate the
+ /// value.
+ ///
+ /// ### Why is this bad?
+ /// The resulting integral value will not match the value of the variant it came from.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum E { X = 256 };
+ /// let _ = E::X as u8;
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub CAST_ENUM_TRUNCATION,
+ suspicious,
+ "casts from an enum type to an integral type which will truncate the value"
+}
+
+declare_clippy_lint! {
++ /// ### What it does
+ /// Checks for `as` casts between raw pointers to slices with differently sized elements.
+ ///
+ /// ### Why is this bad?
+ /// The produced raw pointer to a slice does not update its length metadata. The produced
+ /// pointer will point to a different number of bytes than the original pointer because the
+ /// length metadata of a raw slice pointer is in elements rather than bytes.
+ /// Producing a slice reference from the raw pointer will either create a slice with
+ /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
+ ///
+ /// ### Example
+ /// // Missing data
+ /// ```rust
+ /// let a = [1_i32, 2, 3, 4];
+ /// let p = &a as *const [i32] as *const [u8];
+ /// unsafe {
+ /// println!("{:?}", &*p);
+ /// }
+ /// ```
+ /// // Undefined Behavior (note: also potential alignment issues)
+ /// ```rust
+ /// let a = [1_u8, 2, 3, 4];
+ /// let p = &a as *const [u8] as *const [u32];
+ /// unsafe {
+ /// println!("{:?}", &*p);
+ /// }
+ /// ```
+ /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
+ /// ```rust
+ /// let a = [1_i32, 2, 3, 4];
+ /// let old_ptr = &a as *const [i32];
+ /// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
+ /// // The length comes from the known length of 4 i32s times the 4 bytes per i32
+ /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
+ /// unsafe {
+ /// println!("{:?}", &*new_ptr);
+ /// }
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub CAST_SLICE_DIFFERENT_SIZES,
+ correctness,
+ "casting using `as` between raw pointers to slices of types with different sizes"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an enum tuple constructor to an integer.
+ ///
+ /// ### Why is this bad?
+ /// The cast is easily confused with casting a c-like enum value to an integer.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum E { X(i32) };
+ /// let _ = E::X as usize;
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub CAST_ENUM_CONSTRUCTOR,
+ suspicious,
+ "casts from an enum tuple constructor to an integer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of the `abs()` method that cast the result to unsigned.
+ ///
+ /// ### Why is this bad?
+ /// The `unsigned_abs()` method avoids panic when called on the MIN value.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = -42;
+ /// let y: u32 = x.abs() as u32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x: i32 = -42;
+ /// let y: u32 = x.unsigned_abs();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub CAST_ABS_TO_UNSIGNED,
+ suspicious,
+ "casting the result of `abs()` to an unsigned integer can panic"
+}
+
+pub struct Casts {
+ msrv: Option<RustcVersion>,
+}
+
+impl Casts {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Casts => [
+ CAST_PRECISION_LOSS,
+ CAST_SIGN_LOSS,
+ CAST_POSSIBLE_TRUNCATION,
+ CAST_POSSIBLE_WRAP,
+ CAST_LOSSLESS,
+ CAST_REF_TO_MUT,
+ CAST_PTR_ALIGNMENT,
+ CAST_SLICE_DIFFERENT_SIZES,
+ UNNECESSARY_CAST,
+ FN_TO_NUMERIC_CAST_ANY,
+ FN_TO_NUMERIC_CAST,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ CHAR_LIT_AS_U8,
+ PTR_AS_PTR,
+ CAST_ENUM_TRUNCATION,
+ CAST_ENUM_CONSTRUCTOR,
+ CAST_ABS_TO_UNSIGNED
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Casts {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !in_external_macro(cx.sess(), expr.span) {
- cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
++ ptr_as_ptr::check(cx, expr, self.msrv);
+ }
+
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
+ if is_hir_ty_cfg_dependant(cx, cast_to) {
+ return;
+ }
+ let (cast_from, cast_to) = (
+ cx.typeck_results().expr_ty(cast_expr),
+ cx.typeck_results().expr_ty(expr),
+ );
+
+ if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
+ return;
+ }
+
+ fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+
+ if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
+ cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+ if cast_from.is_numeric() {
+ cast_possible_wrap::check(cx, expr, cast_from, cast_to);
+ cast_precision_loss::check(cx, expr, cast_from, cast_to);
+ cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
- cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
++ cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
+ }
- ptr_as_ptr::check(cx, expr, &self.msrv);
- cast_slice_different_sizes::check(cx, expr, &self.msrv);
++ cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
+ cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
+ }
+ }
+
+ cast_ref_to_mut::check(cx, expr);
+ cast_ptr_alignment::check(cx, expr);
+ char_lit_as_u8::check(cx, expr);
++ ptr_as_ptr::check(cx, expr, self.msrv);
++ cast_slice_different_sizes::check(cx, expr, self.msrv);
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
- if !meets_msrv(msrv.as_ref(), &msrvs::POINTER_CAST) {
+use std::borrow::Cow;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Mutability, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::PTR_AS_PTR;
+
++pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Option<RustcVersion>) {
++ if !meets_msrv(msrv, msrvs::POINTER_CAST) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind;
+ let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr));
+ if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind();
+ if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind();
+ if matches!((from_mutbl, to_mutbl),
+ (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut));
+ // The `U` in `pointer::cast` have to be `Sized`
+ // as explained here: https://github.com/rust-lang/rust/issues/60602.
+ if to_pointee_ty.is_sized(cx.tcx.at(expr.span), cx.param_env);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability);
+ let turbofish = match &cast_to_hir_ty.kind {
+ TyKind::Infer => Cow::Borrowed(""),
+ TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""),
+ _ => Cow::Owned(format!("::<{}>", to_pointee_ty)),
+ };
+ span_lint_and_sugg(
+ cx,
+ PTR_AS_PTR,
+ expr.span,
+ "`as` casting between raw pointers without changing its mutability",
+ "try `pointer::cast`, a safer alternative",
+ format!("{}.cast{}()", cast_expr_sugg.maybe_par(), turbofish),
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- /// # use std::convert::TryFrom;
+//! lint on manually implemented checked conversions that could be transformed into `try_from`
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{meets_msrv, msrvs, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit bounds checking when casting.
+ ///
+ /// ### Why is this bad?
+ /// Reduces the readability of statements & is error prone.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo: u32 = 5;
+ /// # let _ =
+ /// foo <= i32::MAX as u32
+ /// # ;
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
- if !meets_msrv(self.msrv.as_ref(), &msrvs::TRY_FROM) {
+ /// # let foo = 1;
+ /// # let _ =
+ /// i32::try_from(foo).is_ok()
+ /// # ;
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub CHECKED_CONVERSIONS,
+ pedantic,
+ "`try_from` could replace manual bounds checking when casting"
+}
+
+pub struct CheckedConversions {
+ msrv: Option<RustcVersion>,
+}
+
+impl CheckedConversions {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
+ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
- #[derive(Copy, Clone, Debug, PartialEq)]
++ if !meets_msrv(self.msrv, msrvs::TRY_FROM) {
+ return;
+ }
+
+ let result = if_chain! {
+ if !in_external_macro(cx.sess(), item.span);
+ if let ExprKind::Binary(op, left, right) = &item.kind;
+
+ then {
+ match op.node {
+ BinOpKind::Ge | BinOpKind::Le => single_check(item),
+ BinOpKind::And => double_check(cx, left, right),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ };
+
+ if let Some(cv) = result {
+ if let Some(to_type) = cv.to_type {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ CHECKED_CONVERSIONS,
+ item.span,
+ "checked cast can be simplified",
+ "try",
+ format!("{}::try_from({}).is_ok()", to_type, snippet),
+ applicability,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Searches for a single check from unsigned to _ is done
+/// todo: check for case signed -> larger unsigned == only x >= 0
+fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned)
+}
+
+/// Searches for a combination of upper & lower bound checks
+fn double_check<'a>(cx: &LateContext<'_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ let upper_lower = |l, r| {
+ let upper = check_upper_bound(l);
+ let lower = check_lower_bound(r);
+
+ upper.zip(lower).and_then(|(l, r)| l.combine(r, cx))
+ };
+
+ upper_lower(left, right).or_else(|| upper_lower(right, left))
+}
+
+/// Contains the result of a tried conversion check
+#[derive(Clone, Debug)]
+struct Conversion<'a> {
+ cvt: ConversionType,
+ expr_to_cast: &'a Expr<'a>,
+ to_type: Option<&'a str>,
+}
+
+/// The kind of conversion that is checked
++#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum ConversionType {
+ SignedToUnsigned,
+ SignedToSigned,
+ FromUnsigned,
+}
+
+impl<'a> Conversion<'a> {
+ /// Combine multiple conversions if the are compatible
+ pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
+ if self.is_compatible(&other, cx) {
+ // Prefer a Conversion that contains a type-constraint
+ Some(if self.to_type.is_some() { self } else { other })
+ } else {
+ None
+ }
+ }
+
+ /// Checks if two conversions are compatible
+ /// same type of conversion, same 'castee' and same 'to type'
+ pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool {
+ (self.cvt == other.cvt)
+ && (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast))
+ && (self.has_compatible_to_type(other))
+ }
+
+ /// Checks if the to-type is the same (if there is a type constraint)
+ fn has_compatible_to_type(&self, other: &Self) -> bool {
+ match (self.to_type, other.to_type) {
+ (Some(l), Some(r)) => l == r,
+ _ => true,
+ }
+ }
+
+ /// Try to construct a new conversion if the conversion type is valid
+ fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option<Conversion<'a>> {
+ ConversionType::try_new(from_type, to_type).map(|cvt| Conversion {
+ cvt,
+ expr_to_cast,
+ to_type: Some(to_type),
+ })
+ }
+
+ /// Construct a new conversion without type constraint
+ fn new_any(expr_to_cast: &'a Expr<'_>) -> Conversion<'a> {
+ Conversion {
+ cvt: ConversionType::SignedToUnsigned,
+ expr_to_cast,
+ to_type: None,
+ }
+ }
+}
+
+impl ConversionType {
+ /// Creates a conversion type if the type is allowed & conversion is valid
+ #[must_use]
+ fn try_new(from: &str, to: &str) -> Option<Self> {
+ if UINTS.contains(&from) {
+ Some(Self::FromUnsigned)
+ } else if SINTS.contains(&from) {
+ if UINTS.contains(&to) {
+ Some(Self::SignedToUnsigned)
+ } else if SINTS.contains(&to) {
+ Some(Self::SignedToSigned)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr <= (to_type::MAX as from_type)`
+fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ if_chain! {
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind;
+ if let Some((candidate, check)) = normalize_le_ge(op, left, right);
+ if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
+
+ then {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr >= 0|(to_type::MIN as from_type)`
+fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option<Conversion<'a>> {
+ (check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check)))
+ }
+
+ // First of we need a binary containing the expression & the cast
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind {
+ normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
+ } else {
+ None
+ }
+}
+
+/// Check for `expr >= 0`
+fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = &check.kind;
+ if let LitKind::Int(0, _) = &lit.node;
+
+ then {
+ Some(Conversion::new_any(candidate))
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr >= (to_type::MIN as from_type)`
+fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+}
+
+/// Tries to extract the from- and to-type from a cast expression
+fn get_types_from_cast<'a>(
+ expr: &'a Expr<'_>,
+ types: &'a [&str],
+ func: &'a str,
+ assoc_const: &'a str,
+) -> Option<(&'a str, &'a str)> {
+ // `to_type::max_value() as from_type`
+ // or `to_type::MAX as from_type`
+ let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
+ // to_type::max_value(), from_type
+ if let ExprKind::Cast(limit, from_type) = &expr.kind;
+ if let TyKind::Path(ref from_type_path) = &from_type.kind;
+ if let Some(from_sym) = int_ty_to_sym(from_type_path);
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ };
+
+ // `from_type::from(to_type::max_value())`
+ let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
+ if_chain! {
+ // `from_type::from, to_type::max_value()`
+ if let ExprKind::Call(from_func, args) = &expr.kind;
+ // `to_type::max_value()`
+ if args.len() == 1;
+ if let limit = &args[0];
+ // `from_type::from`
+ if let ExprKind::Path(ref path) = &from_func.kind;
+ if let Some(from_sym) = get_implementing_type(path, INTS, "from");
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ }
+ });
+
+ if let Some((limit, from_type)) = limit_from {
+ match limit.kind {
+ // `from_type::from(_)`
+ ExprKind::Call(path, _) => {
+ if let ExprKind::Path(ref path) = path.kind {
+ // `to_type`
+ if let Some(to_type) = get_implementing_type(path, types, func) {
+ return Some((from_type, to_type));
+ }
+ }
+ },
+ // `to_type::MAX`
+ ExprKind::Path(ref path) => {
+ if let Some(to_type) = get_implementing_type(path, types, assoc_const) {
+ return Some((from_type, to_type));
+ }
+ },
+ _ => {},
+ }
+ };
+ None
+}
+
+/// Gets the type which implements the called function
+fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
+ if_chain! {
+ if let QPath::TypeRelative(ty, path) = &path;
+ if path.ident.name.as_str() == function;
+ if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
+ if let [int] = &*tp.segments;
+ then {
+ let name = int.ident.name.as_str();
+ candidates.iter().find(|c| &name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Gets the type as a string, if it is a supported integer
+fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
+ if_chain! {
+ if let QPath::Resolved(_, path) = *path;
+ if let [ty] = &*path.segments;
+ then {
+ let name = ty.ident.name.as_str();
+ INTS.iter().find(|c| &name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Will return the expressions as if they were expr1 <= expr2
+fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ match op.node {
+ BinOpKind::Le => Some((left, right)),
+ BinOpKind::Ge => Some((right, left)),
+ _ => None,
+ }
+}
+
+// Constants
+const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"];
+const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"];
+const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"];
--- /dev/null
- #[allow(clippy::cast_possible_truncation)]
+//! calculate cognitive complexity and warn about overly complex functions
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::LimitStack;
+use rustc_ast::ast::Attribute;
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods with high cognitive complexity.
+ ///
+ /// ### Why is this bad?
+ /// Methods of high cognitive complexity tend to be hard to
+ /// both read and maintain. Also LLVM will tend to optimize small methods better.
+ ///
+ /// ### Known problems
+ /// Sometimes it's hard to find a way to reduce the
+ /// complexity.
+ ///
+ /// ### Example
+ /// No. You'll see it when you get the warning.
+ #[clippy::version = "1.35.0"]
+ pub COGNITIVE_COMPLEXITY,
+ nursery,
+ "functions that should be split up into multiple functions"
+}
+
+pub struct CognitiveComplexity {
+ limit: LimitStack,
+}
+
+impl CognitiveComplexity {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self {
+ limit: LimitStack::new(limit),
+ }
+ }
+}
+
+impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
+
+impl CognitiveComplexity {
- #[allow(clippy::integer_division)]
++ #[expect(clippy::cast_possible_truncation)]
+ fn check<'tcx>(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ body_span: Span,
+ ) {
+ if body_span.from_expansion() {
+ return;
+ }
+
+ let expr = &body.value;
+
+ let mut helper = CcHelper { cc: 1, returns: 0 };
+ helper.visit_expr(expr);
+ let CcHelper { cc, returns } = helper;
+ let ret_ty = cx.typeck_results().node_type(expr.hir_id);
+ let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
+ returns
+ } else {
++ #[expect(clippy::integer_division)]
+ (returns / 2)
+ };
+
+ let mut rust_cc = cc;
+ // prevent degenerate cases where unreachable code contains `return` statements
+ if rust_cc >= ret_adjust {
+ rust_cc -= ret_adjust;
+ }
+
+ if rust_cc > self.limit.limit() {
+ let fn_span = match kind {
+ FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
+ FnKind::Closure => {
+ let header_span = body_span.with_hi(decl.output.span().lo());
+ let pos = snippet_opt(cx, header_span).and_then(|snip| {
+ let low_offset = snip.find('|')?;
+ let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?;
+ let low = header_span.lo() + BytePos(low_offset as u32);
+ let high = low + BytePos(high_offset as u32 + 1);
+
+ Some((low, high))
+ });
+
+ if let Some((low, high)) = pos {
+ Span::new(low, high, header_span.ctxt(), header_span.parent())
+ } else {
+ return;
+ }
+ },
+ };
+
+ span_lint_and_help(
+ cx,
+ COGNITIVE_COMPLEXITY,
+ fn_span,
+ &format!(
+ "the function has a cognitive complexity of ({}/{})",
+ rust_cc,
+ self.limit.limit()
+ ),
+ None,
+ "you could split it up into multiple smaller functions",
+ );
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) {
+ self.check(cx, kind, decl, body, span);
+ }
+ }
+
+ fn enter_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+ fn exit_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+}
+
+struct CcHelper {
+ cc: u64,
+ returns: u64,
+}
+
+impl<'tcx> Visitor<'tcx> for CcHelper {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ walk_expr(self, e);
+ match e.kind {
+ ExprKind::If(_, _, _) => {
+ self.cc += 1;
+ },
+ ExprKind::Match(_, arms, _) => {
+ if arms.len() > 1 {
+ self.cc += 1;
+ }
+ self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
+ },
+ ExprKind::Ret(_) => self.returns += 1,
+ _ => {},
+ }
+ }
+}
--- /dev/null
- if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id));
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::IfLetOrMatch;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq};
+use if_chain::if_chain;
+use rustc_errors::MultiSpan;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
+ /// without adding any branches.
+ ///
+ /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
+ /// cases where merging would most likely make the code more readable.
+ ///
+ /// ### Why is this bad?
+ /// It is unnecessarily verbose and complex.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn func(opt: Option<Result<u64, String>>) {
+ /// let n = match opt {
+ /// Some(n) => match n {
+ /// Ok(n) => n,
+ /// _ => return,
+ /// }
+ /// None => return,
+ /// };
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn func(opt: Option<Result<u64, String>>) {
+ /// let n = match opt {
+ /// Some(Ok(n)) => n,
+ /// _ => return,
+ /// };
+ /// }
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub COLLAPSIBLE_MATCH,
+ style,
+ "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
+}
+
+declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ match IfLetOrMatch::parse(cx, expr) {
+ Some(IfLetOrMatch::Match(_, arms, _)) => {
+ if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
+ for arm in arms {
+ check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
+ }
+ }
+ },
+ Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
+ check_arm(cx, false, pat, body, None, els);
+ },
+ None => {},
+ }
+ }
+}
+
+fn check_arm<'tcx>(
+ cx: &LateContext<'tcx>,
+ outer_is_match: bool,
+ outer_pat: &'tcx Pat<'tcx>,
+ outer_then_body: &'tcx Expr<'tcx>,
+ outer_guard: Option<&'tcx Guard<'tcx>>,
+ outer_else_body: Option<&'tcx Expr<'tcx>>,
+) {
+ let inner_expr = peel_blocks_with_stmt(outer_then_body);
+ if_chain! {
+ if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
+ if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
+ IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
+ IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
+ // if there are more than two arms, collapsing would be non-trivial
+ if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
+ // one of the arms must be "wild-like"
+ if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
+ then {
+ let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
+ Some((scrutinee, then.pat, Some(els.body)))
+ } else {
+ None
+ }
+ },
+ };
+ if outer_pat.span.ctxt() == inner_scrutinee.span.ctxt();
+ // match expression must be a local binding
+ // match <local> { .. }
+ if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
+ if !pat_contains_or(inner_then_pat);
+ // the binding must come from the pattern of the containing match arm
+ // ..<local>.. => match <local> { .. }
+ if let Some(binding_span) = find_pat_binding(outer_pat, binding_id);
+ // the "else" branches must be equal
+ if match (outer_else_body, inner_else_body) {
+ (None, None) => true,
+ (None, Some(e)) | (Some(e), None) => is_unit_expr(e),
+ (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
+ };
+ // the binding must not be used in the if guard
++ if outer_guard.map_or(
++ true,
++ |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
++ );
+ // ...or anywhere in the inner expression
+ if match inner {
+ IfLetOrMatch::IfLet(_, _, body, els) => {
+ !is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
+ },
+ IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
+ };
+ then {
+ let msg = format!(
+ "this `{}` can be collapsed into the outer `{}`",
+ if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
+ if outer_is_match { "match" } else { "if let" },
+ );
+ span_lint_and_then(
+ cx,
+ COLLAPSIBLE_MATCH,
+ inner_expr.span,
+ &msg,
+ |diag| {
+ let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
+ help_span.push_span_label(binding_span, "replace this binding");
+ help_span.push_span_label(inner_then_pat.span, "with this pattern");
+ diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
+ },
+ );
+ }
+ }
+}
+
+/// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
+/// into a single wild arm without any significant loss in semantics or readability.
+fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if arm.guard.is_some() {
+ return false;
+ }
+ match arm.pat.kind {
+ PatKind::Binding(..) | PatKind::Wild => true,
+ PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ _ => false,
+ }
+}
+
+fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
+ let mut span = None;
+ pat.walk_short(|p| match &p.kind {
+ // ignore OR patterns
+ PatKind::Or(_) => false,
+ PatKind::Binding(_bm, _, _ident, _) => {
+ let found = p.hir_id == hir_id;
+ if found {
+ span = Some(p.span);
+ }
+ !found
+ },
+ _ => true,
+ });
+ span
+}
+
+fn pat_contains_or(pat: &Pat<'_>) -> bool {
+ let mut result = false;
+ pat.walk(|p| {
+ let is_or = matches!(p.kind, PatKind::Or(_));
+ result |= is_or;
+ !is_or
+ });
+ result
+}
--- /dev/null
- use clippy_utils::is_in_test_function;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if is_in_test_function(cx.tcx, expr.hir_id) {
+use clippy_utils::macros::root_macro_call_first_node;
+use clippy_utils::source::snippet_with_applicability;
++use clippy_utils::{is_in_cfg_test, is_in_test_function};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of dbg!() macro.
+ ///
+ /// ### Why is this bad?
+ /// `dbg!` macro is intended as a debugging tool. It
+ /// should not be in version control.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// dbg!(true)
+ ///
+ /// // Good
+ /// true
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub DBG_MACRO,
+ restriction,
+ "`dbg!` macro is intended as a debugging tool"
+}
+
+declare_lint_pass!(DbgMacro => [DBG_MACRO]);
+
+impl LateLintPass<'_> for DbgMacro {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
+ // we make an exception for test code
++ if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = match expr.peel_drop_temps().kind {
+ // dbg!()
+ ExprKind::Block(_, _) => String::new(),
+ // dbg!(1)
+ ExprKind::Match(val, ..) => {
+ snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string()
+ },
+ // dbg!(2, 3)
+ ExprKind::Tup(
+ [
+ Expr {
+ kind: ExprKind::Match(first, ..),
+ ..
+ },
+ ..,
+ Expr {
+ kind: ExprKind::Match(last, ..),
+ ..
+ },
+ ],
+ ) => {
+ let snippet = snippet_with_applicability(
+ cx,
+ first.span.source_callsite().to(last.span.source_callsite()),
+ "..",
+ &mut applicability,
+ );
+ format!("({snippet})")
+ },
+ _ => return,
+ };
+
+ span_lint_and_sugg(
+ cx,
+ DBG_MACRO,
+ macro_call.span,
+ "`dbg!` macro is intended as a debugging tool",
+ "ensure to avoid having uses of it in version control",
+ suggestion,
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{has_drop, is_copy};
+use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for literal calls to `Default::default()`.
+ ///
+ /// ### Why is this bad?
+ /// It's more clear to the reader to use the name of the type whose default is
+ /// being gotten than the generic `Default`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let s: String = Default::default();
+ ///
+ /// // Good
+ /// let s = String::default();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DEFAULT_TRAIT_ACCESS,
+ pedantic,
+ "checks for literal calls to `Default::default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for immediate reassignment of fields initialized
+ /// with Default::default().
+ ///
+ /// ### Why is this bad?
+ ///It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
+ ///
+ /// ### Known problems
+ /// Assignments to patterns that are of tuple type are not linted.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let mut a: A = Default::default();
+ /// a.i = 42;
+ /// ```
+ /// Use instead:
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let a = A {
+ /// i: 42,
+ /// .. Default::default()
+ /// };
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub FIELD_REASSIGN_WITH_DEFAULT,
+ style,
+ "binding initialized with Default should have its fields set in the initializer"
+}
+
+#[derive(Default)]
+pub struct Default {
+ // Spans linted by `field_reassign_with_default`.
+ reassigned_linted: FxHashSet<Span>,
+}
+
+impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
+
+impl<'tcx> LateLintPass<'tcx> for Default {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ // Avoid cases already linted by `field_reassign_with_default`
+ if !self.reassigned_linted.contains(&expr.span);
+ if let ExprKind::Call(path, ..) = expr.kind;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
+ if !is_update_syntax_base(cx, expr);
+ // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
+ if let QPath::Resolved(None, _path) = qpath;
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(def, ..) = expr_ty.kind();
+ then {
+ // TODO: Work out a way to put "whatever the imported way of referencing
+ // this type in this file" rather than a fully-qualified type.
+ let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did()));
+ span_lint_and_sugg(
+ cx,
+ DEFAULT_TRAIT_ACCESS,
+ expr.span,
+ &format!("calling `{}` is more clear than this expression", replacement),
+ "try",
+ replacement,
+ Applicability::Unspecified, // First resolve the TODO above
+ );
+ }
+ }
+ }
+
++ #[expect(clippy::too_many_lines)]
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ // start from the `let mut _ = _::default();` and look at all the following
+ // statements, see if they re-assign the fields of the binding
+ let stmts_head = match block.stmts {
+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
+ [head @ .., _] if !head.is_empty() => head,
+ _ => return,
+ };
+ for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
+ // `default` method of the `Default` trait, and store statement index in current block being
+ // checked and the name of the bound variable
+ let (local, variant, binding_name, binding_type, span) = if_chain! {
+ // only take `let ...` statements
+ if let StmtKind::Local(local) = stmt.kind;
+ if let Some(expr) = local.init;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if !expr.span.from_expansion();
+ // only take bindings to identifiers
+ if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
+ // only when assigning `... = Default::default()`
+ if is_expr_default(expr, cx);
+ let binding_type = cx.typeck_results().node_type(binding_id);
+ if let Some(adt) = binding_type.ty_adt_def();
+ if adt.is_struct();
+ let variant = adt.non_enum_variant();
+ if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
+ let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
+ if variant
+ .fields
+ .iter()
+ .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
+ let all_fields_are_copy = variant
+ .fields
+ .iter()
+ .all(|field| {
+ is_copy(cx, cx.tcx.type_of(field.did))
+ });
+ if !has_drop(cx, binding_type) || all_fields_are_copy;
+ then {
+ (local, variant, ident.name, binding_type, expr.span)
+ } else {
+ continue;
+ }
+ };
+
+ // find all "later statement"'s where the fields of the binding set as
+ // Default::default() get reassigned, unless the reassignment refers to the original binding
+ let mut first_assign = None;
+ let mut assigned_fields = Vec::new();
+ let mut cancel_lint = false;
+ for consecutive_statement in &block.stmts[stmt_idx + 1..] {
+ // find out if and which field was set by this `consecutive_statement`
+ if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
+ // interrupt and cancel lint if assign_rhs references the original binding
+ if contains_name(binding_name, assign_rhs) {
+ cancel_lint = true;
+ break;
+ }
+
+ // if the field was previously assigned, replace the assignment, otherwise insert the assignment
+ if let Some(prev) = assigned_fields
+ .iter_mut()
+ .find(|(field_name, _)| field_name == &field_ident.name)
+ {
+ *prev = (field_ident.name, assign_rhs);
+ } else {
+ assigned_fields.push((field_ident.name, assign_rhs));
+ }
+
+ // also set first instance of error for help message
+ if first_assign.is_none() {
+ first_assign = Some(consecutive_statement);
+ }
+ }
+ // interrupt if no field was assigned, since we only want to look at consecutive statements
+ else {
+ break;
+ }
+ }
+
+ // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
+ // construction using `Ty { fields, ..Default::default() }`
+ if !assigned_fields.is_empty() && !cancel_lint {
+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
+ let ext_with_default = !variant
+ .fields
+ .iter()
+ .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
+
+ let field_list = assigned_fields
+ .into_iter()
+ .map(|(field, rhs)| {
+ // extract and store the assigned value for help message
+ let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
+ format!("{}: {}", field, value_snippet)
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ // give correct suggestion if generics are involved (see #6944)
+ let binding_type = if_chain! {
+ if let ty::Adt(adt_def, substs) = binding_type.kind();
+ if !substs.is_empty();
+ then {
+ let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
+ let generic_args = substs.iter().collect::<Vec<_>>();
+ let tys_str = generic_args
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>()
+ .join(", ");
+ format!("{}::<{}>", adt_def_ty_name, &tys_str)
+ } else {
+ binding_type.to_string()
+ }
+ };
+
+ let sugg = if ext_with_default {
+ if field_list.is_empty() {
+ format!("{}::default()", binding_type)
+ } else {
+ format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
+ }
+ } else {
+ format!("{} {{ {} }}", binding_type, field_list)
+ };
+
+ // span lint once per statement that binds default
+ span_lint_and_note(
+ cx,
+ FIELD_REASSIGN_WITH_DEFAULT,
+ first_assign.unwrap().span,
+ "field assignment outside of initializer for an instance created with Default::default()",
+ Some(local.span),
+ &format!(
+ "consider initializing the variable with `{}` and removing relevant reassignments",
+ sugg
+ ),
+ );
+ self.reassigned_linted.insert(span);
+ }
+ }
+ }
+}
+
+/// Checks if the given expression is the `default` method belonging to the `Default` trait.
+fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, _) = &expr.kind;
+ if let ExprKind::Path(qpath) = &fn_expr.kind;
+ if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
+ then {
+ // right hand side of assignment is `Default::default`
+ match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the reassigned field and the assigning expression (right-hand side of assign).
+fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
+ if_chain! {
+ // only take assignments
+ if let StmtKind::Semi(later_expr) = this.kind;
+ if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
+ // only take assignments to fields where the left-hand side field is a field of
+ // the same binding as the previous statement
+ if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
+ if let Some(second_binding_name) = path.segments.last();
+ if second_binding_name.ident.name == binding_name;
+ then {
+ Some((field_ident, assign_rhs))
+ } else {
+ None
+ }
+ }
+}
+
+/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
+fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
+ then {
+ base.hir_id == expr.hir_id
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ intravisit::{walk_expr, walk_stmt, Visitor},
+ Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::{
+ lint::in_external_macro,
+ ty::{self, FloatTy, IntTy, PolyFnSig, Ty},
+};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::iter;
+
+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;
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub DEFAULT_NUMERIC_FALLBACK,
+ restriction,
+ "usage of unconstrained numeric literals which may cause default numeric fallback."
+}
+
+declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
+
+impl<'tcx> LateLintPass<'tcx> 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 !in_external_macro(self.cx.sess(), lit.span);
+ 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_numeric();
+ then {
+ let (suffix, is_float) = match lit_ty.kind() {
+ ty::Int(IntTy::I32) => ("i32", false),
+ ty::Float(FloatTy::F64) => ("f64", true),
+ // Default numeric fallback never results in other types.
+ _ => return,
+ };
+
+ let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
+ src
+ } else {
+ match lit.node {
+ LitKind::Int(src, _) => format!("{}", src),
+ LitKind::Float(src, _) => format!("{}", src),
+ _ => return,
+ }
+ };
+ let sugg = numeric_literal::format(&src, Some(suffix), is_float);
+ 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> {
+ 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 iter::zip(*args, fn_sig.skip_binder().inputs()) {
+ // 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 iter::zip(*args, fn_sig.inputs()) {
+ self.ty_bounds.push(TyBound::Ty(*bound));
+ self.visit_expr(expr);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ },
+
+ ExprKind::Struct(_, fields, base) => {
+ let ty = self.cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ 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(self.cx.tcx) == 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 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 `Ty::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_numeric(self) -> bool {
+ match self {
+ TyBound::Any => true,
+ TyBound::Ty(t) => t.is_numeric(),
+ 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,
+ }
+ }
+}
--- /dev/null
- /// Foo { a: 0_i32 }.b // Undefined behaviour: `b` is allowed to be padding
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{self as hir, HirId, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute).
+ ///
+ /// ### Why is this bad?
+ /// Unions in Rust have unspecified layout by default, despite many people thinking that they
+ /// lay out each field at the start of the union (like C does). That is, there are no guarantees
+ /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly
+ /// specified layout. These cases may lead to undefined behavior in unsafe blocks.
+ ///
+ /// ### Example
+ /// ```rust
+ /// union Foo {
+ /// a: i32,
+ /// b: u32,
+ /// }
+ ///
+ /// fn main() {
+ /// let _x: u32 = unsafe {
- /// Foo { a: 0_i32 }.b // Now defined behaviour, this is just an i32 -> u32 transmute
++ /// Foo { a: 0_i32 }.b // Undefined behavior: `b` is allowed to be padding
+ /// };
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// union Foo {
+ /// a: i32,
+ /// b: u32,
+ /// }
+ ///
+ /// fn main() {
+ /// let _x: u32 = unsafe {
++ /// Foo { a: 0_i32 }.b // Now defined behavior, this is just an i32 -> u32 transmute
+ /// };
+ /// }
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub DEFAULT_UNION_REPRESENTATION,
+ restriction,
+ "unions without a `#[repr(C)]` attribute"
+}
+declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]);
+
+impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) {
+ span_lint_and_help(
+ cx,
+ DEFAULT_UNION_REPRESENTATION,
+ item.span,
+ "this union has the default representation",
+ None,
+ &format!(
+ "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout",
+ cx.tcx.def_path_str(item.def_id.to_def_id())
+ ),
+ );
+ }
+ }
+}
+
+/// Returns true if the given item is a union with at least two non-ZST fields.
+fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Union(data, _) = &item.kind {
+ data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2
+ } else {
+ false
+ }
+}
+
+fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool {
+ if hir_ty.span.from_expansion() {
+ return false;
+ }
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let Ok(layout) = cx.layout_of(ty) {
+ layout.is_zst()
+ } else {
+ false
+ }
+}
+
+fn has_c_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ cx.tcx.hir().attrs(hir_id).iter().any(|attr| {
+ if attr.has_name(sym::repr) {
+ if let Some(items) = attr.meta_item_list() {
+ for item in items {
+ if item.is_word() && matches!(item.name_or_empty(), sym::C) {
+ return true;
+ }
+ }
+ }
+ }
+ false
+ })
+}
--- /dev/null
- /// ```
+// NOTE: if you add a deprecated lint in this file, please add a corresponding test in
+// tests/ui/deprecated.rs
+
+/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This
+/// enables the simple extraction of the metadata without changing the current deprecation
+/// declaration.
+pub struct ClippyDeprecatedLint;
+
+macro_rules! declare_deprecated_lint {
+ { $(#[$attr:meta])* pub $name: ident, $_reason: expr} => {
+ $(#[$attr])*
+ #[allow(dead_code)]
+ pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {};
+ }
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This used to check for `assert!(a == b)` and recommend
+ /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011.
+ #[clippy::version = "pre 1.29.0"]
+ pub SHOULD_ASSERT_EQ,
+ "`assert!()` will be more flexible with RFC 2011"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This used to check for `Vec::extend`, which was slower than
+ /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true.
+ #[clippy::version = "pre 1.29.0"]
+ pub EXTEND_FROM_SLICE,
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// `Range::step_by(0)` used to be linted since it's
+ /// an infinite iterator, which is better expressed by `iter::repeat`,
+ /// but the method has been removed for `Iterator::step_by` which panics
+ /// if given a zero
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_STEP_BY_ZERO,
+ "`iterator.step_by(0)` panics nowadays"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This used to check for `Vec::as_slice`, which was unstable with good
+ /// stable alternatives. `Vec::as_slice` has now been stabilized.
+ #[clippy::version = "pre 1.29.0"]
+ pub UNSTABLE_AS_SLICE,
+ "`Vec::as_slice` has been stabilized in 1.7"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This used to check for `Vec::as_mut_slice`, which was unstable with good
+ /// stable alternatives. `Vec::as_mut_slice` has now been stabilized.
+ #[clippy::version = "pre 1.29.0"]
+ pub UNSTABLE_AS_MUT_SLICE,
+ "`Vec::as_mut_slice` has been stabilized in 1.7"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint should never have applied to non-pointer types, as transmuting
+ /// between non-pointer types of differing alignment is well-defined behavior (it's semantically
+ /// equivalent to a memcpy). This lint has thus been refactored into two separate lints:
+ /// cast_ptr_alignment and transmute_ptr_to_ptr.
+ #[clippy::version = "pre 1.29.0"]
+ pub MISALIGNED_TRANSMUTE,
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint is too subjective, not having a good reason for being in clippy.
+ /// Additionally, compound assignment operators may be overloaded separately from their non-assigning
+ /// counterparts, so this lint may suggest a change in behavior or the code may not compile.
+ #[clippy::version = "1.30.0"]
+ pub ASSIGN_OPS,
+ "using compound assignment operators (e.g., `+=`) is harmless"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The original rule will only lint for `if let`. After
+ /// making it support to lint `match`, naming as `if let` is not suitable for it.
+ /// So, this lint is deprecated.
+ #[clippy::version = "pre 1.29.0"]
+ pub IF_LET_REDUNDANT_PATTERN_MATCHING,
+ "this lint has been changed to redundant_pattern_matching"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint used to suggest replacing `let mut vec =
+ /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The
+ /// replacement has very different performance characteristics so the lint is
+ /// deprecated.
+ #[clippy::version = "pre 1.29.0"]
+ pub UNSAFE_VECTOR_INITIALIZATION,
+ "the replacement suggested by this lint had substantially different behavior"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint has been superseded by #[must_use] in rustc.
+ #[clippy::version = "1.39.0"]
+ pub UNUSED_COLLECT,
+ "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// Associated-constants are now preferred.
+ #[clippy::version = "1.44.0"]
+ pub REPLACE_CONSTS,
+ "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The regex! macro does not exist anymore.
+ #[clippy::version = "1.47.0"]
+ pub REGEX_MACRO,
+ "the regex! macro has been removed from the regex crate in 2018"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint has been replaced by `manual_find_map`, a
+ /// more specific lint.
+ #[clippy::version = "1.51.0"]
+ pub FIND_MAP,
+ "this lint has been replaced by `manual_find_map`, a more specific lint"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint has been replaced by `manual_filter_map`, a
+ /// more specific lint.
+ #[clippy::version = "1.53.0"]
+ pub FILTER_MAP,
+ "this lint has been replaced by `manual_filter_map`, a more specific lint"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The `avoid_breaking_exported_api` config option was added, which
+ /// enables the `enum_variant_names` lint for public items.
+ #[clippy::version = "1.54.0"]
+ pub PUB_ENUM_VARIANT_NAMES,
+ "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The `avoid_breaking_exported_api` config option was added, which
+ /// enables the `wrong_self_conversion` lint for public items.
+ #[clippy::version = "1.54.0"]
+ pub WRONG_PUB_SELF_CONVENTION,
+ "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items"
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::sugg::has_enclosing_paren;
+use clippy_utils::ty::peel_mid_ty_refs;
+use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
+use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
+ Pat, PatKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `deref()` or `deref_mut()` method calls.
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing by `&*x` or `&mut *x` is clearer and more concise,
+ /// when not part of a method chain.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ops::Deref;
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b: &str = a.deref();
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b = &*a;
+ /// ```
+ ///
+ /// This lint excludes
+ /// ```rust,ignore
+ /// let _ = d.unwrap().deref();
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub EXPLICIT_DEREF_METHODS,
+ pedantic,
+ "Explicit use of deref or deref_mut method while not in a method chain."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for address of operations (`&`) that are going to
+ /// be dereferenced immediately by the compiler.
+ ///
+ /// ### Why is this bad?
+ /// Suggests that the receiver of the expression borrows
+ /// the expression.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn fun(_a: &i32) {}
+ ///
+ /// // Bad
+ /// let x: &i32 = &&&&&&5;
+ /// fun(&x);
+ ///
+ /// // Good
+ /// let x: &i32 = &5;
+ /// fun(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BORROW,
+ style,
+ "taking a reference that is going to be automatically dereferenced"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `ref` bindings which create a reference to a reference.
+ ///
+ /// ### Why is this bad?
+ /// The address-of operator at the use site is clearer about the need for a reference.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x = Some("");
+ /// if let Some(ref x) = x {
+ /// // use `x` here
+ /// }
+ ///
+ /// // Good
+ /// let x = Some("");
+ /// if let Some(x) = x {
+ /// // use `&x` here
+ /// }
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub REF_BINDING_TO_REFERENCE,
+ pedantic,
+ "`ref` binding to a reference"
+}
+
+impl_lint_pass!(Dereferencing => [
+ EXPLICIT_DEREF_METHODS,
+ NEEDLESS_BORROW,
+ REF_BINDING_TO_REFERENCE,
+]);
+
+#[derive(Default)]
+pub struct Dereferencing {
+ state: Option<(State, StateData)>,
+
+ // While parsing a `deref` method call in ufcs form, the path to the function is itself an
+ // expression. This is to store the id of that expression so it can be skipped when
+ // `check_expr` is called for it.
+ skip_expr: Option<HirId>,
+
+ /// The body the first local was found in. Used to emit lints when the traversal of the body has
+ /// been finished. Note we can't lint at the end of every body as they can be nested within each
+ /// other.
+ current_body: Option<BodyId>,
+ /// The list of locals currently being checked by the lint.
+ /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted.
+ /// This is needed for or patterns where one of the branches can be linted, but another can not
+ /// be.
+ ///
+ /// e.g. `m!(x) | Foo::Bar(ref x)`
+ ref_locals: FxIndexMap<HirId, Option<RefPat>>,
+}
+
+struct StateData {
+ /// Span of the top level expression
+ span: Span,
+}
+
+enum State {
+ // Any number of deref method calls.
+ DerefMethod {
+ // The number of calls in a sequence which changed the referenced type
+ ty_changed_count: usize,
+ is_final_ufcs: bool,
+ /// The required mutability
+ target_mut: Mutability,
+ },
+ DerefedBorrow {
+ count: usize,
+ required_precedence: i8,
+ msg: &'static str,
+ },
+}
+
+// A reference operation considered by this lint pass
+enum RefOp {
+ Method(Mutability),
+ Deref,
+ AddrOf,
+}
+
+struct RefPat {
+ /// Whether every usage of the binding is dereferenced.
+ always_deref: bool,
+ /// The spans of all the ref bindings for this local.
+ spans: Vec<Span>,
+ /// The applicability of this suggestion.
+ app: Applicability,
+ /// All the replacements which need to be made.
+ replacements: Vec<(Span, String)>,
+}
+
+impl<'tcx> LateLintPass<'tcx> for Dereferencing {
- #[allow(clippy::needless_pass_by_value)]
++ #[expect(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Skip path expressions from deref calls. e.g. `Deref::deref(e)`
+ if Some(expr.hir_id) == self.skip_expr.take() {
+ return;
+ }
+
+ if let Some(local) = path_to_local(expr) {
+ self.check_local_usage(cx, expr, local);
+ }
+
+ // Stop processing sub expressions when a macro call is seen
+ if expr.span.from_expansion() {
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ }
+
+ let typeck = cx.typeck_results();
+ let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) {
+ x
+ } else {
+ // The whole chain of reference operations has been seen
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ };
+
+ match (self.state.take(), kind) {
+ (None, kind) => {
+ let parent = get_parent_node(cx.tcx, expr.hir_id);
+ let expr_ty = typeck.expr_ty(expr);
+
+ match kind {
+ RefOp::Method(target_mut)
+ if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
+ && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
+ {
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count: if deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)) {
+ 0
+ } else {
+ 1
+ },
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ target_mut,
+ },
+ StateData { span: expr.span },
+ ));
+ },
+ RefOp::AddrOf => {
+ // Find the number of times the borrow is auto-derefed.
+ let mut iter = find_adjustments(cx.tcx, typeck, expr).iter();
+ let mut deref_count = 0usize;
+ let next_adjust = loop {
+ match iter.next() {
+ Some(adjust) => {
+ if !matches!(adjust.kind, Adjust::Deref(_)) {
+ break Some(adjust);
+ } else if !adjust.target.is_ref() {
+ deref_count += 1;
+ break iter.next();
+ }
+ deref_count += 1;
+ },
+ None => break None,
+ };
+ };
+
+ // Determine the required number of references before any can be removed. In all cases the
+ // reference made by the current expression will be removed. After that there are four cases to
+ // handle.
+ //
+ // 1. Auto-borrow will trigger in the current position, so no further references are required.
+ // 2. Auto-deref ends at a reference, or the underlying type, so one extra needs to be left to
+ // handle the automatically inserted re-borrow.
+ // 3. Auto-deref hits a user-defined `Deref` impl, so at least one reference needs to exist to
+ // start auto-deref.
+ // 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow
+ // adjustments will not be inserted automatically, then leave one further reference to avoid
+ // moving a mutable borrow.
+ // e.g.
+ // fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
+ // let x = match x {
+ // // Removing the borrow will cause `x` to be moved
+ // Some(x) => &mut *x,
+ // None => y
+ // };
+ // }
+ let deref_msg =
+ "this expression creates a reference which is immediately dereferenced by the compiler";
+ let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
+
+ let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id)
+ {
+ (1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
+ } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
+ next_adjust.map(|a| &a.kind)
+ {
+ if matches!(mutability, AutoBorrowMutability::Mut { .. })
+ && !is_auto_reborrow_position(parent)
+ {
+ (3, 0, deref_msg)
+ } else {
+ (2, 0, deref_msg)
+ }
+ } else {
+ (2, 0, deref_msg)
+ };
+
+ if deref_count >= required_refs {
+ self.state = Some((
+ State::DerefedBorrow {
+ // One of the required refs is for the current borrow expression, the remaining ones
+ // can't be removed without breaking the code. See earlier comment.
+ count: deref_count - required_refs,
+ required_precedence,
+ msg,
+ },
+ StateData { span: expr.span },
+ ));
+ }
+ },
+ _ => (),
+ }
+ },
+ (
+ Some((
+ State::DerefMethod {
+ target_mut,
+ ty_changed_count,
+ ..
+ },
+ data,
+ )),
+ RefOp::Method(_),
+ ) => {
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) {
+ ty_changed_count
+ } else {
+ ty_changed_count + 1
+ },
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ target_mut,
+ },
+ data,
+ ));
+ },
+ (
+ Some((
+ State::DerefedBorrow {
+ count,
+ required_precedence,
+ msg,
+ },
+ data,
+ )),
+ RefOp::AddrOf,
+ ) if count != 0 => {
+ self.state = Some((
+ State::DerefedBorrow {
+ count: count - 1,
+ required_precedence,
+ msg,
+ },
+ data,
+ ));
+ },
+
+ (Some((state, data)), _) => report(cx, expr, state, data),
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind {
+ if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) {
+ // This binding id has been seen before. Add this pattern to the list of changes.
+ if let Some(prev_pat) = opt_prev_pat {
+ if pat.span.from_expansion() {
+ // Doesn't match the context of the previous pattern. Can't lint here.
+ *opt_prev_pat = None;
+ } else {
+ prev_pat.spans.push(pat.span);
+ prev_pat.replacements.push((
+ pat.span,
+ snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut prev_pat.app)
+ .0
+ .into(),
+ ));
+ }
+ }
+ return;
+ }
+
+ if_chain! {
+ if !pat.span.from_expansion();
+ if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
+ // only lint immutable refs, because borrowed `&mut T` cannot be moved out
+ if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
+ self.current_body = self.current_body.or(cx.enclosing_body);
+ self.ref_locals.insert(
+ id,
+ Some(RefPat {
+ always_deref: true,
+ spans: vec![pat.span],
+ app,
+ replacements: vec![(pat.span, snip.into())],
+ }),
+ );
+ }
+ }
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ if Some(body.id()) == self.current_body {
+ for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
+ let replacements = pat.replacements;
+ let app = pat.app;
+ span_lint_and_then(
+ cx,
+ if pat.always_deref {
+ NEEDLESS_BORROW
+ } else {
+ REF_BINDING_TO_REFERENCE
+ },
+ pat.spans,
+ "this pattern creates a reference to a reference",
+ |diag| {
+ diag.multipart_suggestion("try this", replacements, app);
+ },
+ );
+ }
+ self.current_body = None;
+ }
+ }
+}
+
+fn try_parse_ref_op<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ typeck: &'tcx TypeckResults<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(RefOp, &'tcx Expr<'tcx>)> {
+ let (def_id, arg) = match expr.kind {
+ ExprKind::MethodCall(_, [arg], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(path),
+ hir_id,
+ ..
+ },
+ [arg],
+ ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg),
+ ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => {
+ return Some((RefOp::Deref, sub_expr));
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, _, sub_expr) => return Some((RefOp::AddrOf, sub_expr)),
+ _ => return None,
+ };
+ if tcx.is_diagnostic_item(sym::deref_method, def_id) {
+ Some((RefOp::Method(Mutability::Not), arg))
+ } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? {
+ Some((RefOp::Method(Mutability::Mut), arg))
+ } else {
+ None
+ }
+}
+
+// Checks whether the type for a deref call actually changed the type, not just the mutability of
+// the reference.
+fn deref_method_same_type(result_ty: Ty<'_>, arg_ty: Ty<'_>) -> bool {
+ match (result_ty.kind(), arg_ty.kind()) {
+ (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => result_ty == arg_ty,
+
+ // The result type for a deref method is always a reference
+ // Not matching the previous pattern means the argument type is not a reference
+ // This means that the type did change
+ _ => false,
+ }
+}
+
+// Checks whether the parent node is a suitable context for switching from a deref method to the
+// deref operator.
+fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
+ let parent = match parent {
+ Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
+ _ => return true,
+ };
+ match parent.kind {
+ // Leave deref calls in the middle of a method chain.
+ // e.g. x.deref().foo()
+ ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
+
+ // Leave deref calls resulting in a called function
+ // e.g. (x.deref())()
+ ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
+
+ // Makes an ugly suggestion
+ // e.g. *x.deref() => *&*x
+ ExprKind::Unary(UnOp::Deref, _)
+ // Postfix expressions would require parens
+ | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+ | ExprKind::Field(..)
+ | ExprKind::Index(..)
+ | ExprKind::Err => false,
+
+ ExprKind::Box(..)
+ | ExprKind::ConstBlock(..)
+ | ExprKind::Array(_)
+ | ExprKind::Call(..)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Tup(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Lit(..)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::DropTemps(..)
+ | ExprKind::If(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Match(..)
+ | ExprKind::Let(..)
+ | ExprKind::Closure(..)
+ | ExprKind::Block(..)
+ | ExprKind::Assign(..)
+ | ExprKind::AssignOp(..)
+ | ExprKind::Path(..)
+ | ExprKind::AddrOf(..)
+ | ExprKind::Break(..)
+ | ExprKind::Continue(..)
+ | ExprKind::Ret(..)
+ | ExprKind::InlineAsm(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Yield(..) => true,
+ }
+}
+
+/// Checks if the given expression is in a position which can be auto-reborrowed.
+/// Note: This is only correct assuming auto-deref is already occurring.
+fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool {
+ match parent {
+ Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)),
+ Some(Node::Local(_)) => true,
+ _ => false,
+ }
+}
+
+/// Checks if the given expression is a position which can auto-borrow.
+fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
+ if let Some(Node::Expr(parent)) = parent {
+ match parent.kind {
+ // ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
+ ExprKind::Field(..) => true,
+ ExprKind::Call(f, _) => f.hir_id == child_id,
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// Adjustments are sometimes made in the parent block rather than the expression itself.
+fn find_adjustments<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ typeck: &'tcx TypeckResults<'_>,
+ expr: &'tcx Expr<'_>,
+) -> &'tcx [Adjustment<'tcx>] {
+ let map = tcx.hir();
+ let mut iter = map.parent_iter(expr.hir_id);
+ let mut prev = expr;
+
+ loop {
+ match typeck.expr_adjustments(prev) {
+ [] => (),
+ a => break a,
+ };
+
+ match iter.next().map(|(_, x)| x) {
+ Some(Node::Block(_)) => {
+ if let Some((_, Node::Expr(e))) = iter.next() {
+ prev = e;
+ } else {
+ // This shouldn't happen. Blocks are always contained in an expression.
+ break &[];
+ }
+ },
+ Some(Node::Expr(&Expr {
+ kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _),
+ ..
+ })) => {
+ if let Some(Node::Expr(e)) = map.find(id) {
+ prev = e;
+ iter = map.parent_iter(id);
+ } else {
+ // This shouldn't happen. The destination should exist.
+ break &[];
+ }
+ },
+ _ => break &[],
+ }
+ }
+}
+
++#[expect(clippy::needless_pass_by_value)]
+fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) {
+ match state {
+ State::DerefMethod {
+ ty_changed_count,
+ is_final_ufcs,
+ target_mut,
+ } => {
+ let mut app = Applicability::MachineApplicable;
+ let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let (_, ref_count) = peel_mid_ty_refs(ty);
+ let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
+ // a deref call changing &T -> &U requires two deref operators the first time
+ // this occurs. One to remove the reference, a second to call the deref impl.
+ "*".repeat(ty_changed_count + 1)
+ } else {
+ "*".repeat(ty_changed_count)
+ };
+ let addr_of_str = if ty_changed_count < ref_count {
+ // Check if a reborrow from &mut T -> &T is required.
+ if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ "&*"
+ } else {
+ ""
+ }
+ } else if target_mut == Mutability::Mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
+ format!("({})", expr_str)
+ } else {
+ expr_str.into_owned()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_DEREF_METHODS,
+ data.span,
+ match target_mut {
+ Mutability::Not => "explicit `deref` method call",
+ Mutability::Mut => "explicit `deref_mut` method call",
+ },
+ "try this",
+ format!("{}{}{}", addr_of_str, deref_str, expr_str),
+ app,
+ );
+ },
+ State::DerefedBorrow {
+ required_precedence,
+ msg,
+ ..
+ } => {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_BORROW,
+ data.span,
+ msg,
+ "change this to",
+ if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
+ format!("({})", snip)
+ } else {
+ snip.into()
+ },
+ app,
+ );
+ },
+ }
+}
+
+impl Dereferencing {
+ fn check_local_usage(&mut self, cx: &LateContext<'_>, e: &Expr<'_>, local: HirId) {
+ if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
+ if let Some(pat) = outer_pat {
+ // Check for auto-deref
+ if !matches!(
+ cx.typeck_results().expr_adjustments(e),
+ [
+ Adjustment {
+ kind: Adjust::Deref(_),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Deref(_),
+ ..
+ },
+ ..
+ ]
+ ) {
+ match get_parent_expr(cx, e) {
+ // Field accesses are the same no matter the number of references.
+ Some(Expr {
+ kind: ExprKind::Field(..),
+ ..
+ }) => (),
+ Some(&Expr {
+ span,
+ kind: ExprKind::Unary(UnOp::Deref, _),
+ ..
+ }) if !span.from_expansion() => {
+ // Remove explicit deref.
+ let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
+ pat.replacements.push((span, snip.into()));
+ },
+ Some(parent) if !parent.span.from_expansion() => {
+ // Double reference might be needed at this point.
+ if parent.precedence().order() == PREC_POSTFIX {
+ // Parentheses would be needed here, don't lint.
+ *outer_pat = None;
+ } else {
+ pat.always_deref = false;
+ let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
+ pat.replacements.push((e.span, format!("&{}", snip)));
+ }
+ },
+ _ if !e.span.from_expansion() => {
+ // Double reference might be needed at this point.
+ pat.always_deref = false;
+ let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
+ pat.replacements.push((e.span, format!("&{}", snip)));
+ },
+ // Edge case for macros. The span of the identifier will usually match the context of the
+ // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
+ // macros
+ _ => *outer_pat = None,
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
++use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::paths;
+use clippy_utils::ty::{implements_trait, is_copy};
+use clippy_utils::{is_lint_allowed, match_def_path};
+use if_chain::if_chain;
++use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
+use rustc_hir::{
+ BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `Hash` but implementing `PartialEq`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `HashMap`) so it’s probably a bad idea to use a
+ /// default-generated `Hash` implementation with an explicitly defined
+ /// `PartialEq`. In particular, the following must hold for any type:
+ ///
+ /// ```text
+ /// k1 == k2 ⇒ hash(k1) == hash(k2)
+ /// ```
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[derive(Hash)]
+ /// struct Foo;
+ ///
+ /// impl PartialEq for Foo {
+ /// ...
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DERIVE_HASH_XOR_EQ,
+ correctness,
+ "deriving `Hash` but implementing `PartialEq` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `Ord` but implementing `PartialOrd`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `sort`) so it’s probably a bad idea to use a
+ /// default-generated `Ord` implementation with an explicitly defined
+ /// `PartialOrd`. In particular, the following must hold for any type
+ /// implementing `Ord`:
+ ///
+ /// ```text
+ /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// #[derive(PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
+ /// Some(self.cmp(other))
+ /// }
+ /// }
+ ///
+ /// impl Ord for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// or, if you don't need a custom ordering:
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
+ /// struct Foo;
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub DERIVE_ORD_XOR_PARTIAL_ORD,
+ correctness,
+ "deriving `Ord` but implementing `PartialOrd` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `Clone` implementations for `Copy`
+ /// types.
+ ///
+ /// ### Why is this bad?
- /// To avoid surprising behaviour, these traits should
- /// agree and the behaviour of `Copy` cannot be overridden. In almost all
++ /// To avoid surprising behavior, these traits should
++ /// agree and the behavior of `Copy` cannot be overridden. In almost all
+ /// situations a `Copy` type should have a `Clone` implementation that does
+ /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
+ /// gets you.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Copy)]
+ /// struct Foo;
+ ///
+ /// impl Clone for Foo {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPL_IMPL_CLONE_ON_COPY,
+ pedantic,
+ "implementing `Clone` explicitly on `Copy` types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `serde::Deserialize` on a type that
+ /// has methods using `unsafe`.
+ ///
+ /// ### Why is this bad?
+ /// Deriving `serde::Deserialize` will create a constructor
+ /// that may violate invariants hold by another constructor.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use serde::Deserialize;
+ ///
+ /// #[derive(Deserialize)]
+ /// pub struct Foo {
+ /// // ..
+ /// }
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Self {
+ /// // setup here ..
+ /// }
+ ///
+ /// pub unsafe fn parts() -> (&str, &str) {
+ /// // assumes invariants hold
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub UNSAFE_DERIVE_DESERIALIZE,
+ pedantic,
+ "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for types that derive `PartialEq` and could implement `Eq`.
++ ///
++ /// ### Why is this bad?
++ /// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
++ /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
++ /// in APIs that require `Eq` types. It also allows structs containing `T` to derive
++ /// `Eq` themselves.
++ ///
++ /// ### Example
++ /// ```rust
++ /// #[derive(PartialEq)]
++ /// struct Foo {
++ /// i_am_eq: i32,
++ /// i_am_eq_too: Vec<String>,
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// #[derive(PartialEq, Eq)]
++ /// struct Foo {
++ /// i_am_eq: i32,
++ /// i_am_eq_too: Vec<String>,
++ /// }
++ /// ```
++ #[clippy::version = "1.62.0"]
++ pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
++ style,
++ "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
++}
++
+declare_lint_pass!(Derive => [
+ EXPL_IMPL_CLONE_ON_COPY,
+ DERIVE_HASH_XOR_EQ,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
- UNSAFE_DERIVE_DESERIALIZE
++ UNSAFE_DERIVE_DESERIALIZE,
++ DERIVE_PARTIAL_EQ_WITHOUT_EQ
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Derive {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ ..
+ }) = item.kind
+ {
+ let ty = cx.tcx.type_of(item.def_id);
- let is_automatically_derived =
- cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
++ let is_automatically_derived = cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
+
+ check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
+ check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
+
+ if is_automatically_derived {
+ check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
++ check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
+ } else {
+ check_copy_clone(cx, item, trait_ref, ty);
+ }
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_HASH_XOR_EQ` lint.
+fn check_hash_peq<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+ hash_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
+ if let Some(def_id) = trait_ref.trait_def_id();
+ if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
+ then {
+ // Look for the PartialEq implementations for `ty`
+ cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
+ let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
+
+ if peq_is_automatically_derived == hash_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialEq<Foo> for Foo`
+ // For `impl PartialEq<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if peq_is_automatically_derived {
+ "you are implementing `Hash` explicitly but have derived `PartialEq`"
+ } else {
+ "you are deriving `Hash` but have implemented `PartialEq` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_HASH_XOR_EQ,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialEq` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
+fn check_ord_partial_ord<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+ ord_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
+ if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
+ if let Some(def_id) = &trait_ref.trait_def_id();
+ if *def_id == ord_trait_def_id;
+ then {
+ // Look for the PartialOrd implementations for `ty`
+ cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
+ let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
+
+ if partial_ord_is_automatically_derived == ord_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialOrd<Foo> for Foo`
+ // For `impl PartialOrd<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if partial_ord_is_automatically_derived {
+ "you are implementing `Ord` explicitly but have derived `PartialOrd`"
+ } else {
+ "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialOrd` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
+fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
+ let clone_id = match cx.tcx.lang_items().clone_trait() {
+ Some(id) if trait_ref.trait_def_id() == Some(id) => id,
+ _ => return,
+ };
+ let copy_id = match cx.tcx.lang_items().copy_trait() {
+ Some(id) => id,
+ None => return,
+ };
+ let (ty_adt, ty_subs) = match *ty.kind() {
+ // Unions can't derive clone.
+ ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
+ _ => return,
+ };
+ // If the current self type doesn't implement Copy (due to generic constraints), search to see if
+ // there's a Copy impl for any instance of the adt.
+ if !is_copy(cx, ty) {
+ if ty_subs.non_erasable_generics().next().is_some() {
+ let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| {
+ impls
+ .iter()
+ .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
+ });
+ if !has_copy_impl {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
+ // this impl.
+ if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
+ return;
+ }
+
+ span_lint_and_note(
+ cx,
+ EXPL_IMPL_CLONE_ON_COPY,
+ item.span,
+ "you are implementing `Clone` explicitly on a `Copy` type",
+ Some(item.span),
+ "consider deriving `Clone` or removing `Copy`",
+ );
+}
+
+/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
+fn check_unsafe_derive_deserialize<'tcx>(
+ cx: &LateContext<'tcx>,
+ item: &Item<'_>,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+) {
+ fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
+ let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
+ walk_item(&mut visitor, item);
+ visitor.has_unsafe
+ }
+
+ if_chain! {
+ if let Some(trait_def_id) = trait_ref.trait_def_id();
+ if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
+ if let ty::Adt(def, _) = ty.kind();
+ if let Some(local_def_id) = def.did().as_local();
+ let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
+ if cx.tcx.inherent_impls(def.did())
+ .iter()
+ .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
+ .any(|imp| has_unsafe(cx, imp));
+ then {
+ span_lint_and_help(
+ cx,
+ UNSAFE_DERIVE_DESERIALIZE,
+ item.span,
+ "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+ None,
+ "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
+ );
+ }
+ }
+}
+
+struct UnsafeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ has_unsafe: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if_chain! {
+ if let Some(header) = kind.header();
+ if header.unsafety == Unsafety::Unsafe;
+ then {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_fn(self, kind, decl, body_id, span, id);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if let ExprKind::Block(block, _) = expr.kind {
+ if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
++
++/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
++fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
++ if_chain! {
++ if let ty::Adt(adt, substs) = ty.kind();
++ if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
++ if let Some(def_id) = trait_ref.trait_def_id();
++ if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
++ if !implements_trait(cx, ty, eq_trait_def_id, substs);
++ then {
++ // If all of our fields implement `Eq`, we can implement `Eq` too
++ for variant in adt.variants() {
++ for field in &variant.fields {
++ let ty = field.ty(cx.tcx, substs);
++
++ if !implements_trait(cx, ty, eq_trait_def_id, substs) {
++ return;
++ }
++ }
++ }
++
++ span_lint_and_sugg(
++ cx,
++ DERIVE_PARTIAL_EQ_WITHOUT_EQ,
++ span.ctxt().outer_expn_data().call_site,
++ "you are deriving `PartialEq` and can implement `Eq`",
++ "consider deriving `Eq` as well",
++ "PartialEq, Eq".to_string(),
++ Applicability::MachineApplicable,
++ )
++ }
++ }
++}
--- /dev/null
- use clippy_utils::fn_def_id;
+use clippy_utils::diagnostics::span_lint_and_then;
- use rustc_hir::{def::Res, def_id::DefIdMap, Expr};
++use clippy_utils::{fn_def_id, get_parent_expr, path_def_id};
+
- let def_id = match fn_def_id(cx, expr) {
++use rustc_hir::{def::Res, def_id::DefIdMap, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+use crate::utils::conf;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Denies the configured methods and functions in clippy.toml
+ ///
+ /// Note: Even though this lint is warn-by-default, it will only trigger if
+ /// methods are defined in the clippy.toml file.
+ ///
+ /// ### Why is this bad?
+ /// Some methods are undesirable in certain contexts, and it's beneficial to
+ /// lint for them as needed.
+ ///
+ /// ### Example
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// disallowed-methods = [
+ /// # Can use a string as the path of the disallowed method.
+ /// "std::boxed::Box::new",
+ /// # Can also use an inline table with a `path` key.
+ /// { path = "std::time::Instant::now" },
+ /// # When using an inline table, can add a `reason` for why the method
+ /// # is disallowed.
+ /// { path = "std::vec::Vec::leak", reason = "no leaking memory" },
+ /// ]
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// // Example code where clippy issues a warning
+ /// let xs = vec![1, 2, 3, 4];
+ /// xs.leak(); // Vec::leak is disallowed in the config.
+ /// // The diagnostic contains the message "no leaking memory".
+ ///
+ /// let _now = Instant::now(); // Instant::now is disallowed in the config.
+ ///
+ /// let _box = Box::new(3); // Box::new is disallowed in the config.
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// // Example code which does not raise clippy warning
+ /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.
+ /// xs.push(123); // Vec::push is _not_ disallowed in the config.
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub DISALLOWED_METHODS,
+ style,
+ "use of a disallowed method call"
+}
+
+#[derive(Clone, Debug)]
+pub struct DisallowedMethods {
+ conf_disallowed: Vec<conf::DisallowedMethod>,
+ disallowed: DefIdMap<usize>,
+}
+
+impl DisallowedMethods {
+ pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self {
+ Self {
+ conf_disallowed,
+ disallowed: DefIdMap::default(),
+ }
+ }
+}
+
+impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
+
+impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for (index, conf) in self.conf_disallowed.iter().enumerate() {
+ let segs: Vec<_> = conf.path().split("::").collect();
+ if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
+ self.disallowed.insert(id, index);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr)
++ && let ExprKind::Call(receiver, _) = parent.kind
++ && receiver.hir_id == expr.hir_id
++ {
++ None
++ } else {
++ path_def_id(cx, expr)
++ };
++ let def_id = match uncalled_path.or_else(|| fn_def_id(cx, expr)) {
+ Some(def_id) => def_id,
+ None => return,
+ };
+ let conf = match self.disallowed.get(&def_id) {
+ Some(&index) => &self.conf_disallowed[index],
+ None => return,
+ };
+ let msg = format!("use of a disallowed method `{}`", conf.path());
+ span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| {
+ if let conf::DisallowedMethod::WithReason {
+ reason: Some(reason), ..
+ } = conf
+ {
+ diag.note(&format!("{} (from clippy.toml)", reason));
+ }
+ });
+ }
+}
--- /dev/null
- #[allow(clippy::module_name_repetitions)]
+use clippy_utils::attrs::is_doc_hidden;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then};
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::source::{first_line_of_span, snippet_with_applicability};
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind};
+use rustc_ast::token::CommentKind;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::emitter::EmitterWriter;
+use rustc_errors::{Applicability, Handler, MultiSpan, SuggestionStyle};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{AnonConst, Expr};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_parse::maybe_new_parser_from_source_str;
+use rustc_parse::parser::ForceCollect;
+use rustc_session::parse::ParseSess;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::{BytePos, FilePathMapping, SourceMap, Span};
+use rustc_span::{sym, FileName, Pos};
+use std::io;
+use std::ops::Range;
+use std::thread;
+use url::Url;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the presence of `_`, `::` or camel-case words
+ /// outside ticks in documentation.
+ ///
+ /// ### Why is this bad?
+ /// *Rustdoc* supports markdown formatting, `_`, `::` and
+ /// camel-case probably indicates some code which should be included between
+ /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
+ /// consider that.
+ ///
+ /// ### Known problems
+ /// Lots of bad docs won’t be fixed, what the lint checks
+ /// for is limited, and there are still false positives. HTML elements and their
+ /// content are not linted.
+ ///
+ /// In addition, when writing documentation comments, including `[]` brackets
+ /// inside a link text would trip the parser. Therefore, documenting link with
+ /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
+ /// would fail.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// /// Do something with the foo_bar parameter. See also
+ /// /// that::other::module::foo.
+ /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
+ /// fn doit(foo_bar: usize) {}
+ /// ```
+ ///
+ /// ```rust
+ /// // Link text with `[]` brackets should be written as following:
+ /// /// Consume the array and return the inner
+ /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
+ /// /// [SmallVec]: SmallVec
+ /// fn main() {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "presence of `_`, `::` or camel-case outside backticks in documentation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the doc comments of publicly visible
+ /// unsafe functions and warns if there is no `# Safety` section.
+ ///
+ /// ### Why is this bad?
+ /// Unsafe functions should document their safety
+ /// preconditions, so that users can be sure they are using them safely.
+ ///
+ /// ### Examples
+ /// ```rust
+ ///# type Universe = ();
+ /// /// This function should really be documented
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
+ ///
+ /// At least write a line about safety:
+ ///
+ /// ```rust
+ ///# type Universe = ();
+ /// /// # Safety
+ /// ///
+ /// /// This function should not be called before the horsemen are ready.
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub MISSING_SAFETY_DOC,
+ style,
+ "`pub unsafe fn` without `# Safety` docs"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// return a `Result` type and warns if there is no `# Errors` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the type of errors that can be returned from a
+ /// function can help callers write code to handle the errors appropriately.
+ ///
+ /// ### Examples
+ /// Since the following function returns a `Result` it has an `# Errors` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ ///# use std::io;
+ /// /// # Errors
+ /// ///
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
+ /// /// permission to read it.
+ /// pub fn read(filename: String) -> io::Result<String> {
+ /// unimplemented!();
+ /// }
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub MISSING_ERRORS_DOC,
+ pedantic,
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// may panic and warns if there is no `# Panics` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the scenarios in which panicking occurs
+ /// can help callers who do not want to panic to avoid those situations.
+ ///
+ /// ### Examples
+ /// Since the following function may panic it has a `# Panics` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ /// /// # Panics
+ /// ///
+ /// /// Will panic if y is 0
+ /// pub fn divide_by(x: i32, y: i32) -> i32 {
+ /// if y == 0 {
+ /// panic!("Cannot divide by 0")
+ /// } else {
+ /// x / y
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub MISSING_PANICS_DOC,
+ pedantic,
+ "`pub fn` may panic without `# Panics` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `fn main() { .. }` in doctests
+ ///
+ /// ### Why is this bad?
+ /// The test can be shorter (and likely more readable)
+ /// if the `fn main()` is left implicit.
+ ///
+ /// ### Examples
+ /// ``````rust
+ /// /// An example of a doctest with a `main()` function
+ /// ///
+ /// /// # Examples
+ /// ///
+ /// /// ```
+ /// /// fn main() {
+ /// /// // this needs not be in an `fn`
+ /// /// }
+ /// /// ```
+ /// fn needless_main() {
+ /// unimplemented!();
+ /// }
+ /// ``````
+ #[clippy::version = "1.40.0"]
+ pub NEEDLESS_DOCTEST_MAIN,
+ style,
+ "presence of `fn main() {` in code examples"
+}
+
- #[allow(clippy::cast_possible_truncation)]
++#[expect(clippy::module_name_repetitions)]
+#[derive(Clone)]
+pub struct DocMarkdown {
+ valid_idents: FxHashSet<String>,
+ in_trait_impl: bool,
+}
+
+impl DocMarkdown {
+ pub fn new(valid_idents: FxHashSet<String>) -> Self {
+ Self {
+ valid_idents,
+ in_trait_impl: false,
+ }
+ }
+}
+
+impl_lint_pass!(DocMarkdown =>
+ [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
+);
+
+impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
+ check_attrs(cx, &self.valid_idents, attrs);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, 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)) {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(&body.value);
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ },
+ hir::ItemKind::Impl(impl_) => {
+ self.in_trait_impl = impl_.of_trait.is_some();
+ },
+ hir::ItemKind::Trait(_, unsafety, ..) => {
+ if !headers.safety && unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ item.span,
+ "docs for unsafe trait missing `# Safety` section",
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if let hir::ItemKind::Impl { .. } = item.kind {
+ self.in_trait_impl = false;
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
+ if !in_external_macro(cx.tcx.sess, item.span) {
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, None, None);
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+ if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(&body.value);
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ }
+}
+
+fn lint_for_missing_headers<'tcx>(
+ cx: &LateContext<'tcx>,
+ def_id: LocalDefId,
+ span: impl Into<MultiSpan> + Copy,
+ sig: &hir::FnSig<'_>,
+ headers: DocHeaders,
+ body_id: Option<hir::BodyId>,
+ panic_span: Option<Span>,
+) {
+ if !cx.access_levels.is_exported(def_id) {
+ return; // Private functions do not require doc comments
+ }
+
+ // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
+ if cx
+ .tcx
+ .hir()
+ .parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
+ .any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
+ {
+ return;
+ }
+
+ if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ span,
+ "unsafe function's docs miss `# Safety` section",
+ );
+ }
+ if !headers.panics && panic_span.is_some() {
+ span_lint_and_note(
+ cx,
+ MISSING_PANICS_DOC,
+ span,
+ "docs for function which may panic missing `# Panics` section",
+ panic_span,
+ "first possible panic found here",
+ );
+ }
+ if !headers.errors {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+ if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ } else {
+ if_chain! {
+ if let Some(body_id) = body_id;
+ if let Some(future) = cx.tcx.lang_items().future_trait();
+ let typeck = cx.tcx.typeck_body(body_id);
+ let body = cx.tcx.hir().body(body_id);
+ let ret_ty = typeck.expr_ty(&body.value);
+ if implements_trait(cx, ret_ty, future, &[]);
+ if let ty::Opaque(_, subs) = ret_ty.kind();
+ if let Some(gen) = subs.types().next();
+ if let ty::Generator(_, subs, _) = gen.kind();
+ if is_type_diagnostic_item(cx, subs.as_generator().return_ty(), sym::Result);
+ then {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Cleanup documentation decoration.
+///
+/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
+/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
+/// need to keep track of
+/// the spans but this function is inspired from the later.
- #[allow(clippy::unnecessary_wraps)] // we're following a type signature
++#[expect(clippy::cast_possible_truncation)]
+#[must_use]
+pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
+ // one-line comments lose their prefix
+ if comment_kind == CommentKind::Line {
+ let mut doc = doc.to_owned();
+ doc.push('\n');
+ let len = doc.len();
+ // +3 skips the opening delimiter
+ return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]);
+ }
+
+ let mut sizes = vec![];
+ let mut contains_initial_stars = false;
+ for line in doc.lines() {
+ let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
+ debug_assert_eq!(offset as u32 as usize, offset);
+ contains_initial_stars |= line.trim_start().starts_with('*');
+ // +1 adds the newline, +3 skips the opening delimiter
+ sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
+ }
+ if !contains_initial_stars {
+ return (doc.to_string(), sizes);
+ }
+ // remove the initial '*'s if any
+ let mut no_stars = String::with_capacity(doc.len());
+ for line in doc.lines() {
+ let mut chars = line.chars();
+ for c in &mut chars {
+ if c.is_whitespace() {
+ no_stars.push(c);
+ } else {
+ no_stars.push(if c == '*' { ' ' } else { c });
+ break;
+ }
+ }
+ no_stars.push_str(chars.as_str());
+ no_stars.push('\n');
+ }
+
+ (no_stars, sizes)
+}
+
+#[derive(Copy, Clone)]
+struct DocHeaders {
+ safety: bool,
+ errors: bool,
+ panics: bool,
+}
+
+fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
+ use pulldown_cmark::{BrokenLink, CowStr, Options};
+ /// We don't want the parser to choke on intra doc links. Since we don't
+ /// actually care about rendering them, just pretend that all broken links are
+ /// point to a fake address.
++ #[expect(clippy::unnecessary_wraps)] // we're following a type signature
+ fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+ Some(("fake".into(), "fake".into()))
+ }
+
+ let mut doc = String::new();
+ let mut spans = vec![];
+
+ for attr in attrs {
+ if let AttrKind::DocComment(comment_kind, comment) = attr.kind {
+ let (comment, current_spans) = strip_doc_comment_decoration(comment.as_str(), comment_kind, attr.span);
+ spans.extend_from_slice(¤t_spans);
+ doc.push_str(&comment);
+ } else if attr.has_name(sym::doc) {
+ // ignore mix of sugared and non-sugared doc
+ // don't trigger the safety or errors check
+ return DocHeaders {
+ safety: true,
+ errors: true,
+ panics: true,
+ };
+ }
+ }
+
+ let mut current = 0;
+ for &mut (ref mut offset, _) in &mut spans {
+ let offset_copy = *offset;
+ *offset = current;
+ current += offset_copy;
+ }
+
+ if doc.is_empty() {
+ return DocHeaders {
+ safety: false,
+ errors: false,
+ panics: false,
+ };
+ }
+
+ let mut cb = fake_broken_link_callback;
+
+ let parser =
+ pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter();
+ // Iterate over all `Events` and combine consecutive events into one
+ let events = parser.coalesce(|previous, current| {
+ use pulldown_cmark::Event::Text;
+
+ let previous_range = previous.1;
+ let current_range = current.1;
+
+ match (previous.0, current.0) {
+ (Text(previous), Text(current)) => {
+ let mut previous = previous.to_string();
+ previous.push_str(¤t);
+ Ok((Text(previous.into()), previous_range))
+ },
+ (previous, current) => Err(((previous, previous_range), (current, current_range))),
+ }
+ });
+ check_doc(cx, valid_idents, events, &spans)
+}
+
+const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
+
+fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
+ cx: &LateContext<'_>,
+ valid_idents: &FxHashSet<String>,
+ events: Events,
+ spans: &[(usize, Span)],
+) -> DocHeaders {
+ // true if a safety header was found
+ use pulldown_cmark::Event::{
+ Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
+ };
+ use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
+ use pulldown_cmark::{CodeBlockKind, CowStr};
+
+ let mut headers = DocHeaders {
+ safety: false,
+ errors: false,
+ panics: false,
+ };
+ let mut in_code = false;
+ let mut in_link = None;
+ let mut in_heading = false;
+ let mut is_rust = false;
+ let mut edition = None;
+ let mut ticks_unbalanced = false;
+ let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
+ let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
+ for (event, range) in events {
+ match event {
+ Start(CodeBlock(ref kind)) => {
+ in_code = true;
+ if let CodeBlockKind::Fenced(lang) = kind {
+ for item in lang.split(',') {
+ if item == "ignore" {
+ is_rust = false;
+ break;
+ }
+ if let Some(stripped) = item.strip_prefix("edition") {
+ is_rust = true;
+ edition = stripped.parse::<Edition>().ok();
+ } else if item.is_empty() || RUST_CODE.contains(&item) {
+ is_rust = true;
+ }
+ }
+ }
+ },
+ End(CodeBlock(_)) => {
+ in_code = false;
+ is_rust = false;
+ },
+ Start(Link(_, url, _)) => in_link = Some(url),
+ End(Link(..)) => in_link = None,
+ Start(Heading(_, _, _) | Paragraph | Item) => {
+ if let Start(Heading(_, _, _)) = event {
+ in_heading = true;
+ }
+ ticks_unbalanced = false;
+ let (_, span) = get_current_span(spans, range.start);
+ paragraph_span = first_line_of_span(cx, span);
+ },
+ End(Heading(_, _, _) | Paragraph | Item) => {
+ if let End(Heading(_, _, _)) = event {
+ in_heading = false;
+ }
+ if ticks_unbalanced {
+ span_lint_and_help(
+ cx,
+ DOC_MARKDOWN,
+ paragraph_span,
+ "backticks are unbalanced",
+ None,
+ "a backtick may be missing a pair",
+ );
+ } else {
+ for (text, span) in text_to_check {
+ check_text(cx, valid_idents, &text, span);
+ }
+ }
+ text_to_check = Vec::new();
+ },
+ Start(_tag) | End(_tag) => (), // We don't care about other tags
+ Html(_html) => (), // HTML is weird, just ignore it
+ SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
+ FootnoteReference(text) | Text(text) => {
+ let (begin, span) = get_current_span(spans, range.start);
+ paragraph_span = paragraph_span.with_hi(span.hi());
+ ticks_unbalanced |= text.contains('`') && !in_code;
+ if Some(&text) == in_link.as_ref() || ticks_unbalanced {
+ // Probably a link of the form `<http://example.com>`
+ // Which are represented as a link to "http://example.com" with
+ // text "http://example.com" by pulldown-cmark
+ continue;
+ }
+ let trimmed_text = text.trim();
+ headers.safety |= in_heading && trimmed_text == "Safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation Safety";
+ headers.errors |= in_heading && trimmed_text == "Errors";
+ headers.panics |= in_heading && trimmed_text == "Panics";
+ if in_code {
+ if is_rust {
+ let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
+ check_code(cx, &text, edition, span);
+ }
+ } else {
+ // Adjust for the beginning of the current `Event`
+ let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
+ text_to_check.push((text, span));
+ }
+ },
+ }
+ }
+ headers
+}
+
+fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
+ let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
+ Ok(o) => o,
+ Err(e) => e - 1,
+ };
+ spans[index]
+}
+
+fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
+ fn has_needless_main(code: String, edition: Edition) -> bool {
+ rustc_driver::catch_fatal_errors(|| {
+ rustc_span::create_session_globals_then(edition, || {
+ let filename = FileName::anon_source_code(&code);
+
+ let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let fallback_bundle =
+ rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
+ let emitter = EmitterWriter::new(
+ Box::new(io::sink()),
+ None,
+ None,
+ fallback_bundle,
+ false,
+ false,
+ false,
+ None,
+ false,
+ );
+ let handler = Handler::with_emitter(false, None, Box::new(emitter));
+ let sess = ParseSess::with_span_handler(handler, sm);
+
+ let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
+ Ok(p) => p,
+ Err(errs) => {
+ drop(errs);
+ return false;
+ },
+ };
+
+ let mut relevant_main_found = false;
+ loop {
+ match parser.parse_item(ForceCollect::No) {
+ Ok(Some(item)) => match &item.kind {
+ ItemKind::Fn(box Fn {
+ sig, body: 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,
+ FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
+ FnRetTy::Ty(_) => false,
+ };
+
+ if returns_nothing && !is_async && !block.stmts.is_empty() {
+ // This main function should be linted, but only if there are no other functions
+ relevant_main_found = true;
+ } else {
+ // This main function should not be linted, we're done
+ return false;
+ }
+ },
+ // Tests with one of these items are ignored
+ ItemKind::Static(..)
+ | ItemKind::Const(..)
+ | ItemKind::ExternCrate(..)
+ | ItemKind::ForeignMod(..)
+ // Another function was found; this case is ignored
+ | ItemKind::Fn(..) => return false,
+ _ => {},
+ },
+ Ok(None) => break,
+ Err(e) => {
+ e.cancel();
+ return false;
+ },
+ }
+ }
+
+ relevant_main_found
+ })
+ })
+ .ok()
+ .unwrap_or_default()
+ }
+
+ // Because of the global session, we need to create a new session in a different thread with
+ // the edition we need.
+ let text = text.to_owned();
+ if thread::spawn(move || has_needless_main(text, edition))
+ .join()
+ .expect("thread::spawn failed")
+ {
+ span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
+ }
+}
+
+fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
+ for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
+ // Trim punctuation as in `some comment (see foo::bar).`
+ // ^^
+ // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
+ let mut word = word.trim_matches(|c: char| !c.is_alphanumeric() && c != ':');
+
+ // Remove leading or trailing single `:` which may be part of a sentence.
+ if word.starts_with(':') && !word.starts_with("::") {
+ word = word.trim_start_matches(':');
+ }
+ if word.ends_with(':') && !word.ends_with("::") {
+ word = word.trim_end_matches(':');
+ }
+
+ if valid_idents.contains(word) || word.chars().all(|c| c == ':') {
+ continue;
+ }
+
+ // Adjust for the current word
+ let offset = word.as_ptr() as usize - text.as_ptr() as usize;
+ let span = Span::new(
+ span.lo() + BytePos::from_usize(offset),
+ span.lo() + BytePos::from_usize(offset + word.len()),
+ span.ctxt(),
+ span.parent(),
+ );
+
+ check_word(cx, word, span);
+ }
+}
+
+fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
+ /// Checks if a string is camel-case, i.e., contains at least two uppercase
+ /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
+ /// Plurals are also excluded (`IDs` is ok).
+ fn is_camel_case(s: &str) -> bool {
+ if s.starts_with(|c: char| c.is_ascii_digit()) {
+ return false;
+ }
+
+ let s = s.strip_suffix('s').unwrap_or(s);
+
+ s.chars().all(char::is_alphanumeric)
+ && s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
+ && s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
+ }
+
+ fn has_underscore(s: &str) -> bool {
+ s != "_" && !s.contains("\\_") && s.contains('_')
+ }
+
+ fn has_hyphen(s: &str) -> bool {
+ s != "-" && s.contains('-')
+ }
+
+ if let Ok(url) = Url::parse(word) {
+ // try to get around the fact that `foo::bar` parses as a valid URL
+ if !url.cannot_be_a_base() {
+ span_lint(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "you should put bare URLs between `<`/`>` or make a proper Markdown link",
+ );
+
+ return;
+ }
+ }
+
+ // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
+ if has_underscore(word) && has_hyphen(word) {
+ return;
+ }
+
+ if has_underscore(word) || word.contains("::") || is_camel_case(word) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_then(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "item in documentation is missing backticks",
+ |diag| {
+ let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
+ diag.span_suggestion_with_style(
+ span,
+ "try",
+ format!("`{}`", snippet),
+ applicability,
+ // always show the suggestion in a separate line, since the
+ // inline presentation adds another pair of backticks
+ SuggestionStyle::ShowAlways,
+ );
+ },
+ );
+ }
+}
+
+struct FindPanicUnwrap<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ panic_span: Option<Span>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.panic_span.is_some() {
+ return;
+ }
+
+ if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
+ if is_panic(self.cx, macro_call.def_id)
+ || matches!(
+ self.cx.tcx.item_name(macro_call.def_id).as_str(),
+ "assert" | "assert_eq" | "assert_ne" | "todo"
+ )
+ {
+ self.panic_span = Some(macro_call.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
+ {
+ self.panic_span = Some(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ // Panics in const blocks will cause compilation to fail.
+ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- #[allow(clippy::similar_names)]
+//! Lint on unnecessary double comparisons. Some examples:
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for double comparisons that could be simplified to a single expression.
+ ///
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x == y || x < y {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x <= y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DOUBLE_COMPARISONS,
+ complexity,
+ "unnecessary double comparisons that can be simplified"
+}
+
+declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]);
+
+impl<'tcx> DoubleComparisons {
++ #[expect(clippy::similar_names)]
+ fn check_binop(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
+ let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
+ (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
+ (lb.node, llhs, lrhs, rb.node, rlhs, rrhs)
+ },
+ _ => return,
+ };
+ if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
+ return;
+ }
+ macro_rules! lint_double_comparison {
+ ($op:tt) => {{
+ let mut applicability = Applicability::MachineApplicable;
+ let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
+ let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
+ let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str);
+ span_lint_and_sugg(
+ cx,
+ DOUBLE_COMPARISONS,
+ span,
+ "this binary expression can be simplified",
+ "try",
+ sugg,
+ applicability,
+ );
+ }};
+ }
+ #[rustfmt::skip]
+ match (op, lkind, rkind) {
+ (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
+ lint_double_comparison!(<=);
+ },
+ (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
+ lint_double_comparison!(>=);
+ },
+ (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
+ lint_double_comparison!(!=);
+ },
+ (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
+ lint_double_comparison!(==);
+ },
+ _ => (),
+ };
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind {
+ Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_help;
++use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
++use rustc_errors::MultiSpan;
++use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{FileName, Span};
++use std::collections::BTreeMap;
++use std::path::PathBuf;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for files that are included as modules multiple times.
++ ///
++ /// ### Why is this bad?
++ /// Loading a file as a module more than once causes it to be compiled
++ /// multiple times, taking longer and putting duplicate content into the
++ /// module tree.
++ ///
++ /// ### Example
++ /// ```rust,ignore
++ /// // lib.rs
++ /// mod a;
++ /// mod b;
++ /// ```
++ /// ```rust,ignore
++ /// // a.rs
++ /// #[path = "./b.rs"]
++ /// mod b;
++ /// ```
++ ///
++ /// Use instead:
++ ///
++ /// ```rust,ignore
++ /// // lib.rs
++ /// mod a;
++ /// mod b;
++ /// ```
++ /// ```rust,ignore
++ /// // a.rs
++ /// use crate::b;
++ /// ```
++ #[clippy::version = "1.62.0"]
++ pub DUPLICATE_MOD,
++ suspicious,
++ "file loaded as module multiple times"
++}
++
++#[derive(PartialOrd, Ord, PartialEq, Eq)]
++struct Modules {
++ local_path: PathBuf,
++ spans: Vec<Span>,
++}
++
++#[derive(Default)]
++pub struct DuplicateMod {
++ /// map from the canonicalized path to `Modules`, `BTreeMap` to make the
++ /// order deterministic for tests
++ modules: BTreeMap<PathBuf, Modules>,
++}
++
++impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
++
++impl EarlyLintPass for DuplicateMod {
++ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
++ if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, mod_spans)) = &item.kind
++ && let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
++ && let Some(local_path) = real.into_local_path()
++ && let Ok(absolute_path) = local_path.canonicalize()
++ {
++ let modules = self.modules.entry(absolute_path).or_insert(Modules {
++ local_path,
++ spans: Vec::new(),
++ });
++ modules.spans.push(item.span_with_attributes());
++ }
++ }
++
++ fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
++ for Modules { local_path, spans } in self.modules.values() {
++ if spans.len() < 2 {
++ continue;
++ }
++
++ let mut multi_span = MultiSpan::from_spans(spans.clone());
++ let (&first, duplicates) = spans.split_first().unwrap();
++
++ multi_span.push_span_label(first, "first loaded here");
++ for &duplicate in duplicates {
++ multi_span.push_span_label(duplicate, "loaded again here");
++ }
++
++ span_lint_and_help(
++ cx,
++ DUPLICATE_MOD,
++ multi_span,
++ &format!("file is loaded as a module multiple times: `{}`", local_path.display()),
++ None,
++ "replace all but one `mod` item with `use` items",
++ );
++ }
++ }
++}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::higher;
+use clippy_utils::{
+ can_move_expr_to_closure_no_visit,
+ diagnostics::span_lint_and_sugg,
+ is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
+ source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context},
+ SpanlessEq,
+};
+use core::fmt::Write;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ hir_id::HirIdSet,
+ intravisit::{walk_expr, Visitor},
+ Block, Expr, ExprKind, Guard, HirId, Let, Pat, Stmt, StmtKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, SyntaxContext, DUMMY_SP};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of `contains_key` + `insert` on `HashMap`
+ /// or `BTreeMap`.
+ ///
+ /// ### Why is this bad?
+ /// Using `entry` is more efficient.
+ ///
+ /// ### Known problems
+ /// The suggestion may have type inference errors in some cases. e.g.
+ /// ```rust
+ /// let mut map = std::collections::HashMap::new();
+ /// let _ = if !map.contains_key(&0) {
+ /// map.insert(0, 0)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// if !map.contains_key(&k) {
+ /// map.insert(k, v);
+ /// }
+ /// ```
+ /// can both be rewritten as:
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// map.entry(k).or_insert(v);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAP_ENTRY,
+ perf,
+ "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
+}
+
+declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
+
+impl<'tcx> LateLintPass<'tcx> for HashMapPass {
- #[allow(clippy::struct_excessive_bools)]
++ #[expect(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (cond_expr, then_expr, else_expr) = match higher::If::hir(expr) {
+ Some(higher::If { cond, then, r#else }) => (cond, then, r#else),
+ _ => return,
+ };
+
+ let (map_ty, contains_expr) = match try_parse_contains(cx, cond_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let then_search = match find_insert_calls(cx, &contains_expr, then_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let sugg = if let Some(else_expr) = else_expr {
+ let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
+ Some(search) => search,
+ None => return,
+ };
+
+ if then_search.edits.is_empty() && else_search.edits.is_empty() {
+ // No insertions
+ return;
+ } else if then_search.edits.is_empty() || else_search.edits.is_empty() {
+ // if .. { insert } else { .. } or if .. { .. } else { insert }
+ let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
+ (true, true) => (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (true, false) => (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (false, true) => (
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ (false, false) => (
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ };
+ format!(
+ "if let {}::{} = {}.entry({}) {} else {}",
+ map_ty.entry_path(),
+ entry_kind,
+ map_str,
+ key_str,
+ then_str,
+ else_str,
+ )
+ } else {
+ // if .. { insert } else { insert }
+ let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
+ (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ )
+ } else {
+ (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ )
+ };
+ let indent_str = snippet_indent(cx, expr.span);
+ let indent_str = indent_str.as_deref().unwrap_or("");
+ format!(
+ "match {}.entry({}) {{\n{indent} {entry}::{} => {}\n\
+ {indent} {entry}::{} => {}\n{indent}}}",
+ map_str,
+ key_str,
+ then_entry,
+ reindent_multiline(then_str.into(), true, Some(4 + indent_str.len())),
+ else_entry,
+ reindent_multiline(else_str.into(), true, Some(4 + indent_str.len())),
+ entry = map_ty.entry_path(),
+ indent = indent_str,
+ )
+ }
+ } else {
+ if then_search.edits.is_empty() {
+ // no insertions
+ return;
+ }
+
+ // if .. { insert }
+ if !then_search.allow_insert_closure {
+ let (body_str, entry_kind) = if contains_expr.negated {
+ then_search.snippet_vacant(cx, then_expr.span, &mut app)
+ } else {
+ then_search.snippet_occupied(cx, then_expr.span, &mut app)
+ };
+ format!(
+ "if let {}::{} = {}.entry({}) {}",
+ map_ty.entry_path(),
+ entry_kind,
+ map_str,
+ key_str,
+ body_str,
+ )
+ } else if let Some(insertion) = then_search.as_single_insertion() {
+ let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
+ if contains_expr.negated {
+ if insertion.value.can_have_side_effects() {
+ format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, value_str)
+ } else {
+ format!("{}.entry({}).or_insert({});", map_str, key_str, value_str)
+ }
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ } else {
+ let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
+ if contains_expr.negated {
+ format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, block_str)
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MAP_ENTRY,
+ expr.span,
+ &format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name()),
+ "try this",
+ sugg,
+ app,
+ );
+ }
+}
+
+#[derive(Clone, Copy)]
+enum MapType {
+ Hash,
+ BTree,
+}
+impl MapType {
+ fn name(self) -> &'static str {
+ match self {
+ Self::Hash => "HashMap",
+ Self::BTree => "BTreeMap",
+ }
+ }
+ fn entry_path(self) -> &'static str {
+ match self {
+ Self::Hash => "std::collections::hash_map::Entry",
+ Self::BTree => "std::collections::btree_map::Entry",
+ }
+ }
+}
+
+struct ContainsExpr<'tcx> {
+ negated: bool,
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ call_ctxt: SyntaxContext,
+}
+fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(MapType, ContainsExpr<'tcx>)> {
+ let mut negated = false;
+ let expr = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::Unary(UnOp::Not, e) => {
+ negated = !negated;
+ Some(e)
+ },
+ _ => None,
+ });
+ match expr.kind {
+ ExprKind::MethodCall(
+ _,
+ [
+ map,
+ Expr {
+ kind: ExprKind::AddrOf(_, _, key),
+ span: key_span,
+ ..
+ },
+ ],
+ _,
+ ) if key_span.ctxt() == expr.span.ctxt() => {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ let expr = ContainsExpr {
+ negated,
+ map,
+ key,
+ call_ctxt: expr.span.ctxt(),
+ };
+ if match_def_path(cx, id, &paths::BTREEMAP_CONTAINS_KEY) {
+ Some((MapType::BTree, expr))
+ } else if match_def_path(cx, id, &paths::HASHMAP_CONTAINS_KEY) {
+ Some((MapType::Hash, expr))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+struct InsertExpr<'tcx> {
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
+ if let ExprKind::MethodCall(_, [map, key, value], _) = expr.kind {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ if match_def_path(cx, id, &paths::BTREEMAP_INSERT) || match_def_path(cx, id, &paths::HASHMAP_INSERT) {
+ Some(InsertExpr { map, key, value })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+/// An edit that will need to be made to move the expression to use the entry api
+#[derive(Clone, Copy)]
+enum Edit<'tcx> {
+ /// A semicolon that needs to be removed. Used to create a closure for `insert_with`.
+ RemoveSemi(Span),
+ /// An insertion into the map.
+ Insertion(Insertion<'tcx>),
+}
+impl<'tcx> Edit<'tcx> {
+ fn as_insertion(self) -> Option<Insertion<'tcx>> {
+ if let Self::Insertion(i) = self { Some(i) } else { None }
+ }
+}
+#[derive(Clone, Copy)]
+struct Insertion<'tcx> {
+ call: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+
+/// This visitor needs to do a multiple things:
+/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
+/// * Determine if there's an insertion using the same key. There's no need for the entry api
+/// otherwise.
+/// * Determine if the final statement executed is an insertion. This is needed to use
+/// `or_insert_with`.
+/// * Determine if there's any sub-expression that can't be placed in a closure.
+/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
++#[expect(clippy::struct_excessive_bools)]
+struct InsertSearcher<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ /// The map expression used in the contains call.
+ map: &'tcx Expr<'tcx>,
+ /// The key expression used in the contains call.
+ key: &'tcx Expr<'tcx>,
+ /// The context of the top level block. All insert calls must be in the same context.
+ ctxt: SyntaxContext,
+ /// Whether this expression can be safely moved into a closure.
+ allow_insert_closure: bool,
+ /// Whether this expression can use the entry api.
+ can_use_entry: bool,
+ /// Whether this expression is the final expression in this code path. This may be a statement.
+ in_tail_pos: bool,
+ // Is this expression a single insert. A slightly better suggestion can be made in this case.
+ is_single_insert: bool,
+ /// If the visitor has seen the map being used.
+ is_map_used: bool,
+ /// The locations where changes need to be made for the suggestion.
+ edits: Vec<Edit<'tcx>>,
+ /// A stack of loops the visitor is currently in.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+}
+impl<'tcx> InsertSearcher<'_, 'tcx> {
+ /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
+ /// only if they are on separate code paths. This will return whether the map was used in the
+ /// given expression.
+ fn visit_cond_arm(&mut self, e: &'tcx Expr<'_>) -> bool {
+ let is_map_used = self.is_map_used;
+ let in_tail_pos = self.in_tail_pos;
+ self.visit_expr(e);
+ let res = self.is_map_used;
+ self.is_map_used = is_map_used;
+ self.in_tail_pos = in_tail_pos;
+ res
+ }
+
+ /// Visits an expression which is not itself in a tail position, but other sibling expressions
+ /// may be. e.g. if conditions
+ fn visit_non_tail_expr(&mut self, e: &'tcx Expr<'_>) {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.visit_expr(e);
+ self.in_tail_pos = in_tail_pos;
+ }
+}
+impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Semi(e) => {
+ self.visit_expr(e);
+
+ if self.in_tail_pos && self.allow_insert_closure {
+ // The spans are used to slice the top level expression into multiple parts. This requires that
+ // they all come from the same part of the source code.
+ if stmt.span.ctxt() == self.ctxt && e.span.ctxt() == self.ctxt {
+ self.edits
+ .push(Edit::RemoveSemi(stmt.span.trim_start(e.span).unwrap_or(DUMMY_SP)));
+ } else {
+ self.allow_insert_closure = false;
+ }
+ }
+ },
+ StmtKind::Expr(e) => self.visit_expr(e),
+ StmtKind::Local(l) => {
+ self.visit_pat(l.pat);
+ if let Some(e) = l.init {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.is_single_insert = false;
+ self.visit_expr(e);
+ }
+ },
+ StmtKind::Item(_) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.is_single_insert = false;
+ },
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ // If the block is in a tail position, then the last expression (possibly a statement) is in the
+ // tail position. The rest, however, are not.
+ match (block.stmts, block.expr) {
+ ([], None) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ },
+ ([], Some(expr)) => self.visit_expr(expr),
+ (stmts, Some(expr)) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_expr(expr);
+ },
+ ([stmts @ .., stmt], None) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_stmt(stmt);
+ },
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if !self.can_use_entry {
+ return;
+ }
+
+ match try_parse_insert(self.cx, expr) {
+ Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
+ // Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
+ if self.is_map_used
+ || !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)
+ || expr.span.ctxt() != self.ctxt
+ {
+ self.can_use_entry = false;
+ return;
+ }
+
+ self.edits.push(Edit::Insertion(Insertion {
+ call: expr,
+ value: insert_expr.value,
+ }));
+ self.is_map_used = true;
+ self.allow_insert_closure &= self.in_tail_pos;
+
+ // The value doesn't affect whether there is only a single insert expression.
+ let is_single_insert = self.is_single_insert;
+ self.visit_non_tail_expr(insert_expr.value);
+ self.is_single_insert = is_single_insert;
+ },
+ _ if SpanlessEq::new(self.cx).eq_expr(self.map, expr) => {
+ self.is_map_used = true;
+ },
+ _ => match expr.kind {
+ ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(cond_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.visit_cond_arm(then_expr);
+ is_map_used |= self.visit_cond_arm(else_expr);
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Match(scrutinee_expr, arms, _) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(scrutinee_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.is_map_used;
+ for arm in arms {
+ self.visit_pat(arm.pat);
+ if let Some(Guard::If(guard) | Guard::IfLet(&Let { init: guard, .. })) = arm.guard {
+ self.visit_non_tail_expr(guard);
+ }
+ is_map_used |= self.visit_cond_arm(arm.body);
+ }
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Loop(block, ..) => {
+ self.loops.push(expr.hir_id);
+ self.is_single_insert = false;
+ self.allow_insert_closure &= !self.in_tail_pos;
+ // Don't allow insertions inside of a loop.
+ let edit_len = self.edits.len();
+ self.visit_block(block);
+ if self.edits.len() != edit_len {
+ self.can_use_entry = false;
+ }
+ self.loops.pop();
+ },
+ ExprKind::Block(block, _) => self.visit_block(block),
+ ExprKind::InlineAsm(_) => {
+ self.can_use_entry = false;
+ },
+ _ => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.allow_insert_closure &=
+ can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
+ // Sub expressions are no longer in the tail position.
+ self.is_single_insert = false;
+ self.in_tail_pos = false;
+ walk_expr(self, expr);
+ },
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+}
+
+struct InsertSearchResults<'tcx> {
+ edits: Vec<Edit<'tcx>>,
+ allow_insert_closure: bool,
+ is_single_insert: bool,
+}
+impl<'tcx> InsertSearchResults<'tcx> {
+ fn as_single_insertion(&self) -> Option<Insertion<'tcx>> {
+ self.is_single_insert.then(|| self.edits[0].as_insertion().unwrap())
+ }
+
+ fn snippet(
+ &self,
+ cx: &LateContext<'_>,
+ mut span: Span,
+ app: &mut Applicability,
+ write_wrapped: impl Fn(&mut String, Insertion<'_>, SyntaxContext, &mut Applicability),
+ ) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ if is_expr_used_or_unified(cx.tcx, insertion.call) {
+ write_wrapped(&mut res, insertion, ctxt, app);
+ } else {
+ let _ = write!(
+ res,
+ "e.insert({})",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+
+ fn snippet_occupied(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value`
+ let _ = write!(
+ res,
+ "Some(e.insert({}))",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }),
+ "Occupied(mut e)",
+ )
+ }
+
+ fn snippet_vacant(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `None`, but the entry returns a mutable reference.
+ let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
+ write!(
+ res,
+ "e.insert({});\n{}None",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
+ )
+ } else {
+ write!(
+ res,
+ "{{ e.insert({}); None }}",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ )
+ };
+ }),
+ "Vacant(e)",
+ )
+ }
+
+ fn snippet_closure(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for edit in &self.edits {
+ match *edit {
+ Edit::Insertion(insertion) => {
+ // Cut out the value from `map.insert(key, value)`
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ },
+ Edit::RemoveSemi(semi_span) => {
+ // Cut out the semicolon. This allows the value to be returned from the closure.
+ res.push_str(&snippet_with_applicability(cx, span.until(semi_span), "..", app));
+ span = span.trim_start(semi_span).unwrap_or(DUMMY_SP);
+ },
+ }
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+}
+
+fn find_insert_calls<'tcx>(
+ cx: &LateContext<'tcx>,
+ contains_expr: &ContainsExpr<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<InsertSearchResults<'tcx>> {
+ let mut s = InsertSearcher {
+ cx,
+ map: contains_expr.map,
+ key: contains_expr.key,
+ ctxt: expr.span.ctxt(),
+ edits: Vec::new(),
+ is_map_used: false,
+ allow_insert_closure: true,
+ can_use_entry: true,
+ in_tail_pos: true,
+ is_single_insert: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ };
+ s.visit_expr(expr);
+ let allow_insert_closure = s.allow_insert_closure;
+ let is_single_insert = s.is_single_insert;
+ let edits = s.edits;
+ s.can_use_entry.then(|| InsertSearchResults {
+ edits,
+ allow_insert_closure,
+ is_single_insert,
+ })
+}
--- /dev/null
- use std::convert::TryFrom;
+//! lint on C-like enums that are `repr(isize/usize)` and have values that
+//! don't fit into an `i32`
+
+use clippy_utils::consts::{miri_to_const, Constant};
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::util::IntTypeExt;
+use rustc_middle::ty::{self, IntTy, UintTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)]
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for C-like enumerations that are
+ /// `repr(isize/usize)` and have values that don't fit into an `i32`.
+ ///
+ /// ### Why is this bad?
+ /// This will truncate the variant value on 32 bit
+ /// architectures, but works fine on 64 bit.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[cfg(target_pointer_width = "64")]
+ /// #[repr(usize)]
+ /// enum NonPortable {
+ /// X = 0x1_0000_0000,
+ /// Y = 0,
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_CLIKE_UNPORTABLE_VARIANT,
+ correctness,
+ "C-like enums that are `repr(isize/usize)` and have values that don't fit into an `i32`"
+}
+
+declare_lint_pass!(UnportableVariant => [ENUM_CLIKE_UNPORTABLE_VARIANT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
++ #[expect(clippy::cast_possible_wrap)]
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if cx.tcx.data_layout.pointer_size.bits() != 64 {
+ return;
+ }
+ if let ItemKind::Enum(def, _) = &item.kind {
+ for var in def.variants {
+ if let Some(anon_const) = &var.disr_expr {
+ let def_id = cx.tcx.hir().body_owner_def_id(anon_const.body);
+ let mut ty = cx.tcx.type_of(def_id.to_def_id());
+ let constant = cx
+ .tcx
+ .const_eval_poly(def_id.to_def_id())
+ .ok()
+ .map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty));
+ if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
+ if let ty::Adt(adt, _) = ty.kind() {
+ if adt.is_enum() {
+ ty = adt.repr().discr_type().to_ty(cx.tcx);
+ }
+ }
+ match ty.kind() {
+ ty::Int(IntTy::Isize) => {
+ let val = ((val as i128) << 64) >> 64;
+ if i32::try_from(val).is_ok() {
+ continue;
+ }
+ },
+ ty::Uint(UintTy::Usize) if val > u128::from(u32::MAX) => {},
+ _ => continue,
+ }
+ span_lint(
+ cx,
+ ENUM_CLIKE_UNPORTABLE_VARIANT,
+ var.span,
+ "C-like enum variant discriminant is not portable to 32-bit targets",
+ );
+ };
+ }
+ }
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::similar_names)]
+//! lint on enum variants that are prefixed or suffixed by the same characters
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::source::is_present_in_source;
+use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start};
+use rustc_hir::{EnumDef, Item, ItemKind, Variant};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects enumeration variants that are prefixed or suffixed
+ /// by the same characters.
+ ///
+ /// ### Why is this bad?
+ /// Enumeration variant names should specify their variant,
+ /// not repeat the enumeration name.
+ ///
+ /// ### Limitations
+ /// Characters with no casing will be considered when comparing prefixes/suffixes
+ /// This applies to numbers and non-ascii characters without casing
+ /// e.g. `Foo1` and `Foo2` is considered to have different prefixes
+ /// (the prefixes are `Foo1` and `Foo2` respectively), as also `Bar螃`, `Bar蟹`
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Cake {
+ /// BlackForestCake,
+ /// HummingbirdCake,
+ /// BattenbergCake,
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// enum Cake {
+ /// BlackForest,
+ /// Hummingbird,
+ /// Battenberg,
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_VARIANT_NAMES,
+ style,
+ "enums where all variants share a prefix/postfix"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects type names that are prefixed or suffixed by the
+ /// containing module's name.
+ ///
+ /// ### Why is this bad?
+ /// It requires the user to type the module name twice.
+ ///
+ /// ### Example
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForestCake;
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForest;
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub MODULE_NAME_REPETITIONS,
+ pedantic,
+ "type names prefixed/postfixed with their containing module's name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for modules that have the same name as their
+ /// parent module
+ ///
+ /// ### Why is this bad?
+ /// A typical beginner mistake is to have `mod foo;` and
+ /// again `mod foo { ..
+ /// }` in `foo.rs`.
+ /// The expectation is that items inside the inner `mod foo { .. }` are then
+ /// available
+ /// through `foo::x`, but they are only available through
+ /// `foo::foo::x`.
+ /// If this is done on purpose, it would be better to choose a more
+ /// representative module name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // lib.rs
+ /// mod foo;
+ /// // foo.rs
+ /// mod foo {
+ /// ...
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MODULE_INCEPTION,
+ style,
+ "modules that have the same name as their parent module"
+}
+
+pub struct EnumVariantNames {
+ modules: Vec<(Symbol, String)>,
+ threshold: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl EnumVariantNames {
+ #[must_use]
+ pub fn new(threshold: u64, avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ modules: Vec::new(),
+ threshold,
+ avoid_breaking_exported_api,
+ }
+ }
+}
+
+impl_lint_pass!(EnumVariantNames => [
+ ENUM_VARIANT_NAMES,
+ MODULE_NAME_REPETITIONS,
+ MODULE_INCEPTION
+]);
+
+fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
+ let name = variant.ident.name.as_str();
+ let item_name_chars = item_name.chars().count();
+
+ if count_match_start(item_name, name).char_count == item_name_chars
+ && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase())
+ && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric())
+ {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ variant.span,
+ "variant name starts with the enum's name",
+ );
+ }
+}
+
+fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
+ let name = variant.ident.name.as_str();
+ let item_name_chars = item_name.chars().count();
+
+ if count_match_end(item_name, name).char_count == item_name_chars {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ variant.span,
+ "variant name ends with the enum's name",
+ );
+ }
+}
+
+fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_name: &str, span: Span) {
+ if (def.variants.len() as u64) < threshold {
+ return;
+ }
+
+ let first = &def.variants[0].ident.name.as_str();
+ let mut pre = camel_case_split(first);
+ let mut post = pre.clone();
+ post.reverse();
+ for var in def.variants {
+ check_enum_start(cx, item_name, var);
+ check_enum_end(cx, item_name, var);
+ let name = var.ident.name.as_str();
+
+ let variant_split = camel_case_split(name);
+ if variant_split.len() == 1 {
+ return;
+ }
+
+ pre = pre
+ .iter()
+ .zip(variant_split.iter())
+ .take_while(|(a, b)| a == b)
+ .map(|e| *e.0)
+ .collect();
+ post = post
+ .iter()
+ .zip(variant_split.iter().rev())
+ .take_while(|(a, b)| a == b)
+ .map(|e| *e.0)
+ .collect();
+ }
+ let (what, value) = match (pre.is_empty(), post.is_empty()) {
+ (true, true) => return,
+ (false, _) => ("pre", pre.join("")),
+ (true, false) => {
+ post.reverse();
+ ("post", post.join(""))
+ },
+ };
+ span_lint_and_help(
+ cx,
+ ENUM_VARIANT_NAMES,
+ span,
+ &format!("all variants have the same {}fix: `{}`", what, value),
+ None,
+ &format!(
+ "remove the {}fixes and use full paths to \
+ the variants instead of glob imports",
+ what
+ ),
+ );
+}
+
+#[must_use]
+fn to_camel_case(item_name: &str) -> String {
+ let mut s = String::new();
+ let mut up = true;
+ for c in item_name.chars() {
+ if c.is_uppercase() {
+ // we only turn snake case text into CamelCase
+ return item_name.to_string();
+ }
+ if c == '_' {
+ up = true;
+ continue;
+ }
+ if up {
+ up = false;
+ s.extend(c.to_uppercase());
+ } else {
+ s.push(c);
+ }
+ }
+ s
+}
+
+impl LateLintPass<'_> for EnumVariantNames {
+ fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
+ let last = self.modules.pop();
+ assert!(last.is_some());
+ }
+
++ #[expect(clippy::similar_names)]
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ let item_name = item.ident.name.as_str();
+ let item_camel = to_camel_case(item_name);
+ if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
+ if let Some(&(ref mod_name, ref mod_camel)) = self.modules.last() {
+ // constants don't have surrounding modules
+ if !mod_camel.is_empty() {
+ if mod_name == &item.ident.name {
+ if let ItemKind::Mod(..) = item.kind {
+ span_lint(
+ cx,
+ MODULE_INCEPTION,
+ item.span,
+ "module has the same name as its containing module",
+ );
+ }
+ }
+ // The `module_name_repetitions` lint should only trigger if the item has the module in its
+ // name. Having the same name is accepted.
+ if cx.tcx.visibility(item.def_id).is_public() && item_camel.len() > mod_camel.len() {
+ let matching = count_match_start(mod_camel, &item_camel);
+ let rmatching = count_match_end(mod_camel, &item_camel);
+ let nchars = mod_camel.chars().count();
+
+ let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
+
+ if matching.char_count == nchars {
+ match item_camel.chars().nth(nchars) {
+ Some(c) if is_word_beginning(c) => span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name starts with its containing module's name",
+ ),
+ _ => (),
+ }
+ }
+ if rmatching.char_count == nchars {
+ span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name ends with its containing module's name",
+ );
+ }
+ }
+ }
+ }
+ }
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ if !(self.avoid_breaking_exported_api && cx.access_levels.is_exported(item.def_id)) {
+ check_variant(cx, self.threshold, def, item_name, item.span);
+ }
+ }
+ self.modules.push((item.ident.name, item_camel));
+ }
+}
--- /dev/null
- #[allow(clippy::similar_names, clippy::too_many_lines)]
+use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
+use clippy_utils::get_enclosing_block;
+use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
+use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for equal operands to comparison, logical and
+ /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
+ /// `||`, `&`, `|`, `^`, `-` and `/`).
+ ///
+ /// ### Why is this bad?
+ /// This is usually just a typo or a copy and paste error.
+ ///
+ /// ### Known problems
+ /// False negatives: We had some false positives regarding
+ /// calls (notably [racer](https://github.com/phildawes/racer) had one instance
+ /// of `x.pop() && x.pop()`), so we removed matching any function or method
+ /// calls. We may introduce a list of known pure functions in the future.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x + 1 == x + 1 {}
+ /// ```
+ /// or
+ /// ```rust
+ /// # let a = 3;
+ /// # let b = 4;
+ /// assert_eq!(a, a);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EQ_OP,
+ correctness,
+ "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for arguments to `==` which have their address
+ /// taken to satisfy a bound
+ /// and suggests to dereference the other argument instead
+ ///
+ /// ### Why is this bad?
+ /// It is more idiomatic to dereference the other argument.
+ ///
+ /// ### Known problems
+ /// None
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// &x == y
+ ///
+ /// // Good
+ /// x == *y
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OP_REF,
+ style,
+ "taking a reference to satisfy the type constraints on `==`"
+}
+
+declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
+
+impl<'tcx> LateLintPass<'tcx> for EqOp {
- #[allow(clippy::match_same_arms)]
++ #[expect(clippy::similar_names, clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
+ let name = cx.tcx.item_name(macro_call.def_id);
+ matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne")
+ .then(|| (macro_call, name))
+ });
+ if let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn);
+ if eq_expr_value(cx, lhs, rhs);
+ if macro_call.is_local();
+ if !is_in_test_function(cx.tcx, e.hir_id);
+ then {
+ span_lint(
+ cx,
+ EQ_OP,
+ lhs.span.to(rhs.span),
+ &format!("identical args used in this `{}!` macro call", macro_name),
+ );
+ }
+ }
+ if let ExprKind::Binary(op, left, right) = e.kind {
+ if e.span.from_expansion() {
+ return;
+ }
+ let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
+ if let ExprKind::Unary(_, expr) = *expr_kind {
+ expr.span.from_expansion()
+ } else {
+ false
+ }
+ };
+ if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
+ return;
+ }
+ if is_useless_with_eq_exprs(op.node.into())
+ && eq_expr_value(cx, left, right)
+ && !is_in_test_function(cx.tcx, e.hir_id)
+ {
+ span_lint(
+ cx,
+ EQ_OP,
+ e.span,
+ &format!("equal expressions as operands to `{}`", op.node.as_str()),
+ );
+ return;
+ }
+ let (trait_id, requires_ref) = match op.node {
+ BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false),
+ BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false),
+ BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false),
+ BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false),
+ BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false),
+ // don't lint short circuiting ops
+ BinOpKind::And | BinOpKind::Or => return,
+ BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false),
+ BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false),
+ BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false),
+ BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false),
+ BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false),
+ BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true),
+ BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => {
+ (cx.tcx.lang_items().partial_ord_trait(), true)
+ },
+ };
+ if let Some(trait_id) = trait_id {
+ match (&left.kind, &right.kind) {
+ // do not suggest to dereference literals
+ (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
+ // &foo == &bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ let rty = cx.typeck_results().expr_ty(r);
+ let lcpy = is_copy(cx, lty);
+ let rcpy = is_copy(cx, rty);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ // either operator autorefs or both args are copyable
+ if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of both operands",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ multispan_sugg(
+ diag,
+ "use the values directly",
+ vec![(left.span, lsnip), (right.span, rsnip)],
+ );
+ },
+ );
+ } else if lcpy
+ && !rcpy
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ } else if !lcpy
+ && rcpy
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of right operand",
+ |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // &foo == bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ let rty = cx.typeck_results().expr_ty(right);
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ let lcpy = is_copy(cx, lty);
+ if (requires_ref || lcpy)
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // foo == &bar
+ (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let rty = cx.typeck_results().expr_ty(r);
+ if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
+ let lty = cx.typeck_results().expr_ty(left);
+ if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
+ || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
+ {
+ return; // Don't lint
+ }
+ }
+ let rcpy = is_copy(cx, rty);
+ if (requires_ref || rcpy)
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ });
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn in_impl<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ bin_op: DefId,
+) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
+ if_chain! {
+ if let Some(block) = get_enclosing_block(cx, e.hir_id);
+ if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id());
+ let item = cx.tcx.hir().expect_item(impl_def_id.expect_local());
+ if let ItemKind::Impl(item) = &item.kind;
+ if let Some(of_trait) = &item.of_trait;
+ if let Some(seg) = of_trait.path.segments.last();
+ if let Some(Res::Def(_, trait_id)) = seg.res;
+ if trait_id == bin_op;
+ if let Some(generic_args) = seg.args;
+ if let Some(GenericArg::Type(other_ty)) = generic_args.args.last();
+
+ then {
+ Some((item.self_ty, other_ty))
+ }
+ else {
+ None
+ }
+ }
+}
+
+fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let ty::Adt(adt_def, _) = middle_ty.kind();
+ if let Some(local_did) = adt_def.did().as_local();
+ let item = cx.tcx.hir().expect_item(local_did);
+ let middle_ty_id = item.def_id.to_def_id();
+ if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
+ if let Res::Def(_, hir_ty_id) = path.res;
+
+ then {
+ hir_ty_id == middle_ty_id
+ }
+ else {
+ false
+ }
+ }
+}
--- /dev/null
- use std::convert::TryInto;
-
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive
+ /// use of bools in structs.
+ ///
+ /// ### Why is this bad?
+ /// Excessive bools in a struct
+ /// is often a sign that it's used as a state machine,
+ /// which is much better implemented as an enum.
+ /// If it's not the case, excessive bools usually benefit
+ /// from refactoring into two-variant enums for better
+ /// readability and API.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// struct S {
+ /// is_pending: bool,
+ /// is_processing: bool,
+ /// is_finished: bool,
+ /// }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// enum S {
+ /// Pending,
+ /// Processing,
+ /// Finished,
+ /// }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub STRUCT_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in a struct"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive use of
+ /// bools in function definitions.
+ ///
+ /// ### Why is this bad?
+ /// Calls to such functions
+ /// are confusing and error prone, because it's
+ /// hard to remember argument order and you have
+ /// no type system support to back you up. Using
+ /// two-variant enums instead of bools often makes
+ /// API easier to use.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// fn f(is_round: bool, is_hot: bool) { ... }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// enum Shape {
+ /// Round,
+ /// Spiky,
+ /// }
+ ///
+ /// enum Temperature {
+ /// Hot,
+ /// IceCold,
+ /// }
+ ///
+ /// fn f(shape: Shape, temperature: Temperature) { ... }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub FN_PARAMS_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in function parameters"
+}
+
+pub struct ExcessiveBools {
+ max_struct_bools: u64,
+ max_fn_params_bools: u64,
+}
+
+impl ExcessiveBools {
+ #[must_use]
+ pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
+ Self {
+ max_struct_bools,
+ max_fn_params_bools,
+ }
+ }
+
+ fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
+ match fn_sig.header.ext {
+ Extern::Implicit | Extern::Explicit(_) => return,
+ Extern::None => (),
+ }
+
+ let fn_sig_bools = fn_sig
+ .decl
+ .inputs
+ .iter()
+ .filter(|param| is_bool_ty(¶m.ty))
+ .count()
+ .try_into()
+ .unwrap();
+ if self.max_fn_params_bools < fn_sig_bools {
+ span_lint_and_help(
+ cx,
+ FN_PARAMS_EXCESSIVE_BOOLS,
+ span,
+ &format!("more than {} bools in function parameters", self.max_fn_params_bools),
+ None,
+ "consider refactoring bools into two-variant enums",
+ );
+ }
+ }
+}
+
+impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
+
+fn is_bool_ty(ty: &Ty) -> bool {
+ if let TyKind::Path(None, path) = &ty.kind {
+ if let [name] = path.segments.as_slice() {
+ return name.ident.name == sym::bool;
+ }
+ }
+ false
+}
+
+impl EarlyLintPass for ExcessiveBools {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if item.span.from_expansion() {
+ return;
+ }
+ match &item.kind {
+ ItemKind::Struct(variant_data, _) => {
+ if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
+ return;
+ }
+
+ let struct_bools = variant_data
+ .fields()
+ .iter()
+ .filter(|field| is_bool_ty(&field.ty))
+ .count()
+ .try_into()
+ .unwrap();
+ if self.max_struct_bools < struct_bools {
+ span_lint_and_help(
+ cx,
+ STRUCT_EXCESSIVE_BOOLS,
+ item.span,
+ &format!("more than {} bools in a struct", self.max_struct_bools),
+ None,
+ "consider using a state machine or refactoring bools into two-variant enums",
+ );
+ }
+ },
+ ItemKind::Impl(box Impl {
+ of_trait: None, items, ..
+ })
+ | ItemKind::Trait(box Trait { items, .. }) => {
+ for item in items {
+ if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
+ self.check_fn_sig(cx, sig, item.span);
+ }
+ }
+ },
+ ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
+ _ => (),
+ }
+ }
+}
--- /dev/null
- /// use std::convert::TryFrom;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::method_chain_args;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
+ ///
+ /// ### Why is this bad?
+ /// `TryFrom` should be used if there's a possibility of failure.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo(i32);
+ ///
+ /// // Bad
+ /// impl From<String> for Foo {
+ /// fn from(s: String) -> Self {
+ /// Foo(s.parse().unwrap())
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// // Good
+ /// struct Foo(i32);
+ ///
+ /// impl TryFrom<String> for Foo {
+ /// type Error = ();
+ /// fn try_from(s: String) -> Result<Self, Self::Error> {
+ /// if let Ok(parsed) = s.parse() {
+ /// Ok(Foo(parsed))
+ /// } else {
+ /// Err(())
+ /// }
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FALLIBLE_IMPL_FROM,
+ nursery,
+ "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
+}
+
+declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
+
+impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ // check for `impl From<???> for ..`
+ if_chain! {
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+ if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.def_id);
+ then {
+ lint_impl_body(cx, item.span, impl_.items);
+ }
+ }
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
+ use rustc_hir::intravisit::{self, Visitor};
+ use rustc_hir::{Expr, ImplItemKind};
+
+ struct FindPanicUnwrap<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ result: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
+ if is_panic(self.lcx, macro_call.def_id) {
+ self.result.push(expr.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ for impl_item in impl_items {
+ if_chain! {
+ if impl_item.ident.name == sym::from;
+ if let ImplItemKind::Fn(_, body_id) =
+ cx.tcx.hir().impl_item(impl_item.id).kind;
+ then {
+ // check the body for `begin_panic` or `unwrap`
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ lcx: cx,
+ typeck_results: cx.tcx.typeck(impl_item.id.def_id),
+ result: Vec::new(),
+ };
+ fpu.visit_expr(&body.value);
+
+ // if we've found one, lint
+ if !fpu.result.is_empty() {
+ span_lint_and_then(
+ cx,
+ FALLIBLE_IMPL_FROM,
+ impl_span,
+ "consider implementing `TryFrom` instead",
+ move |diag| {
+ diag.help(
+ "`From` is intended for infallible conversions only. \
+ Use `TryFrom` if there's a possibility for the conversion to fail");
+ diag.span_note(fpu.result, "potential failure(s)");
+ });
+ }
+ }
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::cast_possible_truncation)]
+use clippy_utils::consts::{
+ constant, constant_simple, Constant,
+ Constant::{Int, F32, F64},
+};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, peel_blocks, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+use rustc_ast::ast;
+use std::f32::consts as f32_consts;
+use std::f64::consts as f64_consts;
+use sugg::Sugg;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for floating-point expressions that
+ /// can be expressed using built-in methods to improve accuracy
+ /// at the cost of performance.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts accuracy.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = 3f32;
+ /// let _ = a.powf(1.0 / 3.0);
+ /// let _ = (1.0 + a).ln();
+ /// let _ = a.exp() - 1.0;
+ /// ```
+ ///
+ /// is better expressed as
+ ///
+ /// ```rust
+ /// let a = 3f32;
+ /// let _ = a.cbrt();
+ /// let _ = a.ln_1p();
+ /// let _ = a.exp_m1();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub IMPRECISE_FLOPS,
+ nursery,
+ "usage of imprecise floating point operations"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for floating-point expressions that
+ /// can be expressed using built-in methods to improve both
+ /// accuracy and performance.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts accuracy and performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::f32::consts::E;
+ ///
+ /// let a = 3f32;
+ /// let _ = (2f32).powf(a);
+ /// let _ = E.powf(a);
+ /// let _ = a.powf(1.0 / 2.0);
+ /// let _ = a.log(2.0);
+ /// let _ = a.log(10.0);
+ /// let _ = a.log(E);
+ /// let _ = a.powf(2.0);
+ /// let _ = a * 2.0 + 4.0;
+ /// let _ = if a < 0.0 {
+ /// -a
+ /// } else {
+ /// a
+ /// };
+ /// let _ = if a < 0.0 {
+ /// a
+ /// } else {
+ /// -a
+ /// };
+ /// ```
+ ///
+ /// is better expressed as
+ ///
+ /// ```rust
+ /// use std::f32::consts::E;
+ ///
+ /// let a = 3f32;
+ /// let _ = a.exp2();
+ /// let _ = a.exp();
+ /// let _ = a.sqrt();
+ /// let _ = a.log2();
+ /// let _ = a.log10();
+ /// let _ = a.ln();
+ /// let _ = a.powi(2);
+ /// let _ = a.mul_add(2.0, 4.0);
+ /// let _ = a.abs();
+ /// let _ = -a.abs();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub SUBOPTIMAL_FLOPS,
+ nursery,
+ "usage of sub-optimal floating point operations"
+}
+
+declare_lint_pass!(FloatingPointArithmetic => [
+ IMPRECISE_FLOPS,
+ SUBOPTIMAL_FLOPS
+]);
+
+// Returns the specialized log method for a given base if base is constant
+// and is one of 2, 10 and e
+fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), base) {
+ if F32(2.0) == value || F64(2.0) == value {
+ return Some("log2");
+ } else if F32(10.0) == value || F64(10.0) == value {
+ return Some("log10");
+ } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ return Some("ln");
+ }
+ }
+
+ None
+}
+
+// Adds type suffixes and parenthesis to method receivers if necessary
+fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> {
+ let mut suggestion = Sugg::hir(cx, expr, "..");
+
+ if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
+ expr = inner_expr;
+ }
+
+ if_chain! {
+ // if the expression is a float literal and it is unsuffixed then
+ // add a suffix so the suggestion is valid and unambiguous
+ if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
+ if let ExprKind::Lit(lit) = &expr.kind;
+ if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
+ then {
+ let op = format!(
+ "{}{}{}",
+ suggestion,
+ // Check for float literals without numbers following the decimal
+ // separator such as `2.` and adds a trailing zero
+ if sym.as_str().ends_with('.') {
+ "0"
+ } else {
+ ""
+ },
+ float_ty.name_str()
+ ).into();
+
+ suggestion = match suggestion {
+ Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
+ _ => Sugg::NonParen(op)
+ };
+ }
+ }
+
+ suggestion.maybe_par()
+}
+
+fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(method) = get_specialized_log_method(cx, &args[1]) {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "logarithm for bases 2, 10 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
+// suggest usage of `(x + (y - 1)).ln_1p()` instead
+fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &args[0].kind
+ {
+ let recv = match (
+ constant(cx, cx.typeck_results(), lhs),
+ constant(cx, cx.typeck_results(), rhs),
+ ) {
+ (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
+ (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
+ _ => return,
+ };
+
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "ln(1 + x) can be computed more accurately",
+ "consider using",
+ format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// Returns an integer if the float constant is a whole number and it can be
+// converted to an integer without loss of precision. For now we only check
+// ranges [-16777215, 16777216) for type f32 as whole number floats outside
+// this range are lossy and ambiguous.
++#[expect(clippy::cast_possible_truncation)]
+fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
+ match value {
+ F32(num) if num.fract() == 0.0 => {
+ if (-16_777_215.0..16_777_216.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ F64(num) if num.fract() == 0.0 => {
+ if (-2_147_483_648.0..2_147_483_648.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ // Check receiver
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
+ let method = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ "exp"
+ } else if F32(2.0) == value || F64(2.0) == value {
+ "exp2"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "exponent for bases 2 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", prepare_receiver_sugg(cx, &args[1]), method),
+ Applicability::MachineApplicable,
+ );
+ }
+
+ // Check argument
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) {
+ let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
+ (
+ SUBOPTIMAL_FLOPS,
+ "square-root of a number can be computed more efficiently and accurately",
+ format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
+ (
+ IMPRECISE_FLOPS,
+ "cube-root of a number can be computed more accurately",
+ format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if let Some(exponent) = get_integer_from_float_constant(&value) {
+ (
+ SUBOPTIMAL_FLOPS,
+ "exponentiation with integer powers can be computed more efficiently",
+ format!(
+ "{}.powi({})",
+ Sugg::hir(cx, &args[0], ".."),
+ numeric_literal::format(&exponent.to_string(), None, false)
+ ),
+ )
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ help,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) {
+ if value == Int(2) {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let Some(grandparent) = get_parent_expr(cx, parent) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, args, _) = grandparent.kind {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
+ return;
+ }
+ }
+ }
+
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = parent.kind
+ {
+ let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ parent.span,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
+ format!(
+ "{}.mul_add({}, {})",
+ Sugg::hir(cx, &args[0], ".."),
+ Sugg::hir(cx, &args[0], ".."),
+ Sugg::hir(cx, other_addend, ".."),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ add_lhs,
+ add_rhs,
+ ) = args[0].kind
+ {
+ // check if expression of the form x * x + y * y
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
+ if eq_expr_value(cx, lmul_lhs, lmul_rhs);
+ if eq_expr_value(cx, rmul_lhs, rmul_rhs);
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, "..")));
+ }
+ }
+
+ // check if expression of the form x.powi(2) + y.powi(2)
+ if_chain! {
+ if let ExprKind::MethodCall(
+ PathSegment { ident: lmethod_name, .. },
+ [largs_0, largs_1, ..],
+ _
+ ) = &add_lhs.kind;
+ if let ExprKind::MethodCall(
+ PathSegment { ident: rmethod_name, .. },
+ [rargs_0, rargs_1, ..],
+ _
+ ) = &add_rhs.kind;
+ if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
+ if Int(2) == lvalue && Int(2) == rvalue;
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
+ }
+ }
+ }
+
+ None
+}
+
+fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(message) = detect_hypot(cx, args) {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "hypotenuse can be computed more accurately",
+ "consider using",
+ message,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `x.exp() - y` where y > 1
+// and suggest usage of `x.exp_m1() - (y - 1)` instead
+fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
+ if F32(1.0) == value || F64(1.0) == value;
+ if let ExprKind::MethodCall(path, [self_arg, ..], _) = &lhs.kind;
+ if cx.typeck_results().expr_ty(self_arg).is_floating_point();
+ if path.ident.name.as_str() == "exp";
+ then {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "(e.pow(x) - 1) can be computed more accurately",
+ "consider using",
+ format!(
+ "{}.exp_m1()",
+ Sugg::hir(cx, self_arg, "..")
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if cx.typeck_results().expr_ty(rhs).is_floating_point();
+ then {
+ return Some((lhs, rhs));
+ }
+ }
+
+ None
+}
+
+// TODO: Fix rust-lang/rust-clippy#4735
+fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind
+ {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, args, _) = parent.kind {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
+ return;
+ }
+ }
+ }
+
+ let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
+ (inner_lhs, inner_rhs, rhs)
+ } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
+ (inner_lhs, inner_rhs, lhs)
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
+ format!(
+ "{}.mul_add({}, {})",
+ prepare_receiver_sugg(cx, recv),
+ Sugg::hir(cx, arg1, ".."),
+ Sugg::hir(cx, arg2, ".."),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// Returns true iff expr is an expression which tests whether or not
+/// test is positive or an expression which tests whether or not test
+/// is nonnegative.
+/// Used for check-custom-abs function below
+fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// See [`is_testing_positive`]
+fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// Returns true iff expr is some zero literal
+fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match constant_simple(cx, cx.typeck_results(), expr) {
+ Some(Constant::Int(i)) => i == 0,
+ Some(Constant::F32(f)) => f == 0.0,
+ Some(Constant::F64(f)) => f == 0.0,
+ _ => false,
+ }
+}
+
+/// If the two expressions are negations of each other, then it returns
+/// a tuple, in which the first element is true iff expr1 is the
+/// positive expressions, and the second element is the positive
+/// one of the two expressions
+/// If the two expressions are not negations of each other, then it
+/// returns None.
+fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
+ if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
+ if eq_expr_value(cx, expr1_negated, expr2) {
+ return Some((false, expr2));
+ }
+ }
+ if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
+ if eq_expr_value(cx, expr1, expr2_negated) {
+ return Some((true, expr1));
+ }
+ }
+ None
+}
+
+fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr);
+ let if_body_expr = peel_blocks(then);
+ let else_body_expr = peel_blocks(r#else);
+ if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
+ then {
+ let positive_abs_sugg = (
+ "manual implementation of `abs` method",
+ format!("{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let negative_abs_sugg = (
+ "manual implementation of negation of `abs` method",
+ format!("-{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let sugg = if is_testing_positive(cx, cond, body) {
+ if if_expr_positive {
+ positive_abs_sugg
+ } else {
+ negative_abs_sugg
+ }
+ } else if is_testing_negative(cx, cond, body) {
+ if if_expr_positive {
+ negative_abs_sugg
+ } else {
+ positive_abs_sugg
+ }
+ } else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ sugg.0,
+ "try",
+ sugg.1,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, args_a, _) = expr_a.kind;
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, args_b, _) = expr_b.kind;
+ then {
+ return method_name_a.as_str() == method_name_b.as_str() &&
+ args_a.len() == args_b.len() &&
+ (
+ ["ln", "log2", "log10"].contains(&method_name_a.as_str()) ||
+ method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1])
+ );
+ }
+ }
+
+ false
+}
+
+fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // check if expression of the form x.logN() / y.logN()
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind;
+ if are_same_base_logs(cx, lhs, rhs);
+ if let ExprKind::MethodCall(_, [largs_self, ..], _) = &lhs.kind;
+ if let ExprKind::MethodCall(_, [rargs_self, ..], _) = &rhs.kind;
+ then {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "log base can be expressed more clearly",
+ "consider using",
+ format!("{}.log({})", Sugg::hir(cx, largs_self, ".."), Sugg::hir(cx, rargs_self, ".."),),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ div_lhs,
+ div_rhs,
+ ) = &expr.kind;
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Mul, ..
+ },
+ mul_lhs,
+ mul_rhs,
+ ) = &div_lhs.kind;
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs);
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs);
+ then {
+ // TODO: also check for constant values near PI/180 or 180/PI
+ if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
+ (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
+ {
+ let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
+ if_chain! {
+ if let ExprKind::Lit(ref literal) = mul_lhs.kind;
+ if let ast::LitKind::Float(ref value, float_type) = literal.node;
+ if float_type == ast::LitFloatType::Unsuffixed;
+ then {
+ if value.as_str().ends_with('.') {
+ proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
+ } else {
+ proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
+ }
+ }
+ }
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to degrees can be done more accurately",
+ "consider using",
+ proposal,
+ Applicability::MachineApplicable,
+ );
+ } else if
+ (F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
+ (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
+ {
+ let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
+ if_chain! {
+ if let ExprKind::Lit(ref literal) = mul_lhs.kind;
+ if let ast::LitKind::Float(ref value, float_type) = literal.node;
+ if float_type == ast::LitFloatType::Unsuffixed;
+ then {
+ if value.as_str().ends_with('.') {
+ proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
+ } else {
+ proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
+ }
+ }
+ }
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to radians can be done more accurately",
+ "consider using",
+ proposal,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // All of these operations are currently not const.
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ if let ExprKind::MethodCall(path, args, _) = &expr.kind {
+ let recv_ty = cx.typeck_results().expr_ty(&args[0]);
+
+ if recv_ty.is_floating_point() {
+ match path.ident.name.as_str() {
+ "ln" => check_ln1p(cx, expr, args),
+ "log" => check_log_base(cx, expr, args),
+ "powf" => check_powf(cx, expr, args),
+ "powi" => check_powi(cx, expr, args),
+ "sqrt" => check_hypot(cx, expr, args),
+ _ => {},
+ }
+ }
+ } else {
+ check_expm1(cx, expr);
+ check_mul_add(cx, expr);
+ check_custom_abs(cx, expr);
+ check_log_division(cx, expr);
+ check_radians(cx, expr);
+ }
+ }
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::RE_REBALANCING_COHERENCE) {
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Searches for implementations of the `Into<..>` trait and suggests to implement `From<..>` instead.
+ ///
+ /// ### Why is this bad?
+ /// According the std docs implementing `From<..>` is preferred since it gives you `Into<..>` for free where the reverse isn't true.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct StringWrapper(String);
+ ///
+ /// impl Into<StringWrapper> for String {
+ /// fn into(self) -> StringWrapper {
+ /// StringWrapper(self)
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct StringWrapper(String);
+ ///
+ /// impl From<String> for StringWrapper {
+ /// fn from(s: String) -> StringWrapper {
+ /// StringWrapper(s)
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub FROM_OVER_INTO,
+ style,
+ "Warns on implementations of `Into<..>` to use `From<..>`"
+}
+
+pub struct FromOverInto {
+ msrv: Option<RustcVersion>,
+}
+
+impl FromOverInto {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ FromOverInto { msrv }
+ }
+}
+
+impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]);
+
+impl<'tcx> LateLintPass<'tcx> for FromOverInto {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
++ if !meets_msrv(self.msrv, msrvs::RE_REBALANCING_COHERENCE) {
+ return;
+ }
+
+ if_chain! {
+ if let hir::ItemKind::Impl{ .. } = &item.kind;
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+ if cx.tcx.is_diagnostic_item(sym::Into, impl_trait_ref.def_id);
+
+ then {
+ span_lint_and_help(
+ cx,
+ FROM_OVER_INTO,
+ cx.tcx.sess.source_map().guess_head_span(item.span),
+ "an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true",
+ None,
+ &format!("consider to implement `From<{}>` instead", impl_trait_ref.self_ty()),
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::BOOL_THEN) {
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks};
+use if_chain::if_chain;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for if-else that could be written to `bool::then`.
+ ///
+ /// ### Why is this bad?
+ /// Looks a little redundant. Using `bool::then` helps it have less lines of code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = if v.is_empty() {
+ /// println!("true!");
+ /// Some(42)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = v.is_empty().then(|| {
+ /// println!("true!");
+ /// 42
+ /// });
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub IF_THEN_SOME_ELSE_NONE,
+ restriction,
+ "Finds if-else that could be written using `bool::then`"
+}
+
+pub struct IfThenSomeElseNone {
+ msrv: Option<RustcVersion>,
+}
+
+impl IfThenSomeElseNone {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]);
+
+impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) {
++ if !meets_msrv(self.msrv, msrvs::BOOL_THEN) {
+ return;
+ }
+
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ // We only care about the top-most `if` in the chain
+ if is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr);
+ if let ExprKind::Block(then_block, _) = then.kind;
+ if let Some(then_expr) = then_block.expr;
+ if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
+ if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
+ if is_lang_ctor(cx, then_call_qpath, OptionSome);
+ if let ExprKind::Path(ref qpath) = peel_blocks(els).kind;
+ if is_lang_ctor(cx, qpath, OptionNone);
+ if !stmts_contains_early_return(then_block.stmts);
+ then {
+ let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
+ let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {
+ format!("({})", cond_snip)
+ } else {
+ cond_snip.into_owned()
+ };
+ let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, "");
+ let closure_body = if then_block.stmts.is_empty() {
+ arg_snip.into_owned()
+ } else {
+ format!("{{ /* snippet */ {} }}", arg_snip)
+ };
+ let help = format!(
+ "consider using `bool::then` like: `{}.then(|| {})`",
+ cond_snip,
+ closure_body,
+ );
+ span_lint_and_help(
+ cx,
+ IF_THEN_SOME_ELSE_NONE,
+ expr.span,
+ "this could be simplified with `bool::then`",
+ None,
+ &help,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
+ stmts.iter().any(|stmt| {
+ let Stmt { kind: StmtKind::Semi(e), .. } = stmt else { return false };
+
+ contains_return(e)
+ })
+}
--- /dev/null
- #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+
+use rustc_errors::Diagnostic;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{Ty, TypeckResults};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use rustc_typeck::hir_ty_to_ty;
+
+use if_chain::if_chain;
+
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public `impl` or `fn` missing generalization
+ /// over different hashers and implicitly defaulting to the default hashing
+ /// algorithm (`SipHash`).
+ ///
+ /// ### Why is this bad?
+ /// `HashMap` or `HashSet` with custom hashers cannot be
+ /// used with them.
+ ///
+ /// ### Known problems
+ /// Suggestions for replacing constructors can contain
+ /// false-positives. Also applying suggestions can require modification of other
+ /// pieces of code, possibly including external crates.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # use std::hash::{Hash, BuildHasher};
+ /// # trait Serialize {};
+ /// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
+ ///
+ /// pub fn foo(map: &mut HashMap<i32, i32>) { }
+ /// ```
+ /// could be rewritten as
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # use std::hash::{Hash, BuildHasher};
+ /// # trait Serialize {};
+ /// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
+ ///
+ /// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub IMPLICIT_HASHER,
+ pedantic,
+ "missing generalization over different hashers"
+}
+
+declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
++ #[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ use rustc_span::BytePos;
+
+ fn suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ diag: &mut Diagnostic,
+ generics_span: Span,
+ generics_suggestion_span: Span,
+ target: &ImplicitHasherType<'_>,
+ vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
+ ) {
+ let generics_snip = snippet(cx, generics_span, "");
+ // trim `<` `>`
+ let generics_snip = if generics_snip.is_empty() {
+ ""
+ } else {
+ &generics_snip[1..generics_snip.len() - 1]
+ };
+
+ multispan_sugg(
+ diag,
+ "consider adding a type parameter",
+ vec![
+ (
+ generics_suggestion_span,
+ format!(
+ "<{}{}S: ::std::hash::BuildHasher{}>",
+ generics_snip,
+ if generics_snip.is_empty() { "" } else { ", " },
+ if vis.suggestions.is_empty() {
+ ""
+ } else {
+ // request users to add `Default` bound so that generic constructors can be used
+ " + Default"
+ },
+ ),
+ ),
+ (
+ target.span(),
+ format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
+ ),
+ ],
+ );
+
+ if !vis.suggestions.is_empty() {
+ multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
+ }
+ }
+
+ if !cx.access_levels.is_exported(item.def_id) {
+ return;
+ }
+
+ match item.kind {
+ ItemKind::Impl(impl_) => {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(impl_.self_ty);
+
+ for target in &vis.found {
+ if item.span.ctxt() != target.span().ctxt() {
+ return;
+ }
+
+ let generics_suggestion_span = impl_.generics.span.substitute_dummy({
+ let pos = snippet_opt(cx, item.span.until(target.span()))
+ .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
+ if let Some(pos) = pos {
+ Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+ } else {
+ return;
+ }
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
+ ctr_vis.visit_impl_item(item);
+ }
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "impl for `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ },
+ ItemKind::Fn(ref sig, generics, body_id) => {
+ let body = cx.tcx.hir().body(body_id);
+
+ for ty in sig.decl.inputs {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(ty);
+
+ for target in &vis.found {
+ if in_external_macro(cx.sess(), generics.span) {
+ continue;
+ }
+ let generics_suggestion_span = generics.span.substitute_dummy({
+ let pos = snippet_opt(
+ cx,
+ Span::new(
+ item.span.lo(),
+ body.params[0].pat.span.lo(),
+ item.span.ctxt(),
+ item.span.parent(),
+ ),
+ )
+ .and_then(|snip| {
+ let i = snip.find("fn")?;
+ Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32))
+ })
+ .expect("failed to create span for type parameters");
+ Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ ctr_vis.visit_body(body);
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "parameter of type `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+enum ImplicitHasherType<'tcx> {
+ HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
+ HashSet(Span, Ty<'tcx>, Cow<'static, str>),
+}
+
+impl<'tcx> ImplicitHasherType<'tcx> {
+ /// Checks that `ty` is a target type without a `BuildHasher`.
+ fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
+ if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
+ let params: Vec<_> = path
+ .segments
+ .last()
+ .as_ref()?
+ .args
+ .as_ref()?
+ .args
+ .iter()
+ .filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ .collect();
+ let params_len = params.len();
+
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) && params_len == 2 {
+ Some(ImplicitHasherType::HashMap(
+ hir_ty.span,
+ ty,
+ snippet(cx, params[0].span, "K"),
+ snippet(cx, params[1].span, "V"),
+ ))
+ } else if is_type_diagnostic_item(cx, ty, sym::HashSet) && params_len == 1 {
+ Some(ImplicitHasherType::HashSet(
+ hir_ty.span,
+ ty,
+ snippet(cx, params[0].span, "T"),
+ ))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ fn type_name(&self) -> &'static str {
+ match *self {
+ ImplicitHasherType::HashMap(..) => "HashMap",
+ ImplicitHasherType::HashSet(..) => "HashSet",
+ }
+ }
+
+ fn type_arguments(&self) -> String {
+ match *self {
+ ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
+ ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
+ }
+ }
+
+ fn ty(&self) -> Ty<'tcx> {
+ match *self {
+ ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
+ }
+ }
+
+ fn span(&self) -> Span {
+ match *self {
+ ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
+ }
+ }
+}
+
+struct ImplicitHasherTypeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ found: Vec<ImplicitHasherType<'tcx>>,
+}
+
+impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self { cx, found: vec![] }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
+ fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
+ if let Some(target) = ImplicitHasherType::new(self.cx, t) {
+ self.found.push(target);
+ }
+
+ walk_ty(self, t);
+ }
+
+ fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
+ if let Some(target) = ImplicitHasherType::new(self.cx, &inf.to_ty()) {
+ self.found.push(target);
+ }
+
+ walk_inf(self, inf);
+ }
+}
+
+/// Looks for default-hasher-dependent constructors like `HashMap::new`.
+struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ target: &'b ImplicitHasherType<'tcx>,
+ suggestions: BTreeMap<Span, String>,
+}
+
+impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ target,
+ suggestions: BTreeMap::new(),
+ }
+ }
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_body(&mut self, body: &'tcx Body<'_>) {
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
+ walk_body(self, body);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = e.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
+ if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
+ if let Some(ty_did) = ty_path.res.opt_def_id();
+ then {
+ if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
+ return;
+ }
+
+ if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
+ if method.ident.name == sym::new {
+ self.suggestions
+ .insert(e.span, "HashMap::default()".to_string());
+ } else if method.ident.name == sym!(with_capacity) {
+ self.suggestions.insert(
+ e.span,
+ format!(
+ "HashMap::with_capacity_and_hasher({}, Default::default())",
+ snippet(self.cx, args[0].span, "capacity"),
+ ),
+ );
+ }
+ } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
+ if method.ident.name == sym::new {
+ self.suggestions
+ .insert(e.span, "HashSet::default()".to_string());
+ } else if method.ident.name == sym!(with_capacity) {
+ self.suggestions.insert(
+ e.span,
+ format!(
+ "HashSet::with_capacity_and_hasher({}, Default::default())",
+ snippet(self.cx, args[0].span, "capacity"),
+ ),
+ );
+ }
+ }
+ }
+ }
+
+ walk_expr(self, e);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- #[allow(clippy::option_if_let_else)]
+use clippy_utils::{
+ diagnostics::span_lint_and_sugg,
+ get_async_fn_body, is_async_fn,
+ source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
+ visitors::expr_visitor_no_bodies,
+};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{FnKind, Visitor};
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
+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::{Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for missing return statements at the end of a block.
+ ///
+ /// ### Why is this bad?
+ /// Actually omitting the return keyword is idiomatic Rust code. Programmers
+ /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss
+ /// the last returning statement because the only difference is a missing `;`. Especially in bigger
+ /// code with multiple return paths having a `return` keyword makes it easier to find the
+ /// corresponding statements.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ /// add return
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub IMPLICIT_RETURN,
+ restriction,
+ "use a return statement like `return expr` instead of an expression"
+}
+
+declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
+
+fn lint_return(cx: &LateContext<'_>, span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_applicability(cx, span, "..", &mut app);
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_RETURN,
+ span,
+ "missing `return` statement",
+ "add `return` as shown",
+ format!("return {}", snip),
+ app,
+ );
+}
+
+fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_RETURN,
+ break_span,
+ "missing `return` statement",
+ "change `break` to `return` as shown",
+ format!("return {}", snip),
+ app,
+ );
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum LintLocation {
+ /// The lint was applied to a parent expression.
+ Parent,
+ /// The lint was applied to this expression, a child, or not applied.
+ Inner,
+}
+impl LintLocation {
+ fn still_parent(self, b: bool) -> Self {
+ if b { self } else { Self::Inner }
+ }
+
+ fn is_parent(self) -> bool {
+ self == Self::Parent
+ }
+}
+
+// Gets the call site if the span is in a child context. Otherwise returns `None`.
+fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> {
+ (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span))
+}
+
+fn lint_implicit_returns(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ // The context of the function body.
+ ctxt: SyntaxContext,
+ // Whether the expression is from a macro expansion.
+ call_site_span: Option<Span>,
+) -> LintLocation {
+ match expr.kind {
+ ExprKind::Block(
+ Block {
+ expr: Some(block_expr), ..
+ },
+ _,
+ ) => lint_implicit_returns(
+ cx,
+ block_expr,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some()),
+
+ ExprKind::If(_, then_expr, Some(else_expr)) => {
+ // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't
+ // bother checking.
+ let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this if expression.
+ return res;
+ }
+ lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some())
+ },
+
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ let res = lint_implicit_returns(
+ cx,
+ arm.body,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this match expression.
+ return res;
+ }
+ }
+ LintLocation::Inner
+ },
+
+ ExprKind::Loop(block, ..) => {
+ let mut add_return = false;
+ expr_visitor_no_bodies(|e| {
+ if let ExprKind::Break(dest, sub_expr) = e.kind {
+ if dest.target_id.ok() == Some(expr.hir_id) {
+ if call_site_span.is_none() && e.span.ctxt() == ctxt {
+ // At this point sub_expr can be `None` in async functions which either diverge, or return
+ // the unit type.
+ if let Some(sub_expr) = sub_expr {
+ lint_break(cx, e.span, sub_expr.span);
+ }
+ } else {
+ // the break expression is from a macro call, add a return to the loop
+ add_return = true;
+ }
+ }
+ }
+ true
+ })
+ .visit_block(block);
+ if add_return {
- #[allow(clippy::option_if_let_else)]
++ #[expect(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.span);
+ LintLocation::Inner
+ }
+ } else {
+ LintLocation::Inner
+ }
+ },
+
+ // If expressions without an else clause, and blocks without a final expression can only be the final expression
+ // if they are divergent, or return the unit type.
+ ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => {
+ LintLocation::Inner
+ },
+
+ // Any divergent expression doesn't need a return statement.
+ ExprKind::MethodCall(..)
+ | ExprKind::Call(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Index(..)
+ if cx.typeck_results().expr_ty(expr).is_never() =>
+ {
+ LintLocation::Inner
+ },
+
+ _ =>
+ {
++ #[expect(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.span);
+ LintLocation::Inner
+ }
+ },
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_)))
+ || span.ctxt() != body.value.span.ctxt()
+ || in_external_macro(cx.sess(), span)
+ {
+ return;
+ }
+
+ let res_ty = cx.typeck_results().expr_ty(&body.value);
+ if res_ty.is_unit() || res_ty.is_never() {
+ return;
+ }
+
+ let expr = if is_async_fn(kind) {
+ match get_async_fn_body(cx.tcx, body) {
+ Some(e) => e,
+ None => return,
+ }
+ } else {
+ &body.value
+ };
+ lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
+ }
+}
--- /dev/null
- use std::convert::TryInto;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::IfLet;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local};
+use if_chain::if_chain;
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::Ident, Span};
- if meets_msrv(self.msrv.as_ref(), &msrvs::SLICE_PATTERNS);
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// The lint checks for slice bindings in patterns that are only used to
+ /// access individual slice values.
+ ///
+ /// ### Why is this bad?
+ /// Accessing slice values using indices can lead to panics. Using refutable
+ /// patterns can avoid these. Binding to individual values also improves the
+ /// readability as they can be named.
+ ///
+ /// ### Limitations
+ /// This lint currently only checks for immutable access inside `if let`
+ /// patterns.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ ///
+ /// if let Some(slice) = slice {
+ /// println!("{}", slice[0]);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ ///
+ /// if let Some(&[first, ..]) = slice {
+ /// println!("{}", first);
+ /// }
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub INDEX_REFUTABLE_SLICE,
+ nursery,
+ "avoid indexing on slices which could be destructed"
+}
+
+#[derive(Copy, Clone)]
+pub struct IndexRefutableSlice {
+ max_suggested_slice: u64,
+ msrv: Option<RustcVersion>,
+}
+
+impl IndexRefutableSlice {
+ pub fn new(max_suggested_slice_pattern_length: u64, msrv: Option<RustcVersion>) -> Self {
+ Self {
+ max_suggested_slice: max_suggested_slice_pattern_length,
+ msrv,
+ }
+ }
+}
+
+impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
+
+impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
+ if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
+ if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
++ if meets_msrv(self.msrv, msrvs::SLICE_PATTERNS);
+
+ let found_slices = find_slice_values(cx, let_pat);
+ if !found_slices.is_empty();
+ let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
+ if !filtered_slices.is_empty();
+ then {
+ for slice in filtered_slices.values() {
+ lint_slice(cx, slice);
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap<hir::HirId, SliceLintInformation> {
+ let mut removed_pat: FxHashSet<hir::HirId> = FxHashSet::default();
+ let mut slices: FxIndexMap<hir::HirId, SliceLintInformation> = FxIndexMap::default();
+ pat.walk_always(|pat| {
+ if let hir::PatKind::Binding(binding, value_hir_id, ident, sub_pat) = pat.kind {
+ // We'll just ignore mut and ref mut for simplicity sake right now
+ if let hir::BindingAnnotation::Mutable | hir::BindingAnnotation::RefMut = binding {
+ return;
+ }
+
+ // This block catches bindings with sub patterns. It would be hard to build a correct suggestion
+ // for them and it's likely that the user knows what they are doing in such a case.
+ if removed_pat.contains(&value_hir_id) {
+ return;
+ }
+ if sub_pat.is_some() {
+ removed_pat.insert(value_hir_id);
+ slices.remove(&value_hir_id);
+ return;
+ }
+
+ let bound_ty = cx.typeck_results().node_type(pat.hir_id);
+ if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() {
+ // The values need to use the `ref` keyword if they can't be copied.
+ // This will need to be adjusted if the lint want to support mutable access in the future
+ let src_is_ref = bound_ty.is_ref() && binding != hir::BindingAnnotation::Ref;
+ let needs_ref = !(src_is_ref || is_copy(cx, *inner_ty));
+
+ let slice_info = slices
+ .entry(value_hir_id)
+ .or_insert_with(|| SliceLintInformation::new(ident, needs_ref));
+ slice_info.pattern_spans.push(pat.span);
+ }
+ }
+ });
+
+ slices
+}
+
+fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
+ let used_indices = slice
+ .index_use
+ .iter()
+ .map(|(index, _)| *index)
+ .collect::<FxHashSet<_>>();
+
+ let value_name = |index| format!("{}_{}", slice.ident.name, index);
+
+ if let Some(max_index) = used_indices.iter().max() {
+ let opt_ref = if slice.needs_ref { "ref " } else { "" };
+ let pat_sugg_idents = (0..=*max_index)
+ .map(|index| {
+ if used_indices.contains(&index) {
+ format!("{}{}", opt_ref, value_name(index))
+ } else {
+ "_".to_string()
+ }
+ })
+ .collect::<Vec<_>>();
+ let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", "));
+
+ span_lint_and_then(
+ cx,
+ INDEX_REFUTABLE_SLICE,
+ slice.ident.span,
+ "this binding can be a slice pattern to avoid indexing",
+ |diag| {
+ diag.multipart_suggestion(
+ "try using a slice pattern here",
+ slice
+ .pattern_spans
+ .iter()
+ .map(|span| (*span, pat_sugg.clone()))
+ .collect(),
+ Applicability::MaybeIncorrect,
+ );
+
+ diag.multipart_suggestion(
+ "and replace the index expressions here",
+ slice
+ .index_use
+ .iter()
+ .map(|(index, span)| (*span, value_name(*index)))
+ .collect(),
+ Applicability::MaybeIncorrect,
+ );
+
+ // The lint message doesn't contain a warning about the removed index expression,
+ // since `filter_lintable_slices` will only return slices where all access indices
+ // are known at compile time. Therefore, they can be removed without side effects.
+ },
+ );
+ }
+}
+
+#[derive(Debug)]
+struct SliceLintInformation {
+ ident: Ident,
+ needs_ref: bool,
+ pattern_spans: Vec<Span>,
+ index_use: Vec<(u64, Span)>,
+}
+
+impl SliceLintInformation {
+ fn new(ident: Ident, needs_ref: bool) -> Self {
+ Self {
+ ident,
+ needs_ref,
+ pattern_spans: Vec::new(),
+ index_use: Vec::new(),
+ }
+ }
+}
+
+fn filter_lintable_slices<'a, 'tcx>(
+ cx: &'a LateContext<'tcx>,
+ slice_lint_info: FxIndexMap<hir::HirId, SliceLintInformation>,
+ max_suggested_slice: u64,
+ scope: &'tcx hir::Expr<'tcx>,
+) -> FxIndexMap<hir::HirId, SliceLintInformation> {
+ let mut visitor = SliceIndexLintingVisitor {
+ cx,
+ slice_lint_info,
+ max_suggested_slice,
+ };
+
+ intravisit::walk_expr(&mut visitor, scope);
+
+ visitor.slice_lint_info
+}
+
+struct SliceIndexLintingVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ slice_lint_info: FxIndexMap<hir::HirId, SliceLintInformation>,
+ max_suggested_slice: u64,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if let Some(local_id) = path_to_local(expr) {
+ let Self {
+ cx,
+ ref mut slice_lint_info,
+ max_suggested_slice,
+ } = *self;
+
+ if_chain! {
+ // Check if this is even a local we're interested in
+ if let Some(use_info) = slice_lint_info.get_mut(&local_id);
+
+ let map = cx.tcx.hir();
+
+ // Checking for slice indexing
+ let parent_id = map.get_parent_node(expr.hir_id);
+ if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
+ if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind;
+ if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr);
+ if let Ok(index_value) = index_value.try_into();
+ if index_value < max_suggested_slice;
+
+ // Make sure that this slice index is read only
+ let maybe_addrof_id = map.get_parent_node(parent_id);
+ if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
+ if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
+ then {
+ use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
+ return;
+ }
+ }
+
+ // The slice was used for something other than indexing
+ self.slice_lint_info.remove(&local_id);
+ }
+ intravisit::walk_expr(self, expr);
+ }
+}
--- /dev/null
- #[allow(clippy::cast_sign_loss)]
+//! lint on blocks unnecessarily using >= with a + 1 or - 1
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block
+ ///
+ /// ### Why is this bad?
+ /// Readability -- better to use `> y` instead of `>= y + 1`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x >= y + 1 {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x > y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INT_PLUS_ONE,
+ complexity,
+ "instead of using `x >= y + 1`, use `x > y`"
+}
+
+declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]);
+
+// cases:
+// BinOpKind::Ge
+// x >= y + 1
+// x - 1 >= y
+//
+// BinOpKind::Le
+// x + 1 <= y
+// x <= y - 1
+
+#[derive(Copy, Clone)]
+enum Side {
+ Lhs,
+ Rhs,
+}
+
+impl IntPlusOne {
++ #[expect(clippy::cast_sign_loss)]
+ fn check_lit(lit: &Lit, target_value: i128) -> bool {
+ if let LitKind::Int(value, ..) = lit.kind {
+ return value == (target_value as u128);
+ }
+ false
+ }
+
+ fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option<String> {
+ match (binop, &lhs.kind, &rhs.kind) {
+ // case where `x - 1 >= ...` or `-1 + x >= ...`
+ (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
+ match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
+ // `-1 + x`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ // `x - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y + 1` or `... >= 1 + y`
+ (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs))
+ if rhskind.node == BinOpKind::Add =>
+ {
+ match (&rhslhs.kind, &rhsrhs.kind) {
+ // `y + 1` and `1 + y`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `x + 1 <= ...` or `1 + x <= ...`
+ (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _)
+ if lhskind.node == BinOpKind::Add =>
+ {
+ match (&lhslhs.kind, &lhsrhs.kind) {
+ // `1 + x` and `x + 1`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y - 1` or `... >= -1 + y`
+ (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
+ match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
+ // `-1 + y`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ // `y - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+ }
+
+ fn generate_recommendation(
+ cx: &EarlyContext<'_>,
+ binop: BinOpKind,
+ node: &Expr,
+ other_side: &Expr,
+ side: Side,
+ ) -> Option<String> {
+ let binop_string = match binop {
+ BinOpKind::Ge => ">",
+ BinOpKind::Le => "<",
+ _ => return None,
+ };
+ if let Some(snippet) = snippet_opt(cx, node.span) {
+ if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) {
+ let rec = match side {
+ Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)),
+ Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)),
+ };
+ return rec;
+ }
+ }
+ None
+ }
+
+ fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
+ span_lint_and_sugg(
+ cx,
+ INT_PLUS_ONE,
+ block.span,
+ "unnecessary `>= y + 1` or `x - 1 >=`",
+ "change it to",
+ recommendation,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+}
+
+impl EarlyLintPass for IntPlusOne {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
+ if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
+ if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
+ Self::emit_warning(cx, item, rec);
+ }
+ }
+ }
+}
--- /dev/null
- LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION),
- LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::all", Some("clippy_all"), vec![
+ LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(approx_const::APPROX_CONSTANT),
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(attrs::DEPRECATED_SEMVER),
+ LintId::of(attrs::MISMATCHED_TARGET_OS),
+ LintId::of(attrs::USELESS_ATTRIBUTE),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(bit_mask::BAD_BIT_MASK),
+ LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(booleans::LOGIC_BUG),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
+ LintId::of(casts::CAST_ABS_TO_UNSIGNED),
+ LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
+ LintId::of(casts::CAST_ENUM_TRUNCATION),
+ LintId::of(casts::CAST_REF_TO_MUT),
+ LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(copies::IFS_SAME_COND),
+ LintId::of(copies::IF_SAME_THEN_ELSE),
+ LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(dereference::NEEDLESS_BORROW),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ LintId::of(derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
++ LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
+ LintId::of(disallowed_methods::DISALLOWED_METHODS),
+ LintId::of(disallowed_types::DISALLOWED_TYPES),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(drop_forget_ref::DROP_COPY),
+ LintId::of(drop_forget_ref::DROP_NON_DROP),
+ LintId::of(drop_forget_ref::DROP_REF),
+ LintId::of(drop_forget_ref::FORGET_COPY),
+ LintId::of(drop_forget_ref::FORGET_NON_DROP),
+ LintId::of(drop_forget_ref::FORGET_REF),
+ LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
++ LintId::of(duplicate_mod::DUPLICATE_MOD),
+ LintId::of(duration_subsec::DURATION_SUBSEC),
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eq_op::EQ_OP),
+ LintId::of(eq_op::OP_REF),
+ LintId::of(erasing_op::ERASING_OP),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
+ LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
+ LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
+ LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
+ LintId::of(format_push_string::FORMAT_PUSH_STRING),
+ LintId::of(formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(if_let_mutex::IF_LET_MUTEX),
+ LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(infinite_iter::INFINITE_ITER),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
+ LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::ITER_NEXT_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::MISSING_SPIN_LOOP),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::NEVER_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_bits::MANUAL_BITS),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::NEEDLESS_MATCH),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::CLONE_DOUBLE_REF),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::ERR_EXPECT),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::EXTEND_WITH_DRAIN),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::IS_DIGIT_ASCII_RADIX),
+ LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_OVEREAGER_CLONED),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
+ LintId::of(methods::MANUAL_STR_REPEAT),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
+ LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(methods::NEEDLESS_OPTION_TAKE),
+ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::OR_THEN_UNWRAP),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::SUSPICIOUS_SPLITN),
+ LintId::of(methods::UNINIT_ASSUMED_INIT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FIND_MAP),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNNECESSARY_TO_OWNED),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(methods::ZST_OFFSET),
+ LintId::of(minmax::MIN_MAX),
+ LintId::of(misc::CMP_NAN),
+ LintId::of(misc::CMP_OWNED),
+ LintId::of(misc::MODULO_ONE),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
++ LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
+ LintId::of(octal_escapes::OCTAL_ESCAPES),
+ LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::INVALID_NULL_PTR_USAGE),
+ LintId::of(ptr::MUT_FROM_REF),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(ranges::REVERSED_EMPTY_RANGES),
++ LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(regex::INVALID_REGEX),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_assignment::SELF_ASSIGNMENT),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(serde_api::SERDE_API_MISUSE),
++ LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strings::TRIM_SPLIT_WHITESPACE),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(swap::ALMOST_SWAPPED),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(transmute::WRONG_TRANSMUTE),
+ LintId::of(transmuting_null::TRANSMUTING_NULL),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::BOX_COLLECTION),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unicode::INVISIBLE_CHARACTERS),
+ LintId::of(uninit_vec::UNINIT_VEC),
+ LintId::of(unit_hash::UNIT_HASH),
+ LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unit_types::UNIT_CMP),
+ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
+ LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(unwrap::PANICKING_UNWRAP),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+ LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+])
--- /dev/null
- LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ LintId::of(double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(duration_subsec::DURATION_SUBSEC),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::NEEDLESS_MATCH),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
+ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
+ LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(methods::NEEDLESS_OPTION_TAKE),
+ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OR_THEN_UNWRAP),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FIND_MAP),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
++ LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+])
--- /dev/null
- eval_order_dependence::DIVERGING_SUB_EXPRESSION,
- eval_order_dependence::EVAL_ORDER_DEPENDENCE,
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_lints(&[
+ #[cfg(feature = "internal")]
+ utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::COMPILER_LINT_FUNCTIONS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::DEFAULT_LINT,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::IF_CHAIN_STYLE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INTERNING_DEFINED_SYMBOL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INVALID_PATHS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::LINT_WITHOUT_LINT_PASS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::OUTER_EXPN_EXPN_DATA,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::PRODUCE_ICE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::UNNECESSARY_SYMBOL_STR,
+ absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
+ approx_const::APPROX_CONSTANT,
+ arithmetic::FLOAT_ARITHMETIC,
+ arithmetic::INTEGER_ARITHMETIC,
+ as_conversions::AS_CONVERSIONS,
+ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
+ asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
+ assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
+ assign_ops::ASSIGN_OP_PATTERN,
+ assign_ops::MISREFACTORED_ASSIGN_OP,
+ async_yields_async::ASYNC_YIELDS_ASYNC,
+ attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
+ attrs::DEPRECATED_CFG_ATTR,
+ attrs::DEPRECATED_SEMVER,
+ attrs::EMPTY_LINE_AFTER_OUTER_ATTR,
+ attrs::INLINE_ALWAYS,
+ attrs::MISMATCHED_TARGET_OS,
+ attrs::USELESS_ATTRIBUTE,
+ await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
+ await_holding_invalid::AWAIT_HOLDING_LOCK,
+ await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
+ bit_mask::BAD_BIT_MASK,
+ bit_mask::INEFFECTIVE_BIT_MASK,
+ bit_mask::VERBOSE_BIT_MASK,
+ blacklisted_name::BLACKLISTED_NAME,
+ blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
+ bool_assert_comparison::BOOL_ASSERT_COMPARISON,
+ booleans::LOGIC_BUG,
+ booleans::NONMINIMAL_BOOL,
+ borrow_as_ptr::BORROW_AS_PTR,
+ bytecount::NAIVE_BYTECOUNT,
+ bytes_count_to_len::BYTES_COUNT_TO_LEN,
+ cargo::CARGO_COMMON_METADATA,
+ cargo::MULTIPLE_CRATE_VERSIONS,
+ cargo::NEGATIVE_FEATURE_NAMES,
+ cargo::REDUNDANT_FEATURE_NAMES,
+ cargo::WILDCARD_DEPENDENCIES,
+ case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ casts::CAST_ABS_TO_UNSIGNED,
+ casts::CAST_ENUM_CONSTRUCTOR,
+ casts::CAST_ENUM_TRUNCATION,
+ casts::CAST_LOSSLESS,
+ casts::CAST_POSSIBLE_TRUNCATION,
+ casts::CAST_POSSIBLE_WRAP,
+ casts::CAST_PRECISION_LOSS,
+ casts::CAST_PTR_ALIGNMENT,
+ casts::CAST_REF_TO_MUT,
+ casts::CAST_SIGN_LOSS,
+ casts::CAST_SLICE_DIFFERENT_SIZES,
+ casts::CHAR_LIT_AS_U8,
+ casts::FN_TO_NUMERIC_CAST,
+ casts::FN_TO_NUMERIC_CAST_ANY,
+ casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ casts::PTR_AS_PTR,
+ casts::UNNECESSARY_CAST,
+ checked_conversions::CHECKED_CONVERSIONS,
+ cognitive_complexity::COGNITIVE_COMPLEXITY,
+ collapsible_if::COLLAPSIBLE_ELSE_IF,
+ collapsible_if::COLLAPSIBLE_IF,
+ collapsible_match::COLLAPSIBLE_MATCH,
+ comparison_chain::COMPARISON_CHAIN,
+ copies::BRANCHES_SHARING_CODE,
+ copies::IFS_SAME_COND,
+ copies::IF_SAME_THEN_ELSE,
+ copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+ copy_iterator::COPY_ITERATOR,
+ crate_in_macro_def::CRATE_IN_MACRO_DEF,
+ create_dir::CREATE_DIR,
+ dbg_macro::DBG_MACRO,
+ default::DEFAULT_TRAIT_ACCESS,
+ default::FIELD_REASSIGN_WITH_DEFAULT,
+ default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
+ default_union_representation::DEFAULT_UNION_REPRESENTATION,
+ dereference::EXPLICIT_DEREF_METHODS,
+ dereference::NEEDLESS_BORROW,
+ dereference::REF_BINDING_TO_REFERENCE,
+ derivable_impls::DERIVABLE_IMPLS,
+ derive::DERIVE_HASH_XOR_EQ,
+ derive::DERIVE_ORD_XOR_PARTIAL_ORD,
++ derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
+ derive::EXPL_IMPL_CLONE_ON_COPY,
+ derive::UNSAFE_DERIVE_DESERIALIZE,
+ disallowed_methods::DISALLOWED_METHODS,
+ disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,
+ disallowed_types::DISALLOWED_TYPES,
+ doc::DOC_MARKDOWN,
+ doc::MISSING_ERRORS_DOC,
+ doc::MISSING_PANICS_DOC,
+ doc::MISSING_SAFETY_DOC,
+ doc::NEEDLESS_DOCTEST_MAIN,
+ double_comparison::DOUBLE_COMPARISONS,
+ double_parens::DOUBLE_PARENS,
+ drop_forget_ref::DROP_COPY,
+ drop_forget_ref::DROP_NON_DROP,
+ drop_forget_ref::DROP_REF,
+ drop_forget_ref::FORGET_COPY,
+ drop_forget_ref::FORGET_NON_DROP,
+ drop_forget_ref::FORGET_REF,
+ drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
++ duplicate_mod::DUPLICATE_MOD,
+ duration_subsec::DURATION_SUBSEC,
+ else_if_without_else::ELSE_IF_WITHOUT_ELSE,
+ empty_drop::EMPTY_DROP,
+ empty_enum::EMPTY_ENUM,
+ empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
+ entry::MAP_ENTRY,
+ enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
+ enum_variants::ENUM_VARIANT_NAMES,
+ enum_variants::MODULE_INCEPTION,
+ enum_variants::MODULE_NAME_REPETITIONS,
+ eq_op::EQ_OP,
+ eq_op::OP_REF,
+ equatable_if_let::EQUATABLE_IF_LET,
+ erasing_op::ERASING_OP,
+ escape::BOXED_LOCAL,
+ eta_reduction::REDUNDANT_CLOSURE,
+ eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
+ excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+ exhaustive_items::EXHAUSTIVE_ENUMS,
+ exhaustive_items::EXHAUSTIVE_STRUCTS,
+ exit::EXIT,
+ explicit_write::EXPLICIT_WRITE,
+ fallible_impl_from::FALLIBLE_IMPL_FROM,
+ float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
+ float_literal::EXCESSIVE_PRECISION,
+ float_literal::LOSSY_FLOAT_LITERAL,
+ floating_point_arithmetic::IMPRECISE_FLOPS,
+ floating_point_arithmetic::SUBOPTIMAL_FLOPS,
+ format::USELESS_FORMAT,
+ format_args::FORMAT_IN_FORMAT_ARGS,
+ format_args::TO_STRING_IN_FORMAT_ARGS,
+ format_impl::PRINT_IN_FORMAT_IMPL,
+ format_impl::RECURSIVE_FORMAT_IMPL,
+ format_push_string::FORMAT_PUSH_STRING,
+ formatting::POSSIBLE_MISSING_COMMA,
+ formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ 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,
+ functions::NOT_UNSAFE_PTR_ARG_DEREF,
+ functions::RESULT_UNIT_ERR,
+ functions::TOO_MANY_ARGUMENTS,
+ functions::TOO_MANY_LINES,
+ future_not_send::FUTURE_NOT_SEND,
+ get_last_with_len::GET_LAST_WITH_LEN,
+ identity_op::IDENTITY_OP,
+ if_let_mutex::IF_LET_MUTEX,
+ if_not_else::IF_NOT_ELSE,
+ if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
+ implicit_hasher::IMPLICIT_HASHER,
+ implicit_return::IMPLICIT_RETURN,
+ implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
+ inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
+ index_refutable_slice::INDEX_REFUTABLE_SLICE,
+ indexing_slicing::INDEXING_SLICING,
+ indexing_slicing::OUT_OF_BOUNDS_INDEXING,
+ infinite_iter::INFINITE_ITER,
+ infinite_iter::MAYBE_INFINITE_ITER,
+ inherent_impl::MULTIPLE_INHERENT_IMPL,
+ inherent_to_string::INHERENT_TO_STRING,
+ inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
+ init_numbered_fields::INIT_NUMBERED_FIELDS,
+ inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
+ int_plus_one::INT_PLUS_ONE,
+ integer_division::INTEGER_DIVISION,
+ invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
+ items_after_statements::ITEMS_AFTER_STATEMENTS,
+ iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
+ large_const_arrays::LARGE_CONST_ARRAYS,
+ large_enum_variant::LARGE_ENUM_VARIANT,
+ large_include_file::LARGE_INCLUDE_FILE,
+ large_stack_arrays::LARGE_STACK_ARRAYS,
+ len_zero::COMPARISON_TO_EMPTY,
+ len_zero::LEN_WITHOUT_IS_EMPTY,
+ len_zero::LEN_ZERO,
+ let_if_seq::USELESS_LET_IF_SEQ,
+ let_underscore::LET_UNDERSCORE_DROP,
+ let_underscore::LET_UNDERSCORE_LOCK,
+ let_underscore::LET_UNDERSCORE_MUST_USE,
+ lifetimes::EXTRA_UNUSED_LIFETIMES,
+ lifetimes::NEEDLESS_LIFETIMES,
+ literal_representation::DECIMAL_LITERAL_REPRESENTATION,
+ literal_representation::INCONSISTENT_DIGIT_GROUPING,
+ literal_representation::LARGE_DIGIT_GROUPS,
+ literal_representation::MISTYPED_LITERAL_SUFFIXES,
+ literal_representation::UNREADABLE_LITERAL,
+ literal_representation::UNUSUAL_BYTE_GROUPINGS,
+ loops::EMPTY_LOOP,
+ loops::EXPLICIT_COUNTER_LOOP,
+ loops::EXPLICIT_INTO_ITER_LOOP,
+ loops::EXPLICIT_ITER_LOOP,
+ loops::FOR_KV_MAP,
+ loops::FOR_LOOPS_OVER_FALLIBLES,
+ loops::ITER_NEXT_LOOP,
+ loops::MANUAL_FLATTEN,
+ loops::MANUAL_MEMCPY,
+ loops::MISSING_SPIN_LOOP,
+ loops::MUT_RANGE_BOUND,
+ loops::NEEDLESS_COLLECT,
+ loops::NEEDLESS_RANGE_LOOP,
+ loops::NEVER_LOOP,
+ loops::SAME_ITEM_PUSH,
+ loops::SINGLE_ELEMENT_LOOP,
+ loops::WHILE_IMMUTABLE_CONDITION,
+ loops::WHILE_LET_LOOP,
+ loops::WHILE_LET_ON_ITERATOR,
+ macro_use::MACRO_USE_IMPORTS,
+ main_recursion::MAIN_RECURSION,
+ manual_assert::MANUAL_ASSERT,
+ manual_async_fn::MANUAL_ASYNC_FN,
+ manual_bits::MANUAL_BITS,
+ manual_map::MANUAL_MAP,
+ manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+ manual_ok_or::MANUAL_OK_OR,
+ manual_strip::MANUAL_STRIP,
+ manual_unwrap_or::MANUAL_UNWRAP_OR,
+ map_clone::MAP_CLONE,
+ map_err_ignore::MAP_ERR_IGNORE,
+ map_unit_fn::OPTION_MAP_UNIT_FN,
+ map_unit_fn::RESULT_MAP_UNIT_FN,
+ match_on_vec_items::MATCH_ON_VEC_ITEMS,
+ match_result_ok::MATCH_RESULT_OK,
+ match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
+ matches::INFALLIBLE_DESTRUCTURING_MATCH,
+ matches::MATCH_AS_REF,
+ matches::MATCH_BOOL,
+ matches::MATCH_LIKE_MATCHES_MACRO,
+ matches::MATCH_OVERLAPPING_ARM,
+ matches::MATCH_REF_PATS,
+ matches::MATCH_SAME_ARMS,
+ matches::MATCH_SINGLE_BINDING,
+ matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ matches::MATCH_WILD_ERR_ARM,
+ matches::NEEDLESS_MATCH,
+ matches::REDUNDANT_PATTERN_MATCHING,
+ matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ matches::SINGLE_MATCH,
+ matches::SINGLE_MATCH_ELSE,
+ matches::WILDCARD_ENUM_MATCH_ARM,
+ matches::WILDCARD_IN_OR_PATTERNS,
+ mem_forget::MEM_FORGET,
+ mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
+ mem_replace::MEM_REPLACE_WITH_DEFAULT,
+ mem_replace::MEM_REPLACE_WITH_UNINIT,
+ methods::BIND_INSTEAD_OF_MAP,
+ methods::BYTES_NTH,
+ methods::CHARS_LAST_CMP,
+ methods::CHARS_NEXT_CMP,
+ methods::CLONED_INSTEAD_OF_COPIED,
+ methods::CLONE_DOUBLE_REF,
+ methods::CLONE_ON_COPY,
+ methods::CLONE_ON_REF_PTR,
+ methods::ERR_EXPECT,
+ methods::EXPECT_FUN_CALL,
+ methods::EXPECT_USED,
+ methods::EXTEND_WITH_DRAIN,
+ methods::FILETYPE_IS_FILE,
+ methods::FILTER_MAP_IDENTITY,
+ methods::FILTER_MAP_NEXT,
+ methods::FILTER_NEXT,
+ methods::FLAT_MAP_IDENTITY,
+ methods::FLAT_MAP_OPTION,
+ methods::FROM_ITER_INSTEAD_OF_COLLECT,
+ methods::GET_UNWRAP,
+ methods::IMPLICIT_CLONE,
+ methods::INEFFICIENT_TO_STRING,
+ methods::INSPECT_FOR_EACH,
+ methods::INTO_ITER_ON_REF,
+ methods::IS_DIGIT_ASCII_RADIX,
+ methods::ITERATOR_STEP_BY_ZERO,
+ methods::ITER_CLONED_COLLECT,
+ methods::ITER_COUNT,
+ methods::ITER_NEXT_SLICE,
+ methods::ITER_NTH,
+ methods::ITER_NTH_ZERO,
+ methods::ITER_OVEREAGER_CLONED,
+ methods::ITER_SKIP_NEXT,
+ methods::ITER_WITH_DRAIN,
+ methods::MANUAL_FILTER_MAP,
+ methods::MANUAL_FIND_MAP,
+ methods::MANUAL_SATURATING_ARITHMETIC,
+ methods::MANUAL_SPLIT_ONCE,
+ methods::MANUAL_STR_REPEAT,
+ methods::MAP_COLLECT_RESULT_UNIT,
+ methods::MAP_FLATTEN,
+ methods::MAP_IDENTITY,
+ methods::MAP_UNWRAP_OR,
+ methods::NEEDLESS_OPTION_AS_DEREF,
+ methods::NEEDLESS_OPTION_TAKE,
+ methods::NEEDLESS_SPLITN,
+ methods::NEW_RET_NO_SELF,
+ methods::OK_EXPECT,
+ methods::OPTION_AS_REF_DEREF,
+ methods::OPTION_FILTER_MAP,
+ methods::OPTION_MAP_OR_NONE,
+ methods::OR_FUN_CALL,
+ methods::OR_THEN_UNWRAP,
+ methods::RESULT_MAP_OR_INTO_OPTION,
+ methods::SEARCH_IS_SOME,
+ methods::SHOULD_IMPLEMENT_TRAIT,
+ methods::SINGLE_CHAR_ADD_STR,
+ methods::SINGLE_CHAR_PATTERN,
+ methods::SKIP_WHILE_NEXT,
+ methods::STRING_EXTEND_CHARS,
+ methods::SUSPICIOUS_MAP,
+ methods::SUSPICIOUS_SPLITN,
+ methods::UNINIT_ASSUMED_INIT,
+ methods::UNNECESSARY_FILTER_MAP,
+ methods::UNNECESSARY_FIND_MAP,
+ methods::UNNECESSARY_FOLD,
+ methods::UNNECESSARY_JOIN,
+ methods::UNNECESSARY_LAZY_EVALUATIONS,
+ methods::UNNECESSARY_TO_OWNED,
+ methods::UNWRAP_OR_ELSE_DEFAULT,
+ methods::UNWRAP_USED,
+ methods::USELESS_ASREF,
+ methods::WRONG_SELF_CONVENTION,
+ methods::ZST_OFFSET,
+ minmax::MIN_MAX,
+ misc::CMP_NAN,
+ misc::CMP_OWNED,
+ misc::FLOAT_CMP,
+ misc::FLOAT_CMP_CONST,
+ misc::MODULO_ONE,
+ misc::SHORT_CIRCUIT_STATEMENT,
+ misc::TOPLEVEL_REF_ARG,
+ misc::USED_UNDERSCORE_BINDING,
+ misc::ZERO_PTR,
+ misc_early::BUILTIN_TYPE_SHADOW,
+ misc_early::DOUBLE_NEG,
+ misc_early::DUPLICATE_UNDERSCORE_ARGUMENT,
+ misc_early::MIXED_CASE_HEX_LITERALS,
+ misc_early::REDUNDANT_PATTERN,
+ misc_early::SEPARATED_LITERAL_SUFFIX,
+ misc_early::UNNEEDED_FIELD_PATTERN,
+ misc_early::UNNEEDED_WILDCARD_PATTERN,
+ misc_early::UNSEPARATED_LITERAL_SUFFIX,
+ misc_early::ZERO_PREFIXED_LITERAL,
+ missing_const_for_fn::MISSING_CONST_FOR_FN,
+ missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
+ missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
+ missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
++ mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION,
++ mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
+ module_style::MOD_MODULE_FILES,
+ module_style::SELF_NAMED_MODULE_FILES,
+ modulo_arithmetic::MODULO_ARITHMETIC,
+ mut_key::MUTABLE_KEY_TYPE,
+ mut_mut::MUT_MUT,
+ mut_mutex_lock::MUT_MUTEX_LOCK,
+ mut_reference::UNNECESSARY_MUT_PASSED,
+ mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
+ mutex_atomic::MUTEX_ATOMIC,
+ mutex_atomic::MUTEX_INTEGER,
+ needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
+ needless_bitwise_bool::NEEDLESS_BITWISE_BOOL,
+ needless_bool::BOOL_COMPARISON,
+ needless_bool::NEEDLESS_BOOL,
+ needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
+ needless_continue::NEEDLESS_CONTINUE,
+ needless_for_each::NEEDLESS_FOR_EACH,
+ needless_late_init::NEEDLESS_LATE_INIT,
+ needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
+ needless_question_mark::NEEDLESS_QUESTION_MARK,
+ needless_update::NEEDLESS_UPDATE,
+ neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
+ neg_multiply::NEG_MULTIPLY,
+ new_without_default::NEW_WITHOUT_DEFAULT,
+ no_effect::NO_EFFECT,
+ no_effect::NO_EFFECT_UNDERSCORE_BINDING,
+ no_effect::UNNECESSARY_OPERATION,
+ non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
+ non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
+ non_expressive_names::JUST_UNDERSCORES_AND_DIGITS,
+ non_expressive_names::MANY_SINGLE_CHAR_NAMES,
+ non_expressive_names::SIMILAR_NAMES,
+ non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
+ non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
+ nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
+ octal_escapes::OCTAL_ESCAPES,
+ only_used_in_recursion::ONLY_USED_IN_RECURSION,
+ open_options::NONSENSICAL_OPEN_OPTIONS,
+ option_env_unwrap::OPTION_ENV_UNWRAP,
+ option_if_let_else::OPTION_IF_LET_ELSE,
+ overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
+ panic_in_result_fn::PANIC_IN_RESULT_FN,
+ panic_unimplemented::PANIC,
+ panic_unimplemented::TODO,
+ panic_unimplemented::UNIMPLEMENTED,
+ panic_unimplemented::UNREACHABLE,
+ partialeq_ne_impl::PARTIALEQ_NE_IMPL,
+ pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,
+ pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF,
+ path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
+ pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
+ precedence::PRECEDENCE,
+ ptr::CMP_NULL,
+ ptr::INVALID_NULL_PTR_USAGE,
+ ptr::MUT_FROM_REF,
+ ptr::PTR_ARG,
+ ptr_eq::PTR_EQ,
+ ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
+ pub_use::PUB_USE,
+ question_mark::QUESTION_MARK,
+ ranges::MANUAL_RANGE_CONTAINS,
+ ranges::RANGE_MINUS_ONE,
+ ranges::RANGE_PLUS_ONE,
+ ranges::RANGE_ZIP_WITH_LEN,
+ ranges::REVERSED_EMPTY_RANGES,
++ rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT,
+ redundant_clone::REDUNDANT_CLONE,
+ redundant_closure_call::REDUNDANT_CLOSURE_CALL,
+ redundant_else::REDUNDANT_ELSE,
+ redundant_field_names::REDUNDANT_FIELD_NAMES,
+ redundant_pub_crate::REDUNDANT_PUB_CRATE,
+ redundant_slicing::DEREF_BY_SLICING,
+ redundant_slicing::REDUNDANT_SLICING,
+ redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
+ ref_option_ref::REF_OPTION_REF,
+ reference::DEREF_ADDROF,
+ regex::INVALID_REGEX,
+ regex::TRIVIAL_REGEX,
+ repeat_once::REPEAT_ONCE,
+ return_self_not_must_use::RETURN_SELF_NOT_MUST_USE,
+ returns::LET_AND_RETURN,
+ returns::NEEDLESS_RETURN,
+ same_name_method::SAME_NAME_METHOD,
+ self_assignment::SELF_ASSIGNMENT,
+ self_named_constructors::SELF_NAMED_CONSTRUCTORS,
+ semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
+ serde_api::SERDE_API_MISUSE,
+ shadow::SHADOW_REUSE,
+ shadow::SHADOW_SAME,
+ shadow::SHADOW_UNRELATED,
++ significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE,
+ single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES,
+ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
+ size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
+ slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
+ stable_sort_primitive::STABLE_SORT_PRIMITIVE,
+ significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE,
+ strings::STRING_ADD,
+ strings::STRING_ADD_ASSIGN,
+ strings::STRING_FROM_UTF8_AS_BYTES,
+ strings::STRING_LIT_AS_BYTES,
+ strings::STRING_SLICE,
+ strings::STRING_TO_STRING,
+ strings::STR_TO_STRING,
+ strings::TRIM_SPLIT_WHITESPACE,
+ strlen_on_c_strings::STRLEN_ON_C_STRINGS,
+ suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
+ suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
+ swap::ALMOST_SWAPPED,
+ swap::MANUAL_SWAP,
+ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
+ temporary_assignment::TEMPORARY_ASSIGNMENT,
+ to_digit_is_some::TO_DIGIT_IS_SOME,
+ trailing_empty_array::TRAILING_EMPTY_ARRAY,
+ trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
+ trait_bounds::TYPE_REPETITION_IN_BOUNDS,
+ transmute::CROSSPOINTER_TRANSMUTE,
+ transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ transmute::TRANSMUTE_BYTES_TO_STR,
+ transmute::TRANSMUTE_FLOAT_TO_INT,
+ transmute::TRANSMUTE_INT_TO_BOOL,
+ transmute::TRANSMUTE_INT_TO_CHAR,
+ transmute::TRANSMUTE_INT_TO_FLOAT,
+ transmute::TRANSMUTE_NUM_TO_BYTES,
+ transmute::TRANSMUTE_PTR_TO_PTR,
+ transmute::TRANSMUTE_PTR_TO_REF,
+ transmute::TRANSMUTE_UNDEFINED_REPR,
+ transmute::UNSOUND_COLLECTION_TRANSMUTE,
+ transmute::USELESS_TRANSMUTE,
+ transmute::WRONG_TRANSMUTE,
+ transmuting_null::TRANSMUTING_NULL,
+ try_err::TRY_ERR,
+ types::BORROWED_BOX,
+ types::BOX_COLLECTION,
+ types::LINKEDLIST,
+ types::OPTION_OPTION,
+ types::RC_BUFFER,
+ types::RC_MUTEX,
+ types::REDUNDANT_ALLOCATION,
+ types::TYPE_COMPLEXITY,
+ types::VEC_BOX,
+ undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
+ unicode::INVISIBLE_CHARACTERS,
+ unicode::NON_ASCII_LITERAL,
+ unicode::UNICODE_NOT_NFC,
+ uninit_vec::UNINIT_VEC,
+ unit_hash::UNIT_HASH,
+ unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
+ unit_types::LET_UNIT_VALUE,
+ unit_types::UNIT_ARG,
+ unit_types::UNIT_CMP,
+ unnamed_address::FN_ADDRESS_COMPARISONS,
+ unnamed_address::VTABLE_ADDRESS_COMPARISONS,
+ unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS,
+ unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
+ unnecessary_sort_by::UNNECESSARY_SORT_BY,
+ unnecessary_wraps::UNNECESSARY_WRAPS,
+ unnested_or_patterns::UNNESTED_OR_PATTERNS,
+ unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
+ unused_async::UNUSED_ASYNC,
+ unused_io_amount::UNUSED_IO_AMOUNT,
+ unused_self::UNUSED_SELF,
+ unused_unit::UNUSED_UNIT,
+ unwrap::PANICKING_UNWRAP,
+ unwrap::UNNECESSARY_UNWRAP,
+ unwrap_in_result::UNWRAP_IN_RESULT,
+ upper_case_acronyms::UPPER_CASE_ACRONYMS,
+ use_self::USE_SELF,
+ useless_conversion::USELESS_CONVERSION,
+ vec::USELESS_VEC,
+ vec_init_then_push::VEC_INIT_THEN_PUSH,
+ vec_resize_to_zero::VEC_RESIZE_TO_ZERO,
+ verbose_file_reads::VERBOSE_FILE_READS,
+ wildcard_imports::ENUM_GLOB_USE,
+ wildcard_imports::WILDCARD_IMPORTS,
+ write::PRINTLN_EMPTY_STRING,
+ write::PRINT_LITERAL,
+ write::PRINT_STDERR,
+ write::PRINT_STDOUT,
+ write::PRINT_WITH_NEWLINE,
+ write::USE_DEBUG,
+ write::WRITELN_EMPTY_STRING,
+ write::WRITE_LITERAL,
+ write::WRITE_WITH_NEWLINE,
+ zero_div_zero::ZERO_DIVIDED_BY_ZERO,
+ zero_sized_map_values::ZERO_SIZED_MAP_VALUES,
+])
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ LintId::of(arithmetic::FLOAT_ARITHMETIC),
+ LintId::of(arithmetic::INTEGER_ARITHMETIC),
+ LintId::of(as_conversions::AS_CONVERSIONS),
+ LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
+ LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
+ LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
+ LintId::of(create_dir::CREATE_DIR),
+ LintId::of(dbg_macro::DBG_MACRO),
+ LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
+ LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
+ LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
+ LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ LintId::of(empty_drop::EMPTY_DROP),
+ LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS),
+ LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
+ LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
+ LintId::of(exit::EXIT),
+ LintId::of(float_literal::LOSSY_FLOAT_LITERAL),
+ LintId::of(if_then_some_else_none::IF_THEN_SOME_ELSE_NONE),
+ LintId::of(implicit_return::IMPLICIT_RETURN),
+ LintId::of(indexing_slicing::INDEXING_SLICING),
+ LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
+ LintId::of(integer_division::INTEGER_DIVISION),
+ LintId::of(large_include_file::LARGE_INCLUDE_FILE),
+ LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
+ LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
+ LintId::of(map_err_ignore::MAP_ERR_IGNORE),
+ LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
+ LintId::of(matches::WILDCARD_ENUM_MATCH_ARM),
+ LintId::of(mem_forget::MEM_FORGET),
+ LintId::of(methods::CLONE_ON_REF_PTR),
+ LintId::of(methods::EXPECT_USED),
+ LintId::of(methods::FILETYPE_IS_FILE),
+ LintId::of(methods::GET_UNWRAP),
+ LintId::of(methods::UNWRAP_USED),
+ LintId::of(misc::FLOAT_CMP_CONST),
+ LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX),
+ LintId::of(misc_early::UNNEEDED_FIELD_PATTERN),
+ LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX),
+ LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
+ LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES),
+ LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
++ LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION),
+ LintId::of(module_style::MOD_MODULE_FILES),
+ LintId::of(module_style::SELF_NAMED_MODULE_FILES),
+ LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
+ LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
+ LintId::of(panic_unimplemented::PANIC),
+ LintId::of(panic_unimplemented::TODO),
+ LintId::of(panic_unimplemented::UNIMPLEMENTED),
+ LintId::of(panic_unimplemented::UNREACHABLE),
+ LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH),
+ LintId::of(pub_use::PUB_USE),
+ LintId::of(redundant_slicing::DEREF_BY_SLICING),
+ LintId::of(same_name_method::SAME_NAME_METHOD),
+ LintId::of(shadow::SHADOW_REUSE),
+ LintId::of(shadow::SHADOW_SAME),
+ LintId::of(shadow::SHADOW_UNRELATED),
+ LintId::of(single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES),
+ LintId::of(strings::STRING_ADD),
+ LintId::of(strings::STRING_SLICE),
+ LintId::of(strings::STRING_TO_STRING),
+ LintId::of(strings::STR_TO_STRING),
+ LintId::of(try_err::TRY_ERR),
+ LintId::of(types::RC_BUFFER),
+ LintId::of(types::RC_MUTEX),
+ LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
+ LintId::of(unicode::NON_ASCII_LITERAL),
+ LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
+ LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
+ LintId::of(verbose_file_reads::VERBOSE_FILE_READS),
+ LintId::of(write::PRINT_STDERR),
+ LintId::of(write::PRINT_STDOUT),
+ LintId::of(write::USE_DEBUG),
+])
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::style", Some("clippy_style"), vec![
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(dereference::NEEDLESS_BORROW),
++ LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
+ LintId::of(disallowed_methods::DISALLOWED_METHODS),
+ LintId::of(disallowed_types::DISALLOWED_TYPES),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eq_op::OP_REF),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ 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(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_bits::MANUAL_BITS),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::ERR_EXPECT),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::IS_DIGIT_ASCII_RADIX),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(strings::TRIM_SPLIT_WHITESPACE),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+])
--- /dev/null
- LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(casts::CAST_ABS_TO_UNSIGNED),
+ LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
+ LintId::of(casts::CAST_ENUM_TRUNCATION),
+ LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
+ LintId::of(drop_forget_ref::DROP_NON_DROP),
+ LintId::of(drop_forget_ref::FORGET_NON_DROP),
++ LintId::of(duplicate_mod::DUPLICATE_MOD),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(octal_escapes::OCTAL_ESCAPES),
++ LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
++ LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+])
--- /dev/null
- mod eval_order_dependence;
+// error-pattern:cargo-clippy
+
+#![feature(array_windows)]
+#![feature(binary_heap_into_iter_sorted)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(drain_filter)]
+#![feature(iter_intersperse)]
+#![feature(let_chains)]
+#![feature(let_else)]
++#![feature(lint_reasons)]
+#![feature(once_cell)]
+#![feature(rustc_private)]
+#![feature(stmt_expr_attributes)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)]
+#![warn(trivial_casts, trivial_numeric_casts)]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+// Disable this rustc lint for now, as it was also done in rustc
+#![allow(rustc::potential_query_instability)]
+
+// 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_arena;
+extern crate rustc_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_attr;
+extern crate rustc_data_structures;
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_pretty;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir_dataflow;
+extern crate rustc_parse;
+extern crate rustc_parse_format;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+extern crate rustc_typeck;
+
+#[macro_use]
+extern crate clippy_utils;
+
+use clippy_utils::parse_msrv;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::LintId;
+use rustc_session::Session;
+
+/// Macro used to declare a Clippy lint.
+///
+/// Every lint declaration consists of 4 parts:
+///
+/// 1. The documentation, which is used for the website
+/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
+/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
+/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
+/// 4. The `description` that contains a short explanation on what's wrong with code where the
+/// lint is triggered.
+///
+/// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are
+/// enabled by default. As said in the README.md of this repository, if the lint level mapping
+/// changes, please update README.md.
+///
+/// # Example
+///
+/// ```
+/// #![feature(rustc_private)]
+/// extern crate rustc_session;
+/// use rustc_session::declare_tool_lint;
+/// use clippy_lints::declare_clippy_lint;
+///
+/// declare_clippy_lint! {
+/// /// ### What it does
+/// /// Checks for ... (describe what the lint matches).
+/// ///
+/// /// ### Why is this bad?
+/// /// Supply the reason for linting the code.
+/// ///
+/// /// ### Example
+/// /// ```rust
+/// /// // Bad
+/// /// Insert a short example of code that triggers the lint
+/// ///
+/// /// // Good
+/// /// Insert a short example of improved code that doesn't trigger the lint
+/// /// ```
+/// pub LINT_NAME,
+/// pedantic,
+/// "description"
+/// }
+/// ```
+/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
+#[macro_export]
+macro_rules! declare_clippy_lint {
+ { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, suspicious, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+}
+
+#[cfg(feature = "internal")]
+mod deprecated_lints;
+#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
+mod utils;
+
+mod renamed_lints;
+
+// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod absurd_extreme_comparisons;
+mod approx_const;
+mod arithmetic;
+mod as_conversions;
+mod asm_syntax;
+mod assertions_on_constants;
+mod assign_ops;
+mod async_yields_async;
+mod attrs;
+mod await_holding_invalid;
+mod bit_mask;
+mod blacklisted_name;
+mod blocks_in_if_conditions;
+mod bool_assert_comparison;
+mod booleans;
+mod borrow_as_ptr;
+mod bytecount;
+mod bytes_count_to_len;
+mod cargo;
+mod case_sensitive_file_extension_comparisons;
+mod casts;
+mod checked_conversions;
+mod cognitive_complexity;
+mod collapsible_if;
+mod collapsible_match;
+mod comparison_chain;
+mod copies;
+mod copy_iterator;
+mod crate_in_macro_def;
+mod create_dir;
+mod dbg_macro;
+mod default;
+mod default_numeric_fallback;
+mod default_union_representation;
+mod dereference;
+mod derivable_impls;
+mod derive;
+mod disallowed_methods;
+mod disallowed_script_idents;
+mod disallowed_types;
+mod doc;
+mod double_comparison;
+mod double_parens;
+mod drop_forget_ref;
++mod duplicate_mod;
+mod duration_subsec;
+mod else_if_without_else;
+mod empty_drop;
+mod empty_enum;
+mod empty_structs_with_brackets;
+mod entry;
+mod enum_clike;
+mod enum_variants;
+mod eq_op;
+mod equatable_if_let;
+mod erasing_op;
+mod escape;
+mod eta_reduction;
- use crate::utils::conf::TryConf;
+mod excessive_bools;
+mod exhaustive_items;
+mod exit;
+mod explicit_write;
+mod fallible_impl_from;
+mod float_equality_without_abs;
+mod float_literal;
+mod floating_point_arithmetic;
+mod format;
+mod format_args;
+mod format_impl;
+mod format_push_string;
+mod formatting;
+mod from_over_into;
+mod from_str_radix_10;
+mod functions;
+mod future_not_send;
+mod get_last_with_len;
+mod identity_op;
+mod if_let_mutex;
+mod if_not_else;
+mod if_then_some_else_none;
+mod implicit_hasher;
+mod implicit_return;
+mod implicit_saturating_sub;
+mod inconsistent_struct_constructor;
+mod index_refutable_slice;
+mod indexing_slicing;
+mod infinite_iter;
+mod inherent_impl;
+mod inherent_to_string;
+mod init_numbered_fields;
+mod inline_fn_without_body;
+mod int_plus_one;
+mod integer_division;
+mod invalid_upcast_comparisons;
+mod items_after_statements;
+mod iter_not_returning_iterator;
+mod large_const_arrays;
+mod large_enum_variant;
+mod large_include_file;
+mod large_stack_arrays;
+mod len_zero;
+mod let_if_seq;
+mod let_underscore;
+mod lifetimes;
+mod literal_representation;
+mod loops;
+mod macro_use;
+mod main_recursion;
+mod manual_assert;
+mod manual_async_fn;
+mod manual_bits;
+mod manual_map;
+mod manual_non_exhaustive;
+mod manual_ok_or;
+mod manual_strip;
+mod manual_unwrap_or;
+mod map_clone;
+mod map_err_ignore;
+mod map_unit_fn;
+mod match_on_vec_items;
+mod match_result_ok;
+mod match_str_case_mismatch;
+mod matches;
+mod mem_forget;
+mod mem_replace;
+mod methods;
+mod minmax;
+mod misc;
+mod misc_early;
+mod missing_const_for_fn;
+mod missing_doc;
+mod missing_enforced_import_rename;
+mod missing_inline;
++mod mixed_read_write_in_expression;
+mod module_style;
+mod modulo_arithmetic;
+mod mut_key;
+mod mut_mut;
+mod mut_mutex_lock;
+mod mut_reference;
+mod mutable_debug_assertion;
+mod mutex_atomic;
+mod needless_arbitrary_self_type;
+mod needless_bitwise_bool;
+mod needless_bool;
+mod needless_borrowed_ref;
+mod needless_continue;
+mod needless_for_each;
+mod needless_late_init;
+mod needless_pass_by_value;
+mod needless_question_mark;
+mod needless_update;
+mod neg_cmp_op_on_partial_ord;
+mod neg_multiply;
+mod new_without_default;
+mod no_effect;
+mod non_copy_const;
+mod non_expressive_names;
+mod non_octal_unix_permissions;
+mod non_send_fields_in_send_ty;
+mod nonstandard_macro_braces;
+mod octal_escapes;
+mod only_used_in_recursion;
+mod open_options;
+mod option_env_unwrap;
+mod option_if_let_else;
+mod overflow_check_conditional;
+mod panic_in_result_fn;
+mod panic_unimplemented;
+mod partialeq_ne_impl;
+mod pass_by_ref_or_value;
+mod path_buf_push_overwrite;
+mod pattern_type_mismatch;
+mod precedence;
+mod ptr;
+mod ptr_eq;
+mod ptr_offset_with_cast;
+mod pub_use;
+mod question_mark;
+mod ranges;
++mod rc_clone_in_vec_init;
+mod redundant_clone;
+mod redundant_closure_call;
+mod redundant_else;
+mod redundant_field_names;
+mod redundant_pub_crate;
+mod redundant_slicing;
+mod redundant_static_lifetimes;
+mod ref_option_ref;
+mod reference;
+mod regex;
+mod repeat_once;
+mod return_self_not_must_use;
+mod returns;
+mod same_name_method;
+mod self_assignment;
+mod self_named_constructors;
+mod semicolon_if_nothing_returned;
+mod serde_api;
+mod shadow;
+mod significant_drop_in_scrutinee;
+mod single_char_lifetime_names;
+mod single_component_path_imports;
+mod size_of_in_element_count;
+mod slow_vector_initialization;
+mod stable_sort_primitive;
+mod strings;
+mod strlen_on_c_strings;
+mod suspicious_operation_groupings;
+mod suspicious_trait_impl;
+mod swap;
+mod tabs_in_doc_comments;
+mod temporary_assignment;
+mod to_digit_is_some;
+mod trailing_empty_array;
+mod trait_bounds;
+mod transmute;
+mod transmuting_null;
+mod try_err;
+mod types;
+mod undocumented_unsafe_blocks;
+mod unicode;
+mod uninit_vec;
+mod unit_hash;
+mod unit_return_expecting_ord;
+mod unit_types;
+mod unnamed_address;
+mod unnecessary_owned_empty_strings;
+mod unnecessary_self_imports;
+mod unnecessary_sort_by;
+mod unnecessary_wraps;
+mod unnested_or_patterns;
+mod unsafe_removed_from_name;
+mod unused_async;
+mod unused_io_amount;
+mod unused_self;
+mod unused_unit;
+mod unwrap;
+mod unwrap_in_result;
+mod upper_case_acronyms;
+mod use_self;
+mod useless_conversion;
+mod vec;
+mod vec_init_then_push;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wildcard_imports;
+mod write;
+mod zero_div_zero;
+mod zero_sized_map_values;
+// end lints modules, do not remove this comment, it’s used in `update_lints`
+
+pub use crate::utils::conf::Conf;
- error
++use crate::utils::conf::{format_error, TryConf};
+
+/// Register all pre expansion lints
+///
+/// Pre-expansion lints run before any macro expansion has happened.
+///
+/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate
+/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
+
+ let msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(&format!(
+ "error reading Clippy's configuration file. `{}` is not a valid Rust version",
+ s
+ ));
+ None
+ })
+ });
+
+ store.register_pre_expansion_pass(|| Box::new(write::Write::default()));
+ store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
+}
+
+#[doc(hidden)]
+pub fn read_conf(sess: &Session) -> Conf {
+ let file_name = match utils::conf::lookup_conf_file() {
+ Ok(Some(path)) => path,
+ Ok(None) => return Conf::default(),
+ Err(error) => {
+ sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
+ .emit();
+ return Conf::default();
+ },
+ };
+
+ let TryConf { conf, errors } = utils::conf::read(&file_name);
+ // all conf errors are non-fatal, we just use the default conf in case of error
+ for error in errors {
+ sess.struct_err(&format!(
+ "error reading Clippy's configuration file `{}`: {}",
+ file_name.display(),
- #[allow(clippy::too_many_lines)]
++ format_error(error)
+ ))
+ .emit();
+ }
+
+ conf
+}
+
+/// Register all lints and lint groups with the rustc plugin registry
+///
+/// Used in `./src/driver.rs`.
- store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv)));
++#[expect(clippy::too_many_lines)]
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ register_removed_non_tool_lints(store);
+
+ include!("lib.deprecated.rs");
+
+ include!("lib.register_lints.rs");
+ include!("lib.register_restriction.rs");
+ include!("lib.register_pedantic.rs");
+
+ #[cfg(feature = "internal")]
+ include!("lib.register_internal.rs");
+
+ include!("lib.register_all.rs");
+ include!("lib.register_style.rs");
+ include!("lib.register_complexity.rs");
+ include!("lib.register_correctness.rs");
+ include!("lib.register_suspicious.rs");
+ include!("lib.register_perf.rs");
+ include!("lib.register_cargo.rs");
+ include!("lib.register_nursery.rs");
+
+ #[cfg(feature = "internal")]
+ {
+ if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
+ store.register_late_pass(|| Box::new(utils::internal_lints::metadata_collector::MetadataCollector::new()));
+ return;
+ }
+ }
+
+ // all the internal lints
+ #[cfg(feature = "internal")]
+ {
+ store.register_early_pass(|| Box::new(utils::internal_lints::ClippyLintsInternal));
+ store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce));
+ store.register_late_pass(|| Box::new(utils::internal_lints::CollapsibleCalls));
+ store.register_late_pass(|| Box::new(utils::internal_lints::CompilerLintFunctions::new()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::IfChainStyle));
+ store.register_late_pass(|| Box::new(utils::internal_lints::InvalidPaths));
+ store.register_late_pass(|| Box::new(utils::internal_lints::InterningDefinedSymbol::default()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
+ store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
+ store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
+ }
+
+ store.register_late_pass(|| Box::new(utils::dump_hir::DumpHir));
+ store.register_late_pass(|| Box::new(utils::author::Author));
+ let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
+ store.register_late_pass(move || {
+ Box::new(await_holding_invalid::AwaitHolding::new(
+ await_holding_invalid_types.clone(),
+ ))
+ });
+ store.register_late_pass(|| Box::new(serde_api::SerdeApi));
+ let vec_box_size_threshold = conf.vec_box_size_threshold;
+ let type_complexity_threshold = conf.type_complexity_threshold;
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
+ store.register_late_pass(move || {
+ Box::new(types::Types::new(
+ vec_box_size_threshold,
+ type_complexity_threshold,
+ avoid_breaking_exported_api,
+ ))
+ });
+ store.register_late_pass(|| Box::new(booleans::NonminimalBool));
+ store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
+ store.register_late_pass(|| Box::new(eq_op::EqOp));
+ store.register_late_pass(|| Box::new(enum_clike::UnportableVariant));
+ store.register_late_pass(|| Box::new(float_literal::FloatLiteral));
+ let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
+ store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold)));
+ store.register_late_pass(|| Box::new(ptr::Ptr));
+ store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
+ store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
+ store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
+ store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
+ store.register_late_pass(|| Box::new(misc::MiscLints));
+ store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
+ store.register_late_pass(|| Box::new(identity_op::IdentityOp));
+ store.register_late_pass(|| Box::new(erasing_op::ErasingOp));
+ store.register_late_pass(|| Box::new(mut_mut::MutMut));
+ store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed));
+ store.register_late_pass(|| Box::new(len_zero::LenZero));
+ store.register_late_pass(|| Box::new(attrs::Attributes));
+ store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
+ store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
+ store.register_late_pass(|| Box::new(unicode::Unicode));
+ store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
+ store.register_late_pass(|| Box::new(unit_hash::UnitHash));
+ store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
+ store.register_late_pass(|| Box::new(strings::StringAdd));
+ store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn));
+ store.register_late_pass(|| Box::new(implicit_saturating_sub::ImplicitSaturatingSub));
+ store.register_late_pass(|| Box::new(default_numeric_fallback::DefaultNumericFallback));
+ store.register_late_pass(|| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
+ store.register_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
+ store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
+
+ let msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(&format!(
+ "error reading Clippy's configuration file. `{}` is not a valid Rust version",
+ s
+ ));
+ None
+ })
+ });
+
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
++ let allow_expect_in_tests = conf.allow_expect_in_tests;
++ let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
+ store.register_late_pass(move || Box::new(approx_const::ApproxConstant::new(msrv)));
- store.register_late_pass(|| Box::new(eval_order_dependence::EvalOrderDependence));
++ store.register_late_pass(move || {
++ Box::new(methods::Methods::new(
++ avoid_breaking_exported_api,
++ msrv,
++ allow_expect_in_tests,
++ allow_unwrap_in_tests,
++ ))
++ });
+ store.register_late_pass(move || Box::new(matches::Matches::new(msrv)));
+ store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
+ store.register_late_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
+ store.register_late_pass(move || Box::new(manual_strip::ManualStrip::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
+ store.register_late_pass(move || Box::new(checked_conversions::CheckedConversions::new(msrv)));
+ store.register_late_pass(move || Box::new(mem_replace::MemReplace::new(msrv)));
+ store.register_late_pass(move || Box::new(ranges::Ranges::new(msrv)));
+ store.register_late_pass(move || Box::new(from_over_into::FromOverInto::new(msrv)));
+ store.register_late_pass(move || Box::new(use_self::UseSelf::new(msrv)));
+ store.register_late_pass(move || Box::new(missing_const_for_fn::MissingConstForFn::new(msrv)));
+ store.register_late_pass(move || Box::new(needless_question_mark::NeedlessQuestionMark));
+ store.register_late_pass(move || Box::new(casts::Casts::new(msrv)));
+ store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv)));
+ store.register_late_pass(move || Box::new(map_clone::MapClone::new(msrv)));
+
+ store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount));
+ store.register_late_pass(|| Box::new(same_name_method::SameNameMethod));
+ let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
+ store.register_late_pass(move || {
+ Box::new(index_refutable_slice::IndexRefutableSlice::new(
+ max_suggested_slice_pattern_length,
+ msrv,
+ ))
+ });
+ store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore));
+ store.register_late_pass(|| Box::new(shadow::Shadow::default()));
+ store.register_late_pass(|| Box::new(unit_types::UnitTypes));
+ store.register_late_pass(|| Box::new(loops::Loops));
+ store.register_late_pass(|| Box::new(main_recursion::MainRecursion::default()));
+ store.register_late_pass(|| Box::new(lifetimes::Lifetimes));
+ store.register_late_pass(|| Box::new(entry::HashMapPass));
+ store.register_late_pass(|| Box::new(minmax::MinMaxPass));
+ store.register_late_pass(|| Box::new(open_options::OpenOptions));
+ store.register_late_pass(|| Box::new(zero_div_zero::ZeroDiv));
+ store.register_late_pass(|| Box::new(mutex_atomic::Mutex));
+ store.register_late_pass(|| Box::new(needless_update::NeedlessUpdate));
+ store.register_late_pass(|| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
+ store.register_late_pass(|| Box::new(no_effect::NoEffect));
+ store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment));
+ store.register_late_pass(|| Box::new(transmute::Transmute));
+ let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
+ store.register_late_pass(move || {
+ Box::new(cognitive_complexity::CognitiveComplexity::new(
+ cognitive_complexity_threshold,
+ ))
+ });
+ let too_large_for_stack = conf.too_large_for_stack;
+ store.register_late_pass(move || Box::new(escape::BoxedLocal { too_large_for_stack }));
+ store.register_late_pass(move || Box::new(vec::UselessVec { too_large_for_stack }));
+ store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented));
+ store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
+ store.register_late_pass(|| Box::new(derive::Derive));
+ store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
+ store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
+ store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
+ store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
+ store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
+ store.register_late_pass(|| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
+ store.register_late_pass(|| Box::new(regex::Regex));
+ store.register_late_pass(|| Box::new(copies::CopyAndPaste));
+ store.register_late_pass(|| Box::new(copy_iterator::CopyIterator));
+ store.register_late_pass(|| Box::new(format::UselessFormat));
+ store.register_late_pass(|| Box::new(swap::Swap));
+ store.register_late_pass(|| Box::new(overflow_check_conditional::OverflowCheckConditional));
+ store.register_late_pass(|| Box::new(new_without_default::NewWithoutDefault::default()));
+ let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || Box::new(blacklisted_name::BlacklistedName::new(blacklisted_names.clone())));
+ let too_many_arguments_threshold = conf.too_many_arguments_threshold;
+ let too_many_lines_threshold = conf.too_many_lines_threshold;
+ store.register_late_pass(move || {
+ Box::new(functions::Functions::new(
+ too_many_arguments_threshold,
+ too_many_lines_threshold,
+ ))
+ });
+ let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
+ store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
+ store.register_late_pass(|| Box::new(mem_forget::MemForget));
+ store.register_late_pass(|| Box::new(arithmetic::Arithmetic::default()));
+ store.register_late_pass(|| Box::new(assign_ops::AssignOps));
+ store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq));
++ store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
+ store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new()));
+ store.register_late_pass(|| Box::new(missing_inline::MissingInline));
+ store.register_late_pass(move || Box::new(exhaustive_items::ExhaustiveItems));
+ store.register_late_pass(|| Box::new(match_result_ok::MatchResultOk));
+ store.register_late_pass(|| Box::new(partialeq_ne_impl::PartialEqNeImpl));
+ store.register_late_pass(|| Box::new(unused_io_amount::UnusedIoAmount));
+ let enum_variant_size_threshold = conf.enum_variant_size_threshold;
+ store.register_late_pass(move || Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
+ store.register_late_pass(|| Box::new(explicit_write::ExplicitWrite));
+ store.register_late_pass(|| Box::new(needless_pass_by_value::NeedlessPassByValue));
+ let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
+ conf.trivial_copy_size_limit,
+ conf.pass_by_value_size_limit,
+ conf.avoid_breaking_exported_api,
+ &sess.target,
+ );
+ store.register_late_pass(move || Box::new(pass_by_ref_or_value));
+ store.register_late_pass(|| Box::new(ref_option_ref::RefOptionRef));
+ store.register_late_pass(|| Box::new(try_err::TryErr));
+ store.register_late_pass(|| Box::new(bytecount::ByteCount));
+ store.register_late_pass(|| Box::new(infinite_iter::InfiniteIter));
+ store.register_late_pass(|| Box::new(inline_fn_without_body::InlineFnWithoutBody));
+ store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default()));
+ store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher));
+ store.register_late_pass(|| Box::new(fallible_impl_from::FallibleImplFrom));
+ store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons));
+ store.register_late_pass(|| Box::new(question_mark::QuestionMark));
+ store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
+ store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl));
+ store.register_late_pass(|| Box::new(map_unit_fn::MapUnit));
+ store.register_late_pass(|| Box::new(inherent_impl::MultipleInherentImpl));
+ store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
+ store.register_late_pass(|| Box::new(unwrap::Unwrap));
+ store.register_late_pass(|| Box::new(duration_subsec::DurationSubsec));
+ store.register_late_pass(|| Box::new(indexing_slicing::IndexingSlicing));
+ store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst));
+ store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
+ store.register_late_pass(|| Box::new(redundant_clone::RedundantClone));
+ store.register_late_pass(|| Box::new(slow_vector_initialization::SlowVectorInit));
+ store.register_late_pass(|| Box::new(unnecessary_sort_by::UnnecessarySortBy));
+ store.register_late_pass(move || Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api)));
+ store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants));
+ store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull));
+ store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite));
+ store.register_late_pass(|| Box::new(integer_division::IntegerDivision));
+ store.register_late_pass(|| Box::new(inherent_to_string::InherentToString));
+ let max_trait_bounds = conf.max_trait_bounds;
+ store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
+ store.register_late_pass(|| Box::new(comparison_chain::ComparisonChain));
+ store.register_late_pass(|| Box::new(mut_key::MutableKeyType));
+ store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
+ store.register_early_pass(|| Box::new(reference::DerefAddrOf));
+ store.register_early_pass(|| Box::new(double_parens::DoubleParens));
+ store.register_late_pass(|| Box::new(format_impl::FormatImpl::new()));
+ store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
+ store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
+ store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
+ store.register_early_pass(|| Box::new(formatting::Formatting));
+ store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
+ store.register_early_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_late_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
+ store.register_late_pass(|| Box::new(returns::Return));
+ store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
+ store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
+ store.register_early_pass(|| Box::new(precedence::Precedence));
+ store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
+ store.register_early_pass(|| Box::new(redundant_else::RedundantElse));
+ store.register_late_pass(|| Box::new(create_dir::CreateDir));
+ store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType));
+ let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
+ store.register_early_pass(move || {
+ Box::new(literal_representation::LiteralDigitGrouping::new(
+ literal_representation_lint_fraction_readability,
+ ))
+ });
+ let literal_representation_threshold = conf.literal_representation_threshold;
+ store.register_early_pass(move || {
+ Box::new(literal_representation::DecimalLiteralRepresentation::new(
+ literal_representation_threshold,
+ ))
+ });
+ let enum_variant_name_threshold = conf.enum_variant_name_threshold;
+ store.register_late_pass(move || {
+ Box::new(enum_variants::EnumVariantNames::new(
+ enum_variant_name_threshold,
+ avoid_breaking_exported_api,
+ ))
+ });
+ store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
+ let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
+ store.register_late_pass(move || {
+ Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
+ avoid_breaking_exported_api,
+ upper_case_acronyms_aggressive,
+ ))
+ });
+ store.register_late_pass(|| Box::new(default::Default::default()));
+ store.register_late_pass(|| Box::new(unused_self::UnusedSelf));
+ store.register_late_pass(|| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
+ store.register_late_pass(|| Box::new(exit::Exit));
+ store.register_late_pass(|| Box::new(to_digit_is_some::ToDigitIsSome));
+ let array_size_threshold = conf.array_size_threshold;
+ store.register_late_pass(move || Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
+ store.register_late_pass(move || Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
+ store.register_late_pass(|| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
+ store.register_early_pass(|| Box::new(as_conversions::AsConversions));
+ store.register_late_pass(|| Box::new(let_underscore::LetUnderscore));
+ store.register_early_pass(|| Box::new(single_component_path_imports::SingleComponentPathImports));
+ let max_fn_params_bools = conf.max_fn_params_bools;
+ let max_struct_bools = conf.max_struct_bools;
+ store.register_early_pass(move || {
+ Box::new(excessive_bools::ExcessiveBools::new(
+ max_struct_bools,
+ max_fn_params_bools,
+ ))
+ });
+ store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
+ let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
+ store.register_late_pass(move || Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
+ store.register_late_pass(|| Box::new(verbose_file_reads::VerboseFileReads));
+ store.register_late_pass(|| Box::new(redundant_pub_crate::RedundantPubCrate::default()));
+ store.register_late_pass(|| Box::new(unnamed_address::UnnamedAddress));
+ store.register_late_pass(|| Box::new(dereference::Dereferencing::default()));
+ store.register_late_pass(|| Box::new(option_if_let_else::OptionIfLetElse));
+ store.register_late_pass(|| Box::new(future_not_send::FutureNotSend));
+ store.register_late_pass(|| Box::new(if_let_mutex::IfLetMutex));
+ store.register_late_pass(|| Box::new(if_not_else::IfNotElse));
+ store.register_late_pass(|| Box::new(equatable_if_let::PatternEquality));
+ store.register_late_pass(|| Box::new(mut_mutex_lock::MutMutexLock));
+ store.register_late_pass(|| Box::new(match_on_vec_items::MatchOnVecItems));
+ store.register_late_pass(|| Box::new(manual_async_fn::ManualAsyncFn));
+ store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero));
+ store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn));
+ let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
+ store.register_early_pass(move || {
+ Box::new(non_expressive_names::NonExpressiveNames {
+ single_char_binding_names_threshold,
+ })
+ });
+ let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher)));
+ store.register_late_pass(|| Box::new(macro_use::MacroUseImports::default()));
+ store.register_late_pass(|| Box::new(pattern_type_mismatch::PatternTypeMismatch));
+ store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive));
+ store.register_late_pass(|| Box::new(repeat_once::RepeatOnce));
+ store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult));
+ store.register_late_pass(|| Box::new(self_assignment::SelfAssignment));
+ store.register_late_pass(|| Box::new(manual_unwrap_or::ManualUnwrapOr));
+ store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr));
+ store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs));
+ store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
+ store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync));
+ let disallowed_methods = conf.disallowed_methods.clone();
+ store.register_late_pass(move || Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
+ store.register_late_pass(|| Box::new(empty_drop::EmptyDrop));
+ store.register_late_pass(|| Box::new(strings::StrToString));
+ store.register_late_pass(|| Box::new(strings::StringToString));
+ store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues));
+ store.register_late_pass(|| Box::new(vec_init_then_push::VecInitThenPush::default()));
+ store.register_late_pass(|| {
+ Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons)
+ });
+ store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing));
+ store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10));
+ store.register_late_pass(|| Box::new(manual_map::ManualMap));
+ store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
+ store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
+ store.register_early_pass(move || Box::new(module_style::ModStyle));
+ store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
+ let disallowed_types = conf.disallowed_types.clone();
+ store.register_late_pass(move || Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone())));
+ let import_renames = conf.enforced_import_renames.clone();
+ store.register_late_pass(move || {
+ Box::new(missing_enforced_import_rename::ImportRename::new(
+ import_renames.clone(),
+ ))
+ });
+ let scripts = conf.allowed_scripts.clone();
+ store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
+ store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings));
+ store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors));
+ store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator));
+ store.register_late_pass(move || Box::new(manual_assert::ManualAssert));
+ let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
+ store.register_late_pass(move || {
+ Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
+ enable_raw_pointer_heuristic_for_send,
+ ))
+ });
+ store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
+ store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
+ store.register_late_pass(move || Box::new(format_args::FormatArgs));
+ store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
+ store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
+ store.register_late_pass(|| Box::new(needless_late_init::NeedlessLateInit));
+ store.register_late_pass(|| Box::new(return_self_not_must_use::ReturnSelfNotMustUse));
+ store.register_late_pass(|| Box::new(init_numbered_fields::NumberedFields));
+ store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames));
+ store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv)));
+ store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
+ store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
+ store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
+ store.register_late_pass(|| Box::new(significant_drop_in_scrutinee::SignificantDropInScrutinee));
+ store.register_late_pass(|| Box::new(dbg_macro::DbgMacro));
+ let cargo_ignore_publish = conf.cargo_ignore_publish;
+ store.register_late_pass(move || {
+ Box::new(cargo::Cargo {
+ ignore_publish: cargo_ignore_publish,
+ })
+ });
+ store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
+ store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
+ store.register_late_pass(|| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
+ store.register_early_pass(|| Box::new(pub_use::PubUse));
+ store.register_late_pass(|| Box::new(format_push_string::FormatPushString));
+ store.register_late_pass(|| Box::new(bytes_count_to_len::BytesCountToLen));
+ let max_include_file_size = conf.max_include_file_size;
+ store.register_late_pass(move || Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
+ store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace));
++ store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
++ store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
+ // add lints here, do not remove this comment, it's used in `new_lint`
+}
+
+#[rustfmt::skip]
+fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
+ store.register_removed(
+ "should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "reverse_range_loop",
+ "this lint is now included in reversed_empty_ranges",
+ );
+}
+
+/// Register renamed lints.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
+ for (old_name, new_name) in renamed_lints::RENAMED_LINTS {
+ ls.register_renamed(old_name, new_name);
+ }
+}
+
+// only exists to let the dogfood integration test works.
+// Don't run clippy as an executable directly
+#[allow(dead_code)]
+fn main() {
+ panic!("Please use the cargo-clippy executable");
+}
--- /dev/null
- #[allow(clippy::module_name_repetitions)]
+//! Lints concerned with the grouping of digits with underscores in integral or
+//! floating-point literal expressions.
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal::{NumericLiteral, Radix};
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if a long integral or floating-point constant does
+ /// not contain underscores.
+ ///
+ /// ### Why is this bad?
+ /// Reading long numbers is difficult without separators.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: u64 = 61864918973511;
+ ///
+ /// // Good
+ /// let x: u64 = 61_864_918_973_511;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNREADABLE_LITERAL,
+ pedantic,
+ "long literal without underscores"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns for mistyped suffix in literals
+ ///
+ /// ### Why is this bad?
+ /// This is most probably a typo
+ ///
+ /// ### Known problems
+ /// - Does not match on integers too large to fit in the corresponding unsigned type
+ /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
+ ///
+ /// ### Example
+ /// `2_32` => `2_i32`
+ /// `250_8 => `250_u8`
+ /// ```
+ #[clippy::version = "1.30.0"]
+ pub MISTYPED_LITERAL_SUFFIXES,
+ correctness,
+ "mistyped literal suffix"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if an integral or floating-point constant is
+ /// grouped inconsistently with underscores.
+ ///
+ /// ### Why is this bad?
+ /// Readers may incorrectly interpret inconsistently
+ /// grouped digits.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: u64 = 618_64_9189_73_511;
+ ///
+ /// // Good
+ /// let x: u64 = 61_864_918_973_511;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INCONSISTENT_DIGIT_GROUPING,
+ style,
+ "integer literals with digits grouped inconsistently"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if hexadecimal or binary literals are not grouped
+ /// by nibble or byte.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u32 = 0xFFF_FFF;
+ /// let y: u8 = 0b01_011_101;
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub UNUSUAL_BYTE_GROUPINGS,
+ style,
+ "binary or hex literals that aren't grouped by four"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if the digits of an integral or floating-point
+ /// constant are grouped into groups that
+ /// are too large.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u64 = 6186491_8973511;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LARGE_DIGIT_GROUPS,
+ pedantic,
+ "grouping digits into groups that are too large"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if there is a better representation for a numeric literal.
+ ///
+ /// ### Why is this bad?
+ /// Especially for big powers of 2 a hexadecimal representation is more
+ /// readable than a decimal representation.
+ ///
+ /// ### Example
+ /// `255` => `0xFF`
+ /// `65_535` => `0xFFFF`
+ /// `4_042_322_160` => `0xF0F0_F0F0`
+ #[clippy::version = "pre 1.29.0"]
+ pub DECIMAL_LITERAL_REPRESENTATION,
+ restriction,
+ "using decimal representation when hexadecimal would be better"
+}
+
+enum WarningType {
+ UnreadableLiteral,
+ InconsistentDigitGrouping,
+ LargeDigitGroups,
+ DecimalRepresentation,
+ MistypedLiteralSuffix,
+ UnusualByteGroupings,
+}
+
+impl WarningType {
+ fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
+ match self {
+ Self::MistypedLiteralSuffix => span_lint_and_sugg(
+ cx,
+ MISTYPED_LITERAL_SUFFIXES,
+ span,
+ "mistyped literal suffix",
+ "did you mean to write",
+ suggested_format,
+ Applicability::MaybeIncorrect,
+ ),
+ Self::UnreadableLiteral => span_lint_and_sugg(
+ cx,
+ UNREADABLE_LITERAL,
+ span,
+ "long literal lacking separators",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::LargeDigitGroups => span_lint_and_sugg(
+ cx,
+ LARGE_DIGIT_GROUPS,
+ span,
+ "digit groups should be smaller",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::InconsistentDigitGrouping => span_lint_and_sugg(
+ cx,
+ INCONSISTENT_DIGIT_GROUPING,
+ span,
+ "digits grouped inconsistently by underscores",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::DecimalRepresentation => span_lint_and_sugg(
+ cx,
+ DECIMAL_LITERAL_REPRESENTATION,
+ span,
+ "integer literal has a better hexadecimal representation",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::UnusualByteGroupings => span_lint_and_sugg(
+ cx,
+ UNUSUAL_BYTE_GROUPINGS,
+ span,
+ "digits of hex or binary literal not grouped by four",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ };
+ }
+}
+
- #[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct LiteralDigitGrouping {
+ lint_fraction_readability: bool,
+}
+
+impl_lint_pass!(LiteralDigitGrouping => [
+ UNREADABLE_LITERAL,
+ INCONSISTENT_DIGIT_GROUPING,
+ LARGE_DIGIT_GROUPS,
+ MISTYPED_LITERAL_SUFFIXES,
+ UNUSUAL_BYTE_GROUPINGS,
+]);
+
+impl EarlyLintPass for LiteralDigitGrouping {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+// Length of each UUID hyphenated group in hex digits.
+const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
+
+impl LiteralDigitGrouping {
+ pub fn new(lint_fraction_readability: bool) -> Self {
+ Self {
+ lint_fraction_readability,
+ }
+ }
+
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ if_chain! {
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
+ then {
+ if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
+ return;
+ }
+
+ if Self::is_literal_uuid_formatted(&mut num_lit) {
+ return;
+ }
+
+ let result = (|| {
+
+ let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
+ if let Some(fraction) = num_lit.fraction {
+ let fractional_group_size = Self::get_group_size(
+ fraction.rsplit('_'),
+ num_lit.radix,
+ self.lint_fraction_readability)?;
+
+ let consistent = Self::parts_consistent(integral_group_size,
+ fractional_group_size,
+ num_lit.integer.len(),
+ fraction.len());
+ if !consistent {
+ return Err(WarningType::InconsistentDigitGrouping);
+ };
+ }
+
+ Ok(())
+ })();
+
+
+ if let Err(warning_type) = result {
+ let should_warn = match warning_type {
+ | WarningType::UnreadableLiteral
+ | WarningType::InconsistentDigitGrouping
+ | WarningType::UnusualByteGroupings
+ | WarningType::LargeDigitGroups => {
+ !lit.span.from_expansion()
+ }
+ WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
+ true
+ }
+ };
+ if should_warn {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ }
+ }
+ }
+ }
+ }
+
+ // Returns `false` if the check fails
+ fn check_for_mistyped_suffix(
+ cx: &EarlyContext<'_>,
+ span: rustc_span::Span,
+ num_lit: &mut NumericLiteral<'_>,
+ ) -> bool {
+ if num_lit.suffix.is_some() {
+ return true;
+ }
+
+ let (part, mistyped_suffixes, is_float) = if let Some((_, exponent)) = &mut num_lit.exponent {
+ (exponent, &["32", "64"][..], true)
+ } else if num_lit.fraction.is_some() {
+ return true;
+ } else {
+ (&mut num_lit.integer, &["8", "16", "32", "64"][..], false)
+ };
+
+ let mut split = part.rsplit('_');
+ let last_group = split.next().expect("At least one group");
+ if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
+ let main_part = &part[..part.len() - last_group.len()];
+ let missing_char;
+ if is_float {
+ missing_char = 'f';
+ } else {
+ let radix = match num_lit.radix {
+ Radix::Binary => 2,
+ Radix::Octal => 8,
+ Radix::Decimal => 10,
+ Radix::Hexadecimal => 16,
+ };
+ if let Ok(int) = u64::from_str_radix(&main_part.replace('_', ""), radix) {
+ missing_char = match (last_group, int) {
+ ("8", i) if i8::try_from(i).is_ok() => 'i',
+ ("16", i) if i16::try_from(i).is_ok() => 'i',
+ ("32", i) if i32::try_from(i).is_ok() => 'i',
+ ("64", i) if i64::try_from(i).is_ok() => 'i',
+ ("8", u) if u8::try_from(u).is_ok() => 'u',
+ ("16", u) if u16::try_from(u).is_ok() => 'u',
+ ("32", u) if u32::try_from(u).is_ok() => 'u',
+ ("64", _) => 'u',
+ _ => {
+ return true;
+ },
+ }
+ } else {
+ return true;
+ }
+ }
+ *part = main_part;
+ let mut sugg = num_lit.format();
+ sugg.push('_');
+ sugg.push(missing_char);
+ sugg.push_str(last_group);
+ WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
+ false
+ } else {
+ true
+ }
+ }
+
+ /// Checks whether the numeric literal matches the formatting of a UUID.
+ ///
+ /// Returns `true` if the radix is hexadecimal, and the groups match the
+ /// UUID format of 8-4-4-4-12.
+ fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool {
+ if num_lit.radix != Radix::Hexadecimal {
+ return false;
+ }
+
+ // UUIDs should not have a fraction
+ if num_lit.fraction.is_some() {
+ return false;
+ }
+
+ let group_sizes: Vec<usize> = num_lit.integer.split('_').map(str::len).collect();
+ if UUID_GROUP_LENS.len() == group_sizes.len() {
+ iter::zip(&UUID_GROUP_LENS, &group_sizes).all(|(&a, &b)| a == b)
+ } else {
+ false
+ }
+ }
+
+ /// Given the sizes of the digit groups of both integral and fractional
+ /// parts, and the length
+ /// of both parts, determine if the digits have been grouped consistently.
+ #[must_use]
+ fn parts_consistent(
+ int_group_size: Option<usize>,
+ frac_group_size: Option<usize>,
+ int_size: usize,
+ frac_size: usize,
+ ) -> bool {
+ match (int_group_size, frac_group_size) {
+ // No groups on either side of decimal point - trivially consistent.
+ (None, None) => true,
+ // Integral part has grouped digits, fractional part does not.
+ (Some(int_group_size), None) => frac_size <= int_group_size,
+ // Fractional part has grouped digits, integral part does not.
+ (None, Some(frac_group_size)) => int_size <= frac_group_size,
+ // Both parts have grouped digits. Groups should be the same size.
+ (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size,
+ }
+ }
+
+ /// Returns the size of the digit groups (or None if ungrouped) if successful,
+ /// otherwise returns a `WarningType` for linting.
+ fn get_group_size<'a>(
+ groups: impl Iterator<Item = &'a str>,
+ radix: Radix,
+ lint_unreadable: bool,
+ ) -> Result<Option<usize>, WarningType> {
+ let mut groups = groups.map(str::len);
+
+ let first = groups.next().expect("At least one group");
+
+ if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) {
+ return Err(WarningType::UnusualByteGroupings);
+ }
+
+ if let Some(second) = groups.next() {
+ if !groups.all(|x| x == second) || first > second {
+ Err(WarningType::InconsistentDigitGrouping)
+ } else if second > 4 {
+ Err(WarningType::LargeDigitGroups)
+ } else {
+ Ok(Some(second))
+ }
+ } else if first > 5 && lint_unreadable {
+ Err(WarningType::UnreadableLiteral)
+ } else {
+ Ok(None)
+ }
+ }
+}
+
++#[expect(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct DecimalLiteralRepresentation {
+ threshold: u64,
+}
+
+impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
+
+impl EarlyLintPass for DecimalLiteralRepresentation {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+impl DecimalLiteralRepresentation {
+ #[must_use]
+ pub fn new(threshold: u64) -> Self {
+ Self { threshold }
+ }
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ // Lint integral literals.
+ if_chain! {
+ if let LitKind::Int(val, _) = lit.kind;
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
+ if num_lit.radix == Radix::Decimal;
+ if val >= u128::from(self.threshold);
+ then {
+ let hex = format!("{:#X}", val);
+ let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
+ let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ });
+ }
+ }
+ }
+
+ fn do_lint(digits: &str) -> Result<(), WarningType> {
+ if digits.len() == 1 {
+ // Lint for 1 digit literals, if someone really sets the threshold that low
+ if digits == "1"
+ || digits == "2"
+ || digits == "4"
+ || digits == "8"
+ || digits == "3"
+ || digits == "7"
+ || digits == "F"
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else if digits.len() < 4 {
+ // Lint for Literals with a hex-representation of 2 or 3 digits
+ let f = &digits[0..1]; // first digit
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
+ // Powers of 2 minus 1
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else {
+ // Lint for Literals with a hex-representation of 4 digits or more
+ let f = &digits[0..1]; // first digit
+ let m = &digits[1..digits.len() - 1]; // middle digits, except last
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2 with a margin of +15/-16
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
+ // Lint for representations with only 0s and Fs, while allowing 7 as the first
+ // digit
+ || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ }
+
+ Ok(())
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+mod empty_loop;
+mod explicit_counter_loop;
+mod explicit_into_iter_loop;
+mod explicit_iter_loop;
+mod for_kv_map;
+mod for_loops_over_fallibles;
+mod iter_next_loop;
+mod manual_flatten;
+mod manual_memcpy;
+mod missing_spin_loop;
+mod mut_range_bound;
+mod needless_collect;
+mod needless_range_loop;
+mod never_loop;
+mod same_item_push;
+mod single_element_loop;
+mod utils;
+mod while_immutable_condition;
+mod while_let_loop;
+mod while_let_on_iterator;
+
+use clippy_utils::higher;
+use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for for-loops that manually copy items between
+ /// slices that could be optimized by having a memcpy.
+ ///
+ /// ### Why is this bad?
+ /// It is not as fast as a memcpy.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// for i in 0..src.len() {
+ /// dst[i + 64] = src[i];
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// # let src = vec![1];
+ /// # let mut dst = vec![0; 65];
+ /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MANUAL_MEMCPY,
+ perf,
+ "manually copying items between slices"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for looping over the range of `0..len` of some
+ /// collection just to get the values by index.
+ ///
+ /// ### Why is this bad?
+ /// Just iterating the collection itself makes the intent
+ /// more clear and is probably faster.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in 0..vec.len() {
+ /// println!("{}", vec[i]);
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let vec = vec!['a', 'b', 'c'];
+ /// for i in vec {
+ /// println!("{}", i);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_RANGE_LOOP,
+ style,
+ "for-looping over a range of indices where an iterator over items would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `x.iter()` where `&x` will do, and
+ /// suggests the latter.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Known problems
+ /// False negatives. We currently only warn on some known
+ /// types.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // with `y` a `Vec` or slice:
+ /// # let y = vec![1];
+ /// for x in y.iter() {
+ /// // ..
+ /// }
+ /// ```
+ /// can be rewritten to
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in &y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `y.into_iter()` where `y` will do, and
+ /// suggests the latter.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y = vec![1];
+ /// // with `y` a `Vec` or slice:
+ /// for x in y.into_iter() {
+ /// // ..
+ /// }
+ /// ```
+ /// can be rewritten to
+ /// ```rust
+ /// # let y = vec![1];
+ /// for x in y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_INTO_ITER_LOOP,
+ pedantic,
+ "for-looping over `_.into_iter()` when `_` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops on `x.next()`.
+ ///
+ /// ### Why is this bad?
+ /// `next()` returns either `Some(value)` if there was a
+ /// value, or `None` otherwise. The insidious thing is that `Option<_>`
+ /// implements `IntoIterator`, so that possibly one value will be iterated,
+ /// leading to some hard to find bugs. No one will want to write such code
+ /// [except to win an Underhanded Rust
+ /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr).
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for x in y.next() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_NEXT_LOOP,
+ correctness,
+ "for-looping over `_.next()` which is probably not intended"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `for` loops over `Option` or `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability. This is more clearly expressed as an `if
+ /// let`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// for x in opt {
+ /// // ..
+ /// }
+ ///
+ /// // Good
+ /// if let Some(x) = opt {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let res: Result<i32, std::io::Error> = Ok(1);
+ ///
+ /// // Bad
+ /// for x in &res {
+ /// // ..
+ /// }
+ ///
+ /// // Good
+ /// if let Ok(x) = res {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub FOR_LOOPS_OVER_FALLIBLES,
+ suspicious,
+ "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `loop + match` combinations that are easier
+ /// written as a `while let` loop.
+ ///
+ /// ### Why is this bad?
+ /// The `while let` loop is usually shorter and more
+ /// readable.
+ ///
+ /// ### Known problems
+ /// Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)).
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # let y = Some(1);
+ /// loop {
+ /// let x = match y {
+ /// Some(x) => x,
+ /// None => break,
+ /// };
+ /// // .. do something with x
+ /// }
+ /// // is easier written as
+ /// while let Some(x) = y {
+ /// // .. do something with x
+ /// };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_LET_LOOP,
+ complexity,
+ "`loop { if let { ... } else break }`, which can be written as a `while let` loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions collecting an iterator when collect
+ /// is not needed.
+ ///
+ /// ### Why is this bad?
+ /// `collect` causes the allocation of a new data structure,
+ /// when this allocation may not be needed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iterator = vec![1].into_iter();
+ /// let len = iterator.clone().collect::<Vec<_>>().len();
+ /// // should be
+ /// let len = iterator.count();
+ /// ```
+ #[clippy::version = "1.30.0"]
+ pub NEEDLESS_COLLECT,
+ perf,
+ "collecting an iterator when collect is not needed"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks `for` loops over slices with an explicit counter
+ /// and suggests the use of `.enumerate()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `.enumerate()` makes the intent more clear,
+ /// declutters the code and may be faster in some instances.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = vec![1];
+ /// # fn bar(bar: usize, baz: usize) {}
+ /// let mut i = 0;
+ /// for item in &v {
+ /// bar(i, *item);
+ /// i += 1;
+ /// }
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let v = vec![1];
+ /// # fn bar(bar: usize, baz: usize) {}
+ /// for (i, item) in v.iter().enumerate() { bar(i, *item); }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_COUNTER_LOOP,
+ complexity,
+ "for-looping with an explicit counter when `_.enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty `loop` expressions.
+ ///
+ /// ### Why is this bad?
+ /// These busy loops burn CPU cycles without doing
+ /// anything. It is _almost always_ a better idea to `panic!` than to have
+ /// a busy loop.
+ ///
+ /// If panicking isn't possible, think of the environment and either:
+ /// - block on something
+ /// - sleep the thread for some microseconds
+ /// - yield or pause the thread
+ ///
+ /// For `std` targets, this can be done with
+ /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html)
+ /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html).
+ ///
+ /// For `no_std` targets, doing this is more complicated, especially because
+ /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will
+ /// probably need to invoke some target-specific intrinsic. Examples include:
+ /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html)
+ /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html)
+ ///
+ /// ### Example
+ /// ```no_run
+ /// loop {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LOOP,
+ suspicious,
+ "empty `loop {}`, which should block or sleep"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `while let` expressions on iterators.
+ ///
+ /// ### Why is this bad?
+ /// Readability. A simple `for` loop is shorter and conveys
+ /// the intent better.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// while let Some(val) = iter() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_LET_ON_ITERATOR,
+ style,
+ "using a `while let` loop instead of a for loop on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iterating a map (`HashMap` or `BTreeMap`) and
+ /// ignoring either the keys or values.
+ ///
+ /// ### Why is this bad?
+ /// Readability. There are `keys` and `values` methods that
+ /// can be used to express that don't need the values or keys.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for (k, _) in &map {
+ /// ..
+ /// }
+ /// ```
+ ///
+ /// could be replaced by
+ ///
+ /// ```ignore
+ /// for k in map.keys() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FOR_KV_MAP,
+ style,
+ "looping on a map using `iter` when `keys` or `values` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops that will always `break`, `return` or
+ /// `continue` an outer loop.
+ ///
+ /// ### Why is this bad?
+ /// This loop never loops, all it does is obfuscating the
+ /// code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// loop {
+ /// ..;
+ /// break;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEVER_LOOP,
+ correctness,
+ "any loop that will always `break` or `return`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for loops which have a range bound that is a mutable variable
+ ///
+ /// ### Why is this bad?
+ /// One might think that modifying the mutable variable changes the loop bounds
+ ///
+ /// ### Known problems
+ /// False positive when mutation is followed by a `break`, but the `break` is not immediately
+ /// after the mutation:
+ ///
+ /// ```rust
+ /// let mut x = 5;
+ /// for _ in 0..x {
+ /// x += 1; // x is a range bound that is mutated
+ /// ..; // some other expression
+ /// break; // leaves the loop, so mutation is not an issue
+ /// }
+ /// ```
+ ///
+ /// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut foo = 42;
+ /// for i in 0..foo {
+ /// foo -= 1;
+ /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUT_RANGE_BOUND,
+ suspicious,
+ "for loop over a range where one of the bounds is a mutable variable"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether variables used within while loop condition
+ /// can be (and are) mutated in the body.
+ ///
+ /// ### Why is this bad?
+ /// If the condition is unchanged, entering the body of the loop
+ /// will lead to an infinite loop.
+ ///
+ /// ### Known problems
+ /// If the `while`-loop is in a closure, the check for mutation of the
+ /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is
+ /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let i = 0;
+ /// while i > 10 {
+ /// println!("let me loop forever!");
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_IMMUTABLE_CONDITION,
+ correctness,
+ "variables used within while expression are not mutated in the body"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop is being used to push a constant
+ /// value into a Vec.
+ ///
+ /// ### Why is this bad?
+ /// This kind of operation can be expressed more succinctly with
+ /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
+ /// have better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = Vec::new();
+ /// for _ in 0..20 {
+ /// vec.push(item1);
+ /// }
+ /// for _ in 0..30 {
+ /// vec.push(item2);
+ /// }
+ /// ```
+ /// could be written as
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = vec![item1; 20];
+ /// vec.resize(20 + 30, item2);
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub SAME_ITEM_PUSH,
+ style,
+ "the same item is pushed inside of a for loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop has a single element.
+ ///
+ /// ### Why is this bad?
+ /// There is no reason to have a loop of a
+ /// single element.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// for item in &[item1] {
+ /// println!("{}", item);
+ /// }
+ /// ```
+ /// could be written as
+ /// ```rust
+ /// let item1 = 2;
+ /// let item = &item1;
+ /// println!("{}", item);
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub SINGLE_ELEMENT_LOOP,
+ complexity,
+ "there is no reason to have a single element loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for unnecessary `if let` usage in a for loop
+ /// where only the `Some` or `Ok` variant of the iterator element is used.
+ ///
+ /// ### Why is this bad?
+ /// It is verbose and can be simplified
+ /// by first calling the `flatten` method on the `Iterator`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x {
+ /// if let Some(n) = n {
+ /// println!("{}", n);
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x.into_iter().flatten() {
+ /// println!("{}", n);
+ /// }
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub MANUAL_FLATTEN,
+ complexity,
+ "for loops over `Option`s or `Result`s with a single expression can be simplified"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for empty spin loops
+ ///
+ /// ### Why is this bad?
+ /// The loop body should have something like `thread::park()` or at least
+ /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve
+ /// energy. Perhaps even better use an actual lock, if possible.
+ ///
+ /// ### Known problems
+ /// This lint doesn't currently trigger on `while let` or
+ /// `loop { match .. { .. } }` loops, which would be considered idiomatic in
+ /// combination with e.g. `AtomicBool::compare_exchange_weak`.
+ ///
+ /// ### Example
+ ///
+ /// ```ignore
+ /// use core::sync::atomic::{AtomicBool, Ordering};
+ /// let b = AtomicBool::new(true);
+ /// // give a ref to `b` to another thread,wait for it to become false
+ /// while b.load(Ordering::Acquire) {};
+ /// ```
+ /// Use instead:
+ /// ```rust,no_run
+ ///# use core::sync::atomic::{AtomicBool, Ordering};
+ ///# let b = AtomicBool::new(true);
+ /// while b.load(Ordering::Acquire) {
+ /// std::hint::spin_loop()
+ /// }
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub MISSING_SPIN_LOOP,
+ perf,
+ "An empty busy waiting loop"
+}
+
+declare_lint_pass!(Loops => [
+ MANUAL_MEMCPY,
+ MANUAL_FLATTEN,
+ NEEDLESS_RANGE_LOOP,
+ EXPLICIT_ITER_LOOP,
+ EXPLICIT_INTO_ITER_LOOP,
+ ITER_NEXT_LOOP,
+ FOR_LOOPS_OVER_FALLIBLES,
+ WHILE_LET_LOOP,
+ NEEDLESS_COLLECT,
+ EXPLICIT_COUNTER_LOOP,
+ EMPTY_LOOP,
+ WHILE_LET_ON_ITERATOR,
+ FOR_KV_MAP,
+ NEVER_LOOP,
+ MUT_RANGE_BOUND,
+ WHILE_IMMUTABLE_CONDITION,
+ SAME_ITEM_PUSH,
+ SINGLE_ELEMENT_LOOP,
+ MISSING_SPIN_LOOP,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Loops {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let for_loop = higher::ForLoop::hir(expr);
+ if let Some(higher::ForLoop {
+ pat,
+ arg,
+ body,
+ loop_id,
+ span,
+ }) = for_loop
+ {
+ // we don't want to check expanded macros
+ // this check is not at the top of the function
+ // since higher::for_loop expressions are marked as expansions
+ if body.span.from_expansion() {
+ return;
+ }
+ check_for_loop(cx, pat, arg, body, expr, span);
+ if let ExprKind::Block(block, _) = body.kind {
+ never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
+ }
+ }
+
+ // we don't want to check expanded macros
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // check for never_loop
+ if let ExprKind::Loop(block, ..) = expr.kind {
+ never_loop::check(cx, block, expr.hir_id, expr.span, None);
+ }
+
+ // check for `loop { if let {} else break }` that could be `while let`
+ // (also matches an explicit "match" instead of "if let")
+ // (even if the "match" or "if let" is used for declaration)
+ if let ExprKind::Loop(block, _, LoopSource::Loop, _) = expr.kind {
+ // also check for empty `loop {}` statements, skipping those in #[panic_handler]
+ empty_loop::check(cx, expr, block);
+ while_let_loop::check(cx, expr, block);
+ }
+
+ while_let_on_iterator::check(cx, expr);
+
+ if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
+ while_immutable_condition::check(cx, condition, body);
+ missing_spin_loop::check(cx, condition, body);
+ }
+
+ needless_collect::check(expr, cx);
+ }
+}
+
+fn check_for_loop<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+ span: Span,
+) {
+ let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
+ if !is_manual_memcpy_triggered {
+ needless_range_loop::check(cx, pat, arg, body, expr);
+ explicit_counter_loop::check(cx, pat, arg, body, expr);
+ }
+ check_for_loop_arg(cx, pat, arg);
+ for_kv_map::check(cx, pat, arg, body);
+ mut_range_bound::check(cx, arg, body);
+ single_element_loop::check(cx, pat, arg, body, expr);
+ same_item_push::check(cx, pat, arg, body, expr);
+ manual_flatten::check(cx, pat, arg, body, span);
+}
+
+fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
+ let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
+
+ if let ExprKind::MethodCall(method, [self_arg], _) = arg.kind {
+ let method_name = method.ident.as_str();
+ // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
+ match method_name {
+ "iter" | "iter_mut" => explicit_iter_loop::check(cx, self_arg, arg, method_name),
+ "into_iter" => {
+ explicit_iter_loop::check(cx, self_arg, arg, method_name);
+ explicit_into_iter_loop::check(cx, self_arg, arg);
+ },
+ "next" => {
+ next_loop_linted = iter_next_loop::check(cx, arg);
+ },
+ _ => {},
+ }
+ }
+
+ if !next_loop_linted {
+ for_loops_over_fallibles::check(cx, pat, arg);
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use super::NEEDLESS_RANGE_LOOP;
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::has_iter_method;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::middle::region;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::{sym, Symbol};
+use std::iter::{self, Iterator};
+use std::mem;
+
+/// Checks for looping over a range and then indexing a sequence with it.
+/// The iteratee must be a range literal.
++#[expect(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ if let Some(higher::Range {
+ start: Some(start),
+ ref end,
+ limits,
+ }) = higher::Range::hir(arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
+ let mut visitor = VarVisitor {
+ cx,
+ var: canonical_id,
+ indexed_mut: FxHashSet::default(),
+ indexed_indirectly: FxHashMap::default(),
+ indexed_directly: FxHashMap::default(),
+ referenced: FxHashSet::default(),
+ nonindex: false,
+ prefer_mutable: false,
+ };
+ walk_expr(&mut visitor, body);
+
+ // linting condition: we only indexed one variable, and indexed it directly
+ if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
+ let (indexed, (indexed_extent, indexed_ty)) = visitor
+ .indexed_directly
+ .into_iter()
+ .next()
+ .expect("already checked that we have exactly 1 element");
+
+ // ensure that the indexed variable was declared before the loop, see #601
+ if let Some(indexed_extent) = indexed_extent {
+ let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
+ let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
+ if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
+ return;
+ }
+ }
+
+ // don't lint if the container that is indexed does not have .iter() method
+ let has_iter = has_iter_method(cx, indexed_ty);
+ if has_iter.is_none() {
+ return;
+ }
+
+ // don't lint if the container that is indexed into is also used without
+ // indexing
+ if visitor.referenced.contains(&indexed) {
+ return;
+ }
+
+ let starts_at_zero = is_integer_const(cx, start, 0);
+
+ let skip = if starts_at_zero {
+ String::new()
+ } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) {
+ return;
+ } else {
+ format!(".skip({})", snippet(cx, start.span, ".."))
+ };
+
+ let mut end_is_start_plus_val = false;
+
+ let take = if let Some(end) = *end {
+ let mut take_expr = end;
+
+ if let ExprKind::Binary(ref op, left, right) = end.kind {
+ if op.node == BinOpKind::Add {
+ let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
+ let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
+
+ if start_equal_left {
+ take_expr = right;
+ } else if start_equal_right {
+ take_expr = left;
+ }
+
+ end_is_start_plus_val = start_equal_left | start_equal_right;
+ }
+ }
+
+ if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
+ String::new()
+ } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
+ return;
+ } else {
+ match limits {
+ ast::RangeLimits::Closed => {
+ let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
+ format!(".take({})", take_expr + sugg::ONE)
+ },
+ ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")),
+ }
+ }
+ } else {
+ String::new()
+ };
+
+ let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
+ ("mut ", "iter_mut")
+ } else {
+ ("", "iter")
+ };
+
+ let take_is_empty = take.is_empty();
+ let mut method_1 = take;
+ let mut method_2 = skip;
+
+ if end_is_start_plus_val {
+ mem::swap(&mut method_1, &mut method_2);
+ }
+
+ if visitor.nonindex {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_RANGE_LOOP,
+ arg.span,
+ &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
+ |diag| {
+ multispan_sugg(
+ diag,
+ "consider using an iterator",
+ vec![
+ (pat.span, format!("({}, <item>)", ident.name)),
+ (
+ arg.span,
+ format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
+ ),
+ ],
+ );
+ },
+ );
+ } else {
+ let repl = if starts_at_zero && take_is_empty {
+ format!("&{}{}", ref_mut, indexed)
+ } else {
+ format!("{}.{}(){}{}", indexed, method, method_1, method_2)
+ };
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_RANGE_LOOP,
+ arg.span,
+ &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
+ |diag| {
+ multispan_sugg(
+ diag,
+ "consider using an iterator",
+ vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(method, len_args, _) = expr.kind;
+ if len_args.len() == 1;
+ if method.ident.name == sym::len;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = len_args[0].kind;
+ if path.segments.len() == 1;
+ if path.segments[0].ident.name == var;
+ then {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn is_end_eq_array_len<'tcx>(
+ cx: &LateContext<'tcx>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ indexed_ty: Ty<'tcx>,
+) -> bool {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = end.kind;
+ if let ast::LitKind::Int(end_int, _) = lit.node;
+ if let ty::Array(_, arr_len_const) = indexed_ty.kind();
+ if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env);
+ then {
+ return match limits {
+ ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
+ ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
+ };
+ }
+ }
+
+ false
+}
+
+struct VarVisitor<'a, 'tcx> {
+ /// context reference
+ cx: &'a LateContext<'tcx>,
+ /// var name to look for as index
+ var: HirId,
+ /// indexed variables that are used mutably
+ indexed_mut: FxHashSet<Symbol>,
+ /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
+ indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
+ /// subset of `indexed` of vars that are indexed directly: `v[i]`
+ /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
+ indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
+ /// Any names that are used outside an index operation.
+ /// Used to detect things like `&mut vec` used together with `vec[i]`
+ referenced: FxHashSet<Symbol>,
+ /// has the loop variable been used in expressions other than the index of
+ /// an index op?
+ nonindex: bool,
+ /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
+ /// takes `&mut self`
+ prefer_mutable: bool,
+}
+
+impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
+ fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if_chain! {
+ // the indexed container is referenced by a name
+ if let ExprKind::Path(ref seqpath) = seqexpr.kind;
+ if let QPath::Resolved(None, seqvar) = *seqpath;
+ if seqvar.segments.len() == 1;
+ if is_local_used(self.cx, idx, self.var);
+ then {
+ if self.prefer_mutable {
+ self.indexed_mut.insert(seqvar.segments[0].ident.name);
+ }
+ let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
+ let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
+ match res {
+ Res::Local(hir_id) => {
+ let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
+ let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id).unwrap();
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
+ );
+ } else {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ Res::Def(DefKind::Static (_)| DefKind::Const, ..) => {
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
+ );
+ } else {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ _ => (),
+ }
+ }
+ }
+ true
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // a range index op
+ if let ExprKind::MethodCall(meth, [args_0, args_1, ..], _) = &expr.kind;
+ if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
+ || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
+ if !self.check(args_1, args_0, expr);
+ then { return }
+ }
+
+ if_chain! {
+ // an index op
+ if let ExprKind::Index(seqexpr, idx) = expr.kind;
+ if !self.check(idx, seqexpr, expr);
+ then { return }
+ }
+
+ if_chain! {
+ // directly using a variable
+ if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
+ if let Res::Local(local_id) = path.res;
+ then {
+ if local_id == self.var {
+ self.nonindex = true;
+ } else {
+ // not the correct variable, but still a variable
+ self.referenced.insert(path.segments[0].ident.name);
+ }
+ }
+ }
+
+ let old = self.prefer_mutable;
+ match expr.kind {
+ ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
+ self.prefer_mutable = true;
+ self.visit_expr(lhs);
+ self.prefer_mutable = false;
+ self.visit_expr(rhs);
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ self.visit_expr(expr);
+ },
+ ExprKind::Call(f, args) => {
+ self.visit_expr(f);
+ for expr in args {
+ let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = *ty.kind() {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::MethodCall(_, args, _) => {
+ let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ for (ty, expr) in iter::zip(self.cx.tcx.fn_sig(def_id).inputs().skip_binder(), args) {
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = *ty.kind() {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::Closure(_, _, body_id, ..) => {
+ let body = self.cx.tcx.hir().body(body_id);
+ self.visit_expr(&body.value);
+ },
+ _ => walk_expr(self, expr),
+ }
+ self.prefer_mutable = old;
+ }
+}
--- /dev/null
- #[derive(Debug, PartialEq)]
+use clippy_utils::ty::{has_iter_method, implements_trait};
+use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
+use if_chain::if_chain;
+use rustc_ast::ast::{LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_typeck::hir_ty_to_ty;
+use std::iter::Iterator;
+
++#[derive(Debug, PartialEq, Eq)]
+enum IncrementVisitorVarState {
+ Initial, // Not examined yet
+ IncrOnce, // Incremented exactly once, may be a loop counter
+ DontWarn,
+}
+
+/// Scan a for loop for variables that are incremented exactly once and not used after that.
+pub(super) struct IncrementVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ states: HirIdMap<IncrementVisitorVarState>, // incremented variables
+ depth: u32, // depth of conditional expressions
+ done: bool,
+}
+
+impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ states: HirIdMap::default(),
+ depth: 0,
+ done: false,
+ }
+ }
+
+ pub(super) fn into_results(self) -> impl Iterator<Item = HirId> {
+ self.states.into_iter().filter_map(|(id, state)| {
+ if state == IncrementVisitorVarState::IncrOnce {
+ Some(id)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.done {
+ return;
+ }
+
+ // If node is a variable
+ if let Some(def_id) = path_to_local(expr) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial);
+ if *state == IncrementVisitorVarState::IncrOnce {
+ *state = IncrementVisitorVarState::DontWarn;
+ return;
+ }
+
+ match parent.kind {
+ ExprKind::AssignOp(op, lhs, rhs) => {
+ if lhs.hir_id == expr.hir_id {
+ *state = if op.node == BinOpKind::Add
+ && is_integer_const(self.cx, rhs, 1)
+ && *state == IncrementVisitorVarState::Initial
+ && self.depth == 0
+ {
+ IncrementVisitorVarState::IncrOnce
+ } else {
+ // Assigned some other value or assigned multiple times
+ IncrementVisitorVarState::DontWarn
+ };
+ }
+ },
+ ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if is_loop(expr) || is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else if let ExprKind::Continue(_) = expr.kind {
+ self.done = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+}
+
+enum InitializeVisitorState<'hir> {
+ Initial, // Not examined yet
+ Declared(Symbol, Option<Ty<'hir>>), // Declared but not (yet) initialized
+ Initialized {
+ name: Symbol,
+ ty: Option<Ty<'hir>>,
+ initializer: &'hir Expr<'hir>,
+ },
+ DontWarn,
+}
+
+/// Checks whether a variable is initialized at the start of a loop and not modified
+/// and used after the loop.
+pub(super) struct InitializeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here.
+ var_id: HirId,
+ state: InitializeVisitorState<'tcx>,
+ depth: u32, // depth of conditional expressions
+ past_loop: bool,
+}
+
+impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self {
+ Self {
+ cx,
+ end_expr,
+ var_id,
+ state: InitializeVisitorState::Initial,
+ depth: 0,
+ past_loop: false,
+ }
+ }
+
+ pub(super) fn get_result(&self) -> Option<(Symbol, Option<Ty<'tcx>>, &'tcx Expr<'tcx>)> {
+ if let InitializeVisitorState::Initialized { name, ty, initializer } = self.state {
+ Some((name, ty, initializer))
+ } else {
+ None
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ // Look for declarations of the variable
+ if_chain! {
+ if l.pat.hir_id == self.var_id;
+ if let PatKind::Binding(.., ident, _) = l.pat.kind;
+ then {
+ let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty));
+
+ self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
+ InitializeVisitorState::Initialized {
+ initializer: init,
+ ty,
+ name: ident.name,
+ }
+ })
+ }
+ }
+
+ walk_local(self, l);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if matches!(self.state, InitializeVisitorState::DontWarn) {
+ return;
+ }
+ if expr.hir_id == self.end_expr.hir_id {
+ self.past_loop = true;
+ return;
+ }
+ // No need to visit expressions before the variable is
+ // declared
+ if matches!(self.state, InitializeVisitorState::Initial) {
+ return;
+ }
+
+ // If node is the desired variable, see how it's used
+ if path_to_local_id(expr, self.var_id) {
+ if self.past_loop {
+ self.state = InitializeVisitorState::DontWarn;
+ return;
+ }
+
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ match parent.kind {
+ ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = if self.depth == 0 {
+ match self.state {
+ InitializeVisitorState::Declared(name, mut ty) => {
+ if ty.is_none() {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Int(_, LitIntType::Unsuffixed),
+ ..
+ }) = rhs.kind
+ {
+ ty = None;
+ } else {
+ ty = self.cx.typeck_results().expr_ty_opt(rhs);
+ }
+ }
+
+ InitializeVisitorState::Initialized {
+ initializer: rhs,
+ ty,
+ name,
+ }
+ },
+ InitializeVisitorState::Initialized { ty, name, .. } => {
+ InitializeVisitorState::Initialized {
+ initializer: rhs,
+ ty,
+ name,
+ }
+ },
+ _ => InitializeVisitorState::DontWarn,
+ }
+ } else {
+ InitializeVisitorState::DontWarn
+ }
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if !self.past_loop && is_loop(expr) {
+ self.state = InitializeVisitorState::DontWarn;
+ } else if is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+fn is_loop(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::Loop(..))
+}
+
+fn is_conditional(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..))
+}
+
+#[derive(PartialEq, Eq)]
+pub(super) enum Nesting {
+ Unknown, // no nesting detected yet
+ RuledOut, // the iterator is initialized or assigned within scope
+ LookFurther, // no nesting detected, no further walk required
+}
+
+use self::Nesting::{LookFurther, RuledOut, Unknown};
+
+pub(super) struct LoopNestVisitor {
+ pub(super) hir_id: HirId,
+ pub(super) iterator: HirId,
+ pub(super) nesting: Nesting,
+}
+
+impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if stmt.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ } else if self.nesting == Unknown {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if expr.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ return;
+ }
+ match expr.kind {
+ ExprKind::Assign(path, _, _) | ExprKind::AssignOp(_, path, _) => {
+ if path_to_local_id(path, self.iterator) {
+ self.nesting = RuledOut;
+ }
+ },
+ _ => walk_expr(self, expr),
+ }
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx Pat<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if let PatKind::Binding(_, id, ..) = pat.kind {
+ if id == self.iterator {
+ self.nesting = RuledOut;
+ return;
+ }
+ }
+ walk_pat(self, pat);
+ }
+}
+
+/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
+/// actual `Iterator` that the loop uses.
+pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {
+ let impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
+ });
+ if impls_iterator {
+ format!(
+ "{}",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ )
+ } else {
+ // (&x).into_iter() ==> x.iter()
+ // (&mut x).into_iter() ==> x.iter_mut()
+ let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
+ match &arg_ty.kind() {
+ ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, *inner_ty).is_some() => {
+ let method_name = match mutbl {
+ Mutability::Mut => "iter_mut",
+ Mutability::Not => "iter",
+ };
+ let caller = match &arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner,
+ _ => arg,
+ };
+ format!(
+ "{}.{}()",
+ sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
+ method_name,
+ )
+ },
+ _ => format!(
+ "{}.into_iter()",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ ),
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use super::WHILE_LET_ON_ITERATOR;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{
+ get_enclosing_loop_or_closure, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_span::{symbol::sym, Symbol};
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
+ if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
+ // check for `Some(..)` pattern
+ if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind;
+ if let Res::Def(_, pat_did) = pat_path.res;
+ if match_def_path(cx, pat_did, &paths::OPTION_SOME);
+ // check for call to `Iterator::next`
+ if let ExprKind::MethodCall(method_name, [iter_expr], _) = let_expr.kind;
+ if method_name.ident.name == sym::next;
+ if is_trait_method(cx, let_expr, sym::Iterator);
+ if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr);
+ // get the loop containing the match expression
+ if !uses_iter(cx, &iter_expr_struct, if_then);
+ then {
+ (let_expr, iter_expr_struct, iter_expr, some_pat, expr)
+ } else {
+ return;
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ let loop_var = if let Some(some_pat) = some_pat.first() {
+ if is_refutable(cx, some_pat) {
+ // Refutable patterns don't work with for loops.
+ return;
+ }
+ snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
+ } else {
+ "_".into()
+ };
+
+ // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
+ // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
+ // afterwards a mutable borrow of a field isn't necessary.
+ let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
+ || !iter_expr_struct.can_move
+ || !iter_expr_struct.fields.is_empty()
+ || needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
+ {
+ ".by_ref()"
+ } else {
+ ""
+ };
+
+ let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ WHILE_LET_ON_ITERATOR,
+ expr.span.with_hi(scrutinee_expr.span.hi()),
+ "this loop could be written as a `for` loop",
+ "try",
+ format!("for {} in {}{}", loop_var, iterator, by_ref),
+ applicability,
+ );
+}
+
+#[derive(Debug)]
+struct IterExpr {
+ /// The fields used, in order of child to parent.
+ fields: Vec<Symbol>,
+ /// The path being used.
+ path: Res,
+ /// Whether or not the iterator can be moved.
+ can_move: bool,
+}
+
+/// Parses any expression to find out which field of which variable is used. Will return `None` if
+/// the expression might have side effects.
+fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExpr> {
+ let mut fields = Vec::new();
+ let mut can_move = true;
+ loop {
+ if cx
+ .typeck_results()
+ .expr_adjustments(e)
+ .iter()
+ .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
+ {
+ // Custom deref impls need to borrow the whole value as it's captured by reference
+ can_move = false;
+ fields.clear();
+ }
+ match e.kind {
+ ExprKind::Path(ref path) => {
+ break Some(IterExpr {
+ fields,
+ path: cx.qpath_res(path, e.hir_id),
+ can_move,
+ });
+ },
+ ExprKind::Field(base, name) => {
+ fields.push(name.name);
+ e = base;
+ },
+ // Dereferencing a pointer has no side effects and doesn't affect which field is being used.
+ ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base,
+
+ // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
+ // already been seen.
+ ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
+ can_move = false;
+ fields.clear();
+ e = base;
+ },
+ ExprKind::Unary(UnOp::Deref, base) => {
+ can_move = false;
+ fields.clear();
+ e = base;
+ },
+
+ // No effect and doesn't affect which field is being used.
+ ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
+ _ => break None,
+ }
+ }
+}
+
+fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
+ loop {
+ match (&e.kind, fields) {
+ (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
+ e = base;
+ fields = tail_fields;
+ },
+ (ExprKind::Path(path), []) => {
+ break cx.qpath_res(path, e.hir_id) == path_res;
+ },
+ (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
+ _ => break false,
+ }
+ }
+}
+
+/// Checks if the given expression is the same field as, is a child of, or is the parent of the
+/// given field. Used to check if the expression can be used while the given field is borrowed
+/// mutably. e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but
+/// `x.z`, and `y` will return false.
+fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
+ match expr.kind {
+ ExprKind::Field(base, name) => {
+ if let Some((head_field, tail_fields)) = fields.split_first() {
+ if name.name == *head_field && is_expr_same_field(cx, base, tail_fields, path_res) {
+ return true;
+ }
+ // Check if the expression is a parent field
+ let mut fields_iter = tail_fields.iter();
+ while let Some(field) = fields_iter.next() {
+ if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) {
+ return true;
+ }
+ }
+ }
+
+ // Check if the expression is a child field.
+ let mut e = base;
+ loop {
+ match e.kind {
+ ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true,
+ ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
+ ExprKind::Path(ref path) if fields.is_empty() => {
+ break cx.qpath_res(path, e.hir_id) == path_res;
+ },
+ _ => break false,
+ }
+ }
+ },
+ // If the path matches, this is either an exact match, or the expression is a parent of the field.
+ ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
+ ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
+ is_expr_same_child_or_parent_field(cx, base, fields, path_res)
+ },
+ _ => false,
+ }
+}
+
+/// Strips off all field and path expressions. This will return true if a field or path has been
+/// skipped. Used to skip them after failing to check for equality.
+fn skip_fields_and_path<'tcx>(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
+ let mut e = expr;
+ let e = loop {
+ match e.kind {
+ ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
+ ExprKind::Path(_) => return (None, true),
+ _ => break e,
+ }
+ };
+ (Some(e), e.hir_id != expr.hir_id)
+}
+
+/// Checks if the given expression uses the iterator.
+fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ uses_iter: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.uses_iter {
+ // return
+ } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.uses_iter = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
+ if is_res_used(self.cx, self.iter_expr.path, id) {
+ self.uses_iter = true;
+ }
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ let mut v = V {
+ cx,
+ iter_expr,
+ uses_iter: false,
+ };
+ v.visit_expr(container);
+ v.uses_iter
+}
+
++#[expect(clippy::too_many_lines)]
+fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &Expr<'_>) -> bool {
+ struct AfterLoopVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ loop_id: HirId,
+ after_loop: bool,
+ used_iter: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.used_iter {
+ return;
+ }
+ if self.after_loop {
+ if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.used_iter = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
+ self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
+ } else {
+ walk_expr(self, e);
+ }
+ } else if self.loop_id == e.hir_id {
+ self.after_loop = true;
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ struct NestedLoopVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ local_id: HirId,
+ loop_id: HirId,
+ after_loop: bool,
+ found_local: bool,
+ used_after: bool,
+ }
+ impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
+ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ if !self.after_loop {
+ l.pat.each_binding_or_first(&mut |_, id, _, _| {
+ if id == self.local_id {
+ self.found_local = true;
+ }
+ });
+ }
+ if let Some(e) = l.init {
+ self.visit_expr(e);
+ }
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.used_after {
+ return;
+ }
+ if self.after_loop {
+ if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.used_after = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
+ self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
+ } else {
+ walk_expr(self, e);
+ }
+ } else if e.hir_id == self.loop_id {
+ self.after_loop = true;
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) {
+ // The iterator expression will be used on the next iteration (for loops), or on the next call (for
+ // closures) unless it is declared within the enclosing expression. TODO: Check for closures
+ // used where an `FnOnce` type is expected.
+ let local_id = match iter_expr.path {
+ Res::Local(id) => id,
+ _ => return true,
+ };
+ let mut v = NestedLoopVisitor {
+ cx,
+ iter_expr,
+ local_id,
+ loop_id: loop_expr.hir_id,
+ after_loop: false,
+ found_local: false,
+ used_after: false,
+ };
+ v.visit_expr(e);
+ v.used_after || !v.found_local
+ } else {
+ let mut v = AfterLoopVisitor {
+ cx,
+ iter_expr,
+ loop_id: loop_expr.hir_id,
+ after_loop: false,
+ used_iter: false,
+ };
+ v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
+ v.used_iter
+ }
+}
--- /dev/null
- #[allow(clippy::module_name_repetitions)]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use hir::def::{DefKind, Res};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{edition::Edition, sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[macro_use] use...`.
+ ///
+ /// ### Why is this bad?
+ /// Since the Rust 2018 edition you can import
+ /// macro's directly, this is considered idiomatic.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[macro_use]
+ /// use some_macro;
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub MACRO_USE_IMPORTS,
+ pedantic,
+ "#[macro_use] is no longer needed"
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+struct PathAndSpan {
+ path: String,
+ span: Span,
+}
+
+/// `MacroRefData` includes the name of the macro.
+#[derive(Debug, Clone)]
+pub struct MacroRefData {
+ name: String,
+}
+
+impl MacroRefData {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+#[derive(Default)]
- #[allow(clippy::too_many_lines)]
++#[expect(clippy::module_name_repetitions)]
+pub struct MacroUseImports {
+ /// the actual import path used and the span of the attribute above it.
+ imports: Vec<(String, Span)>,
+ /// the span of the macro reference, kept to ensure only one reference is used per macro call.
+ collected: FxHashSet<Span>,
+ mac_refs: Vec<MacroRefData>,
+}
+
+impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
+
+impl MacroUseImports {
+ fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ let name = if name.contains("::") {
+ name.split("::").last().unwrap().to_string()
+ } else {
+ name.to_string()
+ };
+
+ self.mac_refs.push(MacroRefData::new(name));
+ self.collected.insert(call_site);
+ }
+ }
+
+ fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ self.mac_refs.push(MacroRefData::new(name.to_string()));
+ self.collected.insert(call_site);
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if_chain! {
+ if cx.sess().opts.edition >= Edition::Edition2018;
+ if let hir::ItemKind::Use(path, _kind) = &item.kind;
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
+ if let Res::Def(DefKind::Mod, id) = path.res;
+ if !id.is_local();
+ then {
+ for kid in cx.tcx.module_children(id).iter() {
+ if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
+ let span = mac_attr.span;
+ let def_path = cx.tcx.def_path_str(mac_id);
+ self.imports.push((def_path, span));
+ }
+ }
+ } else {
+ if item.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, item.span);
+ }
+ }
+ }
+ }
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+ if attr.span.from_expansion() {
+ self.push_unique_macro(cx, attr.span);
+ }
+ }
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ if expr.span.from_expansion() {
+ self.push_unique_macro(cx, expr.span);
+ }
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
+ if stmt.span.from_expansion() {
+ self.push_unique_macro(cx, stmt.span);
+ }
+ }
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+ if pat.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, pat.span);
+ }
+ }
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) {
+ if ty.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, ty.span);
+ }
+ }
+ fn check_crate_post(&mut self, cx: &LateContext<'_>) {
+ let mut used = FxHashMap::default();
+ let mut check_dup = vec![];
+ for (import, span) in &self.imports {
+ let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
+
+ if let Some(idx) = found_idx {
+ self.mac_refs.remove(idx);
+ let seg = import.split("::").collect::<Vec<_>>();
+
+ match seg.as_slice() {
+ // an empty path is impossible
+ // a path should always consist of 2 or more segments
+ [] | [_] => return,
+ [root, item] => {
+ if !check_dup.contains(&(*item).to_string()) {
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push((*item).to_string());
+ check_dup.push((*item).to_string());
+ }
+ },
+ [root, rest @ ..] => {
+ if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) {
+ let filtered = rest
+ .iter()
+ .filter_map(|item| {
+ if check_dup.contains(&(*item).to_string()) {
+ None
+ } else {
+ Some((*item).to_string())
+ }
+ })
+ .collect::<Vec<_>>();
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push(filtered.join("::"));
+ check_dup.extend(filtered);
+ } else {
+ let rest = rest.to_vec();
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push(rest.join("::"));
+ check_dup.extend(rest.iter().map(ToString::to_string));
+ }
+ },
+ }
+ }
+ }
+
+ let mut suggestions = vec![];
+ for ((root, span), path) in used {
+ if path.len() == 1 {
+ suggestions.push((span, format!("{}::{}", root, path[0])));
+ } else {
+ suggestions.push((span, format!("{}::{{{}}}", root, path.join(", "))));
+ }
+ }
+
+ // If mac_refs is not empty we have encountered an import we could not handle
+ // such as `std::prelude::v1::foo` or some other macro that expands to an import.
+ if self.mac_refs.is_empty() {
+ for (span, import) in suggestions {
+ let help = format!("use {};", import);
+ span_lint_and_sugg(
+ cx,
+ MACRO_USE_IMPORTS,
+ *span,
+ "`macro_use` attributes are no longer needed in the Rust 2018 edition",
+ "remove the attribute and import the macro directly, try",
+ help,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- /// recursing into main() seems like an unintuitive antipattern we should be able to detect.
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet;
+use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for recursion using the entrypoint.
+ ///
+ /// ### Why is this bad?
+ /// Apart from special setups (which we could detect following attributes like #![no_std]),
++ /// recursing into main() seems like an unintuitive anti-pattern we should be able to detect.
+ ///
+ /// ### Example
+ /// ```no_run
+ /// fn main() {
+ /// main();
+ /// }
+ /// ```
+ #[clippy::version = "1.38.0"]
+ pub MAIN_RECURSION,
+ style,
+ "recursion using the entrypoint"
+}
+
+#[derive(Default)]
+pub struct MainRecursion {
+ has_no_std_attr: bool,
+}
+
+impl_lint_pass!(MainRecursion => [MAIN_RECURSION]);
+
+impl LateLintPass<'_> for MainRecursion {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ self.has_no_std_attr = is_no_std_crate(cx);
+ }
+
+ fn check_expr_post(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if self.has_no_std_attr {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Call(func, _) = &expr.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind;
+ if let Some(def_id) = path.res.opt_def_id();
+ if is_entrypoint_fn(cx, def_id);
+ then {
+ span_lint_and_help(
+ cx,
+ MAIN_RECURSION,
+ func.span,
+ &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
+ None,
+ "consider using another function for this recursion"
+ )
+ }
+ }
+ }
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::MANUAL_BITS) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{get_parent_expr, meets_msrv, msrvs};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of `std::mem::size_of::<T>() * 8` when
+ /// `T::BITS` is available.
+ ///
+ /// ### Why is this bad?
+ /// Can be written as the shorter `T::BITS`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// std::mem::size_of::<usize>() * 8;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// usize::BITS as usize;
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub MANUAL_BITS,
+ style,
+ "manual implementation of `size_of::<T>() * 8` can be simplified with `T::BITS`"
+}
+
+#[derive(Clone)]
+pub struct ManualBits {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualBits {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualBits => [MANUAL_BITS]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualBits {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if !meets_msrv(self.msrv, msrvs::MANUAL_BITS) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind;
+ if let BinOpKind::Mul = &bin_op.node;
+ if let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr);
+ if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_));
+ if let ExprKind::Lit(lit) = &other_expr.kind;
+ if let LitKind::Int(8, _) = lit.node;
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let ty_snip = snippet_with_applicability(cx, real_ty.span, "..", &mut app);
+ let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_BITS,
+ expr.span,
+ "usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
+ "consider using",
+ sugg,
+ app,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn get_one_size_of_ty<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr1: &'tcx Expr<'_>,
+ expr2: &'tcx Expr<'_>,
+) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>, &'tcx Expr<'tcx>)> {
+ match (get_size_of_ty(cx, expr1), get_size_of_ty(cx, expr2)) {
+ (Some((real_ty, resolved_ty)), None) => Some((real_ty, resolved_ty, expr2)),
+ (None, Some((real_ty, resolved_ty))) => Some((real_ty, resolved_ty, expr1)),
+ _ => None,
+ }
+}
+
+fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>)> {
+ if_chain! {
+ if let ExprKind::Call(count_func, _func_args) = expr.kind;
+ if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
+
+ if let QPath::Resolved(_, count_func_path) = count_func_qpath;
+ if let Some(segment_zero) = count_func_path.segments.get(0);
+ if let Some(args) = segment_zero.args;
+ if let Some(GenericArg::Type(real_ty)) = args.args.get(0);
+
+ if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
+ if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);
+ then {
+ cx.typeck_results().node_substs(count_func.hir_id).types().next().map(|resolved_ty| (real_ty, resolved_ty))
+ } else {
+ None
+ }
+ }
+}
+
+fn create_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, base_sugg: String) -> String {
+ if let Some(parent_expr) = get_parent_expr(cx, expr) {
+ if is_ty_conversion(parent_expr) {
+ return base_sugg;
+ }
+
+ // These expressions have precedence over casts, the suggestion therefore
+ // needs to be wrapped into parentheses
+ match parent_expr.kind {
+ ExprKind::Unary(..) | ExprKind::AddrOf(..) | ExprKind::MethodCall(..) => {
+ return format!("({base_sugg} as usize)");
+ },
+ _ => {},
+ }
+ }
+
+ format!("{base_sugg} as usize")
+}
+
+fn is_ty_conversion(expr: &Expr<'_>) -> bool {
+ if let ExprKind::Cast(..) = expr.kind {
+ true
+ } else if let ExprKind::MethodCall(path, [_], _) = expr.kind
+ && path.ident.name == rustc_span::sym::try_into
+ {
+ // This is only called for `usize` which implements `TryInto`. Therefore,
+ // we don't have to check here if `self` implements the `TryInto` trait.
+ true
+ } else {
+ false
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::IfLetOrMatch;
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
+use clippy_utils::{
+ can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
+ peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
+};
+use rustc_ast::util::parser::PREC_POSTFIX;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{
+ def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
+ QPath, UnsafeSource,
+};
+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::{sym, SyntaxContext};
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// match Some(0) {
+ /// Some(x) => Some(x + 1),
+ /// None => None,
+ /// };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// Some(0).map(|x| x + 1);
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub MANUAL_MAP,
+ style,
+ "reimplementation of `map`"
+}
+
+declare_lint_pass!(ManualMap => [MANUAL_MAP]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualMap {
++ #[expect(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) {
+ Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else),
+ Some(IfLetOrMatch::Match(
+ scrutinee,
+ [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
+ _,
+ )) => (scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body),
+ _ => return,
+ };
+ if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ 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)
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
+ {
+ return;
+ }
+
+ let expr_ctxt = expr.span.ctxt();
+ let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
+ try_parse_pattern(cx, then_pat, expr_ctxt),
+ else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
+ ) {
+ (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, false)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
+ (then_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
+ (then_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, false, expr_ctxt) {
+ Some(expr) => expr,
+ None => return,
+ };
+
+ // These two lints will go back and forth with each other.
+ if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
+ && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
+ {
+ return;
+ }
+
+ // `map` won't perform any adjustments.
+ if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
+ 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 => "",
+ };
+
+ match can_move_expr_to_closure(cx, some_expr.expr) {
+ Some(captures) => {
+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
+ // TODO: check all the references made in the scrutinee expression. This will require interacting
+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
+ if let Some(binding_ref_mutability) = binding_ref {
+ let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
+ ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
+ _ => None,
+ });
+ if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
+ match captures.get(l) {
+ Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
+ Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
+ return;
+ },
+ Some(CaptureKind::Ref(Mutability::Not)) | None => (),
+ }
+ }
+ }
+ },
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+
+ // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
+ // it's being passed by value.
+ let scrutinee = peel_hir_expr_refs(scrutinee).0;
+ let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
+ let scrutinee_str =
+ if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
+ format!("({})", scrutinee_str)
+ } else {
+ scrutinee_str.into()
+ };
+
+ let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
+ if_chain! {
+ if !some_expr.needs_unsafe_block;
+ if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
+ if func.span.ctxt() == some_expr.expr.span.ctxt();
+ then {
+ snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
+ } else {
+ if path_to_local_id(some_expr.expr, id)
+ && !is_lint_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 {
+ ""
+ };
+ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
+ if some_expr.needs_unsafe_block {
+ format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip)
+ } else {
+ format!("|{}{}| {}", annotation, some_binding, expr_snip)
+ }
+ }
+ }
+ } else if !is_wild_none && explicit_ref.is_none() {
+ // TODO: handle explicit reference annotations.
+ let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
+ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
+ if some_expr.needs_unsafe_block {
+ format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip)
+ } else {
+ format!("|{}| {}", pat_snip, expr_snip)
+ }
+ } 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",
+ if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
+ format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
+ } else {
+ 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<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ match expr.kind {
+ ExprKind::Call(func, [arg])
+ if path_to_local_id(arg, binding)
+ && cx.typeck_results().expr_adjustments(arg).is_empty()
+ && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
+ {
+ 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,
+ },
+}
+
+struct SomeExpr<'tcx> {
+ expr: &'tcx Expr<'tcx>,
+ needs_unsafe_block: bool,
+}
+
+// Try to parse into a recognized `Option` pattern.
+// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
+fn try_parse_pattern<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
+ fn f<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ ref_count: usize,
+ ctxt: SyntaxContext,
+ ) -> Option<OptionPat<'tcx>> {
+ match pat.kind {
+ PatKind::Wild => Some(OptionPat::Wild),
+ PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
+ PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
+ PatKind::TupleStruct(ref qpath, [pattern], _)
+ if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
+ {
+ Some(OptionPat::Some { pattern, ref_count })
+ },
+ _ => None,
+ }
+ }
+ f(cx, pat, 0, ctxt)
+}
+
+// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
+fn get_some_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ needs_unsafe_block: bool,
+ ctxt: SyntaxContext,
+) -> Option<SomeExpr<'tcx>> {
+ // TODO: Allow more complex expressions.
+ match expr.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(ref qpath),
+ ..
+ },
+ [arg],
+ ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
+ expr: arg,
+ needs_unsafe_block,
+ }),
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(expr),
+ rules,
+ ..
+ },
+ _,
+ ) => get_some_expr(
+ cx,
+ expr,
+ needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
+ ctxt,
+ ),
+ _ => None,
+ }
+}
+
+// Checks for the `None` value.
+fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(peel_blocks(expr).kind, ExprKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
+}
--- /dev/null
- use clippy_utils::{is_lint_allowed, meets_msrv, msrvs};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
- #[allow(clippy::module_name_repetitions)]
++use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs};
+use rustc_ast::ast::{self, VisibilityKind};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{self as hir, Expr, ExprKind, QPath};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::ty::DefIdTree;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::{DefId, LocalDefId};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of the non-exhaustive pattern.
+ ///
+ /// ### Why is this bad?
+ /// Using the #[non_exhaustive] attribute expresses better the intent
+ /// and allows possible optimizations when applied to enums.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// _c: (),
+ /// }
+ ///
+ /// enum E {
+ /// A,
+ /// B,
+ /// #[doc(hidden)]
+ /// _C,
+ /// }
+ ///
+ /// struct T(pub i32, pub i32, ());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// enum E {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// struct T(pub i32, pub i32);
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MANUAL_NON_EXHAUSTIVE,
+ style,
+ "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
+}
+
- #[allow(clippy::module_name_repetitions)]
++#[expect(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveStruct {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualNonExhaustiveStruct {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
+
- if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
++#[expect(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveEnum {
+ msrv: Option<RustcVersion>,
+ constructed_enum_variants: FxHashSet<(DefId, DefId)>,
+ potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
+}
+
+impl ManualNonExhaustiveEnum {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ constructed_enum_variants: FxHashSet::default(),
+ potential_enums: Vec::new(),
+ }
+ }
+}
+
+impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
+
+impl EarlyLintPass for ManualNonExhaustiveStruct {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
- if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
++ if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
+
+ if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
+ let (fields, delimiter) = match variant_data {
+ ast::VariantData::Struct(fields, _) => (&**fields, '{'),
+ ast::VariantData::Tuple(fields, _) => (&**fields, '('),
+ ast::VariantData::Unit(_) => return,
+ };
+ if fields.len() <= 1 {
+ return;
+ }
+ let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
+ VisibilityKind::Public => None,
+ VisibilityKind::Inherited => Some(Ok(f)),
+ _ => Some(Err(())),
+ });
+ if let Some(Ok(field)) = iter.next()
+ && iter.next().is_none()
+ && field.ty.kind.is_unit()
+ && field.ident.map_or(true, |name| name.as_str().starts_with('_'))
+ {
+ span_lint_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ item.span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
+ && let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
+ && let Some(snippet) = snippet_opt(cx, header_span)
+ {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
+ format!("#[non_exhaustive] {}", snippet),
+ Applicability::Unspecified,
+ );
+ }
+ diag.span_help(field.span, "remove this field");
+ }
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
- && cx.tcx.is_doc_hidden(id.to_def_id()))
++ if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
+
+ if let hir::ItemKind::Enum(def, _) = &item.kind
+ && def.variants.len() > 1
+ {
+ let mut iter = def.variants.iter().filter_map(|v| {
+ let id = cx.tcx.hir().local_def_id(v.id);
+ (matches!(v.data, hir::VariantData::Unit(_))
+ && v.ident.as_str().starts_with('_')
++ && is_doc_hidden(cx.tcx.hir().attrs(v.id)))
+ .then(|| (id, v.span))
+ });
+ if let Some((id, span)) = iter.next()
+ && iter.next().is_none()
+ {
+ self.potential_enums.push((item.def_id, id, item.span, span));
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
+ && let [.., name] = p.segments
+ && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
+ && name.ident.as_str().starts_with('_')
+ {
+ let variant_id = cx.tcx.parent(id);
+ let enum_id = cx.tcx.parent(variant_id);
+
+ self.constructed_enum_variants.insert((enum_id, variant_id));
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ for &(enum_id, _, enum_span, variant_span) in
+ self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
+ !self
+ .constructed_enum_variants
+ .contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
+ && !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
+ })
+ {
+ span_lint_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ enum_span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
+ && let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
+ && let Some(snippet) = snippet_opt(cx, header_span)
+ {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
+ format!("#[non_exhaustive] {}", snippet),
+ Applicability::Unspecified,
+ );
+ }
+ diag.span_help(variant_span, "remove this variant");
+ },
+ );
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) {
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{eq_expr_value, higher, match_def_path, meets_msrv, msrvs, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::BinOpKind;
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
+ /// the pattern's length.
+ ///
+ /// ### Why is this bad?
+ /// Using `str:strip_{prefix,suffix}` is safer and may have better performance as there is no
+ /// slicing which may panic and the compiler does not need to insert this panic code. It is
+ /// also sometimes more readable as it removes the need for duplicating or storing the pattern
+ /// used by `str::{starts,ends}_with` and in the slicing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if s.starts_with("hello, ") {
+ /// assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if let Some(end) = s.strip_prefix("hello, ") {
+ /// assert_eq!(end.to_uppercase(), "WORLD!");
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub MANUAL_STRIP,
+ complexity,
+ "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
+}
+
+pub struct ManualStrip {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum StripKind {
+ Prefix,
+ Suffix,
+}
+
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
+ if let ExprKind::MethodCall(_, [target_arg, pattern], _) = cond.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
+ if let ExprKind::Path(target_path) = &target_arg.kind;
+ then {
+ let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
+ StripKind::Prefix
+ } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
+ StripKind::Suffix
+ } else {
+ return;
+ };
+ let target_res = cx.qpath_res(target_path, target_arg.hir_id);
+ if target_res == Res::Err {
+ return;
+ };
+
+ if_chain! {
+ if let Res::Local(hir_id) = target_res;
+ if let Some(used_mutably) = mutated_variables(then, cx);
+ if used_mutably.contains(&hir_id);
+ then {
+ return;
+ }
+ }
+
+ let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
+ if !strippings.is_empty() {
+
+ let kind_word = match strip_kind {
+ StripKind::Prefix => "prefix",
+ StripKind::Suffix => "suffix",
+ };
+
+ let test_span = expr.span.until(then.span);
+ span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {} manually", kind_word), |diag| {
+ diag.span_note(test_span, &format!("the {} was tested here", kind_word));
+ multispan_sugg(
+ diag,
+ &format!("try using the `strip_{}` method", kind_word),
+ vec![(test_span,
+ format!("if let Some(<stripped>) = {}.strip_{}({}) ",
+ snippet(cx, target_arg.span, ".."),
+ kind_word,
+ snippet(cx, pattern.span, "..")))]
+ .into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
+ );
+ });
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
+fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::MethodCall(_, [arg], _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, method_def_id, &paths::STR_LEN);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+}
+
+// Returns the length of the `expr` if it's a constant string or char.
+fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
+ let (value, _) = constant(cx, cx.typeck_results(), expr)?;
+ match value {
+ Constant::Str(value) => Some(value.len() as u128),
+ Constant::Char(value) => Some(value.len_utf8() as u128),
+ _ => None,
+ }
+}
+
+// Tests if `expr` equals the length of the pattern.
+fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Int(n, _),
+ ..
+ }) = expr.kind
+ {
+ constant_length(cx, pattern).map_or(false, |length| length == n)
+ } else {
+ len_arg(cx, expr).map_or(false, |arg| eq_expr_value(cx, pattern, arg))
+ }
+}
+
+// Tests if `expr` is a `&str`.
+fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match cx.typeck_results().expr_ty_adjusted(expr).kind() {
+ ty::Ref(_, ty, _) => ty.is_str(),
+ _ => false,
+ }
+}
+
+// Removes the outer `AddrOf` expression if needed.
+fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, unref) = &expr.kind {
+ unref
+ } else {
+ expr
+ }
+}
+
+// Find expressions where `target` is stripped using the length of `pattern`.
+// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}`
+// method.
+fn find_stripping<'tcx>(
+ cx: &LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Vec<Span> {
+ struct StrippingFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'tcx>,
+ results: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ if_chain! {
+ if is_ref_str(self.cx, ex);
+ let unref = peel_ref(ex);
+ if let ExprKind::Index(indexed, index) = &unref.kind;
+ if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
+ if let ExprKind::Path(path) = &indexed.kind;
+ if self.cx.qpath_res(path, ex.hir_id) == self.target;
+ then {
+ match (self.strip_kind, start, end) {
+ (StripKind::Prefix, Some(start), None) => {
+ if eq_pattern_length(self.cx, self.pattern, start) {
+ self.results.push(ex.span);
+ return;
+ }
+ },
+ (StripKind::Suffix, None, Some(end)) => {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
+ if let Some(left_arg) = len_arg(self.cx, left);
+ if let ExprKind::Path(left_path) = &left_arg.kind;
+ if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
+ if eq_pattern_length(self.cx, self.pattern, right);
+ then {
+ self.results.push(ex.span);
+ return;
+ }
+ }
+ },
+ _ => {}
+ }
+ }
+ }
+
+ walk_expr(self, ex);
+ }
+ }
+
+ let mut finder = StrippingFinder {
+ cx,
+ strip_kind,
+ target,
+ pattern,
+ results: vec![],
+ };
+ walk_expr(&mut finder, expr);
+ finder.results
+}
--- /dev/null
- let (message, sugg_method) = if is_copy && meets_msrv(self.msrv.as_ref(), &msrvs::ITERATOR_COPIED) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
+use clippy_utils::{is_trait_method, meets_msrv, msrvs, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `map(|x| x.clone())` or
+ /// dereferencing closures for `Copy` types, on `Iterator` or `Option`,
+ /// and suggests `cloned()` or `copied()` instead
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.map(|i| *i);
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.cloned();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAP_CLONE,
+ style,
+ "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types"
+}
+
+pub struct MapClone {
+ msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(MapClone => [MAP_CLONE]);
+
+impl MapClone {
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MapClone {
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if_chain! {
+ if let hir::ExprKind::MethodCall(method, args, _) = e.kind;
+ if args.len() == 2;
+ if method.ident.name == sym::map;
+ let ty = cx.typeck_results().expr_ty(&args[0]);
+ if is_type_diagnostic_item(cx, ty, sym::Option) || is_trait_method(cx, e, sym::Iterator);
+ if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind;
+ then {
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = peel_blocks(&closure_body.value);
+ match closure_body.params[0].pat.kind {
+ hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
+ hir::BindingAnnotation::Unannotated, .., name, None
+ ) = inner.kind {
+ if ident_eq(name, closure_expr) {
+ self.lint_explicit_closure(cx, e.span, args[0].span, true);
+ }
+ },
+ hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => {
+ match closure_expr.kind {
+ hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
+ if ident_eq(name, inner) {
+ if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
+ self.lint_explicit_closure(cx, e.span, args[0].span, true);
+ }
+ }
+ },
+ hir::ExprKind::MethodCall(method, [obj], _) => if_chain! {
+ if ident_eq(name, obj) && method.ident.name == sym::clone;
+ if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id);
+ if let Some(trait_id) = cx.tcx.trait_of_item(fn_id);
+ if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id);
+ // no autoderefs
+ if !cx.typeck_results().expr_adjustments(obj).iter()
+ .any(|a| matches!(a.kind, Adjust::Deref(Some(..))));
+ then {
+ let obj_ty = cx.typeck_results().expr_ty(obj);
+ if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
+ if matches!(mutability, Mutability::Not) {
+ let copy = is_copy(cx, *ty);
+ self.lint_explicit_closure(cx, e.span, args[0].span, copy);
+ }
+ } else {
+ lint_needless_cloning(cx, e.span, args[0].span);
+ }
+ }
+ },
+ _ => {},
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
+ path.segments.len() == 1 && path.segments[0].ident == name
+ } else {
+ false
+ }
+}
+
+fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ root.trim_start(receiver).unwrap(),
+ "you are needlessly cloning iterator elements",
+ "remove the `map` call",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+}
+
+impl MapClone {
+ fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) {
+ let mut applicability = Applicability::MachineApplicable;
+
++ let (message, sugg_method) = if is_copy && meets_msrv(self.msrv, msrvs::ITERATOR_COPIED) {
+ ("you are using an explicit closure for copying elements", "copied")
+ } else {
+ ("you are using an explicit closure for cloning elements", "cloned")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ replace,
+ message,
+ &format!("consider calling the dedicated `{}` method", sugg_method),
+ format!(
+ "{}.{}()",
+ snippet_with_applicability(cx, root, "..", &mut applicability),
+ sugg_method,
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
+use core::cmp::Ordering;
+use core::iter;
+use core::slice;
+use rustc_arena::DroplessArena;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Symbol;
+use std::collections::hash_map::Entry;
+
+use super::MATCH_SAME_ARMS;
+
- #[allow(clippy::similar_names)]
++#[expect(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
+ let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(arm.body);
+ h.finish()
+ };
+
+ let arena = DroplessArena::default();
+ let normalized_pats: Vec<_> = arms
+ .iter()
+ .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat))
+ .collect();
+
+ // The furthest forwards a pattern can move without semantic changes
+ let forwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[i + 1..]
+ .iter()
+ .enumerate()
+ .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
+ .unwrap_or(normalized_pats.len())
+ })
+ .collect();
+
+ // The furthest backwards a pattern can move without semantic changes
+ let backwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[..i]
+ .iter()
+ .enumerate()
+ .rev()
+ .zip(forwards_blocking_idxs[..i].iter().copied().rev())
+ .skip_while(|&(_, forward_block)| forward_block > i)
+ .find_map(|((j, other), forward_block)| {
+ (forward_block == i || pat.has_overlapping_values(other)).then(|| j)
+ })
+ .unwrap_or(0)
+ })
+ .collect();
+
+ let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
+ let min_index = usize::min(lindex, rindex);
+ let max_index = usize::max(lindex, rindex);
+
+ let mut local_map: HirIdMap<HirId> = HirIdMap::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 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
+ // If both arms overlap with an arm in between then these can't be merged either.
+ !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
+ && lhs.guard.is_none()
+ && rhs.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();
+ for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
+ if matches!(arm2.pat.kind, PatKind::Wild) {
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ arm1.span,
+ "this match arm has an identical body to the `_` wildcard arm",
+ |diag| {
+ diag.span_suggestion(
+ arm1.span,
+ "try removing the arm",
+ String::new(),
+ Applicability::MaybeIncorrect,
+ )
+ .help("or try changing either arm body")
+ .span_note(arm2.span, "`_` wildcard arm here");
+ },
+ );
+ } else {
+ let back_block = backwards_blocking_idxs[j];
+ let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
+ (arm1, arm2)
+ } else {
+ (arm2, arm1)
+ };
+
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ keep_arm.span,
+ "this match arm has an identical body to another arm",
+ |diag| {
+ let move_pat_snip = snippet(cx, move_arm.pat.span, "<pat2>");
+ let keep_pat_snip = snippet(cx, keep_arm.pat.span, "<pat1>");
+
+ diag.span_suggestion(
+ keep_arm.pat.span,
+ "try merging the arm patterns",
+ format!("{} | {}", keep_pat_snip, move_pat_snip),
+ Applicability::MaybeIncorrect,
+ )
+ .help("or try changing either arm body")
+ .span_note(move_arm.span, "other arm here");
+ },
+ );
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum NormalizedPat<'a> {
+ Wild,
+ Struct(Option<DefId>, &'a [(Symbol, Self)]),
+ Tuple(Option<DefId>, &'a [Self]),
+ Or(&'a [Self]),
+ Path(Option<DefId>),
+ LitStr(Symbol),
+ LitBytes(&'a [u8]),
+ LitInt(u128),
+ LitBool(bool),
+ Range(PatRange),
+ /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise
+ /// the first value contains everything before the `..` wildcard pattern, and the second value
+ /// contains everything afterwards. Note that either side, or both sides, may contain zero
+ /// patterns.
+ Slice(&'a [Self], Option<&'a [Self]>),
+}
+
+#[derive(Clone, Copy)]
+struct PatRange {
+ start: u128,
+ end: u128,
+ bounds: RangeEnd,
+}
+impl PatRange {
+ fn contains(&self, x: u128) -> bool {
+ x >= self.start
+ && match self.bounds {
+ RangeEnd::Included => x <= self.end,
+ RangeEnd::Excluded => x < self.end,
+ }
+ }
+
+ fn overlaps(&self, other: &Self) -> bool {
+ // Note: Empty ranges are impossible, so this is correct even though it would return true if an
+ // empty exclusive range were to reside within an inclusive range.
+ (match self.bounds {
+ RangeEnd::Included => self.end >= other.start,
+ RangeEnd::Excluded => self.end > other.start,
+ } && match other.bounds {
+ RangeEnd::Included => self.start <= other.end,
+ RangeEnd::Excluded => self.start < other.end,
+ })
+ }
+}
+
+/// Iterates over the pairs of fields with matching names.
+fn iter_matching_struct_fields<'a>(
+ left: &'a [(Symbol, NormalizedPat<'a>)],
+ right: &'a [(Symbol, NormalizedPat<'a>)],
+) -> impl Iterator<Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>)> + 'a {
+ struct Iter<'a>(
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ );
+ impl<'a> Iterator for Iter<'a> {
+ type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>);
+ fn next(&mut self) -> Option<Self::Item> {
+ // Note: all the fields in each slice are sorted by symbol value.
+ let mut left = self.0.next()?;
+ let mut right = self.1.next()?;
+ loop {
+ match left.0.cmp(&right.0) {
+ Ordering::Equal => return Some((&left.1, &right.1)),
+ Ordering::Less => left = self.0.next()?,
+ Ordering::Greater => right = self.1.next()?,
+ }
+ }
+ }
+ }
+ Iter(left.iter(), right.iter())
+}
+
- #[allow(clippy::too_many_lines)]
++#[expect(clippy::similar_names)]
+impl<'a> NormalizedPat<'a> {
++ #[expect(clippy::too_many_lines)]
+ fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
+ match pat.kind {
+ PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
+ PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => {
+ Self::from_pat(cx, arena, pat)
+ },
+ PatKind::Struct(ref path, fields, _) => {
+ let fields =
+ arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat))));
+ fields.sort_by_key(|&(name, _)| name);
+ Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields)
+ },
+ PatKind::TupleStruct(ref path, pats, wild_idx) => {
+ let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() {
+ Some(x) => x,
+ None => return Self::Wild,
+ };
+ let (var_id, variant) = if adt.is_enum() {
+ match cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ Some(x) => (Some(x), adt.variant_with_ctor_id(x)),
+ None => return Self::Wild,
+ }
+ } else {
+ (None, adt.non_enum_variant())
+ };
+ let (front, back) = match wild_idx {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(var_id, pats)
+ },
+ PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()),
+ PatKind::Tuple(pats, wild_idx) => {
+ let field_count = match cx.typeck_results().pat_ty(pat).kind() {
+ ty::Tuple(subs) => subs.len(),
+ _ => return Self::Wild,
+ };
+ let (front, back) = match wild_idx {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(None, pats)
+ },
+ PatKind::Lit(e) => match &e.kind {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Str(sym, _) => Self::LitStr(sym),
+ LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
+ LitKind::Byte(val) => Self::LitInt(val.into()),
+ LitKind::Char(val) => Self::LitInt(val.into()),
+ LitKind::Int(val, _) => Self::LitInt(val),
+ LitKind::Bool(val) => Self::LitBool(val),
+ LitKind::Float(..) | LitKind::Err(_) => Self::Wild,
+ },
+ _ => Self::Wild,
+ },
+ PatKind::Range(start, end, bounds) => {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ let start = match start {
+ None => 0,
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => val,
+ LitKind::Char(val) => val.into(),
+ LitKind::Byte(val) => val.into(),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ let (end, bounds) = match end {
+ None => (u128::MAX, RangeEnd::Included),
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => (val, bounds),
+ LitKind::Char(val) => (val.into(), bounds),
+ LitKind::Byte(val) => (val.into(), bounds),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ Self::Range(PatRange { start, end, bounds })
+ },
+ PatKind::Slice(front, wild_pat, back) => Self::Slice(
+ arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ ),
+ }
+ }
+
+ /// Checks if two patterns overlap in the values they can match assuming they are for the same
+ /// type.
+ fn has_overlapping_values(&self, other: &Self) -> bool {
+ match (*self, *other) {
+ (Self::Wild, _) | (_, Self::Wild) => true,
+ (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => {
+ pats.iter().any(|pat| pat.has_overlapping_values(other))
+ },
+ (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => {
+ if lpath != rpath {
+ return false;
+ }
+ iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => {
+ if lpath != rpath {
+ return false;
+ }
+ lpats
+ .iter()
+ .zip(rpats.iter())
+ .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Path(x), Self::Path(y)) => x == y,
+ (Self::LitStr(x), Self::LitStr(y)) => x == y,
+ (Self::LitBytes(x), Self::LitBytes(y)) => x == y,
+ (Self::LitInt(x), Self::LitInt(y)) => x == y,
+ (Self::LitBool(x), Self::LitBool(y)) => x == y,
+ (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
+ (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
+ (Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
+ lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
+ },
+ (Self::Slice(pats, None), Self::Slice(front, Some(back)))
+ | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
+ // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
+ // then the minium length required will be greater than the length of `pats`.
+ if pats.len() < front.len() + back.len() {
+ return false;
+ }
+ pats[..front.len()]
+ .iter()
+ .zip(front.iter())
+ .chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
+ .all(|(x, y)| x.has_overlapping_values(y))
+ },
+ (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
+ .iter()
+ .zip(rfront.iter())
+ .chain(lback.iter().rev().zip(rback.iter().rev()))
+ .all(|(x, y)| x.has_overlapping_values(y)),
+
+ // Enums can mix unit variants with tuple/struct variants. These can never overlap.
+ (Self::Path(_), Self::Tuple(..) | Self::Struct(..))
+ | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false,
+
+ // Tuples can be matched like a struct.
+ (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => {
+ // TODO: check fields here.
+ x == y
+ },
+
+ // TODO: Lit* with Path, Range with Path, LitBytes with Slice
+ _ => true,
+ }
+ }
+}
+
+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: HirIdSet) -> bool {
+ let mut result = true;
+ pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
+ result && ids.is_empty()
+}
--- /dev/null
- use clippy_utils::source::{indent_of, snippet_block, snippet_with_applicability};
+use clippy_utils::diagnostics::span_lint_and_sugg;
- use rustc_hir::{Arm, Expr, ExprKind, Local, Node, PatKind};
++use clippy_utils::macros::HirNode;
++use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
+use rustc_errors::Applicability;
- #[allow(clippy::too_many_lines)]
- pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
++use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
+use rustc_lint::LateContext;
++use rustc_span::Span;
+
+use super::MATCH_SINGLE_BINDING;
+
- // If this match is in a local (`let`) stmt
- let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
- (
- parent_let_node.span,
++enum AssignmentExpr {
++ Assign { span: Span, match_span: Span },
++ Local { span: Span, pat_span: Span },
++}
++
++#[expect(clippy::too_many_lines)]
++pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
+ if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
+ return;
+ }
+
+ let matched_vars = ex.span;
+ let bind_names = arms[0].pat.span;
+ let match_body = peel_blocks(arms[0].body);
+ let mut snippet_body = if match_body.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
+ } else {
+ snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
+ };
+
+ // Do we need to add ';' to suggestion ?
+ match match_body.kind {
+ ExprKind::Block(block, _) => {
+ // macro + expr_ty(body) == ()
+ if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ _ => {
+ // expr_ty(body) == ()
+ if cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ }
+
+ let mut applicability = Applicability::MaybeIncorrect;
+ match arms[0].pat.kind {
+ PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
- snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
++ let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
++ Some(AssignmentExpr::Assign { span, match_span }) => {
++ let sugg = sugg_with_curlies(
++ cx,
++ (ex, expr),
++ (bind_names, matched_vars),
++ &*snippet_body,
++ &mut applicability,
++ Some(span),
++ );
++
++ span_lint_and_sugg(
++ cx,
++ MATCH_SINGLE_BINDING,
++ span.to(match_span),
++ "this assignment could be simplified",
++ "consider removing the `match` expression",
++ sugg,
++ applicability,
++ );
++
++ return;
++ },
++ Some(AssignmentExpr::Local { span, pat_span }) => (
++ span,
+ format!(
+ "let {} = {};\n{}let {} = {};",
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
- )
- } else {
- // If we are in closure, we need curly braces around suggestion
- let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
- let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
- if let Some(parent_expr) = get_parent_expr(cx, expr) {
- if let ExprKind::Closure(..) = parent_expr.kind {
- cbrace_end = format!("\n{}}}", indent);
- // Fix body indent due to the closure
- indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- cbrace_start = format!("{{\n{}", indent);
- }
- }
- // If the parent is already an arm, and the body is another match statement,
- // we need curly braces around suggestion
- let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
- if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
- if let ExprKind::Match(..) = arm.body.kind {
- cbrace_end = format!("\n{}}}", indent);
- // Fix body indent due to the match
- indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- cbrace_start = format!("{{\n{}", indent);
- }
- }
- (
- expr.span,
- format!(
- "{}let {} = {};\n{}{}{}",
- cbrace_start,
- snippet_with_applicability(cx, bind_names, "..", &mut applicability),
- snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
- indent,
- snippet_body,
- cbrace_end
- ),
- )
++ snippet_with_applicability(cx, pat_span, "..", &mut applicability),
+ snippet_body
+ ),
- "consider using `let` statement",
++ ),
++ None => {
++ let sugg = sugg_with_curlies(
++ cx,
++ (ex, expr),
++ (bind_names, matched_vars),
++ &*snippet_body,
++ &mut applicability,
++ None,
++ );
++ (expr.span, sugg)
++ },
+ };
++
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ target_span,
+ "this match could be written as a `let` statement",
- /// Returns true if the `ex` match expression is in a local (`let`) statement
- fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
++ "consider using a `let` statement",
+ sugg,
+ applicability,
+ );
+ },
+ PatKind::Wild => {
+ if ex.can_have_side_effects() {
+ let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
+ let sugg = format!(
+ "{};\n{}{}",
+ snippet_with_applicability(cx, ex.span, "..", &mut applicability),
+ indent,
+ snippet_body
+ );
++
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its scrutinee and body",
+ "consider using the scrutinee and body instead",
+ sugg,
+ applicability,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its body itself",
+ "consider using the match body instead",
+ snippet_body,
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+}
+
- if_chain! {
- if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
- if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
- then {
- return Some(parent_let_expr);
- }
++/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
++fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
+ let map = &cx.tcx.hir();
++
++ if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
++ return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
++ Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
++ span: parent_let_expr.span,
++ pat_span: parent_let_expr.pat.span(),
++ }),
++ Some(Node::Expr(Expr {
++ kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
++ ..
++ })) => Some(AssignmentExpr::Assign {
++ span: parent_assign_expr.span,
++ match_span: match_expr.span,
++ }),
++ _ => None,
++ };
+ }
++
+ None
+}
++
++fn sugg_with_curlies<'a>(
++ cx: &LateContext<'a>,
++ (ex, match_expr): (&Expr<'a>, &Expr<'a>),
++ (bind_names, matched_vars): (Span, Span),
++ snippet_body: &str,
++ applicability: &mut Applicability,
++ assignment: Option<Span>,
++) -> String {
++ let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
++
++ let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
++ if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
++ if let ExprKind::Closure(..) = parent_expr.kind {
++ cbrace_end = format!("\n{}}}", indent);
++ // Fix body indent due to the closure
++ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
++ cbrace_start = format!("{{\n{}", indent);
++ }
++ }
++
++ // If the parent is already an arm, and the body is another match statement,
++ // we need curly braces around suggestion
++ let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
++ if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
++ if let ExprKind::Match(..) = arm.body.kind {
++ cbrace_end = format!("\n{}}}", indent);
++ // Fix body indent due to the match
++ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
++ cbrace_start = format!("{{\n{}", indent);
++ }
++ }
++
++ let assignment_str = assignment.map_or_else(String::new, |span| {
++ let mut s = snippet(cx, span, "..").to_string();
++ s.push_str(" = ");
++ s
++ });
++
++ format!(
++ "{}let {} = {};\n{}{}{}{}",
++ cbrace_start,
++ snippet_with_applicability(cx, bind_names, "..", applicability),
++ snippet_with_applicability(cx, matched_vars, "..", applicability),
++ indent,
++ assignment_str,
++ snippet_body,
++ cbrace_end
++ )
++}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns};
+use rustc_errors::Applicability;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::{Arm, Expr, PatKind, PathSegment, QPath, Ty, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, VariantDef};
+use rustc_span::sym;
+
+use super::{MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_ENUM_MATCH_ARM};
+
- #[allow(clippy::match_same_arms)]
++#[expect(clippy::too_many_lines)]
+pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+ let ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ let adt_def = match ty.kind() {
+ ty::Adt(adt_def, _)
+ if adt_def.is_enum()
+ && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) =>
+ {
+ adt_def
+ },
+ _ => return,
+ };
+
+ // First pass - check for violation, but don't do much book-keeping because this is hopefully
+ // the uncommon case, and the book-keeping is slightly expensive.
+ let mut wildcard_span = None;
+ let mut wildcard_ident = None;
+ let mut has_non_wild = false;
+ for arm in arms {
+ match peel_hir_pat_refs(arm.pat).0.kind {
+ PatKind::Wild => wildcard_span = Some(arm.pat.span),
+ PatKind::Binding(_, _, ident, None) => {
+ wildcard_span = Some(arm.pat.span);
+ wildcard_ident = Some(ident);
+ },
+ _ => has_non_wild = true,
+ }
+ }
+ let wildcard_span = match wildcard_span {
+ Some(x) if has_non_wild => x,
+ _ => return,
+ };
+
+ // Accumulate the variants which should be put in place of the wildcard because they're not
+ // already covered.
+ let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x));
+ let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect();
+
+ let mut path_prefix = CommonPrefixSearcher::None;
+ for arm in arms {
+ // Guards mean that this case probably isn't exhaustively covered. Technically
+ // this is incorrect, as we should really check whether each variant is exhaustively
+ // covered by the set of guards that cover it, but that's really hard to do.
+ recurse_or_patterns(arm.pat, |pat| {
+ let path = match &peel_hir_pat_refs(pat).0.kind {
+ PatKind::Path(path) => {
+ let id = match cx.qpath_res(path, pat.hir_id) {
+ Res::Def(
+ DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst,
+ _,
+ ) => return,
+ Res::Def(_, id) => id,
+ _ => return,
+ };
+ if arm.guard.is_none() {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ path
+ },
+ PatKind::TupleStruct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ }
+ path
+ },
+ PatKind::Struct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
+ missing_variants.retain(|e| e.def_id != id);
+ }
+ }
+ path
+ },
+ _ => return,
+ };
+ match path {
+ QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
+ QPath::TypeRelative(
+ Ty {
+ kind: TyKind::Path(QPath::Resolved(_, path)),
+ ..
+ },
+ _,
+ ) => path_prefix.with_prefix(path.segments),
+ _ => (),
+ }
+ });
+ }
+
+ let format_suggestion = |variant: &VariantDef| {
+ format!(
+ "{}{}{}{}",
+ if let Some(ident) = wildcard_ident {
+ format!("{} @ ", ident.name)
+ } else {
+ String::new()
+ },
+ if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
+ let mut s = String::new();
+ for seg in path_prefix {
+ s.push_str(seg.ident.as_str());
+ s.push_str("::");
+ }
+ s
+ } else {
+ let mut s = cx.tcx.def_path_str(adt_def.did());
+ s.push_str("::");
+ s
+ },
+ variant.name,
+ match variant.ctor_kind {
+ CtorKind::Fn if variant.fields.len() == 1 => "(_)",
+ CtorKind::Fn => "(..)",
+ CtorKind::Const => "",
+ CtorKind::Fictive => "{ .. }",
+ }
+ )
+ };
+
+ match missing_variants.as_slice() {
+ [] => (),
+ [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg(
+ cx,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ wildcard_span,
+ "wildcard matches only a single variant and will also match any future added variants",
+ "try this",
+ format_suggestion(x),
+ Applicability::MaybeIncorrect,
+ ),
+ variants => {
+ let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
+ let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden {
+ suggestions.push("_".into());
+ "wildcard matches known variants and will also match future added variants"
+ } else {
+ "wildcard match will also match any future added variants"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ WILDCARD_ENUM_MATCH_ARM,
+ wildcard_span,
+ message,
+ "try this",
+ suggestions.join(" | "),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ };
+}
+
+enum CommonPrefixSearcher<'a> {
+ None,
+ Path(&'a [PathSegment<'a>]),
+ Mixed,
+}
+impl<'a> CommonPrefixSearcher<'a> {
+ fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
+ match path {
+ [path @ .., _] => self.with_prefix(path),
+ [] => (),
+ }
+ }
+
+ fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
+ match self {
+ Self::None => *self = Self::Path(path),
+ Self::Path(self_path)
+ if path
+ .iter()
+ .map(|p| p.ident.name)
+ .eq(self_path.iter().map(|p| p.ident.name)) => {},
+ Self::Path(_) => *self = Self::Mixed,
+ Self::Mixed => (),
+ }
+ }
+}
+
+fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
+ cx.tcx.is_doc_hidden(variant_def.def_id) || cx.tcx.has_attr(variant_def.def_id, sym::unstable)
+}
--- /dev/null
- if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO)
+use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{Span, SpanData, SyntaxContext};
+
+mod infallible_destructuring_match;
+mod match_as_ref;
+mod match_bool;
+mod match_like_matches;
+mod match_ref_pats;
+mod match_same_arms;
+mod match_single_binding;
+mod match_wild_enum;
+mod match_wild_err_arm;
+mod needless_match;
+mod overlapping_arms;
+mod redundant_pattern_match;
+mod rest_pat_in_fully_bound_struct;
+mod single_match;
+mod wild_in_or_pats;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for matches with a single arm where an `if let`
+ /// will usually suffice.
+ ///
+ /// ### Why is this bad?
+ /// Just readability – `if let` nests less than a `match`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn bar(stool: &str) {}
+ /// # let x = Some("abc");
+ /// // Bad
+ /// match x {
+ /// Some(ref foo) => bar(foo),
+ /// _ => (),
+ /// }
+ ///
+ /// // Good
+ /// if let Some(ref foo) = x {
+ /// bar(foo);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_MATCH,
+ style,
+ "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for matches with two arms where an `if let else` will
+ /// usually suffice.
+ ///
+ /// ### Why is this bad?
+ /// Just readability – `if let` nests less than a `match`.
+ ///
+ /// ### Known problems
+ /// Personal style preferences may differ.
+ ///
+ /// ### Example
+ /// Using `match`:
+ ///
+ /// ```rust
+ /// # fn bar(foo: &usize) {}
+ /// # let other_ref: usize = 1;
+ /// # let x: Option<&usize> = Some(&1);
+ /// match x {
+ /// Some(ref foo) => bar(foo),
+ /// _ => bar(&other_ref),
+ /// }
+ /// ```
+ ///
+ /// Using `if let` with `else`:
+ ///
+ /// ```rust
+ /// # fn bar(foo: &usize) {}
+ /// # let other_ref: usize = 1;
+ /// # let x: Option<&usize> = Some(&1);
+ /// if let Some(ref foo) = x {
+ /// bar(foo);
+ /// } else {
+ /// bar(&other_ref);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_MATCH_ELSE,
+ pedantic,
+ "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for matches where all arms match a reference,
+ /// suggesting to remove the reference and deref the matched expression
+ /// instead. It also checks for `if let &foo = bar` blocks.
+ ///
+ /// ### Why is this bad?
+ /// It just makes the code less readable. That reference
+ /// destructuring adds nothing to the code.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// match x {
+ /// &A(ref y) => foo(y),
+ /// &B => bar(),
+ /// _ => frob(&x),
+ /// }
+ ///
+ /// // Good
+ /// match *x {
+ /// A(ref y) => foo(y),
+ /// B => bar(),
+ /// _ => frob(x),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_REF_PATS,
+ style,
+ "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for matches where match expression is a `bool`. It
+ /// suggests to replace the expression with an `if...else` block.
+ ///
+ /// ### Why is this bad?
+ /// It makes the code less readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn foo() {}
+ /// # fn bar() {}
+ /// let condition: bool = true;
+ /// match condition {
+ /// true => foo(),
+ /// false => bar(),
+ /// }
+ /// ```
+ /// Use if/else instead:
+ /// ```rust
+ /// # fn foo() {}
+ /// # fn bar() {}
+ /// let condition: bool = true;
+ /// if condition {
+ /// foo();
+ /// } else {
+ /// bar();
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_BOOL,
+ pedantic,
+ "a `match` on a boolean expression instead of an `if..else` block"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for overlapping match arms.
+ ///
+ /// ### Why is this bad?
+ /// It is likely to be an error and if not, makes the code
+ /// less obvious.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 5;
+ /// match x {
+ /// 1..=10 => println!("1 ... 10"),
+ /// 5..=15 => println!("5 ... 15"),
+ /// _ => (),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_OVERLAPPING_ARM,
+ style,
+ "a `match` with overlapping arms"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for arm which matches all errors with `Err(_)`
+ /// and take drastic actions like `panic!`.
+ ///
+ /// ### Why is this bad?
+ /// It is generally a bad practice, similar to
+ /// catching all exceptions in java with `catch(Exception)`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: Result<i32, &str> = Ok(3);
+ /// match x {
+ /// Ok(_) => println!("ok"),
+ /// Err(_) => panic!("err"),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_WILD_ERR_ARM,
+ pedantic,
+ "a `match` with `Err(_)` arm and take drastic actions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for match which is used to add a reference to an
+ /// `Option` value.
+ ///
+ /// ### Why is this bad?
+ /// Using `as_ref()` or `as_mut()` instead is shorter.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: Option<()> = None;
+ ///
+ /// // Bad
+ /// let r: Option<&()> = match x {
+ /// None => None,
+ /// Some(ref v) => Some(v),
+ /// };
+ ///
+ /// // Good
+ /// let r: Option<&()> = x.as_ref();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_AS_REF,
+ complexity,
+ "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard enum matches using `_`.
+ ///
+ /// ### Why is this bad?
+ /// New enum variants added by library updates can be missed.
+ ///
+ /// ### Known problems
+ /// Suggested replacements may be incorrect if guards exhaustively cover some
+ /// variants, and also may not use correct path to enum if it's not present in the current scope.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # enum Foo { A(usize), B(usize) }
+ /// # let x = Foo::B(1);
+ /// // Bad
+ /// match x {
+ /// Foo::A(_) => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match x {
+ /// Foo::A(_) => {},
+ /// Foo::B(_) => {},
+ /// }
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub WILDCARD_ENUM_MATCH_ARM,
+ restriction,
+ "a wildcard enum match arm using `_`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard enum matches for a single variant.
+ ///
+ /// ### Why is this bad?
+ /// New enum variants added by library updates can be missed.
+ ///
+ /// ### Known problems
+ /// Suggested replacements may not use correct path to enum
+ /// if it's not present in the current scope.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # enum Foo { A, B, C }
+ /// # let x = Foo::B;
+ /// // Bad
+ /// match x {
+ /// Foo::A => {},
+ /// Foo::B => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match x {
+ /// Foo::A => {},
+ /// Foo::B => {},
+ /// Foo::C => {},
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ pedantic,
+ "a wildcard enum match for a single variant"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard pattern used with others patterns in same match arm.
+ ///
+ /// ### Why is this bad?
+ /// Wildcard pattern already covers any other pattern as it will match anyway.
+ /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// match "foo" {
+ /// "a" => {},
+ /// "bar" | _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match "foo" {
+ /// "a" => {},
+ /// _ => {},
+ /// }
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub WILDCARD_IN_OR_PATTERNS,
+ complexity,
+ "a wildcard pattern used with others patterns in same match arm"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for matches being used to destructure a single-variant enum
+ /// or tuple struct where a `let` will suffice.
+ ///
+ /// ### Why is this bad?
+ /// Just readability – `let` doesn't nest, whereas a `match` does.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Wrapper {
+ /// Data(i32),
+ /// }
+ ///
+ /// let wrapper = Wrapper::Data(42);
+ ///
+ /// let data = match wrapper {
+ /// Wrapper::Data(i) => i,
+ /// };
+ /// ```
+ ///
+ /// The correct use would be:
+ /// ```rust
+ /// enum Wrapper {
+ /// Data(i32),
+ /// }
+ ///
+ /// let wrapper = Wrapper::Data(42);
+ /// let Wrapper::Data(data) = wrapper;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INFALLIBLE_DESTRUCTURING_MATCH,
+ style,
+ "a `match` statement with a single infallible arm instead of a `let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for useless match that binds to only one value.
+ ///
+ /// ### Why is this bad?
+ /// Readability and needless complexity.
+ ///
+ /// ### Known problems
+ /// Suggested replacements may be incorrect when `match`
+ /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 1;
+ /// # let b = 2;
+ ///
+ /// // Bad
+ /// match (a, b) {
+ /// (c, d) => {
+ /// // useless match
+ /// }
+ /// }
+ ///
+ /// // Good
+ /// let (c, d) = (a, b);
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub MATCH_SINGLE_BINDING,
+ complexity,
+ "a match with a single binding instead of using `let` statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
+ ///
+ /// ### Why is this bad?
+ /// Correctness and readability. It's like having a wildcard pattern after
+ /// matching all enum variants explicitly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct A { a: i32 }
+ /// let a = A { a: 5 };
+ ///
+ /// // Bad
+ /// match a {
+ /// A { a: 5, .. } => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match a {
+ /// A { a: 5 } => {},
+ /// _ => {},
+ /// }
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ restriction,
+ "a match on a struct that binds all fields but still uses the wildcard pattern"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lint for redundant pattern matching over `Result`, `Option`,
+ /// `std::task::Poll` or `std::net::IpAddr`
+ ///
+ /// ### Why is this bad?
+ /// It's more concise and clear to just use the proper
+ /// utility function
+ ///
+ /// ### Known problems
+ /// This will change the drop order for the matched type. Both `if let` and
+ /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
+ /// value before entering the block. For most types this change will not matter, but for a few
+ /// types this will not be an acceptable change (e.g. locks). See the
+ /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
+ /// drop order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// if let Ok(_) = Ok::<i32, i32>(42) {}
+ /// if let Err(_) = Err::<i32, i32>(42) {}
+ /// if let None = None::<()> {}
+ /// if let Some(_) = Some(42) {}
+ /// if let Poll::Pending = Poll::Pending::<()> {}
+ /// if let Poll::Ready(_) = Poll::Ready(42) {}
+ /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
+ /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
+ /// match Ok::<i32, i32>(42) {
+ /// Ok(_) => true,
+ /// Err(_) => false,
+ /// };
+ /// ```
+ ///
+ /// The more idiomatic use would be:
+ ///
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// if Ok::<i32, i32>(42).is_ok() {}
+ /// if Err::<i32, i32>(42).is_err() {}
+ /// if None::<()>.is_none() {}
+ /// if Some(42).is_some() {}
+ /// if Poll::Pending::<()>.is_pending() {}
+ /// if Poll::Ready(42).is_ready() {}
+ /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+ /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+ /// Ok::<i32, i32>(42).is_ok();
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub REDUNDANT_PATTERN_MATCHING,
+ style,
+ "use the proper utility function avoiding an `if let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match` or `if let` expressions producing a
+ /// `bool` that could be written using `matches!`
+ ///
+ /// ### Why is this bad?
+ /// Readability and needless complexity.
+ ///
+ /// ### Known problems
+ /// This lint falsely triggers, if there are arms with
+ /// `cfg` attributes that remove an arm evaluating to `false`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some(5);
+ ///
+ /// // Bad
+ /// let a = match x {
+ /// Some(0) => true,
+ /// _ => false,
+ /// };
+ ///
+ /// let a = if let Some(0) = x {
+ /// true
+ /// } else {
+ /// false
+ /// };
+ ///
+ /// // Good
+ /// let a = matches!(x, Some(0));
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub MATCH_LIKE_MATCHES_MACRO,
+ style,
+ "a match that could be written with the matches! macro"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match` with identical arm bodies.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a copy & paste error. If arm bodies
+ /// are the same on purpose, you can factor them
+ /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
+ ///
+ /// ### Known problems
+ /// False positive possible with order dependent `match`
+ /// (see issue
+ /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => bar(), // <= oops
+ /// }
+ /// ```
+ ///
+ /// This should probably be
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => baz(), // <= fixed
+ /// }
+ /// ```
+ ///
+ /// or if the original code was not a typo:
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar | Baz => bar(), // <= shows the intent better
+ /// Quz => quz(),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_SAME_ARMS,
+ pedantic,
+ "`match` with identical arm bodies"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
+ /// when function signatures are the same.
+ ///
+ /// ### Why is this bad?
+ /// This `match` block does nothing and might not be what the coder intended.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn foo() -> Result<(), i32> {
+ /// match result {
+ /// Ok(val) => Ok(val),
+ /// Err(err) => Err(err),
+ /// }
+ /// }
+ ///
+ /// fn bar() -> Option<i32> {
+ /// if let Some(val) = option {
+ /// Some(val)
+ /// } else {
+ /// None
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be replaced as
+ ///
+ /// ```rust,ignore
+ /// fn foo() -> Result<(), i32> {
+ /// result
+ /// }
+ ///
+ /// fn bar() -> Option<i32> {
+ /// option
+ /// }
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub NEEDLESS_MATCH,
+ complexity,
+ "`match` or match-like `if let` that are unnecessary"
+}
+
+#[derive(Default)]
+pub struct Matches {
+ msrv: Option<RustcVersion>,
+ infallible_destructuring_match_linted: bool,
+}
+
+impl Matches {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Matches::default()
+ }
+ }
+}
+
+impl_lint_pass!(Matches => [
+ SINGLE_MATCH,
+ MATCH_REF_PATS,
+ MATCH_BOOL,
+ SINGLE_MATCH_ELSE,
+ MATCH_OVERLAPPING_ARM,
+ MATCH_WILD_ERR_ARM,
+ MATCH_AS_REF,
+ WILDCARD_ENUM_MATCH_ARM,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ WILDCARD_IN_OR_PATTERNS,
+ MATCH_SINGLE_BINDING,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ REDUNDANT_PATTERN_MATCHING,
+ MATCH_LIKE_MATCHES_MACRO,
+ MATCH_SAME_ARMS,
+ NEEDLESS_MATCH,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Matches {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Match(ex, arms, source) = expr.kind {
+ if !span_starts_with(cx, expr.span, "match") {
+ return;
+ }
+ if !contains_cfg_arm(cx, expr, ex, arms) {
+ if source == MatchSource::Normal {
- if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
++ if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
+ && match_like_matches::check_match(cx, expr, ex, arms))
+ {
+ match_same_arms::check(cx, arms);
+ }
+
+ redundant_pattern_match::check_match(cx, expr, ex, arms);
+ single_match::check(cx, ex, arms, expr);
+ match_bool::check(cx, ex, arms, expr);
+ overlapping_arms::check(cx, ex, arms);
+ match_wild_enum::check(cx, ex, arms);
+ match_as_ref::check(cx, ex, arms, expr);
+ needless_match::check_match(cx, ex, arms, expr);
+
+ if self.infallible_destructuring_match_linted {
+ self.infallible_destructuring_match_linted = false;
+ } else {
+ match_single_binding::check(cx, ex, arms, expr);
+ }
+ }
+ match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
+ }
+
+ // These don't depend on a relationship between multiple arms
+ match_wild_err_arm::check(cx, ex, arms);
+ wild_in_or_pats::check(cx, arms);
+ } else {
++ if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
+ match_like_matches::check(cx, expr);
+ }
+ redundant_pattern_match::check(cx, expr);
+ needless_match::check(cx, expr);
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
+ self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local);
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ rest_pat_in_fully_bound_struct::check(cx, pat);
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Checks if there are any arms with a `#[cfg(..)]` attribute.
+fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
+ let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else {
+ // Shouldn't happen, but treat this as though a `cfg` attribute were found
+ return true;
+ };
+
+ let start = scrutinee_span.hi();
+ let mut arm_spans = arms.iter().map(|arm| {
+ let data = arm.span.data();
+ (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
+ });
+ let end = e.span.hi();
+
+ // Walk through all the non-code space before each match arm. The space trailing the final arm is
+ // handled after the `try_fold` e.g.
+ //
+ // match foo {
+ // _________^- everything between the scrutinee and arm1
+ //| arm1 => (),
+ //|---^___________^ everything before arm2
+ //| #[cfg(feature = "enabled")]
+ //| arm2 => some_code(),
+ //|---^____________________^ everything before arm3
+ //| // some comment about arm3
+ //| arm3 => some_code(),
+ //|---^____________________^ everything after arm3
+ //| #[cfg(feature = "disabled")]
+ //| arm4 = some_code(),
+ //|};
+ //|^
+ let found = arm_spans.try_fold(start, |start, range| {
+ let Some((end, next_start)) = range else {
+ // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were
+ // found.
+ return Err(());
+ };
+ let span = SpanData {
+ lo: start,
+ hi: end,
+ ctxt: SyntaxContext::root(),
+ parent: None,
+ }
+ .span();
+ (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
+ });
+ match found {
+ Ok(start) => {
+ let span = SpanData {
+ lo: start,
+ hi: end,
+ ctxt: SyntaxContext::root(),
+ parent: None,
+ }
+ .span();
+ span_contains_cfg(cx, span)
+ },
+ Err(()) => true,
+ }
+}
+
+/// Checks if the given span contains a `#[cfg(..)]` attribute
+fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
+ let Some(snip) = snippet_opt(cx, s) else {
+ // Assume true. This would require either an invalid span, or one which crosses file boundaries.
+ return true;
+ };
+ let mut pos = 0usize;
+ let mut iter = tokenize(&snip).map(|t| {
+ let start = pos;
+ pos += t.len;
+ (t.kind, start..pos)
+ });
+
+ // Search for the token sequence [`#`, `[`, `cfg`]
+ while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
+ let mut iter = iter.by_ref().skip_while(|(t, _)| {
+ matches!(
+ t,
+ TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
+ )
+ });
+ if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
+ && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
+ {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- #[allow(clippy::too_many_arguments)]
+use super::REDUNDANT_PATTERN_MATCHING;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::needs_ordered_drop;
+use clippy_utils::{higher, match_def_path};
+use clippy_utils::{is_lang_ctor, is_trait_method, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, PollPending};
+use rustc_hir::{
+ intravisit::{walk_expr, Visitor},
+ Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
+};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
+use rustc_span::sym;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some(higher::IfLet {
+ if_else,
+ let_pat,
+ let_expr,
+ ..
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
+ } else if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
+ find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
+ }
+}
+
+// Extract the generic arguments out of a type
+fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
+ if_chain! {
+ if let ty::Adt(_, subs) = ty.kind();
+ if let Some(sub) = subs.get(index);
+ if let GenericArgKind::Type(sub_ty) = sub.unpack();
+ then {
+ Some(sub_ty)
+ } else {
+ None
+ }
+ }
+}
+
+// Checks if there are any temporaries created in the given expression for which drop order
+// matters.
+fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ res: bool,
+ }
+ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ // Taking the reference of a value leaves a temporary
+ // e.g. In `&String::new()` the string is a temporary value.
+ // Remaining fields are temporary values
+ // e.g. In `(String::new(), 0).1` the string is a temporary value.
+ ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
+ if !matches!(expr.kind, ExprKind::Path(_)) {
+ if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
+ self.res = true;
+ } else {
+ self.visit_expr(expr);
+ }
+ }
+ },
+ // the base type is alway taken by reference.
+ // e.g. In `(vec![0])[0]` the vector is a temporary value.
+ ExprKind::Index(base, index) => {
+ if !matches!(base.kind, ExprKind::Path(_)) {
+ if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
+ self.res = true;
+ } else {
+ self.visit_expr(base);
+ }
+ }
+ self.visit_expr(index);
+ },
+ // Method calls can take self by reference.
+ // e.g. In `String::new().len()` the string is a temporary value.
+ ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
+ if !matches!(self_arg.kind, ExprKind::Path(_)) {
+ let self_by_ref = self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
+ if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
+ self.res = true;
+ } else {
+ self.visit_expr(self_arg);
+ }
+ }
+ args.iter().for_each(|arg| self.visit_expr(arg));
+ },
+ // Either explicitly drops values, or changes control flow.
+ ExprKind::DropTemps(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Break(..)
+ | ExprKind::Yield(..)
+ | ExprKind::Block(Block { expr: None, .. }, _)
+ | ExprKind::Loop(..) => (),
+
+ // Only consider the final expression.
+ ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
+
+ _ => walk_expr(self, expr),
+ }
+ }
+ }
+
+ let mut v = V { cx, res: false };
+ v.visit_expr(expr);
+ v.res
+}
+
+fn find_sugg_for_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ let_pat: &Pat<'_>,
+ let_expr: &'tcx Expr<'_>,
+ keyword: &'static str,
+ has_else: bool,
+) {
+ // also look inside refs
+ // if we have &None for example, peel it so we can detect "if let None = x"
+ let check_pat = match let_pat.kind {
+ PatKind::Ref(inner, _mutability) => inner,
+ _ => let_pat,
+ };
+ let op_ty = cx.typeck_results().expr_ty(let_expr);
+ // Determine which function should be used, and the type contained by the corresponding
+ // variant.
+ let (good_method, inner_ty) = match check_pat.kind {
+ PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
+ if let PatKind::Wild = sub_pat.kind {
+ let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
+ let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
+ let lang_items = cx.tcx.lang_items();
+ if Some(id) == lang_items.result_ok_variant() {
+ ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
+ } else if Some(id) == lang_items.result_err_variant() {
+ ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
+ } else if Some(id) == lang_items.option_some_variant() {
+ ("is_some()", op_ty)
+ } else if Some(id) == lang_items.poll_ready_variant() {
+ ("is_ready()", op_ty)
+ } else if match_def_path(cx, id, &paths::IPADDR_V4) {
+ ("is_ipv4()", op_ty)
+ } else if match_def_path(cx, id, &paths::IPADDR_V6) {
+ ("is_ipv6()", op_ty)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ },
+ PatKind::Path(ref path) => {
+ let method = if is_lang_ctor(cx, path, OptionNone) {
+ "is_none()"
+ } else if is_lang_ctor(cx, path, PollPending) {
+ "is_pending()"
+ } else {
+ return;
+ };
+ // `None` and `Pending` don't have an inner type.
+ (method, cx.tcx.types.unit)
+ },
+ _ => return,
+ };
+
+ // If this is the last expression in a block or there is an else clause then the whole
+ // type needs to be considered, not just the inner type of the branch being matched on.
+ // Note the last expression in a block is dropped after all local bindings.
+ let check_ty = if has_else
+ || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
+ {
+ op_ty
+ } else {
+ inner_ty
+ };
+
+ // All temporaries created in the scrutinee expression are dropped at the same time as the
+ // scrutinee would be, so they have to be considered as well.
+ // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
+ // for the duration if body.
+ let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
+
+ // check that `while_let_on_iterator` lint does not trigger
+ if_chain! {
+ if keyword == "while";
+ if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind;
+ if method_path.ident.name == sym::next;
+ if is_trait_method(cx, let_expr, sym::Iterator);
+ then {
+ return;
+ }
+ }
+
+ let result_expr = match &let_expr.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ ExprKind::Unary(UnOp::Deref, deref) => deref,
+ _ => let_expr,
+ };
+
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ let_pat.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ let expr_span = expr.span;
+
+ // if/while let ... = ... { ... }
+ // ^^^
+ let op_span = result_expr.span.source_callsite();
+
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^
+ let span = expr_span.until(op_span.shrink_to_hi());
+
+ let app = if needs_drop {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+
+ let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
+ .maybe_par()
+ .to_string();
+
+ diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
+
+ if needs_drop {
+ diag.note("this will change drop order of the result, as well as all temporaries");
+ diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
+ }
+ },
+ );
+}
+
+pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
+ if arms.len() == 2 {
+ let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
+
+ let found_good_method = match node_pair {
+ (
+ PatKind::TupleStruct(ref path_left, patterns_left, _),
+ PatKind::TupleStruct(ref path_right, patterns_right, _),
+ ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
+ if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::RESULT_OK,
+ &paths::RESULT_ERR,
+ "is_ok()",
+ "is_err()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::IPADDR_V4,
+ &paths::IPADDR_V6,
+ "is_ipv4()",
+ "is_ipv6()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
+ | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
+ if patterns.len() == 1 =>
+ {
+ if let PatKind::Wild = patterns[0].kind {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::OPTION_SOME,
+ &paths::OPTION_NONE,
+ "is_some()",
+ "is_none()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::POLL_READY,
+ &paths::POLL_PENDING,
+ "is_ready()",
+ "is_pending()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+
+ if let Some(good_method) = found_good_method {
+ let span = expr.span.to(op.span);
+ let result_expr = match &op.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ _ => op,
+ };
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ expr.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try this",
+ format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
+ Applicability::MaybeIncorrect, // snippet
+ );
+ },
+ );
+ }
+ }
+}
+
++#[expect(clippy::too_many_arguments)]
+fn find_good_method_for_match<'a>(
+ cx: &LateContext<'_>,
+ arms: &[Arm<'_>],
+ path_left: &QPath<'_>,
+ path_right: &QPath<'_>,
+ expected_left: &[&str],
+ expected_right: &[&str],
+ should_be_left: &'a str,
+ should_be_right: &'a str,
+) -> Option<&'a str> {
+ let left_id = cx
+ .typeck_results()
+ .qpath_res(path_left, arms[0].pat.hir_id)
+ .opt_def_id()?;
+ let right_id = cx
+ .typeck_results()
+ .qpath_res(path_right, arms[1].pat.hir_id)
+ .opt_def_id()?;
+ let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
+ (&(*arms[0].body).kind, &(*arms[1].body).kind)
+ } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
+ (&(*arms[1].body).kind, &(*arms[0].body).kind)
+ } else {
+ return None;
+ };
+
+ match body_node_pair {
+ (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
+ (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
+ (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
+ _ => None,
+ },
+ _ => None,
+ }
+}
--- /dev/null
- if meets_msrv(self.msrv.as_ref(), &msrvs::MEM_TAKE) {
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::is_non_aggregate_primitive_type;
+use clippy_utils::{is_default_equivalent, is_lang_ctor, meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace()` on an `Option` with
+ /// `None`.
+ ///
+ /// ### Why is this bad?
+ /// `Option` already has the method `take()` for
+ /// taking its current value (Some(..) or None) and replacing it with
+ /// `None`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::mem;
+ ///
+ /// let mut an_option = Some(0);
+ /// let replaced = mem::replace(&mut an_option, None);
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut an_option = Some(0);
+ /// let taken = an_option.take();
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub MEM_REPLACE_OPTION_WITH_NONE,
+ style,
+ "replacing an `Option` with `None` instead of `take()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace(&mut _, mem::uninitialized())`
+ /// and `mem::replace(&mut _, mem::zeroed())`.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to undefined behavior even if the
+ /// value is overwritten later, because the uninitialized value may be
+ /// observed in the case of a panic.
+ ///
+ /// ### Example
+ /// ```
+ /// use std::mem;
+ ///# fn may_panic(v: Vec<i32>) -> Vec<i32> { v }
+ ///
+ /// #[allow(deprecated, invalid_value)]
+ /// fn myfunc (v: &mut Vec<i32>) {
+ /// let taken_v = unsafe { mem::replace(v, mem::uninitialized()) };
+ /// let new_v = may_panic(taken_v); // undefined behavior on panic
+ /// mem::forget(mem::replace(v, new_v));
+ /// }
+ /// ```
+ ///
+ /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution,
+ /// at the cost of either lazily creating a replacement value or aborting
+ /// on panic, to ensure that the uninitialized value cannot be observed.
+ #[clippy::version = "1.39.0"]
+ pub MEM_REPLACE_WITH_UNINIT,
+ correctness,
+ "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `std::mem::replace` on a value of type
+ /// `T` with `T::default()`.
+ ///
+ /// ### Why is this bad?
+ /// `std::mem` module already has the method `take` to
+ /// take the current value and replace it with the default value of that type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let replaced = std::mem::replace(&mut text, String::default());
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let taken = std::mem::take(&mut text);
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub MEM_REPLACE_WITH_DEFAULT,
+ style,
+ "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`"
+}
+
+impl_lint_pass!(MemReplace =>
+ [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
+
+fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ if let ExprKind::Path(ref replacement_qpath) = src.kind {
+ // Check that second argument is `Option::None`
+ if is_lang_ctor(cx, replacement_qpath, OptionNone) {
+ // Since this is a late pass (already type-checked),
+ // and we already know that the second argument is an
+ // `Option`, we do not need to check the first
+ // argument's type. All that's left is to get
+ // replacee's path.
+ let replaced_path = match dest.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
+ if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
+ replaced_path
+ } else {
+ return;
+ }
+ },
+ ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
+ _ => return,
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_OPTION_WITH_NONE,
+ expr_span,
+ "replacing an `Option` with `None`",
+ "consider `Option::take()` instead",
+ format!(
+ "{}.take()",
+ snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ if_chain! {
+ // check if replacement is mem::MaybeUninit::uninit().assume_init()
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id);
+ if cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::MaybeUninit::uninit().assume_init()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ return;
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Call(repl_func, repl_args) = src.kind;
+ if repl_args.is_empty();
+ if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ then {
+ if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::uninitialized()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) &&
+ !cx.typeck_results().expr_ty(src).is_primitive() {
+ span_lint_and_help(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::zeroed()`",
+ None,
+ "consider using a default value or the `take_mut` crate instead",
+ );
+ }
+ }
+ }
+}
+
+fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ // disable lint for primitives
+ let expr_type = cx.typeck_results().expr_ty_adjusted(src);
+ if is_non_aggregate_primitive_type(expr_type) {
+ return;
+ }
+ // disable lint for Option since it is covered in another lint
+ if let ExprKind::Path(q) = &src.kind {
+ if is_lang_ctor(cx, q, OptionNone) {
+ return;
+ }
+ }
+ if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
+ span_lint_and_then(
+ cx,
+ MEM_REPLACE_WITH_DEFAULT,
+ expr_span,
+ "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
+ |diag| {
+ if !expr_span.from_expansion() {
+ let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
+
+ diag.span_suggestion(
+ expr_span,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+}
+
+pub struct MemReplace {
+ msrv: Option<RustcVersion>,
+}
+
+impl MemReplace {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MemReplace {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // Check that `expr` is a call to `mem::replace()`
+ if let ExprKind::Call(func, func_args) = expr.kind;
+ if let ExprKind::Path(ref func_qpath) = func.kind;
+ if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
+ if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
+ if let [dest, src] = func_args;
+ then {
+ check_replace_option_with_none(cx, src, dest, expr.span);
+ check_replace_with_uninit(cx, src, dest, expr.span);
++ if meets_msrv(self.msrv, msrvs::MEM_TAKE) {
+ check_replace_with_default(cx, src, dest, expr.span);
+ }
+ }
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option<&RustcVersion>) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::{get_iterator_item_ty, is_copy};
+use clippy_utils::{is_trait_method, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span};
+
+use super::CLONED_INSTEAD_OF_COPIED;
+
- if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, &msrvs::OPTION_COPIED) =>
++pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option<RustcVersion>) {
+ let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
+ let inner_ty = match recv_ty.kind() {
+ // `Option<T>` -> `T`
+ ty::Adt(adt, subst)
- _ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) => {
++ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, msrvs::OPTION_COPIED) =>
+ {
+ subst.type_at(0)
+ },
++ _ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) => {
+ match get_iterator_item_ty(cx, recv_ty) {
+ // <T as Iterator>::Item
+ Some(ty) => ty,
+ _ => return,
+ }
+ },
+ _ => return,
+ };
+ match inner_ty.kind() {
+ // &T where T: Copy
+ ty::Ref(_, ty, _) if is_copy(cx, *ty) => {},
+ _ => return,
+ };
+ span_lint_and_sugg(
+ cx,
+ CLONED_INSTEAD_OF_COPIED,
+ span,
+ "used `cloned` where `copied` could be used instead",
+ "try",
+ "copied".into(),
+ Applicability::MachineApplicable,
+ );
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use super::ERR_EXPECT;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
+use rustc_errors::Applicability;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_middle::ty::Ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ _expr: &rustc_hir::Expr<'_>,
+ recv: &rustc_hir::Expr<'_>,
- if meets_msrv(msrv, &msrvs::EXPECT_ERR);
++ msrv: Option<RustcVersion>,
+ expect_span: Span,
+ err_span: Span,
+) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ // Test the version to make sure the lint can be showed (expect_err has been
+ // introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
++ if meets_msrv(msrv, msrvs::EXPECT_ERR);
+
+ // Grabs the `Result<T, E>` type
+ let result_type = cx.typeck_results().expr_ty(recv);
+ // Tests if the T type in a `Result<T, E>` is not None
+ if let Some(data_type) = get_data_type(cx, result_type);
+ // Tests if the T type in a `Result<T, E>` implements debug
+ if has_debug_impl(data_type, cx);
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ERR_EXPECT,
+ err_span.to(expect_span),
+ "called `.err().expect()` on a `Result` value",
+ "try",
+ "expect_err".to_string(),
+ Applicability::MachineApplicable
+ );
+ }
+ };
+}
+
+/// Given a `Result<T, E>` type, return its data (`T`).
+fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
+ match ty.kind() {
+ ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(),
+ _ => None,
+ }
+}
+
+/// Given a type, very if the Debug trait has been impl'd
+fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ cx.tcx
+ .get_diagnostic_item(sym::Debug)
+ .map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
+}
--- /dev/null
- pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+use clippy_utils::diagnostics::span_lint_and_help;
++use clippy_utils::is_in_test_function;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::EXPECT_USED;
+
+/// lint use of `expect()` for `Option`s and `Result`s
++pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_expect_in_tests: bool) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
+ Some((EXPECT_USED, "an Option", "None"))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
+ Some((EXPECT_USED, "a Result", "Err"))
+ } else {
+ None
+ };
+
++ if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
++ return;
++ }
++
+ if let Some((lint, kind, none_value)) = mess {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
+ &format!("used `expect()` on `{}` value", kind,),
+ None,
+ &format!("if this value is an `{}`, it will panic", none_value,),
+ );
+ }
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::{is_trait_method, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::FILTER_MAP_NEXT;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
- if !meets_msrv(msrv, &msrvs::ITERATOR_FIND_MAP) {
++ msrv: Option<RustcVersion>,
+) {
+ if is_trait_method(cx, expr, sym::Iterator) {
++ if !meets_msrv(msrv, msrvs::ITERATOR_FIND_MAP) {
+ return;
+ }
+
+ let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find_map(..)` instead";
+ let filter_snippet = snippet(cx, arg.span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ let iter_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ FILTER_MAP_NEXT,
+ expr.span,
+ msg,
+ "try this",
+ format!("{}.find_map({})", iter_snippet, filter_snippet),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, FILTER_MAP_NEXT, expr.span, msg);
+ }
+ }
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+//! Lint for `c.is_digit(10)`
+
+use super::IS_DIGIT_ASCII_RADIX;
+use clippy_utils::{
+ consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
+ source::snippet_with_applicability,
+};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ self_arg: &'tcx Expr<'_>,
+ radix: &'tcx Expr<'_>,
- if !meets_msrv(msrv, &msrvs::IS_ASCII_DIGIT) {
++ msrv: Option<RustcVersion>,
+) {
++ if !meets_msrv(msrv, msrvs::IS_ASCII_DIGIT) {
+ return;
+ }
+
+ if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
+ return;
+ }
+
+ if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) {
+ let (num, replacement) = match radix_val {
+ FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
+ FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
+ _ => return,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_sugg(
+ cx,
+ IS_DIGIT_ASCII_RADIX,
+ expr.span,
+ &format!("use of `char::is_digit` with literal radix of {}", num),
+ "try",
+ format!(
+ "{}.{}()",
+ snippet_with_applicability(cx, self_arg.span, "..", &mut applicability),
+ replacement
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::sym;
+
+use super::MAP_UNWRAP_OR;
+
+/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
+/// Return true if lint triggered
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ map_arg: &'tcx hir::Expr<'_>,
+ unwrap_arg: &'tcx hir::Expr<'_>,
- if is_result && !meets_msrv(msrv, &msrvs::RESULT_MAP_OR_ELSE) {
++ msrv: Option<RustcVersion>,
+) -> bool {
+ // lint if the caller of `map()` is an `Option`
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
++ if is_result && !meets_msrv(msrv, msrvs::RESULT_MAP_OR_ELSE) {
+ return false;
+ }
+
+ if is_option || is_result {
+ // Don't make a suggestion that may fail to compile due to mutably borrowing
+ // the same variable twice.
+ let map_mutated_vars = mutated_variables(recv, cx);
+ let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
+ if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
+ if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // lint message
+ let msg = if is_option {
+ "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"
+ } else {
+ "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \
+ `.map_or_else(<g>, <f>)` instead"
+ };
+ // get snippets for args to map() and unwrap_or_else()
+ let map_snippet = snippet(cx, map_arg.span, "..");
+ let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
+ // lint, with note if neither arg is > 1 line and both map() and
+ // unwrap_or_else() have the same span
+ let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
+ let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt();
+ if same_span && !multiline {
+ let var_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ MAP_UNWRAP_OR,
+ expr.span,
+ msg,
+ "try this",
+ format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ } else if same_span && multiline {
+ span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
+ return true;
+ }
+ }
+
+ false
+}
--- /dev/null
- "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`"
+mod bind_instead_of_map;
+mod bytes_nth;
+mod chars_cmp;
+mod chars_cmp_with_unwrap;
+mod chars_last_cmp;
+mod chars_last_cmp_with_unwrap;
+mod chars_next_cmp;
+mod chars_next_cmp_with_unwrap;
+mod clone_on_copy;
+mod clone_on_ref_ptr;
+mod cloned_instead_of_copied;
+mod err_expect;
+mod expect_fun_call;
+mod expect_used;
+mod extend_with_drain;
+mod filetype_is_file;
+mod filter_map;
+mod filter_map_identity;
+mod filter_map_next;
+mod filter_next;
+mod flat_map_identity;
+mod flat_map_option;
+mod from_iter_instead_of_collect;
+mod get_unwrap;
+mod implicit_clone;
+mod inefficient_to_string;
+mod inspect_for_each;
+mod into_iter_on_ref;
+mod is_digit_ascii_radix;
+mod iter_cloned_collect;
+mod iter_count;
+mod iter_next_slice;
+mod iter_nth;
+mod iter_nth_zero;
+mod iter_overeager_cloned;
+mod iter_skip_next;
+mod iter_with_drain;
+mod iterator_step_by_zero;
+mod manual_saturating_arithmetic;
+mod manual_str_repeat;
+mod map_collect_result_unit;
+mod map_flatten;
+mod map_identity;
+mod map_unwrap_or;
+mod needless_option_as_deref;
+mod needless_option_take;
+mod ok_expect;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod or_then_unwrap;
+mod search_is_some;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
+mod str_splitn;
+mod string_extend_chars;
+mod suspicious_map;
+mod suspicious_splitn;
+mod uninit_assumed_init;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_iter_cloned;
+mod unnecessary_join;
+mod unnecessary_lazy_eval;
+mod unnecessary_to_owned;
+mod unwrap_or_else_default;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, TraitRef, Ty};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `cloned()` on an `Iterator` or `Option` where
+ /// `copied()` could be used instead.
+ ///
+ /// ### Why is this bad?
+ /// `copied()` is better because it guarantees that the type being cloned
+ /// implements `Copy`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1, 2, 3].iter().cloned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// [1, 2, 3].iter().copied();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub CLONED_INSTEAD_OF_COPIED,
+ pedantic,
+ "used `cloned` where `copied` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
+ ///
+ /// ### Why is this bad?
+ /// It's often inefficient to clone all elements of an iterator, when eventually, only some
+ /// of them will be consumed.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ ///
+ /// // Bad
+ /// vec.iter().cloned().take(10);
+ ///
+ /// // Good
+ /// vec.iter().take(10).cloned();
+ ///
+ /// // Bad
+ /// vec.iter().cloned().last();
+ ///
+ /// // Good
+ /// vec.iter().last().cloned();
+ ///
+ /// ```
+ /// ### Known Problems
+ /// This `lint` removes the side of effect of cloning items in the iterator.
+ /// A code that relies on that side-effect could fail.
+ ///
+ #[clippy::version = "1.59.0"]
+ pub ITER_OVEREAGER_CLONED,
+ perf,
+ "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+ /// used instead.
+ ///
+ /// ### Why is this bad?
+ /// When applicable, `filter_map()` is more clear since it shows that
+ /// `Option` is used to produce 0 or 1 items.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub FLAT_MAP_OPTION,
+ pedantic,
+ "used `flat_map` where `filter_map` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.unwrap()` calls on `Option`s and on `Result`s.
+ ///
+ /// ### Why is this bad?
+ /// It is better to handle the `None` or `Err` case,
+ /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of
+ /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is
+ /// `Allow` by default.
+ ///
+ /// `result.unwrap()` will let the thread panic on `Err` values.
+ /// Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// Even if you want to panic on errors, not all `Error`s implement good
+ /// messages on display. Therefore, it may be beneficial to look at the places
+ /// where they may get displayed. Activate this lint to do just that.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.unwrap();
+ ///
+ /// // Good
+ /// opt.expect("more helpful message");
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let res: Result<usize, ()> = Ok(1);
+ ///
+ /// // Bad
+ /// res.unwrap();
+ ///
+ /// // Good
+ /// res.expect("more helpful message");
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub UNWRAP_USED,
+ restriction,
+ "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.expect()` calls on `Option`s and `Result`s.
+ ///
+ /// ### Why is this bad?
+ /// Usually it is better to handle the `None` or `Err` case.
+ /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why
+ /// this lint is `Allow` by default.
+ ///
+ /// `result.expect()` will let the thread panic on `Err`
+ /// values. Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// ### Examples
+ /// ```rust,ignore
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.expect("one");
+ ///
+ /// // Good
+ /// let opt = Some(1);
+ /// opt?;
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let res: Result<usize, ()> = Ok(1);
+ ///
+ /// // Bad
+ /// res.expect("one");
+ ///
+ /// // Good
+ /// res?;
+ /// # Ok::<(), ()>(())
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub EXPECT_USED,
+ restriction,
+ "using `.expect()` on `Result` or `Option`, which might be better handled"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods that should live in a trait
+ /// implementation of a `std` trait (see [llogiq's blog
+ /// post](http://llogiq.github.io/2015/07/30/traits.html) for further
+ /// information) instead of an inherent implementation.
+ ///
+ /// ### Why is this bad?
+ /// Implementing the traits improve ergonomics for users of
+ /// the code, often with very little cost. Also people seeing a `mul(...)`
+ /// method
+ /// may expect `*` to work equally, so you should have good reason to disappoint
+ /// them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn add(&self, other: &X) -> X {
+ /// // ..
+ /// # X
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SHOULD_IMPLEMENT_TRAIT,
+ style,
+ "defining a method that should be implementing a std trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods with certain name prefixes and which
+ /// doesn't match how self is taken. The actual rules are:
+ ///
+ /// |Prefix |Postfix |`self` taken | `self` type |
+ /// |-------|------------|-------------------------------|--------------|
+ /// |`as_` | none |`&self` or `&mut self` | any |
+ /// |`from_`| none | none | any |
+ /// |`into_`| none |`self` | any |
+ /// |`is_` | none |`&mut self` or `&self` or none | any |
+ /// |`to_` | `_mut` |`&mut self` | any |
+ /// |`to_` | not `_mut` |`self` | `Copy` |
+ /// |`to_` | not `_mut` |`&self` | not `Copy` |
+ ///
+ /// Note: Clippy doesn't trigger methods with `to_` prefix in:
+ /// - Traits definition.
+ /// Clippy can not tell if a type that implements a trait is `Copy` or not.
+ /// - Traits implementation, when `&self` is taken.
+ /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+ /// (see e.g. the `std::string::ToString` trait).
+ ///
+ /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required.
+ ///
+ /// Please find more info here:
+ /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ ///
+ /// ### Why is this bad?
+ /// Consistency breeds readability. If you follow the
+ /// conventions, your users won't be surprised that they, e.g., need to supply a
+ /// mutable reference to a `as_..` function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct X;
+ /// impl X {
+ /// fn as_str(self) -> &'static str {
+ /// // ..
+ /// # ""
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRONG_SELF_CONVENTION,
+ style,
+ "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `ok().expect(..)`.
+ ///
+ /// ### Why is this bad?
+ /// Because you usually call `expect()` on the `Result`
+ /// directly to get a better error message.
+ ///
+ /// ### Known problems
+ /// The error type needs to implement `Debug`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ ///
+ /// // Bad
+ /// x.ok().expect("why did I do this again?");
+ ///
+ /// // Good
+ /// x.expect("why did I do this again?");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OK_EXPECT,
+ style,
+ "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.err().expect()` calls on the `Result` type.
+ ///
+ /// ### Why is this bad?
+ /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`.
+ ///
+ /// ### Example
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.err().expect("Testing err().expect()");
+ /// ```
+ /// Use instead:
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.expect_err("Testing expect_err");
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub ERR_EXPECT,
+ style,
+ r#"using `.err().expect("")` when `.expect_err("")` can be used"#
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
+ /// `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written as `_.unwrap_or_default`, which is
+ /// simpler and more concise.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let x = Some(1);
+ ///
+ /// // Bad
+ /// x.unwrap_or_else(Default::default);
+ /// x.unwrap_or_else(u32::default);
+ ///
+ /// // Good
+ /// x.unwrap_or_default();
+ /// ```
+ #[clippy::version = "1.56.0"]
+ pub UNWRAP_OR_ELSE_DEFAULT,
+ style,
+ "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
+ /// `result.map(_).unwrap_or_else(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written more concisely (resp.) as
+ /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let x = Some(1);
+ ///
+ /// // Bad
+ /// x.map(|a| a + 1).unwrap_or(0);
+ ///
+ /// // Good
+ /// x.map_or(0, |a| a + 1);
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let x: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ ///
+ /// // Bad
+ /// x.map(|a| a + 1).unwrap_or_else(some_function);
+ ///
+ /// // Good
+ /// x.map_or_else(some_function, |a| a + 1);
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MAP_UNWRAP_OR,
+ pedantic,
+ "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, _)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.and_then(_)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.map_or(None, |a| Some(a + 1));
+ ///
+ /// // Good
+ /// opt.and_then(|a| Some(a + 1));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_MAP_OR_NONE,
+ style,
+ "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, Some)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ok()`.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.map_or(None, Some));
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.ok());
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub RESULT_MAP_OR_INTO_OPTION,
+ style,
+ "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
+ /// `_.or_else(|x| Err(y))`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.map(|x| y)` or `_.map_err(|x| y)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().and_then(|s| Some(s.len()));
+ /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) });
+ /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) });
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().map(|s| s.len());
+ /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });
+ /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub BIND_INSTEAD_OF_MAP,
+ complexity,
+ "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().filter(|x| **x == 0).next();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FILTER_NEXT,
+ complexity,
+ "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.skip_while(condition).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(!condition)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().skip_while(|x| **x == 0).next();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x != 0);
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub SKIP_WHILE_NEXT,
+ complexity,
+ "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![vec![1]];
+ /// let opt = Some(5);
+ ///
+ /// // Bad
+ /// vec.iter().map(|x| x.iter()).flatten();
+ /// opt.map(|x| Some(x * 2)).flatten();
+ ///
+ /// // Good
+ /// vec.iter().flat_map(|x| x.iter());
+ /// opt.and_then(|x| Some(x * 2));
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub MAP_FLATTEN,
+ complexity,
+ "using combinations of `flatten` and `map` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).map(_)` that can be written more simply
+ /// as `filter_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `filter` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .filter(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FILTER_MAP,
+ complexity,
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.find(_).map(_)` that can be written more simply
+ /// as `find_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `find` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .find(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).find_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FIND_MAP,
+ complexity,
+ "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter_map(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find_map(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
+ /// ```
+ /// Can be written as
+ ///
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
+ /// ```
+ #[clippy::version = "1.36.0"]
+ pub FILTER_MAP_NEXT,
+ pedantic,
+ "using combination of `filter_map` and `next` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `flat_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flat_map(|x| x);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for an iterator or string search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as:
+ /// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
+ /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ ///
+ /// let _ = "hello world".find("world").is_none();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ ///
+ /// let _ = !"hello world".contains("world");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.chars().next()` on a `str` to check
+ /// if it starts with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.starts_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_NEXT_CMP,
+ style,
+ "using `.chars().next()` to check if a string starts with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+ /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
+ /// `unwrap_or_default` instead.
+ ///
+ /// ### Why is this bad?
+ /// The function will always be called and potentially
+ /// allocate an object acting as the default.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantic of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or(String::new());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_else(String::new);
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_default();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OR_FUN_CALL,
+ perf,
+ "using any `*or` method with a function call, which suggests `*or_else`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.or(…).unwrap()` calls to Options and Results.
+ ///
+ /// ### Why is this bad?
+ /// You should use `.unwrap_or(…)` instead for clarity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # type Error = &'static str;
+ /// # let result: Result<&str, Error> = Err("error");
+ /// let value = result.or::<Error>(Ok(fallback)).unwrap();
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.or(Some(fallback)).unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # let result: Result<&str, &str> = Err("error");
+ /// let value = result.unwrap_or(fallback);
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.unwrap_or(fallback);
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub OR_THEN_UNWRAP,
+ complexity,
+ "checks for `.or(…).unwrap()` calls to Options and Results."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
+ /// etc., and suggests to use `unwrap_or_else` instead
+ ///
+ /// ### Why is this bad?
+ /// The function will always be called.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantics of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(&format!("Err {}: {}", err_code, err_msg));
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPECT_FUN_CALL,
+ perf,
+ "using any `expect` method with a function call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a `Copy` type.
+ ///
+ /// ### Why is this bad?
+ /// The only reason `Copy` types implement `Clone` is for
+ /// generics, not for using the `clone` method on a concrete type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_COPY,
+ complexity,
+ "using `clone` on a `Copy` type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a ref-counted pointer,
+ /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+ /// function syntax instead (e.g., `Rc::clone(foo)`).
+ ///
+ /// ### Why is this bad?
+ /// Calling '.clone()' on an Rc, Arc, or Weak
+ /// can obscure the fact that only the pointer is being cloned, not the underlying
+ /// data.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// let x = Rc::new(1);
+ ///
+ /// // Bad
+ /// x.clone();
+ ///
+ /// // Good
+ /// Rc::clone(&x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_REF_PTR,
+ restriction,
+ "using 'clone' on a ref-counted pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on an `&&T`.
+ ///
+ /// ### Why is this bad?
+ /// Cloning an `&&T` copies the inner `&T`, instead of
+ /// cloning the underlying `T`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = vec![1];
+ /// let y = &&x;
+ /// let z = y.clone();
+ /// println!("{:p} {:p}", *y, z); // prints out the same pointer
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_DOUBLE_REF,
+ correctness,
+ "using `clone` on `&&T`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.to_string()` on an `&&T` where
+ /// `T` implements `ToString` directly (like `&&str` or `&&String`).
+ ///
+ /// ### Why is this bad?
+ /// This bypasses the specialized implementation of
+ /// `ToString` and instead goes through the more expensive string formatting
+ /// facilities.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Generic implementation for `T: Display` is used (slow)
+ /// ["foo", "bar"].iter().map(|s| s.to_string());
+ ///
+ /// // OK, the specialized impl is used
+ /// ["foo", "bar"].iter().map(|&s| s.to_string());
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub INEFFICIENT_TO_STRING,
+ pedantic,
+ "using `to_string` on `&&T` where `T: ToString`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `new` not returning a type that contains `Self`.
+ ///
+ /// ### Why is this bad?
+ /// As a convention, `new` methods are used to make a new
+ /// instance of a type.
+ ///
+ /// ### Example
+ /// In an impl block:
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEW_RET_NO_SELF,
+ style,
+ "not returning type containing `Self` in a `new` method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string methods that receive a single-character
+ /// `str` as an argument, e.g., `_.split("x")`.
+ ///
+ /// ### Why is this bad?
+ /// Performing these methods using a `char` is faster than
+ /// using a `str`.
+ ///
+ /// ### Known problems
+ /// Does not catch multi-byte unicode characters.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// _.split("x");
+ ///
+ /// // Good
+ /// _.split('x');
+ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_CHAR_PATTERN,
+ perf,
+ "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// ### Why is this bad?
+ /// This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// ### Example
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITERATOR_STEP_BY_ZERO,
+ correctness,
+ "using `Iterator::step_by(0)`, which will panic at runtime"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for indirect collection of populated `Option`
+ ///
+ /// ### Why is this bad?
+ /// `Option` is like a collection of 0-1 things, so `flatten`
+ /// automatically does this without suspicious-looking `unwrap` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().flatten();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub OPTION_FILTER_MAP,
+ complexity,
+ "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `iter.nth(0)`.
+ ///
+ /// ### Why is this bad?
+ /// `iter.next()` is equivalent to
+ /// `iter.nth(0)`, as they both consume the next element,
+ /// but is more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// // Bad
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().nth(0);
+ ///
+ /// // Good
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().next();
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub ITER_NTH_ZERO,
+ style,
+ "replace `iter.nth(0)` with `iter.next()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.iter().nth()` (and the related
+ /// `.iter_mut().nth()`) on standard library types with *O*(1) element access.
+ ///
+ /// ### Why is this bad?
+ /// `.get()` and `.get_mut()` are more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.get(3);
+ /// let bad_slice = &some_vec[..].get(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_NTH,
+ perf,
+ "using `.iter().nth()` on a standard library type with O(1) element access"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.skip(x).next()` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.nth(x)` is cleaner
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().skip(3).next();
+ /// let bad_slice = &some_vec[..].iter().skip(3).next();
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_SKIP_NEXT,
+ style,
+ "using `.skip(x).next()` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
+ ///
+ /// ### Why is this bad?
+ /// `.into_iter()` is simpler with better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let mut foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.drain(..).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.into_iter().collect();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub ITER_WITH_DRAIN,
+ nursery,
+ "replace `.drain(..)` with `.into_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.get().unwrap()` (or
+ /// `.get_mut().unwrap`) on a standard library type which implements `Index`
+ ///
+ /// ### Why is this bad?
+ /// Using the Index trait (`[]`) is more clear and more
+ /// concise.
+ ///
+ /// ### Known problems
+ /// Not a replacement for error handling: Using either
+ /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic`
+ /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a
+ /// temporary placeholder for dealing with the `Option` type, then this does
+ /// not mitigate the need for error handling. If there is a chance that `.get()`
+ /// will be `None` in your program, then it is advisable that the `None` case
+ /// is handled in a future refactor instead of using `.unwrap()` or the Index
+ /// trait.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec.get(3).unwrap();
+ /// *some_vec.get_mut(0).unwrap() = 1;
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec[3];
+ /// some_vec[0] = 1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for occurrences where one vector gets extended instead of append
+ ///
+ /// ### Why is this bad?
+ /// Using `append` instead of `extend` is more concise and faster
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// // Bad
+ /// a.extend(b.drain(..));
+ ///
+ /// // Good
+ /// a.append(&mut b);
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub EXTEND_WITH_DRAIN,
+ perf,
+ "using vec.append(&mut vec) to move the full range of a vector to another"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.extend(s.chars())` where s is a
+ /// `&str` or `String`.
+ ///
+ /// ### Why is this bad?
+ /// `.push_str(s)` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.extend(abc.chars());
+ /// s.extend(def.chars());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.push_str(abc);
+ /// s.push_str(&def);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_EXTEND_CHARS,
+ style,
+ "using `x.extend(s.chars())` where s is a `&str` or `String`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.cloned().collect()` on slice to
+ /// create a `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// `.to_vec()` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s[..].iter().cloned().collect();
+ /// ```
+ /// The better use would be:
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s.to_vec();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_CLONED_COLLECT,
+ style,
+ "using `.cloned().collect()` on slice to create a `Vec`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.chars().last()` or
+ /// `_.chars().next_back()` on a `str` to check if it ends with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ends_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "_";
+ ///
+ /// // Bad
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-');
+ ///
+ /// // Good
+ /// name.ends_with('_') || name.ends_with('-');
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_LAST_CMP,
+ style,
+ "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.as_ref()` or `.as_mut()` where the
+ /// types before and after the call are the same.
+ ///
+ /// ### Why is this bad?
+ /// The call is unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x.as_ref());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_ASREF,
+ complexity,
+ "using `as_ref` where the types before and after the call are the same"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `fold` when a more succinct alternative exists.
+ /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
+ /// `sum` or `product`.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+ /// ```
+ /// This could be written as:
+ /// ```rust
+ /// let _ = (0..3).any(|x| x > 2);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_FOLD,
+ style,
+ "using `fold` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
+ /// More specifically it checks if the closure provided is only performing one of the
+ /// filter or map operations and suggests the appropriate option.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).filter(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).filter_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1);
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub UNNECESSARY_FILTER_MAP,
+ complexity,
+ "using `filter_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `find_map` calls that could be replaced by `find` or `map`. More
+ /// specifically it checks if the closure provided is only performing one of the
+ /// find or map operations and suggests the appropriate option.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).find(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).find_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1).next();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_FIND_MAP,
+ complexity,
+ "using `find_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `into_iter` calls on references which should be replaced by `iter`
+ /// or `iter_mut`.
+ ///
+ /// ### Why is this bad?
+ /// Readability. Calling `into_iter` on a reference will not move out its
+ /// content into the resulting iterator, which is confusing. It is better just call `iter` or
+ /// `iter_mut` directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let _ = (&vec![3, 4, 5]).into_iter();
+ ///
+ /// // Good
+ /// let _ = (&vec![3, 4, 5]).iter();
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub INTO_ITER_ON_REF,
+ style,
+ "using `.into_iter()` on a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `map` followed by a `count`.
+ ///
+ /// ### Why is this bad?
+ /// It looks suspicious. Maybe `map` was confused with `filter`.
+ /// If the `map` call is intentional, this should be rewritten
+ /// using `inspect`. Or, if you intend to drive the iterator to
+ /// completion, you can just use `for_each` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).map(|x| x + 2).count();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub SUSPICIOUS_MAP,
+ suspicious,
+ "suspicious usage of map"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `MaybeUninit::uninit().assume_init()`.
+ ///
+ /// ### Why is this bad?
+ /// For most types, this is undefined behavior.
+ ///
+ /// ### Known problems
+ /// For now, we accept empty tuples and tuples / arrays
+ /// of `MaybeUninit`. There may be other types that allow uninitialized
+ /// data, but those are not yet rigorously defined.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Beware the UB
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
+ /// ```
+ ///
+ /// Note that the following is OK:
+ ///
+ /// ```rust
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: [MaybeUninit<bool>; 5] = unsafe {
+ /// MaybeUninit::uninit().assume_init()
+ /// };
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub UNINIT_ASSUMED_INIT,
+ correctness,
+ "`MaybeUninit::uninit().assume_init()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
+ ///
+ /// ### Why is this bad?
+ /// These can be written simply with `saturating_add/sub` methods.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.checked_add(y).unwrap_or(u32::MAX);
+ /// let sub = x.checked_sub(y).unwrap_or(u32::MIN);
+ /// ```
+ ///
+ /// can be written using dedicated methods for saturating addition/subtraction as:
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.saturating_add(y);
+ /// let sub = x.saturating_sub(y);
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub MANUAL_SATURATING_ARITHMETIC,
+ style,
- /// use std::iter::FromIterator;
- ///
++ "`.checked_add/sub(x).unwrap_or(MAX/MIN)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+ /// zero-sized types
+ ///
+ /// ### Why is this bad?
+ /// This is a no-op, and likely unintended
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub ZST_OFFSET,
+ correctness,
+ "Check for offset calculations on raw pointers to zero-sized types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `FileType::is_file()`.
+ ///
+ /// ### Why is this bad?
+ /// When people testing a file type with `FileType::is_file`
+ /// they are testing whether a path is something they can get bytes from. But
+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if filetype.is_file() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ ///
+ /// should be written as:
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if !filetype.is_dir() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.as_deref()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_ref().map(String::as_str)
+ /// # ;
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_deref()
+ /// # ;
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub OPTION_AS_REF_DEREF,
+ complexity,
+ "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `iter().next()` on a Slice or an Array
+ ///
+ /// ### Why is this bad?
+ /// These can be shortened into `.get()`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a[2..].iter().next();
+ /// b.iter().next();
+ /// ```
+ /// should be written as:
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a.get(2);
+ /// b.get(0);
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub ITER_NEXT_SLICE,
+ style,
+ "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns when using `push_str`/`insert_str` with a single-character string literal
+ /// where `push`/`insert` with a `char` would work fine.
+ ///
+ /// ### Why is this bad?
+ /// It's less clear that we are pushing a single character.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert_str(0, "R");
+ /// string.push_str("R");
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert(0, 'R');
+ /// string.push('R');
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub SINGLE_CHAR_ADD_STR,
+ style,
+ "`push_str()` or `insert_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// ### Why is this bad?
+ /// Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// ### Known problems
+ /// It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).collect::<Result<(), _>()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `try_for_each` instead is more readable and idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// (0..3).try_for_each(|t| Err(t));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MAP_COLLECT_RESULT_UNIT,
+ style,
+ "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `from_iter()` function calls on types that implement the `FromIterator`
+ /// trait.
+ ///
+ /// ### Why is this bad?
+ /// It is recommended style to use collect. See
+ /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
+ ///
+ /// ### Example
+ /// ```rust
- pub fn new(avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>) -> Self {
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v = Vec::from_iter(five_fives);
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v: Vec<i32> = five_fives.collect();
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub FROM_ITER_INSTEAD_OF_COLLECT,
+ pedantic,
+ "use `.collect()` instead of `::from_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `inspect().for_each()`.
+ ///
+ /// ### Why is this bad?
+ /// It is the same as performing the computation
+ /// inside `inspect` at the beginning of the closure in `for_each`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .inspect(|&x| println!("inspect the number: {}", x))
+ /// .for_each(|&x| {
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .for_each(|&x| {
+ /// println!("inspect the number: {}", x);
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub INSPECT_FOR_EACH,
+ complexity,
+ "using `.inspect().for_each()`, which can be replaced with `.for_each()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `filter_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.filter_map(|x| x);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub FILTER_MAP_IDENTITY,
+ complexity,
+ "call to `filter_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map(f)` where `f` is the identity function.
+ ///
+ /// ### Why is this bad?
+ /// It can be written more concisely without the call to `map`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| 2*x).collect();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub MAP_IDENTITY,
+ complexity,
+ "using iterator.map(|x| x)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.bytes().nth()`.
+ ///
+ /// ### Why is this bad?
+ /// `.as_bytes().get()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let _ = "Hello".bytes().nth(3);
+ ///
+ /// // Good
+ /// let _ = "Hello".as_bytes().get(3);
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub BYTES_NTH,
+ style,
+ "replace `.bytes().nth()` with `.as_bytes().get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
+ ///
+ /// ### Why is this bad?
+ /// These methods do the same thing as `_.clone()` but may be confusing as
+ /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.to_vec();
+ /// let c = a.to_owned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.clone();
+ /// let c = a.clone();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub IMPLICIT_CLONE,
+ pedantic,
+ "implicitly cloning a value by invoking a function on its dereferenced type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.iter().count()`.
+ ///
+ /// ### Why is this bad?
+ /// `.len()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.iter().count();
+ /// let _ = &some_vec[..].iter().count();
+ ///
+ /// // Good
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.len();
+ /// let _ = &some_vec[..].len();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub ITER_COUNT,
+ complexity,
+ "replace `.iter().count()` with `.len()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to [`splitn`]
+ /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
+ /// related functions with either zero or one splits.
+ ///
+ /// ### Why is this bad?
+ /// These calls don't actually split the value and are
+ /// likely to be intended as a different number.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let s = "";
+ /// for x in s.splitn(1, ":") {
+ /// // use x
+ /// }
+ ///
+ /// // Good
+ /// let s = "";
+ /// for x in s.splitn(2, ":") {
+ /// // use x
+ /// }
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub SUSPICIOUS_SPLITN,
+ correctness,
+ "checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of `str::repeat`
+ ///
+ /// ### Why is this bad?
+ /// These are both harder to read, as well as less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: String = std::iter::repeat('x').take(10).collect();
+ ///
+ /// // Good
+ /// let x: String = "x".repeat(10);
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub MANUAL_STR_REPEAT,
+ perf,
+ "manual implementation of `str::repeat`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn(2, _)`
+ ///
+ /// ### Why is this bad?
+ /// `split_once` is both clearer in intent and slightly more efficient.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// let s = "key=value=add";
+ /// let (key, value) = s.splitn(2, '=').next_tuple()?;
+ /// let value = s.splitn(2, '=').nth(1)?;
+ ///
+ /// let mut parts = s.splitn(2, '=');
+ /// let key = parts.next()?;
+ /// let value = parts.next()?;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// // Good
+ /// let s = "key=value=add";
+ /// let (key, value) = s.split_once('=')?;
+ /// let value = s.split_once('=')?.1;
+ ///
+ /// let (key, value) = s.split_once('=')?;
+ /// ```
+ ///
+ /// ### Limitations
+ /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()`
+ /// in two separate `let` statements that immediately follow the `splitn()`
+ #[clippy::version = "1.57.0"]
+ pub MANUAL_SPLIT_ONCE,
+ complexity,
+ "replace `.splitn(2, pat)` with `.split_once(pat)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same.
+ /// ### Why is this bad?
+ /// The function `split` is simpler and there is no performance difference in these cases, considering
+ /// that both functions return a lazy iterator.
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let str = "key=value=add";
+ /// let _ = str.splitn(3, '=').next().unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // Good
+ /// let str = "key=value=add";
+ /// let _ = str.split('=').next().unwrap();
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub NEEDLESS_SPLITN,
+ complexity,
+ "usages of `str::splitn` that can be replaced with `str::split`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned)
+ /// and other `to_owned`-like functions.
+ ///
+ /// ### Why is this bad?
+ /// The unnecessary calls result in useless allocations.
+ ///
+ /// ### Known problems
+ /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an
+ /// owned copy of a resource and the resource is later used mutably. See
+ /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy().to_string());
+ /// fn foo(s: &str) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy());
+ /// fn foo(s: &str) {}
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNNECESSARY_TO_OWNED,
+ perf,
+ "unnecessary calls to `to_owned`-like functions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.collect::<String>()` is more concise and might be more performant
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
+ /// println!("{}", output);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ /// println!("{}", output);
+ /// ```
+ /// ### Known problems
+ /// While `.collect::<String>()` is sometimes more performant, there are cases where
+ /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+ /// will prevent loop unrolling and will result in a negative performance impact.
+ ///
+ /// Additionally, differences have been observed between aarch64 and x86_64 assembly output,
+ /// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_JOIN,
+ pedantic,
+ "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`,
+ /// for example, `Option<&T>::as_deref()` returns the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code and improving readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a;
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub NEEDLESS_OPTION_AS_DEREF,
+ complexity,
+ "no-op use of `deref` or `deref_mut` method to `Option`."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds usages of [`char::is_digit`]
+ /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
+ /// can be replaced with [`is_ascii_digit`]
+ /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
+ /// [`is_ascii_hexdigit`]
+ /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
+ ///
+ /// ### Why is this bad?
+ /// `is_digit(..)` is slower and requires specifying the radix.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let c: char = '6';
+ /// c.is_digit(10);
+ /// c.is_digit(16);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let c: char = '6';
+ /// c.is_ascii_digit();
+ /// c.is_ascii_hexdigit();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub IS_DIGIT_ASCII_RADIX,
+ style,
+ "use of `char::is_digit(..)` with literal radix of 10 or 16"
+}
+
+declare_clippy_lint! {
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref().take();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub NEEDLESS_OPTION_TAKE,
+ complexity,
+ "using `.as_ref().take()` on a temporary value"
+}
+
+pub struct Methods {
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
++ allow_expect_in_tests: bool,
++ allow_unwrap_in_tests: bool,
+}
+
+impl Methods {
+ #[must_use]
- check_methods(cx, expr, self.msrv.as_ref());
++ pub fn new(
++ avoid_breaking_exported_api: bool,
++ msrv: Option<RustcVersion>,
++ allow_expect_in_tests: bool,
++ allow_unwrap_in_tests: bool,
++ ) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ msrv,
++ allow_expect_in_tests,
++ allow_unwrap_in_tests,
+ }
+ }
+}
+
+impl_lint_pass!(Methods => [
+ UNWRAP_USED,
+ EXPECT_USED,
+ SHOULD_IMPLEMENT_TRAIT,
+ WRONG_SELF_CONVENTION,
+ OK_EXPECT,
+ UNWRAP_OR_ELSE_DEFAULT,
+ MAP_UNWRAP_OR,
+ RESULT_MAP_OR_INTO_OPTION,
+ OPTION_MAP_OR_NONE,
+ BIND_INSTEAD_OF_MAP,
+ OR_FUN_CALL,
+ OR_THEN_UNWRAP,
+ EXPECT_FUN_CALL,
+ CHARS_NEXT_CMP,
+ CHARS_LAST_CMP,
+ CLONE_ON_COPY,
+ CLONE_ON_REF_PTR,
+ CLONE_DOUBLE_REF,
+ ITER_OVEREAGER_CLONED,
+ CLONED_INSTEAD_OF_COPIED,
+ FLAT_MAP_OPTION,
+ INEFFICIENT_TO_STRING,
+ NEW_RET_NO_SELF,
+ SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_ADD_STR,
+ SEARCH_IS_SOME,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP_IDENTITY,
+ MAP_IDENTITY,
+ MANUAL_FILTER_MAP,
+ MANUAL_FIND_MAP,
+ OPTION_FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_COUNT,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ BYTES_NTH,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ STRING_EXTEND_CHARS,
+ ITER_CLONED_COLLECT,
+ ITER_WITH_DRAIN,
+ USELESS_ASREF,
+ UNNECESSARY_FOLD,
+ UNNECESSARY_FILTER_MAP,
+ UNNECESSARY_FIND_MAP,
+ INTO_ITER_ON_REF,
+ SUSPICIOUS_MAP,
+ UNINIT_ASSUMED_INIT,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ MAP_COLLECT_RESULT_UNIT,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ INSPECT_FOR_EACH,
+ IMPLICIT_CLONE,
+ SUSPICIOUS_SPLITN,
+ MANUAL_STR_REPEAT,
+ EXTEND_WITH_DRAIN,
+ MANUAL_SPLIT_ONCE,
+ NEEDLESS_SPLITN,
+ UNNECESSARY_TO_OWNED,
+ UNNECESSARY_JOIN,
+ ERR_EXPECT,
+ NEEDLESS_OPTION_AS_DEREF,
+ IS_DIGIT_ASCII_RADIX,
+ NEEDLESS_OPTION_TAKE,
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx str, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, args, _) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) {
+ let name = path.ident.name.as_str();
+ return Some((name, args, path.ident.span));
+ }
+ }
+ None
+}
+
+impl<'tcx> LateLintPass<'tcx> for Methods {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
- unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv.as_ref());
++ self.check_methods(cx, expr);
+
+ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ from_iter_instead_of_collect::check(cx, expr, args, func);
+ },
+ hir::ExprKind::MethodCall(method_call, args, _) => {
+ let method_span = method_call.ident.span;
+ or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
+ expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
+ clone_on_copy::check(cx, expr, method_call.ident.name, args);
+ clone_on_ref_ptr::check(cx, expr, method_call.ident.name, args);
+ inefficient_to_string::check(cx, expr, method_call.ident.name, args);
+ single_char_add_str::check(cx, expr, args);
+ into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args);
+ single_char_pattern::check(cx, expr, method_call.ident.name, args);
- #[allow(clippy::too_many_lines)]
- fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) {
- if let Some((name, [recv, args @ ..], span)) = method_call(expr) {
- match (name, args) {
- ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
- zst_offset::check(cx, expr, recv);
- },
- ("and_then", [arg]) => {
- let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
- let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
- if !biom_option_linted && !biom_result_linted {
- unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
- }
- },
- ("as_deref" | "as_deref_mut", []) => {
- needless_option_as_deref::check(cx, expr, recv, name);
- },
- ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
- ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
- ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
- ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, msrv),
- ("collect", []) => match method_call(recv) {
- Some((name @ ("cloned" | "copied"), [recv2], _)) => {
- iter_cloned_collect::check(cx, name, expr, recv2);
++ unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv);
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ },
+ _ => (),
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.def_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
+ if_chain! {
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
+ if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next();
+
+ let method_sig = cx.tcx.fn_sig(impl_item.def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
+
+ let first_arg_ty = method_sig.inputs().iter().next();
+
+ // check conventions w.r.t. conversion method names and predicates
+ if let Some(first_arg_ty) = first_arg_ty;
+
+ then {
+ // if this impl block implements a trait, lint in trait definition instead
+ if !implements_trait && cx.access_levels.is_exported(impl_item.def_id) {
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name &&
+ sig.decl.inputs.len() == method_config.param_count &&
+ method_config.output_type.matches(&sig.decl.output) &&
+ method_config.self_kind.matches(cx, self_ty, *first_arg_ty) &&
+ fn_header_equals(method_config.fn_header, sig.header) &&
+ method_config.lifetime_param_cond(impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name,
+ method_config.trait_name,
+ method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ )
+ );
+ }
+ }
+ }
+
+ if sig.decl.implicit_self.has_implicit_self()
+ && !(self.avoid_breaking_exported_api
+ && cx.access_levels.is_exported(impl_item.def_id))
+ {
+ wrong_self_convention::check(
+ cx,
+ name,
+ self_ty,
+ *first_arg_ty,
+ first_arg.pat.span,
+ implements_trait,
+ false
+ );
+ }
+ }
+ }
+
+ // if this impl block implements a trait, lint in trait definition instead
+ if implements_trait {
+ return;
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
+ // walk the return type and check for Self (this does not check associated types)
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(ret_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(ret_ty, self_ty) {
+ return;
+ }
+
+ // if return type is impl trait, check the associated types
+ if let ty::Opaque(def_id, _) = *ret_ty.kind() {
+ // one of the associated types must be Self
+ for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
+ if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ let assoc_ty = match projection_predicate.term {
+ ty::Term::Ty(ty) => ty,
+ ty::Term::Const(_c) => continue,
+ };
+ // walk the associated type and check for Self
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(assoc_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(assoc_ty, self_ty) {
+ return;
+ }
+ }
+ }
+ }
+
+ if name == "new" && ret_ty != self_ty {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+
+ if_chain! {
+ if let TraitItemKind::Fn(ref sig, _) = item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
+
+ then {
+ let first_arg_span = first_arg_ty.span;
+ let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ wrong_self_convention::check(
+ cx,
+ item.ident.name.as_str(),
+ self_ty,
+ first_arg_ty,
+ first_arg_span,
+ false,
+ true
+ );
+ }
+ }
+
+ if_chain! {
+ if item.ident.name == sym::new;
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id());
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ if !contains_ty(ret_ty, self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
- Some(("map", [m_recv, m_arg], _)) => {
- map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
++impl Methods {
++ #[allow(clippy::too_many_lines)]
++ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if let Some((name, [recv, args @ ..], span)) = method_call(expr) {
++ match (name, args) {
++ ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
++ zst_offset::check(cx, expr, recv);
++ },
++ ("and_then", [arg]) => {
++ let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
++ let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
++ if !biom_option_linted && !biom_result_linted {
++ unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
++ }
++ },
++ ("as_deref" | "as_deref_mut", []) => {
++ needless_option_as_deref::check(cx, expr, recv, name);
++ },
++ ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
++ ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
++ ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
++ ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv),
++ ("collect", []) => match method_call(recv) {
++ Some((name @ ("cloned" | "copied"), [recv2], _)) => {
++ iter_cloned_collect::check(cx, name, expr, recv2);
++ },
++ Some(("map", [m_recv, m_arg], _)) => {
++ map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
++ },
++ Some(("take", [take_self_arg, take_arg], _)) => {
++ if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
++ manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
++ }
++ },
++ _ => {},
++ },
++ (name @ "count", args @ []) => match method_call(recv) {
++ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
++ Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
++ iter_count::check(cx, expr, recv2, name2);
++ },
++ Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
++ _ => {},
++ },
++ ("drain", [arg]) => {
++ iter_with_drain::check(cx, expr, recv, span, arg);
++ },
++ ("expect", [_]) => match method_call(recv) {
++ Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
++ Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
++ _ => expect_used::check(cx, expr, recv, self.allow_expect_in_tests),
++ },
++ ("extend", [arg]) => {
++ string_extend_chars::check(cx, expr, recv, arg);
++ extend_with_drain::check(cx, expr, recv, arg);
++ },
++ ("filter_map", [arg]) => {
++ unnecessary_filter_map::check(cx, expr, arg, name);
++ filter_map_identity::check(cx, expr, arg, span);
+ },
- Some(("take", [take_self_arg, take_arg], _)) => {
- if meets_msrv(msrv, &msrvs::STR_REPEAT) {
- manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
++ ("find_map", [arg]) => {
++ unnecessary_filter_map::check(cx, expr, arg, name);
+ },
- _ => {},
- },
- (name @ "count", args @ []) => match method_call(recv) {
- Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
- Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
- iter_count::check(cx, expr, recv2, name2);
++ ("flat_map", [arg]) => {
++ flat_map_identity::check(cx, expr, arg, span);
++ flat_map_option::check(cx, expr, arg, span);
++ },
++ (name @ "flatten", args @ []) => match method_call(recv) {
++ Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
++ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
++ _ => {},
++ },
++ ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
++ ("for_each", [_]) => {
++ if let Some(("inspect", [_, _], span2)) = method_call(recv) {
++ inspect_for_each::check(cx, expr, span2);
+ }
+ },
- Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
- _ => {},
- },
- ("drain", [arg]) => {
- iter_with_drain::check(cx, expr, recv, span, arg);
- },
- ("expect", [_]) => match method_call(recv) {
- Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
- Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, msrv, span, err_span),
- _ => expect_used::check(cx, expr, recv),
- },
- ("extend", [arg]) => {
- string_extend_chars::check(cx, expr, recv, arg);
- extend_with_drain::check(cx, expr, recv, arg);
- },
- ("filter_map", [arg]) => {
- unnecessary_filter_map::check(cx, expr, arg, name);
- filter_map_identity::check(cx, expr, arg, span);
- },
- ("find_map", [arg]) => {
- unnecessary_filter_map::check(cx, expr, arg, name);
- },
- ("flat_map", [arg]) => {
- flat_map_identity::check(cx, expr, arg, span);
- flat_map_option::check(cx, expr, arg, span);
- },
- (name @ "flatten", args @ []) => match method_call(recv) {
- Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
- Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
- _ => {},
- },
- ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
- ("for_each", [_]) => {
- if let Some(("inspect", [_, _], span2)) = method_call(recv) {
- inspect_for_each::check(cx, expr, span2);
- }
- },
- ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
- ("is_file", []) => filetype_is_file::check(cx, expr, recv),
- ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, msrv),
- ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
- ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
- ("join", [join_arg]) => {
- if let Some(("collect", _, span)) = method_call(recv) {
- unnecessary_join::check(cx, expr, recv, join_arg, span);
- }
- },
- ("last", args @ []) | ("skip", args @ [_]) => {
- if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
- if let ("cloned", []) = (name2, args2) {
- iter_overeager_cloned::check(cx, expr, recv2, name, args);
++ ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
++ ("is_file", []) => filetype_is_file::check(cx, expr, recv),
++ ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
++ ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
++ ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
++ ("join", [join_arg]) => {
++ if let Some(("collect", _, span)) = method_call(recv) {
++ unnecessary_join::check(cx, expr, recv, join_arg, span);
++ }
+ },
- }
- },
- (name @ ("map" | "map_err"), [m_arg]) => {
- if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) {
- match (name, args) {
- ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv),
- ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv),
- ("filter", [f_arg]) => {
- filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
- },
- ("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true),
- _ => {},
++ ("last", args @ []) | ("skip", args @ [_]) => {
++ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
++ if let ("cloned", []) = (name2, args2) {
++ iter_overeager_cloned::check(cx, expr, recv2, name, args);
++ }
+ }
- }
- map_identity::check(cx, expr, recv, m_arg, name, span);
- },
- ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
- (name @ "next", args @ []) => {
- if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) {
- match (name2, args2) {
- ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
- ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
- ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, msrv),
- ("iter", []) => iter_next_slice::check(cx, expr, recv2),
- ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
- ("skip_while", [_]) => skip_while_next::check(cx, expr),
- _ => {},
++ },
++ (name @ ("map" | "map_err"), [m_arg]) => {
++ if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) {
++ match (name, args) {
++ ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
++ ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
++ ("filter", [f_arg]) => {
++ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
++ },
++ ("find", [f_arg]) => {
++ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true);
++ },
++ _ => {},
++ }
+ }
- }
- },
- ("nth", args @ [n_arg]) => match method_call(recv) {
- Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
- Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
- Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
- Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
- _ => iter_nth_zero::check(cx, expr, recv, n_arg),
- },
- ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
- ("or_else", [arg]) => {
- if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
- unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
- }
- },
- ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
- if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
- suspicious_splitn::check(cx, name, expr, recv, count);
- str_splitn::check(cx, name, expr, recv, pat_arg, count, msrv);
- }
- },
- ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
- if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
- suspicious_splitn::check(cx, name, expr, recv, count);
- }
- },
- ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
- ("take", args @ [_arg]) => {
- if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
- if let ("cloned", []) = (name2, args2) {
- iter_overeager_cloned::check(cx, expr, recv2, name, args);
++ map_identity::check(cx, expr, recv, m_arg, name, span);
++ },
++ ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
++ (name @ "next", args @ []) => {
++ if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) {
++ match (name2, args2) {
++ ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
++ ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
++ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
++ ("iter", []) => iter_next_slice::check(cx, expr, recv2),
++ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
++ ("skip_while", [_]) => skip_while_next::check(cx, expr),
++ _ => {},
++ }
+ }
- }
- },
- ("take", []) => needless_option_take::check(cx, expr, recv),
- ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
- implicit_clone::check(cx, name, expr, recv);
- },
- ("unwrap", []) => {
- match method_call(recv) {
- Some(("get", [recv, get_arg], _)) => {
- get_unwrap::check(cx, expr, recv, get_arg, false);
- },
- Some(("get_mut", [recv, get_arg], _)) => {
- get_unwrap::check(cx, expr, recv, get_arg, true);
++ },
++ ("nth", args @ [n_arg]) => match method_call(recv) {
++ Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
++ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
++ Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
++ Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
++ _ => iter_nth_zero::check(cx, expr, recv, n_arg),
++ },
++ ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
++ ("or_else", [arg]) => {
++ if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
++ unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
+ }
- Some(("or", [recv, or_arg], or_span)) => {
- or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
++ },
++ ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
++ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
++ suspicious_splitn::check(cx, name, expr, recv, count);
++ str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
++ }
++ },
++ ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
++ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
++ suspicious_splitn::check(cx, name, expr, recv, count);
++ }
++ },
++ ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
++ ("take", args @ [_arg]) => {
++ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
++ if let ("cloned", []) = (name2, args2) {
++ iter_overeager_cloned::check(cx, expr, recv2, name, args);
++ }
++ }
++ },
++ ("take", []) => needless_option_take::check(cx, expr, recv),
++ ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
++ implicit_clone::check(cx, name, expr, recv);
++ },
++ ("unwrap", []) => {
++ match method_call(recv) {
++ Some(("get", [recv, get_arg], _)) => {
++ get_unwrap::check(cx, expr, recv, get_arg, false);
++ },
++ Some(("get_mut", [recv, get_arg], _)) => {
++ get_unwrap::check(cx, expr, recv, get_arg, true);
++ },
++ Some(("or", [recv, or_arg], or_span)) => {
++ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
++ },
++ _ => {},
++ }
++ unwrap_used::check(cx, expr, recv, self.allow_unwrap_in_tests);
++ },
++ ("unwrap_or", [u_arg]) => match method_call(recv) {
++ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
++ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
- }
- unwrap_used::check(cx, expr, recv);
- },
- ("unwrap_or", [u_arg]) => match method_call(recv) {
- Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
- manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
++ Some(("map", [m_recv, m_arg], span)) => {
++ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
+ _ => {},
- Some(("map", [m_recv, m_arg], span)) => {
- option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
- },
- ("unwrap_or_else", [u_arg]) => match method_call(recv) {
- Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {},
- _ => {
- unwrap_or_else_default::check(cx, expr, recv, u_arg);
- unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
- },
- },
- _ => {},
++ ("unwrap_or_else", [u_arg]) => match method_call(recv) {
++ Some(("map", [recv, map_arg], _))
++ if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
++ _ => {
++ unwrap_or_else_default::check(cx, expr, recv, u_arg);
++ unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
++ },
+ },
+ _ => {},
- #[derive(Clone, Copy, PartialEq, Debug)]
++ }
+ }
+ }
+}
+
+fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
+ if let Some((name @ ("find" | "position" | "rposition"), [f_recv, arg], span)) = method_call(recv) {
+ search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
+ }
+}
+
+/// Used for `lint_binary_expr_with_method_call`.
+#[derive(Copy, Clone)]
+struct BinaryExprInfo<'a> {
+ expr: &'a hir::Expr<'a>,
+ chain: &'a hir::Expr<'a>,
+ other: &'a hir::Expr<'a>,
+ eq: bool,
+}
+
+/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) {
+ macro_rules! lint_with_both_lhs_and_rhs {
+ ($func:expr, $cx:expr, $info:ident) => {
+ if !$func($cx, $info) {
+ ::std::mem::swap(&mut $info.chain, &mut $info.other);
+ if $func($cx, $info) {
+ return;
+ }
+ }
+ };
+ }
+
+ lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
+}
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ // FIXME: default doesn't work
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+];
+
++#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No,
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = *ty.kind() {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => return false,
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ !matches_value(cx, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty)
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => matches_none(cx, parent_ty, ty),
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "`self` by value",
+ Self::Ref => "`self` by reference",
+ Self::RefMut => "`self` by mutable reference",
+ Self::No => "no `self`",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn is_bool(ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ matches!(path.res, Res::PrimTy(PrimTy::Bool))
+ } else {
+ false
+ }
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::OPTION_AS_REF_DEREF;
+
+/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ as_ref_recv: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ is_mut: bool,
- if !meets_msrv(msrv, &msrvs::OPTION_AS_DEREF) {
++ msrv: Option<RustcVersion>,
+) {
++ if !meets_msrv(msrv, msrvs::OPTION_AS_DEREF) {
+ return;
+ }
+
+ let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
+
+ let option_ty = cx.typeck_results().expr_ty(as_ref_recv);
+ if !is_type_diagnostic_item(cx, option_ty, sym::Option) {
+ return;
+ }
+
+ let deref_aliases: [&[&str]; 9] = [
+ &paths::DEREF_TRAIT_METHOD,
+ &paths::DEREF_MUT_TRAIT_METHOD,
+ &paths::CSTRING_AS_C_STR,
+ &paths::OS_STRING_AS_OS_STR,
+ &paths::PATH_BUF_AS_PATH,
+ &paths::STRING_AS_STR,
+ &paths::STRING_AS_MUT_STR,
+ &paths::VEC_AS_SLICE,
+ &paths::VEC_AS_MUT_SLICE,
+ ];
+
+ let is_deref = match map_arg.kind {
+ hir::ExprKind::Path(ref expr_qpath) => cx
+ .qpath_res(expr_qpath, map_arg.hir_id)
+ .opt_def_id()
+ .map_or(false, |fun_def_id| {
+ deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
+ }),
+ hir::ExprKind::Closure(_, _, body_id, _, _) => {
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = peel_blocks(&closure_body.value);
+
+ match &closure_expr.kind {
+ hir::ExprKind::MethodCall(_, args, _) => {
+ if_chain! {
+ if args.len() == 1;
+ if path_to_local_id(&args[0], closure_body.params[0].pat.hir_id);
+ let adj = cx
+ .typeck_results()
+ .expr_adjustments(&args[0])
+ .iter()
+ .map(|x| &x.kind)
+ .collect::<Box<[_]>>();
+ if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
+ then {
+ let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
+ deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
+ } else {
+ false
+ }
+ }
+ },
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, inner) if same_mutability(m) => {
+ if_chain! {
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind;
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind;
+ then {
+ path_to_local_id(inner2, closure_body.params[0].pat.hir_id)
+ } else {
+ false
+ }
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ };
+
+ if is_deref {
+ let current_method = if is_mut {
+ format!(".as_mut().map({})", snippet(cx, map_arg.span, ".."))
+ } else {
+ format!(".as_ref().map({})", snippet(cx, map_arg.span, ".."))
+ };
+ let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
+ let hint = format!("{}.{}()", snippet(cx, as_ref_recv.span, ".."), method_hint);
+ let suggestion = format!("try using {} instead", method_hint);
+
+ let msg = format!(
+ "called `{0}` on an Option value. This can be done more directly \
+ by calling `{1}` instead",
+ current_method, hint
+ );
+ span_lint_and_sugg(
+ cx,
+ OPTION_AS_REF_DEREF,
+ expr.span,
+ &msg,
+ &suggestion,
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::usage::local_used_after_expr;
+use clippy_utils::visitors::expr_visitor;
+use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{
+ BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
+};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span, Symbol, SyntaxContext};
+
+use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+ count: u128,
- let manual = count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE);
++ msrv: Option<RustcVersion>,
+) {
+ if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
+ return;
+ }
+
+ let needless = |usage_kind| match usage_kind {
+ IterUsageKind::Nth(n) => count > n + 1,
+ IterUsageKind::NextTuple => count > 2,
+ };
- #[derive(Debug, PartialEq)]
++ let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE);
+
+ match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
+ Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
+ Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage),
+ None if manual => {
+ check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
+ },
+ _ => {},
+ }
+}
+
+fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
+ let mut app = Applicability::MachineApplicable;
+ let r = if method_name == "splitn" { "" } else { "r" };
+
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_SPLITN,
+ expr.span,
+ &format!("unnecessary use of `{r}splitn`"),
+ "try this",
+ format!(
+ "{}.{r}split({})",
+ snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0,
+ snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0,
+ ),
+ app,
+ );
+}
+
+fn check_manual_split_once(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+ usage: &IterUsage,
+) {
+ let ctxt = expr.span.ctxt();
+ let (msg, reverse) = if method_name == "splitn" {
+ ("manual implementation of `split_once`", false)
+ } else {
+ ("manual implementation of `rsplit_once`", true)
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+ let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+ let sugg = match usage.kind {
+ IterUsageKind::NextTuple => {
+ if reverse {
+ format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
+ } else {
+ format!("{self_snip}.split_once({pat_snip})")
+ }
+ },
+ IterUsageKind::Nth(1) => {
+ let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
+
+ match usage.unwrap_kind {
+ Some(UnwrapKind::Unwrap) => {
+ format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
+ },
+ Some(UnwrapKind::QuestionMark) => {
+ format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
+ },
+ None => {
+ format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
+ },
+ }
+ },
+ IterUsageKind::Nth(_) => return,
+ };
+
+ span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
+}
+
+/// checks for
+///
+/// ```
+/// let mut iter = "a.b.c".splitn(2, '.');
+/// let a = iter.next();
+/// let b = iter.next();
+/// ```
+fn check_manual_split_once_indirect(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+) -> Option<()> {
+ let ctxt = expr.span.ctxt();
+ let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
+ if let (_, Node::Local(local)) = parents.next()?
+ && let PatKind::Binding(BindingAnnotation::Mutable, iter_binding_id, iter_ident, None) = local.pat.kind
+ && let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
+ && let (_, Node::Block(enclosing_block)) = parents.next()?
+
+ && let mut stmts = enclosing_block
+ .stmts
+ .iter()
+ .skip_while(|stmt| stmt.hir_id != iter_stmt_id)
+ .skip(1)
+
+ && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+ && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+ && first.unwrap_kind == second.unwrap_kind
+ && first.name != second.name
+ && !local_used_after_expr(cx, iter_binding_id, second.init_expr)
+ {
+ let (r, lhs, rhs) = if method_name == "splitn" {
+ ("", first.name, second.name)
+ } else {
+ ("r", second.name, first.name)
+ };
+ let msg = format!("manual implementation of `{r}split_once`");
+
+ let mut app = Applicability::MachineApplicable;
+ let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+ let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+ span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| {
+ diag.span_label(first.span, "first usage here");
+ diag.span_label(second.span, "second usage here");
+
+ let unwrap = match first.unwrap_kind {
+ UnwrapKind::Unwrap => ".unwrap()",
+ UnwrapKind::QuestionMark => "?",
+ };
+ diag.span_suggestion_verbose(
+ local.span,
+ &format!("try `{r}split_once`"),
+ format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
+ app,
+ );
+
+ let remove_msg = format!("remove the `{iter_ident}` usages");
+ diag.span_suggestion(
+ first.span,
+ &remove_msg,
+ String::new(),
+ app,
+ );
+ diag.span_suggestion(
+ second.span,
+ &remove_msg,
+ String::new(),
+ app,
+ );
+ });
+ }
+
+ Some(())
+}
+
+#[derive(Debug)]
+struct IndirectUsage<'a> {
+ name: Symbol,
+ span: Span,
+ init_expr: &'a Expr<'a>,
+ unwrap_kind: UnwrapKind,
+}
+
+/// returns `Some(IndirectUsage)` for e.g.
+///
+/// ```ignore
+/// let name = binding.next()?;
+/// let name = binding.next().unwrap();
+/// ```
+fn indirect_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ stmt: &Stmt<'tcx>,
+ binding: HirId,
+ ctxt: SyntaxContext,
+) -> Option<IndirectUsage<'tcx>> {
+ if let StmtKind::Local(Local {
+ pat:
+ Pat {
+ kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None),
+ ..
+ },
+ init: Some(init_expr),
+ hir_id: local_hir_id,
+ ..
+ }) = stmt.kind
+ {
+ let mut path_to_binding = None;
+ expr_visitor(cx, |expr| {
+ if path_to_local_id(expr, binding) {
+ path_to_binding = Some(expr);
+ }
+
+ path_to_binding.is_none()
+ })
+ .visit_expr(init_expr);
+
+ let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
+ let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
+
+ let (parent_id, _) = parents.find(|(_, node)| {
+ !matches!(
+ node,
+ Node::Expr(Expr {
+ kind: ExprKind::Match(.., MatchSource::TryDesugar),
+ ..
+ })
+ )
+ })?;
+
+ if let IterUsage {
+ kind: IterUsageKind::Nth(0),
+ unwrap_kind: Some(unwrap_kind),
+ ..
+ } = iter_usage
+ {
+ if parent_id == *local_hir_id {
+ return Some(IndirectUsage {
+ name: ident.name,
+ span: stmt.span,
+ init_expr,
+ unwrap_kind,
+ });
+ }
+ }
+ }
+
+ None
+}
+
+#[derive(Debug, Clone, Copy)]
+enum IterUsageKind {
+ Nth(u128),
+ NextTuple,
+}
+
++#[derive(Debug, PartialEq, Eq)]
+enum UnwrapKind {
+ Unwrap,
+ QuestionMark,
+}
+
+#[derive(Debug)]
+struct IterUsage {
+ kind: IterUsageKind,
+ unwrap_kind: Option<UnwrapKind>,
+ span: Span,
+}
+
+#[allow(clippy::too_many_lines)]
+fn parse_iter_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ ctxt: SyntaxContext,
+ mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
+) -> Option<IterUsage> {
+ let (kind, span) = match iter.next() {
+ Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
+ let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
+ (name, args)
+ } else {
+ return None;
+ };
+ let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
+ let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
+
+ match (name.ident.as_str(), args) {
+ ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
+ ("next_tuple", []) => {
+ return if_chain! {
+ if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
+ if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
+ if let ty::Tuple(subs) = subs.type_at(0).kind();
+ if subs.len() == 2;
+ then {
+ Some(IterUsage {
+ kind: IterUsageKind::NextTuple,
+ span: e.span,
+ unwrap_kind: None
+ })
+ } else {
+ None
+ }
+ };
+ },
+ ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
+ if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
+ let span = if name.ident.as_str() == "nth" {
+ e.span
+ } else {
+ if_chain! {
+ if let Some((_, Node::Expr(next_expr))) = iter.next();
+ if let ExprKind::MethodCall(next_name, [_], _) = next_expr.kind;
+ if next_name.ident.name == sym::next;
+ if next_expr.span.ctxt() == ctxt;
+ if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
+ if cx.tcx.trait_of_item(next_id) == Some(iter_id);
+ then {
+ next_expr.span
+ } else {
+ return None;
+ }
+ }
+ };
+ (IterUsageKind::Nth(idx), span)
+ } else {
+ return None;
+ }
+ },
+ _ => return None,
+ }
+ },
+ _ => return None,
+ };
+
+ let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
+ match e.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
+ ..
+ },
+ _,
+ ) => {
+ let parent_span = e.span.parent_callsite().unwrap();
+ if parent_span.ctxt() == ctxt {
+ (Some(UnwrapKind::QuestionMark), parent_span)
+ } else {
+ (None, span)
+ }
+ },
+ _ if e.span.ctxt() != ctxt => (None, span),
+ ExprKind::MethodCall(name, [_], _)
+ if name.ident.name == sym::unwrap
+ && cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
+ {
+ (Some(UnwrapKind::Unwrap), e.span)
+ },
+ _ => (None, span),
+ }
+ } else {
+ (None, span)
+ };
+
+ Some(IterUsage {
+ kind,
+ unwrap_kind,
+ span,
+ })
+}
--- /dev/null
- msrv: Option<&RustcVersion>,
+use super::implicit_clone::is_clone_like;
+use super::unnecessary_iter_cloned::{self, is_into_iter};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{
+ contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
+};
+use clippy_utils::{meets_msrv, msrvs};
+
+use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
+use rustc_errors::Applicability;
+use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
+use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Symbol};
+use std::cmp::max;
+
+use super::UNNECESSARY_TO_OWNED;
+
+pub fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ args: &'tcx [Expr<'tcx>],
- if let Some(target_ty) = match adjustments[..]
- {
++ msrv: Option<RustcVersion>,
+) {
+ if_chain! {
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let [receiver] = args;
+ then {
+ if is_cloned_or_copied(cx, method_name, method_def_id) {
+ unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
+ } else if is_to_owned_like(cx, method_name, method_def_id) {
+ // At this point, we know the call is of a `to_owned`-like function. The functions
+ // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
+ // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
+ // argument in a `into_iter` call, or an argument in the call of some other function.
+ if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
+ return;
+ }
+ if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
+ return;
+ }
+ check_other_call_arg(cx, expr, method_name, receiver);
+ }
+ }
+ }
+}
+
+/// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
+/// call of a `to_owned`-like function is unnecessary.
+#[allow(clippy::too_many_lines)]
+fn check_addr_of_expr(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ method_def_id: DefId,
+ receiver: &Expr<'_>,
+) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
+ let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
- ..
++ if let
+ // For matching uses of `Cow::from`
+ [
+ Adjustment {
+ kind: Adjust::Deref(None),
- ..
++ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
+ ]
+ // For matching uses of arrays
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
- ..
++ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Pointer(_),
+ target: target_ty,
+ },
+ ]
+ // For matching everything else
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
- ] => Some(target_ty),
- _ => None,
- };
++ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Deref(Some(OverloadedDeref { .. })),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
- // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This
- // restriction is to ensure there is not overlap between `redundant_clone` and this lint.
- if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id);
++ ] = adjustments[..];
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
- let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
- let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
++ let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
++ let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
++ // Only flag cases satisfying at least one of the following three conditions:
++ // * the referent and receiver types are distinct
++ // * the referent/receiver type is a copyable array
++ // * the method is `Cow::into_owned`
++ // This restriction is to ensure there is no overlap between `redundant_clone` and this
++ // lint. It also avoids the following false positive:
++ // https://github.com/rust-lang/rust-clippy/issues/8759
++ // Arrays are a bit of a corner case. Non-copyable arrays are handled by
++ // `redundant_clone`, but copyable arrays are not.
++ if *referent_ty != receiver_ty
++ || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
++ || is_cow_into_owned(cx, method_name, method_def_id);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
- msrv: Option<&RustcVersion>,
+ if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!(
+ "{:&>width$}{}",
+ "",
+ receiver_snippet,
+ width = n_target_refs - n_receiver_refs
+ ),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ if_chain! {
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
+ if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
+ then {
+ if n_receiver_refs > 0 {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ receiver_snippet,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ expr.span.with_lo(receiver.span.hi()),
+ &format!("unnecessary use of `{}`", method_name),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ return true;
+ }
+ }
+ if_chain! {
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{}.as_ref()", receiver_snippet),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ }
+ }
+ false
+}
+
+/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
+/// call of a `to_owned`-like function is unnecessary.
+fn check_into_iter_call_arg(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
- let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) { "copied" } else { "cloned" };
++ msrv: Option<RustcVersion>,
+) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let Some(callee_def_id) = fn_def_id(cx, parent);
+ if is_into_iter(cx, callee_def_id);
+ if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ let parent_ty = cx.typeck_results().expr_ty(parent);
+ if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
+ if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
+ return true;
+ }
++ let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
++ "copied"
++ } else {
++ "cloned"
++ };
+ // The next suggestion may be incorrect because the removal of the `to_owned`-like
+ // function could cause the iterator to hold a reference to a resource that is used
+ // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
+/// of a `to_owned`-like function is unnecessary.
+fn check_other_call_arg<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ receiver: &'tcx Expr<'tcx>,
+) -> bool {
+ if_chain! {
+ if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
+ if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
+ if let Some(input) = fn_sig.inputs().get(i);
+ let (input, n_refs) = peel_mid_ty_refs(*input);
+ if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
+ if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
+ if let [trait_predicate] = trait_predicates
+ .iter()
+ .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
+ .collect::<Vec<_>>()[..];
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
+ // types of `trait_predicate.trait_ref.substs`.
+ if if trait_predicate.def_id() == deref_trait_id {
+ if let [projection_predicate] = projection_predicates[..] {
+ let normalized_ty =
+ cx.tcx
+ .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
+ implements_trait(cx, receiver_ty, deref_trait_id, &[])
+ && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
+ .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
+ } else {
+ false
+ }
+ } else if trait_predicate.def_id() == as_ref_trait_id {
+ let composed_substs = compose_substs(
+ cx,
+ &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
+ call_substs,
+ );
+ implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
+ } else {
+ false
+ };
+ // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
+ // `Target = T`.
+ if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
+ let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
+ // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
+ // `T` must not be instantiated with a reference
+ // (https://github.com/rust-lang/rust-clippy/issues/8507).
+ if (n_refs == 0 && !receiver_ty.is_ref())
+ || trait_predicate.def_id() != as_ref_trait_id
+ || !contains_ty(fn_sig.output(), input);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ maybe_arg.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
+/// expression found (if any) along with the immediately prior expression.
+fn skip_addr_of_ancestors<'tcx>(
+ cx: &LateContext<'tcx>,
+ mut expr: &'tcx Expr<'tcx>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ while let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
+ expr = parent;
+ } else {
+ return Some((parent, expr));
+ }
+ }
+ None
+}
+
+/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
+/// `Substs`, and arguments.
+fn get_callee_substs_and_args<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
+ if_chain! {
+ if let ExprKind::Call(callee, args) = expr.kind;
+ let callee_ty = cx.typeck_results().expr_ty(callee);
+ if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
+ then {
+ let substs = cx.typeck_results().node_substs(callee.hir_id);
+ return Some((*callee_def_id, substs, args));
+ }
+ }
+ if_chain! {
+ if let ExprKind::MethodCall(_, args, _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ then {
+ let substs = cx.typeck_results().node_substs(expr.hir_id);
+ return Some((method_def_id, substs, args));
+ }
+ }
+ None
+}
+
+/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
+fn get_input_traits_and_projections<'tcx>(
+ cx: &LateContext<'tcx>,
+ callee_def_id: DefId,
+ input: Ty<'tcx>,
+) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
+ let mut trait_predicates = Vec::new();
+ let mut projection_predicates = Vec::new();
+ for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() {
+ // `substs` should have 1 + n elements. The first is the type on the left hand side of an
+ // `as`. The remaining n are trait parameters.
+ let is_input_substs = |substs: SubstsRef<'tcx>| {
+ if_chain! {
+ if let Some(arg) = substs.iter().next();
+ if let GenericArgKind::Type(arg_ty) = arg.unpack();
+ if arg_ty == input;
+ then { true } else { false }
+ }
+ };
+ match predicate.kind().skip_binder() {
+ PredicateKind::Trait(trait_predicate) => {
+ if is_input_substs(trait_predicate.trait_ref.substs) {
+ trait_predicates.push(trait_predicate);
+ }
+ },
+ PredicateKind::Projection(projection_predicate) => {
+ if is_input_substs(projection_predicate.projection_ty.substs) {
+ projection_predicates.push(projection_predicate);
+ }
+ },
+ _ => {},
+ }
+ }
+ (trait_predicates, projection_predicates)
+}
+
+/// Composes two substitutions by applying the latter to the types of the former.
+fn compose_substs<'tcx>(
+ cx: &LateContext<'tcx>,
+ left: &[GenericArg<'tcx>],
+ right: SubstsRef<'tcx>,
+) -> Vec<GenericArg<'tcx>> {
+ left.iter()
+ .map(|arg| {
+ if let GenericArgKind::Type(arg_ty) = arg.unpack() {
+ let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
+ GenericArg::from(normalized_ty)
+ } else {
+ *arg
+ }
+ })
+ .collect()
+}
+
+/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
+fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
+ && is_diag_trait_item(cx, method_def_id, sym::Iterator)
+}
+
+/// Returns true if the named method can be used to convert the receiver to its "owned"
+/// representation.
+fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ is_clone_like(cx, &*method_name.as_str(), method_def_id)
+ || is_cow_into_owned(cx, method_name, method_def_id)
+ || is_to_string(cx, method_name, method_def_id)
+}
+
+/// Returns true if the named method is `Cow::into_owned`.
+fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
+}
+
+/// Returns true if the named method is `ToString::to_string`.
+fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString)
+}
--- /dev/null
- pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+use clippy_utils::diagnostics::span_lint_and_help;
++use clippy_utils::is_in_test_function;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNWRAP_USED;
+
+/// lint use of `unwrap()` for `Option`s and `Result`s
++pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
+ Some((UNWRAP_USED, "an Option", "None"))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
+ Some((UNWRAP_USED, "a Result", "Err"))
+ } else {
+ None
+ };
+
++ if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
++ return;
++ }
++
+ if let Some((lint, kind, none_value)) = mess {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
+ &format!("used `unwrap()` on `{}` value", kind,),
+ None,
+ &format!(
+ "if you don't want to handle the `{}` case gracefully, consider \
+ using `expect()` to provide a better panic message",
+ none_value,
+ ),
+ );
+ }
+}
--- /dev/null
- use clippy_utils::ty::implements_trait;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::{snippet, snippet_opt};
- get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
- last_path_segment, match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
++use clippy_utils::ty::{implements_trait, is_copy};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
+ StmtKind, TyKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::source_map::{ExpnKind, Span};
+use rustc_span::symbol::sym;
+
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{
- #[allow(clippy::too_many_lines)]
++ get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment,
++ match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
+};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function arguments and let bindings denoted as
+ /// `ref`.
+ ///
+ /// ### Why is this bad?
+ /// The `ref` declaration makes the function take an owned
+ /// value, but turns the argument into a reference (which means that the value
+ /// is destroyed when exiting the function). This adds not much value: either
+ /// take a reference type, or take an owned value and create references in the
+ /// body.
+ ///
+ /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
+ /// type of `x` is more obvious with the former.
+ ///
+ /// ### Known problems
+ /// If the argument is dereferenced within the function,
+ /// removing the `ref` will lead to errors. This can be fixed by removing the
+ /// dereferences, e.g., changing `*x` to `x` within the function.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// fn foo(ref x: u8) -> bool {
+ /// true
+ /// }
+ ///
+ /// // Good
+ /// fn foo(x: &u8) -> bool {
+ /// true
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TOPLEVEL_REF_ARG,
+ style,
+ "an entire binding declared as `ref`, in a function argument or a `let` statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparisons to NaN.
+ ///
+ /// ### Why is this bad?
+ /// NaN does not compare meaningfully to anything – not
+ /// even itself – so those comparisons are simply wrong.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1.0;
+ ///
+ /// // Bad
+ /// if x == f32::NAN { }
+ ///
+ /// // Good
+ /// if x.is_nan() { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_NAN,
+ correctness,
+ "comparisons to `NAN`, which will always return false, probably not intended"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for (in-)equality comparisons on floating-point
+ /// values (apart from zero), except in functions called `*eq*` (which probably
+ /// implement equality for a type involving floats).
+ ///
+ /// ### Why is this bad?
+ /// Floating point calculations are usually imprecise, so
+ /// asking if two values are *exactly* equal is asking for trouble. For a good
+ /// guide on what to do, see [the floating point
+ /// guide](http://www.floating-point-gui.de/errors/comparison).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1.2331f64;
+ /// let y = 1.2332f64;
+ ///
+ /// // Bad
+ /// if y == 1.23f64 { }
+ /// if y != x {} // where both are floats
+ ///
+ /// // Good
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (y - 1.23f64).abs() < error_margin { }
+ /// if (y - x).abs() > error_margin { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP,
+ pedantic,
+ "using `==` or `!=` on float values instead of comparing difference with an epsilon"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for conversions to owned values just for the sake
+ /// of a comparison.
+ ///
+ /// ### Why is this bad?
+ /// The comparison can operate on a reference, so creating
+ /// an owned value effectively throws it away directly afterwards, which is
+ /// needlessly consuming code and heap space.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x.to_owned() == y {}
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x == y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_OWNED,
+ perf,
+ "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for getting the remainder of a division by one or minus
+ /// one.
+ ///
+ /// ### Why is this bad?
+ /// The result for a divisor of one can only ever be zero; for
+ /// minus one it can cause panic/overflow (if the left operand is the minimal value of
+ /// the respective integer type) or results in zero. No one will write such code
+ /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
+ /// contest, it's probably a bad idea. Use something more underhanded.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// let a = x % 1;
+ /// let a = x % -1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MODULO_ONE,
+ correctness,
+ "taking a number modulo +/-1, which can either panic/overflow or always returns 0"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of bindings with a single leading
+ /// underscore.
+ ///
+ /// ### Why is this bad?
+ /// A single leading underscore is usually used to indicate
+ /// that a binding will not be used. Using such a binding breaks this
+ /// expectation.
+ ///
+ /// ### Known problems
+ /// The lint does not work properly with desugaring and
+ /// macro, it has been allowed in the mean time.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _x = 0;
+ /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
+ /// // underscore. We should rename `_x` to `x`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USED_UNDERSCORE_BINDING,
+ pedantic,
+ "using a binding which is prefixed with an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of short circuit boolean conditions as
+ /// a
+ /// statement.
+ ///
+ /// ### Why is this bad?
+ /// Using a short circuit boolean condition as a statement
+ /// may hide the fact that the second part is executed or not depending on the
+ /// outcome of the first part.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// f() && g(); // We should write `if f() { g(); }`.
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SHORT_CIRCUIT_STATEMENT,
+ complexity,
+ "using a short circuit boolean condition as a statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Catch casts from `0` to some pointer type
+ ///
+ /// ### Why is this bad?
+ /// This generally means `null` and is better expressed as
+ /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let a = 0 as *const u32;
+ ///
+ /// // Good
+ /// let a = std::ptr::null::<u32>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_PTR,
+ style,
+ "using `0 as *{const, mut} T`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for (in-)equality comparisons on floating-point
+ /// value and constant, except in functions called `*eq*` (which probably
+ /// implement equality for a type involving floats).
+ ///
+ /// ### Why is this bad?
+ /// Floating point calculations are usually imprecise, so
+ /// asking if two values are *exactly* equal is asking for trouble. For a good
+ /// guide on what to do, see [the floating point
+ /// guide](http://www.floating-point-gui.de/errors/comparison).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: f64 = 1.0;
+ /// const ONE: f64 = 1.00;
+ ///
+ /// // Bad
+ /// if x == ONE { } // where both are floats
+ ///
+ /// // Good
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (x - ONE).abs() < error_margin { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP_CONST,
+ restriction,
+ "using `==` or `!=` on float constants instead of comparing difference with an epsilon"
+}
+
+declare_lint_pass!(MiscLints => [
+ TOPLEVEL_REF_ARG,
+ CMP_NAN,
+ FLOAT_CMP,
+ CMP_OWNED,
+ MODULO_ONE,
+ USED_UNDERSCORE_BINDING,
+ SHORT_CIRCUIT_STATEMENT,
+ ZERO_PTR,
+ FLOAT_CMP_CONST
+]);
+
+impl<'tcx> LateLintPass<'tcx> for MiscLints {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ k: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if let FnKind::Closure = k {
+ // Does not apply to closures
+ return;
+ }
+ if in_external_macro(cx.tcx.sess, span) {
+ return;
+ }
+ for arg in iter_input_pats(decl, body) {
+ if let PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..) = arg.pat.kind {
+ span_lint(
+ cx,
+ TOPLEVEL_REF_ARG,
+ arg.pat.span,
+ "`ref` directly on a function argument is ignored. \
+ Consider using a reference type instead",
+ );
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, stmt.span);
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(an, .., name, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut;
+ then {
+ // use the macro callsite when the init span (but not the whole local span)
+ // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
+ let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, init, "..")
+ } else {
+ Sugg::hir(cx, init, "..")
+ };
+ let (mutopt, initref) = if an == BindingAnnotation::RefMut {
+ ("mut ", sugg_init.mut_addr())
+ } else {
+ ("", sugg_init.addr())
+ };
+ let tyopt = if let Some(ty) = local.ty {
+ format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, ".."))
+ } else {
+ String::new()
+ };
+ span_lint_hir_and_then(
+ cx,
+ TOPLEVEL_REF_ARG,
+ init.hir_id,
+ local.pat.span,
+ "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
+ |diag| {
+ diag.span_suggestion(
+ stmt.span,
+ "try",
+ format!(
+ "let {name}{tyopt} = {initref};",
+ name=snippet(cx, name.span, ".."),
+ tyopt=tyopt,
+ initref=initref,
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ };
+ if_chain! {
+ if let StmtKind::Semi(expr) = stmt.kind;
+ if let ExprKind::Binary(ref binop, a, b) = expr.kind;
+ if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
+ if let Some(sugg) = Sugg::hir_opt(cx, a);
+ then {
+ span_lint_hir_and_then(
+ cx,
+ SHORT_CIRCUIT_STATEMENT,
+ expr.hir_id,
+ stmt.span,
+ "boolean short circuit operator in statement may be clearer using an explicit test",
+ |diag| {
+ let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg };
+ diag.span_suggestion(
+ stmt.span,
+ "replace it with",
+ format!(
+ "if {} {{ {}; }}",
+ sugg,
+ &snippet(cx, b.span, ".."),
+ ),
+ Applicability::MachineApplicable, // snippet
+ );
+ });
+ }
+ };
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::Cast(e, ty) => {
+ check_cast(cx, expr.span, e, ty);
+ return;
+ },
+ ExprKind::Binary(ref cmp, left, right) => {
+ check_binary(cx, expr, cmp, left, right);
+ return;
+ },
+ _ => {},
+ }
+ if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
+ // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
+ return;
+ }
+ let sym;
+ let binding = match expr.kind {
+ ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
+ let binding = last_path_segment(qpath).ident.as_str();
+ if binding.starts_with('_') &&
+ !binding.starts_with("__") &&
+ binding != "_result" && // FIXME: #944
+ is_used(cx, expr) &&
+ // don't lint if the declaration is in a macro
+ non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
+ {
+ Some(binding)
+ } else {
+ None
+ }
+ },
+ ExprKind::Field(_, ident) => {
+ sym = ident.name;
+ let name = sym.as_str();
+ if name.starts_with('_') && !name.starts_with("__") {
+ Some(name)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+ if let Some(binding) = binding {
+ span_lint(
+ cx,
+ USED_UNDERSCORE_BINDING,
+ expr.span,
+ &format!(
+ "used binding `{}` which is prefixed with an underscore. A leading \
+ underscore signals that a binding will not be used",
+ binding
+ ),
+ );
+ }
+ }
+}
+
+fn get_lint_and_message(
+ is_comparing_constants: bool,
+ is_comparing_arrays: bool,
+) -> (&'static rustc_lint::Lint, &'static str) {
+ if is_comparing_constants {
+ (
+ FLOAT_CMP_CONST,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` constant arrays"
+ } else {
+ "strict comparison of `f32` or `f64` constant"
+ },
+ )
+ } else {
+ (
+ FLOAT_CMP,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` arrays"
+ } else {
+ "strict comparison of `f32` or `f64`"
+ },
+ )
+ }
+}
+
+fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
+ if_chain! {
+ if !in_constant(cx, cmp_expr.hir_id);
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), expr);
+ if match value {
+ Constant::F32(num) => num.is_nan(),
+ Constant::F64(num) => num.is_nan(),
+ _ => false,
+ };
+ then {
+ span_lint(
+ cx,
+ CMP_NAN,
+ cmp_expr.span,
+ "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
+ );
+ }
+ }
+}
+
+fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
+ res
+ } else {
+ false
+ }
+}
+
+fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ match constant(cx, cx.typeck_results(), expr) {
+ Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
+ Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
+ Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
+ _ => false,
+ }),
+ _ => false,
+ }
+}
+
+// Return true if `expr` is the result of `signum()` invoked on a float value.
+fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ // The negation of a signum is still a signum
+ if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
+ return is_signum(cx, child_expr);
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind;
+ if sym!(signum) == method_name.ident.name;
+ // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
+ // the method call)
+ then {
+ return is_float(cx, self_arg);
+ }
+ }
+ false
+}
+
+fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
+
+ if let ty::Array(arr_ty, _) = value {
+ return matches!(arr_ty.kind(), ty::Float(_));
+ };
+
+ matches!(value, ty::Float(_))
+}
+
+fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
+}
+
- let (arg_ty, snip) = match expr.kind {
- ExprKind::MethodCall(.., args, _) if args.len() == 1 => {
- if_chain!(
- if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
- if is_diag_trait_item(cx, expr_def_id, sym::ToString)
- || is_diag_trait_item(cx, expr_def_id, sym::ToOwned);
- then {
- (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, ".."))
- } else {
- return;
- }
- )
++#[expect(clippy::too_many_lines)]
+fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
+ #[derive(Default)]
+ struct EqImpl {
+ ty_eq_other: bool,
+ other_eq_ty: bool,
+ }
+
+ impl EqImpl {
+ fn is_implemented(&self) -> bool {
+ self.ty_eq_other || self.other_eq_ty
+ }
+ }
+
+ fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
+ cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
+ ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
+ other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
+ })
+ }
+
- ExprKind::Call(path, [arg]) => {
++ let typeck = cx.typeck_results();
++ let (arg, arg_span) = match expr.kind {
++ ExprKind::MethodCall(.., [arg], _)
++ if typeck
++ .type_dependent_def_id(expr.hir_id)
++ .and_then(|id| cx.tcx.trait_of_item(id))
++ .map_or(false, |id| {
++ matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
++ }) =>
++ {
++ (arg, arg.span)
+ },
- .is_some()
- {
- (cx.typeck_results().expr_ty(arg), snippet(cx, arg.span, ".."))
- } else {
- return;
- }
++ ExprKind::Call(path, [arg])
+ if path_def_id(cx, path)
+ .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
- let other_ty = cx.typeck_results().expr_ty(other);
++ .map_or(false, |idx| match idx {
++ 0 => true,
++ 1 => !is_copy(cx, typeck.expr_ty(expr)),
++ _ => false,
++ }) =>
++ {
++ (arg, arg.span)
+ },
+ _ => return,
+ };
+
- expr_snip = format!("*{}", snip);
++ let arg_ty = typeck.expr_ty(arg);
++ let other_ty = typeck.expr_ty(other);
+
+ let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
+ let with_deref = arg_ty
+ .builtin_deref(true)
+ .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
+ .unwrap_or_default();
+
+ if !with_deref.is_implemented() && !without_deref.is_implemented() {
+ return;
+ }
+
+ let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
+
+ let lint_span = if other_gets_derefed {
+ expr.span.to(other.span)
+ } else {
+ expr.span
+ };
+
+ span_lint_and_then(
+ cx,
+ CMP_OWNED,
+ lint_span,
+ "this creates an owned instance just for comparison",
+ |diag| {
+ // This also catches `PartialEq` implementations that call `to_owned`.
+ if other_gets_derefed {
+ diag.span_label(lint_span, "try implementing the comparison without allocating");
+ return;
+ }
+
++ let arg_snip = snippet(cx, arg_span, "..");
+ let expr_snip;
+ let eq_impl;
+ if with_deref.is_implemented() {
- expr_snip = snip.to_string();
++ expr_snip = format!("*{}", arg_snip);
+ eq_impl = with_deref;
+ } else {
++ expr_snip = arg_snip.to_string();
+ eq_impl = without_deref;
+ };
+
+ let span;
+ let hint;
+ if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
+ span = expr.span;
+ hint = expr_snip;
+ } else {
+ span = expr.span.to(other.span);
+
+ let cmp_span = if other.span < expr.span {
+ other.span.between(expr.span)
+ } else {
+ expr.span.between(other.span)
+ };
+ if eq_impl.ty_eq_other {
+ hint = format!(
+ "{}{}{}",
+ expr_snip,
+ snippet(cx, cmp_span, ".."),
+ snippet(cx, other.span, "..")
+ );
+ } else {
+ hint = format!(
+ "{}{}{}",
+ snippet(cx, other.span, ".."),
+ snippet(cx, cmp_span, ".."),
+ expr_snip
+ );
+ }
+ }
+
+ diag.span_suggestion(
+ span,
+ "try",
+ hint,
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+}
+
+/// Heuristic to see if an expression is used. Should be compatible with
+/// `unused_variables`'s idea
+/// of what it means for an expression to be "used".
+fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
+ ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
+ _ => is_used(cx, parent),
+ })
+}
+
+/// Tests whether an expression is in a macro expansion (e.g., something
+/// generated by `#[derive(...)]` or the like).
+fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
+ use rustc_span::hygiene::MacroKind;
+ if expr.span.from_expansion() {
+ let data = expr.span.ctxt().outer_expn_data();
+ matches!(data.kind, ExpnKind::Macro(MacroKind::Attr, _))
+ } else {
+ false
+ }
+}
+
+/// Tests whether `res` is a variable defined outside a macro.
+fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
+ if let def::Res::Local(id) = res {
+ !cx.tcx.hir().span(id).from_expansion()
+ } else {
+ false
+ }
+}
+
+fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
+ if_chain! {
+ if let TyKind::Ptr(ref mut_ty) = ty.kind;
+ if let ExprKind::Lit(ref lit) = e.kind;
+ if let LitKind::Int(0, _) = lit.node;
+ if !in_constant(cx, e.hir_id);
+ then {
+ let (msg, sugg_fn) = match mut_ty.mutbl {
+ Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"),
+ Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"),
+ };
+
+ let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
+ (format!("{}()", sugg_fn), Applicability::MachineApplicable)
+ } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
+ (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable)
+ } else {
+ // `MaybeIncorrect` as type inference may not work with the suggested code
+ (format!("{}()", sugg_fn), Applicability::MaybeIncorrect)
+ };
+ span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
+ }
+ }
+}
+
+fn check_binary<'a>(
+ cx: &LateContext<'a>,
+ expr: &Expr<'_>,
+ cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
+ left: &'a Expr<'_>,
+ right: &'a Expr<'_>,
+) {
+ let op = cmp.node;
+ if op.is_comparison() {
+ check_nan(cx, left, expr);
+ check_nan(cx, right, expr);
+ check_to_owned(cx, left, right, true);
+ check_to_owned(cx, right, left, false);
+ }
+ if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
+ if is_allowed(cx, left) || is_allowed(cx, right) {
+ return;
+ }
+
+ // Allow comparing the results of signum()
+ if is_signum(cx, left) && is_signum(cx, right) {
+ return;
+ }
+
+ if let Some(name) = get_item_name(cx, expr) {
+ let name = name.as_str();
+ if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
+ return;
+ }
+ }
+ let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
+ let (lint, msg) = get_lint_and_message(
+ is_named_constant(cx, left) || is_named_constant(cx, right),
+ is_comparing_arrays,
+ );
+ span_lint_and_then(cx, lint, expr.span, msg, |diag| {
+ let lhs = Sugg::hir(cx, left, "..");
+ let rhs = Sugg::hir(cx, right, "..");
+
+ if !is_comparing_arrays {
+ diag.span_suggestion(
+ expr.span,
+ "consider comparing them within some margin of error",
+ format!(
+ "({}).abs() {} error_margin",
+ lhs - rhs,
+ if op == BinOpKind::Eq { '<' } else { '>' }
+ ),
+ Applicability::HasPlaceholders, // snippet
+ );
+ }
+ diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
+ });
+ } else if op == BinOpKind::Rem {
+ if is_integer_const(cx, right, 1) {
+ span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
+ }
+
+ if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
+ if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
+ span_lint(
+ cx,
+ MODULO_ONE,
+ expr.span,
+ "any number modulo -1 will panic/overflow or result in 0",
+ );
+ }
+ };
+ }
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::CONST_IF_MATCH) {
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::qualify_min_const_fn::is_min_const_fn;
+use clippy_utils::ty::has_drop;
+use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method};
+use rustc_hir as hir;
+use rustc_hir::def_id::CRATE_DEF_ID;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests the use of `const` in functions and methods where possible.
+ ///
+ /// ### Why is this bad?
+ /// Not having the function const prevents callers of the function from being const as well.
+ ///
+ /// ### Known problems
+ /// Const functions are currently still being worked on, with some features only being available
+ /// on nightly. This lint does not consider all edge cases currently and the suggestions may be
+ /// incorrect if you are using this lint on stable.
+ ///
+ /// Also, the lint only runs one pass over the code. Consider these two non-const functions:
+ ///
+ /// ```rust
+ /// fn a() -> i32 {
+ /// 0
+ /// }
+ /// fn b() -> i32 {
+ /// a()
+ /// }
+ /// ```
+ ///
+ /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time
+ /// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
+ /// will suggest to make `b` const, too.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
+ ///
+ /// Could be a const fn:
+ ///
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// const fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub MISSING_CONST_FOR_FN,
+ nursery,
+ "Lint functions definitions that could be made `const fn`"
+}
+
+impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]);
+
+pub struct MissingConstForFn {
+ msrv: Option<RustcVersion>,
+}
+
+impl MissingConstForFn {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'_>,
+ kind: FnKind<'_>,
+ _: &FnDecl<'_>,
+ _: &Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
- if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
++ if !meets_msrv(self.msrv, msrvs::CONST_IF_MATCH) {
+ return;
+ }
+
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Building MIR for `fn`s with unsatisfiable preds results in ICE.
+ if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Perform some preliminary checks that rule out constness on the Clippy side. This way we
+ // can skip the actual const check and return early.
+ match kind {
+ FnKind::ItemFn(_, generics, header, ..) => {
+ let has_const_generic_params = generics
+ .params
+ .iter()
+ .any(|param| matches!(param.kind, GenericParamKind::Const { .. }));
+
+ if already_const(header) || has_const_generic_params {
+ return;
+ }
+ },
+ FnKind::Method(_, sig, ..) => {
+ if trait_ref_of_method(cx, def_id).is_some()
+ || already_const(sig.header)
+ || method_accepts_dropable(cx, sig.decl.inputs)
+ {
+ return;
+ }
+ },
+ FnKind::Closure => return,
+ }
+
+ // Const fns are not allowed as methods in a trait.
+ {
+ let parent = cx.tcx.hir().get_parent_item(hir_id);
+ if parent != CRATE_DEF_ID {
+ if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent) {
+ if let hir::ItemKind::Trait(..) = &item.kind {
+ return;
+ }
+ }
+ }
+ }
+
+ let mir = cx.tcx.optimized_mir(def_id);
+
++ if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) {
+ if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
+ cx.tcx.sess.span_err(span, err.as_ref());
+ }
+ } else {
+ span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns true if any of the method parameters is a type that implements `Drop`. The method
+/// can't be made const then, because `drop` can't be const-evaluated.
+fn method_accepts_dropable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
+ // If any of the params are droppable, return true
+ param_tys.iter().any(|hir_ty| {
+ let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ has_drop(cx, ty_ty)
+ })
+}
+
+// We don't have to lint on something that's already `const`
+#[must_use]
+fn already_const(header: hir::FnHeader) -> bool {
+ header.constness == Constness::Const
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
++use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
++use if_chain::if_chain;
++use rustc_hir::intravisit::{walk_expr, Visitor};
++use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty;
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for a read and a write to the same variable where
++ /// whether the read occurs before or after the write depends on the evaluation
++ /// order of sub-expressions.
++ ///
++ /// ### Why is this bad?
++ /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands),
++ /// the operands of these expressions are evaluated before applying the effects of the expression.
++ ///
++ /// ### Known problems
++ /// Code which intentionally depends on the evaluation
++ /// order, or which is correct for any evaluation order.
++ ///
++ /// ### Example
++ /// ```rust
++ /// let mut x = 0;
++ ///
++ /// // Bad
++ /// let a = {
++ /// x = 1;
++ /// 1
++ /// } + x;
++ /// // Unclear whether a is 1 or 2.
++ ///
++ /// // Good
++ /// let tmp = {
++ /// x = 1;
++ /// 1
++ /// };
++ /// let a = tmp + x;
++ /// ```
++ #[clippy::version = "pre 1.29.0"]
++ pub MIXED_READ_WRITE_IN_EXPRESSION,
++ restriction,
++ "whether a variable read occurs before a write depends on sub-expression evaluation order"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for diverging calls that are not match arms or
++ /// statements.
++ ///
++ /// ### Why is this bad?
++ /// It is often confusing to read. In addition, the
++ /// sub-expression evaluation order for Rust is not well documented.
++ ///
++ /// ### Known problems
++ /// Someone might want to use `some_bool || panic!()` as a
++ /// shorthand.
++ ///
++ /// ### Example
++ /// ```rust,no_run
++ /// # fn b() -> bool { true }
++ /// # fn c() -> bool { true }
++ /// let a = b() || panic!() || c();
++ /// // `c()` is dead, `panic!()` is only called if `b()` returns `false`
++ /// let x = (a, b, c, panic!());
++ /// // can simply be replaced by `panic!()`
++ /// ```
++ #[clippy::version = "pre 1.29.0"]
++ pub DIVERGING_SUB_EXPRESSION,
++ complexity,
++ "whether an expression contains a diverging sub expression"
++}
++
++declare_lint_pass!(EvalOrderDependence => [MIXED_READ_WRITE_IN_EXPRESSION, DIVERGING_SUB_EXPRESSION]);
++
++impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ // Find a write to a local variable.
++ let var = if_chain! {
++ if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind;
++ if let Some(var) = path_to_local(lhs);
++ if expr.span.desugaring_kind().is_none();
++ then { var } else { return; }
++ };
++ let mut visitor = ReadVisitor {
++ cx,
++ var,
++ write_expr: expr,
++ last_expr: expr,
++ };
++ check_for_unsequenced_reads(&mut visitor);
++ }
++ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
++ match stmt.kind {
++ StmtKind::Local(local) => {
++ if let Local { init: Some(e), .. } = local {
++ DivergenceVisitor { cx }.visit_expr(e);
++ }
++ },
++ StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
++ StmtKind::Item(..) => {},
++ }
++ }
++}
++
++struct DivergenceVisitor<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++}
++
++impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
++ fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
++ match e.kind {
++ ExprKind::Closure(..) => {},
++ ExprKind::Match(e, arms, _) => {
++ self.visit_expr(e);
++ for arm in arms {
++ if let Some(Guard::If(if_expr)) = arm.guard {
++ self.visit_expr(if_expr);
++ }
++ // make sure top level arm expressions aren't linted
++ self.maybe_walk_expr(&*arm.body);
++ }
++ },
++ _ => walk_expr(self, e),
++ }
++ }
++ fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
++ span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
++ }
++}
++
++impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
++ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
++ match e.kind {
++ ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
++ ExprKind::Call(func, _) => {
++ let typ = self.cx.typeck_results().expr_ty(func);
++ match typ.kind() {
++ ty::FnDef(..) | ty::FnPtr(_) => {
++ let sig = typ.fn_sig(self.cx.tcx);
++ if self.cx.tcx.erase_late_bound_regions(sig).output().kind() == &ty::Never {
++ self.report_diverging_sub_expr(e);
++ }
++ },
++ _ => {},
++ }
++ },
++ ExprKind::MethodCall(..) => {
++ let borrowed_table = self.cx.typeck_results();
++ if borrowed_table.expr_ty(e).is_never() {
++ self.report_diverging_sub_expr(e);
++ }
++ },
++ _ => {
++ // do not lint expressions referencing objects of type `!`, as that required a
++ // diverging expression
++ // to begin with
++ },
++ }
++ self.maybe_walk_expr(e);
++ }
++ fn visit_block(&mut self, _: &'tcx Block<'_>) {
++ // don't continue over blocks, LateLintPass already does that
++ }
++}
++
++/// Walks up the AST from the given write expression (`vis.write_expr`) looking
++/// for reads to the same variable that are unsequenced relative to the write.
++///
++/// This means reads for which there is a common ancestor between the read and
++/// the write such that
++///
++/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x`
++/// and `|| x = 1` don't necessarily evaluate `x`), and
++///
++/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s,
++/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined
++/// evaluation order.
++///
++/// When such a read is found, the lint is triggered.
++fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) {
++ let map = &vis.cx.tcx.hir();
++ let mut cur_id = vis.write_expr.hir_id;
++ loop {
++ let parent_id = map.get_parent_node(cur_id);
++ if parent_id == cur_id {
++ break;
++ }
++ let parent_node = match map.find(parent_id) {
++ Some(parent) => parent,
++ None => break,
++ };
++
++ let stop_early = match parent_node {
++ Node::Expr(expr) => check_expr(vis, expr),
++ Node::Stmt(stmt) => check_stmt(vis, stmt),
++ Node::Item(_) => {
++ // We reached the top of the function, stop.
++ break;
++ },
++ _ => StopEarly::KeepGoing,
++ };
++ match stop_early {
++ StopEarly::Stop => break,
++ StopEarly::KeepGoing => {},
++ }
++
++ cur_id = parent_id;
++ }
++}
++
++/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If
++/// `check_expr` weren't an independent function, this would be unnecessary and
++/// we could just use `break`).
++enum StopEarly {
++ KeepGoing,
++ Stop,
++}
++
++fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly {
++ if expr.hir_id == vis.last_expr.hir_id {
++ return StopEarly::KeepGoing;
++ }
++
++ match expr.kind {
++ ExprKind::Array(_)
++ | ExprKind::Tup(_)
++ | ExprKind::MethodCall(..)
++ | ExprKind::Call(_, _)
++ | ExprKind::Assign(..)
++ | ExprKind::Index(_, _)
++ | ExprKind::Repeat(_, _)
++ | ExprKind::Struct(_, _, _) => {
++ walk_expr(vis, expr);
++ },
++ ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => {
++ if op.node == BinOpKind::And || op.node == BinOpKind::Or {
++ // x && y and x || y always evaluate x first, so these are
++ // strictly sequenced.
++ } else {
++ walk_expr(vis, expr);
++ }
++ },
++ ExprKind::Closure(_, _, _, _, _) => {
++ // Either
++ //
++ // * `var` is defined in the closure body, in which case we've reached the top of the enclosing
++ // function and can stop, or
++ //
++ // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate
++ // its body, we don't necessarily have a write, so we need to stop to avoid generating false
++ // positives.
++ //
++ // This is also the only place we need to stop early (grrr).
++ return StopEarly::Stop;
++ },
++ // All other expressions either have only one child or strictly
++ // sequence the evaluation order of their sub-expressions.
++ _ => {},
++ }
++
++ vis.last_expr = expr;
++
++ StopEarly::KeepGoing
++}
++
++fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
++ match stmt.kind {
++ StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
++ // If the declaration is of a local variable, check its initializer
++ // expression if it has one. Otherwise, keep going.
++ StmtKind::Local(local) => local
++ .init
++ .as_ref()
++ .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
++ StmtKind::Item(..) => StopEarly::KeepGoing,
++ }
++}
++
++/// A visitor that looks for reads from a variable.
++struct ReadVisitor<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++ /// The ID of the variable we're looking for.
++ var: HirId,
++ /// The expressions where the write to the variable occurred (for reporting
++ /// in the lint).
++ write_expr: &'tcx Expr<'tcx>,
++ /// The last (highest in the AST) expression we've checked, so we know not
++ /// to recheck it.
++ last_expr: &'tcx Expr<'tcx>,
++}
++
++impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
++ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
++ if expr.hir_id == self.last_expr.hir_id {
++ return;
++ }
++
++ if path_to_local_id(expr, self.var) {
++ // Check that this is a read, not a write.
++ if !is_in_assignment_position(self.cx, expr) {
++ span_lint_and_note(
++ self.cx,
++ MIXED_READ_WRITE_IN_EXPRESSION,
++ expr.span,
++ &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)),
++ Some(self.write_expr.span),
++ "whether read occurs before this write depends on evaluation order",
++ );
++ }
++ }
++ match expr.kind {
++ // We're about to descend a closure. Since we don't know when (or
++ // if) the closure will be evaluated, any reads in it might not
++ // occur here (or ever). Like above, bail to avoid false positives.
++ ExprKind::Closure(_, _, _, _, _) |
++
++ // We want to avoid a false positive when a variable name occurs
++ // only to have its address taken, so we stop here. Technically,
++ // this misses some weird cases, eg.
++ //
++ // ```rust
++ // let mut x = 0;
++ // let a = foo(&{x = 1; x}, x);
++ // ```
++ //
++ // TODO: fix this
++ ExprKind::AddrOf(_, _, _) => {
++ return;
++ }
++ _ => {}
++ }
++
++ walk_expr(self, expr);
++ }
++}
++
++/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
++fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
++ if let Some(parent) = get_parent_expr(cx, expr) {
++ if let ExprKind::Assign(lhs, ..) = parent.kind {
++ return lhs.hir_id == expr.hir_id;
++ }
++ }
++ false
++}
--- /dev/null
- /// Therefore mutating something in a `debug_assert!` macro results in different behaviour
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function/method calls with a mutable
+ /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
+ ///
+ /// ### Why is this bad?
+ /// In release builds `debug_assert!` macros are optimized out by the
+ /// compiler.
++ /// Therefore mutating something in a `debug_assert!` macro results in different behavior
+ /// between a release and debug build.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// debug_assert_eq!(vec![3].pop(), Some(3));
+ /// // or
+ /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
+ /// debug_assert!(take_a_mut_parameter(&mut 5));
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub DEBUG_ASSERT_WITH_MUT_CALL,
+ nursery,
+ "mutable arguments in `debug_assert{,_ne,_eq}!`"
+}
+
+declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
+
+impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, e) else { return };
+ let macro_name = cx.tcx.item_name(macro_call.def_id);
+ if !matches!(
+ macro_name.as_str(),
+ "debug_assert" | "debug_assert_eq" | "debug_assert_ne"
+ ) {
+ return;
+ }
+ let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn) else { return };
+ for arg in [lhs, rhs] {
+ let mut visitor = MutArgVisitor::new(cx);
+ visitor.visit_expr(arg);
+ if let Some(span) = visitor.expr_span() {
+ span_lint(
+ cx,
+ DEBUG_ASSERT_WITH_MUT_CALL,
+ span,
+ &format!(
+ "do not call a function with mutable arguments inside of `{}!`",
+ macro_name
+ ),
+ );
+ }
+ }
+ }
+}
+
+struct MutArgVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ expr_span: Option<Span>,
+ found: bool,
+}
+
+impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ expr_span: None,
+ found: false,
+ }
+ }
+
+ fn expr_span(&self) -> Option<Span> {
+ if self.found { self.expr_span } else { None }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::If(..) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::Path(_) => {
+ if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
+ if adj
+ .iter()
+ .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut)))
+ {
+ self.found = true;
+ return;
+ }
+ }
+ },
+ // Don't check await desugars
+ ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return,
+ _ if !self.found => self.expr_span = Some(expr.span),
+ _ => return,
+ }
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- fn suggesstion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using
+ /// a lazy and.
+ ///
+ /// ### Why is this bad?
+ /// The bitwise operators do not support short-circuiting, so it may hinder code performance.
+ /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold`
+ ///
+ /// ### Known problems
+ /// This lint evaluates only when the right side is determined to have no side effects. At this time, that
+ /// determination is quite conservative.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x & !y {} // where both x and y are booleans
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x && !y {}
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub NEEDLESS_BITWISE_BOOL,
+ pedantic,
+ "Boolean expressions that use bitwise rather than lazy operators"
+}
+
+declare_lint_pass!(NeedlessBitwiseBool => [NEEDLESS_BITWISE_BOOL]);
+
+fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if !expr.span.from_expansion();
+ if let (&ExprKind::Binary(ref op, _, right), &ty::Bool) = (&expr.kind, &ty.kind());
+ if op.node == BinOpKind::BitAnd || op.node == BinOpKind::BitOr;
+ if let ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) = right.kind;
+ if !right.can_have_side_effects();
+ then {
+ return true;
+ }
+ }
+ false
+}
+
- if let Some(sugg) = suggesstion_snippet(cx, expr) {
++fn suggestion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ if let ExprKind::Binary(ref op, left, right) = expr.kind {
+ if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) {
+ let op_snippet = match op.node {
+ BinOpKind::BitAnd => "&&",
+ _ => "||",
+ };
+ return Some(format!("{} {} {}", l_snippet, op_snippet, r_snippet));
+ }
+ }
+ None
+}
+
+impl LateLintPass<'_> for NeedlessBitwiseBool {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if is_bitwise_operation(cx, expr) {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_BITWISE_BOOL,
+ expr.span,
+ "use of bitwise operator instead of lazy operator between booleans",
+ |diag| {
++ if let Some(sugg) = suggestion_snippet(cx, expr) {
+ diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable);
+ }
+ },
+ );
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::ptr::get_spans;
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{get_trait_def_id, is_self, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::Attribute;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind};
+use rustc_hir::{HirIdMap, HirIdSet};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::{self, TypeFoldable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::misc::can_type_implement_copy;
+use rustc_typeck::expr_use_visitor as euv;
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, but not
+ /// consuming them in its
+ /// body.
+ ///
+ /// ### Why is this bad?
+ /// Taking arguments by reference is more flexible and can
+ /// sometimes avoid
+ /// unnecessary allocations.
+ ///
+ /// ### Known problems
+ /// * This lint suggests taking an argument by reference,
+ /// however sometimes it is better to let users decide the argument type
+ /// (by using `Borrow` trait, for example), depending on how the function is used.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(v: Vec<i32>) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
+ /// should be
+ /// ```rust
+ /// fn foo(v: &[i32]) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_PASS_BY_VALUE,
+ pedantic,
+ "functions taking arguments by value, but not consuming them in its body"
+}
+
+declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]);
+
+macro_rules! need {
+ ($e: expr) => {
+ if let Some(x) = $e {
+ x
+ } else {
+ return;
+ }
+ };
+}
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
++ #[expect(clippy::too_many_lines)]
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ match kind {
+ FnKind::ItemFn(.., header) => {
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ if header.abi != Abi::Rust || requires_exact_signature(attrs) {
+ return;
+ }
+ },
+ FnKind::Method(..) => (),
+ FnKind::Closure => return,
+ }
+
+ // Exclude non-inherent impls
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ // Allow `Borrow` or functions to be taken by value
+ let allowed_traits = [
+ need!(cx.tcx.lang_items().fn_trait()),
+ need!(cx.tcx.lang_items().fn_once_trait()),
+ need!(cx.tcx.lang_items().fn_mut_trait()),
+ need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)),
+ ];
+
+ let sized_trait = need!(cx.tcx.lang_items().sized_trait());
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
+ .filter(|p| !p.is_global())
+ .filter_map(|obligation| {
+ // Note that we do not want to deal with qualified predicates here.
+ match obligation.predicate.kind().no_bound_vars() {
+ Some(ty::PredicateKind::Trait(pred)) if pred.def_id() != sized_trait => Some(pred),
+ _ => None,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Collect moved variables and spans which will need dereferencings from the
+ // function body.
+ let MovedVariablesCtxt {
+ moved_vars,
+ spans_need_deref,
+ ..
+ } = {
+ let mut ctx = MovedVariablesCtxt::default();
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
+ .consume_body(body);
+ });
+ ctx
+ };
+
+ let fn_sig = cx.tcx.fn_sig(fn_def_id);
+ let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
+
+ for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() {
+ // All spans generated from a proc-macro invocation are the same...
+ if span == input.span {
+ return;
+ }
+
+ // Ignore `self`s.
+ if idx == 0 {
+ if let PatKind::Binding(.., ident, _) = arg.pat.kind {
+ if ident.name == kw::SelfLower {
+ continue;
+ }
+ }
+ }
+
+ //
+ // * Exclude a type that is specifically bounded by `Borrow`.
+ // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
+ // `serde::Serialize`)
+ let (implements_borrow_trait, all_borrowable_trait) = {
+ let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::<Vec<_>>();
+
+ (
+ preds.iter().any(|t| cx.tcx.is_diagnostic_item(sym::Borrow, t.def_id())),
+ !preds.is_empty() && {
+ let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty);
+ preds.iter().all(|t| {
+ let ty_params = t.trait_ref.substs.iter().skip(1).collect::<Vec<_>>();
+ implements_trait(cx, ty_empty_region, t.def_id(), &ty_params)
+ })
+ },
+ )
+ };
+
+ if_chain! {
+ if !is_self(arg);
+ if !ty.is_mutable_ptr();
+ if !is_copy(cx, ty);
+ if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[]));
+ if !implements_borrow_trait;
+ if !all_borrowable_trait;
+
+ if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind;
+ if !moved_vars.contains(&canonical_id);
+ then {
+ if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
+ continue;
+ }
+
+ // Dereference suggestion
+ let sugg = |diag: &mut Diagnostic| {
+ if let ty::Adt(def, ..) = ty.kind() {
+ if let Some(span) = cx.tcx.hir().span_if_local(def.did()) {
+ if can_type_implement_copy(
+ cx.tcx,
+ cx.param_env,
+ ty,
+ traits::ObligationCause::dummy_with_span(span),
+ ).is_ok() {
+ diag.span_help(span, "consider marking this type as `Copy`");
+ }
+ }
+ }
+
+ let deref_span = spans_need_deref.get(&canonical_id);
+ if_chain! {
+ if is_type_diagnostic_item(cx, ty, sym::Vec);
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]);
+ if let TyKind::Path(QPath::Resolved(_, path)) = input.kind;
+ if let Some(elem_ty) = path.segments.iter()
+ .find(|seg| seg.ident.name == sym::Vec)
+ .and_then(|ps| ps.args.as_ref())
+ .map(|params| params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }).unwrap());
+ then {
+ let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ slice_ty,
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{}` to", x)),
+ )
+ .as_ref(),
+ suggestion,
+ Applicability::Unspecified,
+ );
+ }
+
+ // cannot be destructured, no need for `*` suggestion
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ if is_type_diagnostic_item(cx, ty, sym::String) {
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ "&str".to_string(),
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{}` to", x))
+ )
+ .as_ref(),
+ suggestion,
+ Applicability::Unspecified,
+ );
+ }
+
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
+
+ // Suggests adding `*` to dereference the added reference.
+ if let Some(deref_span) = deref_span {
+ spans.extend(
+ deref_span
+ .iter()
+ .copied()
+ .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
+ );
+ spans.sort_by_key(|&(span, _)| span);
+ }
+ multispan_sugg(diag, "consider taking a reference instead", spans);
+ };
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_PASS_BY_VALUE,
+ input.span,
+ "this argument is passed by value, but not consumed in the function body",
+ sugg,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Functions marked with these attributes must have the exact signature.
+fn requires_exact_signature(attrs: &[Attribute]) -> bool {
+ attrs.iter().any(|attr| {
+ [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
+ .iter()
+ .any(|&allow| attr.has_name(allow))
+ })
+}
+
+#[derive(Default)]
+struct MovedVariablesCtxt {
+ moved_vars: HirIdSet,
+ /// Spans which need to be prefixed with `*` for dereferencing the
+ /// suggested additional reference.
+ spans_need_deref: HirIdMap<FxHashSet<Span>>,
+}
+
+impl MovedVariablesCtxt {
+ fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) {
+ if let euv::PlaceBase::Local(vid) = cmt.place.base {
+ self.moved_vars.insert(vid);
+ }
+ }
+}
+
+impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt {
+ fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId) {
+ self.move_common(cmt);
+ }
+
+ fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
+
+ fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn fake_read(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
--- /dev/null
- #[allow(clippy::match_same_arms)]
+use clippy_utils::consts::{self, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for multiplication by -1 as a form of negation.
+ ///
+ /// ### Why is this bad?
+ /// It's more readable to just negate.
+ ///
+ /// ### Known problems
+ /// This only catches integers (for now).
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// let a = x * -1;
+ ///
+ /// // Good
+ /// let b = -x;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEG_MULTIPLY,
+ style,
+ "multiplying integers by `-1`"
+}
+
+declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]);
+
+impl<'tcx> LateLintPass<'tcx> for NegMultiply {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, left, right) = e.kind {
+ if BinOpKind::Mul == op.node {
+ match (&left.kind, &right.kind) {
+ (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {},
+ (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right),
+ (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left),
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Lit(ref l) = lit.kind;
+ if consts::lit_to_mir_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1);
+ if cx.typeck_results().expr_ty(exp).is_integral();
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = format!("-{}", snippet_with_applicability(cx, exp.span, "..", &mut applicability));
+ span_lint_and_sugg(
+ cx,
+ NEG_MULTIPLY,
+ span,
+ "this multiplication by -1 can be written more succinctly",
+ "consider using",
+ suggestion,
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::return_ty;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::DiagnosticExt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::HirIdSet;
+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::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public types with a `pub fn new() -> Self` method and no
+ /// implementation of
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
+ ///
+ /// ### Why is this bad?
+ /// The user might expect to be able to use
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
+ /// type can be constructed without arguments.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// pub struct Foo(Bar);
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Self {
+ /// Foo(Bar::new())
+ /// }
+ /// }
+ /// ```
+ ///
+ /// To fix the lint, add a `Default` implementation that delegates to `new`:
+ ///
+ /// ```ignore
+ /// pub struct Foo(Bar);
+ ///
+ /// impl Default for Foo {
+ /// fn default() -> Self {
+ /// Foo::new()
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEW_WITHOUT_DEFAULT,
+ style,
+ "`pub fn new() -> Self` method without `Default` implementation"
+}
+
+#[derive(Clone, Default)]
+pub struct NewWithoutDefault {
+ impling_types: Option<HirIdSet>,
+}
+
+impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]);
+
+impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: None,
+ generics,
+ self_ty: impl_self_ty,
+ items,
+ ..
+ }) = item.kind
+ {
+ for assoc_item in *items {
+ if assoc_item.kind == (hir::AssocItemKind::Fn { has_self: false }) {
+ let impl_item = cx.tcx.hir().impl_item(assoc_item.id);
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
+ let name = impl_item.ident.name;
+ let id = impl_item.hir_id();
+ if sig.header.constness == hir::Constness::Const {
+ // can't be implemented by default
+ return;
+ }
+ if sig.header.unsafety == hir::Unsafety::Unsafe {
+ // can't be implemented for unsafe new
+ return;
+ }
+ if cx.tcx.is_doc_hidden(impl_item.def_id) {
+ // shouldn't be implemented when it is hidden in docs
+ return;
+ }
+ if impl_item
+ .generics
+ .params
+ .iter()
+ .any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. }))
+ {
+ // when the result of `new()` depends on a type parameter we should not require
+ // an
+ // impl of `Default`
+ return;
+ }
+ if_chain! {
+ if sig.decl.inputs.is_empty();
+ if name == sym::new;
+ if cx.access_levels.is_reachable(impl_item.def_id);
+ let self_def_id = cx.tcx.hir().get_parent_item(id);
+ let self_ty = cx.tcx.type_of(self_def_id);
+ if self_ty == return_ty(cx, id);
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ then {
+ if self.impling_types.is_none() {
+ let mut impls = HirIdSet::default();
+ cx.tcx.for_each_impl(default_trait_id, |d| {
+ if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
+ if let Some(local_def_id) = ty_def.did().as_local() {
+ impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
+ }
+ }
+ });
+ self.impling_types = Some(impls);
+ }
+
+ // Check if a Default implementation exists for the Self type, regardless of
+ // generics
+ if_chain! {
+ if let Some(ref impling_types) = self.impling_types;
+ if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
+ if let Some(self_local_did) = self_def.did().as_local();
+ let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
+ if impling_types.contains(&self_id);
+ then {
+ return;
+ }
+ }
+
+ let generics_sugg = snippet(cx, generics.span, "");
+ let self_ty_fmt = self_ty.to_string();
+ let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);
+ span_lint_hir_and_then(
+ cx,
+ NEW_WITHOUT_DEFAULT,
+ id,
+ impl_item.span,
+ &format!(
+ "you should consider adding a `Default` implementation for `{}`",
+ self_type_snip
+ ),
+ |diag| {
+ diag.suggest_prepend_item(
+ cx,
+ item.span,
+ "try adding this",
+ &create_new_without_default_suggest_msg(&self_type_snip, &generics_sugg),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn create_new_without_default_suggest_msg(self_type_snip: &str, generics_sugg: &str) -> String {
+ #[rustfmt::skip]
+ format!(
+"impl{} Default for {} {{
+ fn default() -> Self {{
+ Self::new()
+ }}
+}}", generics_sugg, self_type_snip)
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use rustc_ast::ast::{
+ self, Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, Item, ItemKind, Local, Pat, PatKind,
+};
+use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor};
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::{Ident, Symbol};
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for names that are very similar and thus confusing.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to distinguish between names that differ only
+ /// by a single character.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let checked_exp = something;
+ /// let checked_expr = something_else;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SIMILAR_NAMES,
+ pedantic,
+ "similarly named items and bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for too many variables whose name consists of a
+ /// single character.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let (a, b, c, d, e, f, g) = (...);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MANY_SINGLE_CHAR_NAMES,
+ pedantic,
+ "too many single character bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if you have variables whose name consists of just
+ /// underscores and digits.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _1 = 1;
+ /// let ___1 = 1;
+ /// let __1___2 = 11;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub JUST_UNDERSCORES_AND_DIGITS,
+ style,
+ "unclear name"
+}
+
+#[derive(Copy, Clone)]
+pub struct NonExpressiveNames {
+ pub single_char_binding_names_threshold: u64,
+}
+
+impl_lint_pass!(NonExpressiveNames => [SIMILAR_NAMES, MANY_SINGLE_CHAR_NAMES, JUST_UNDERSCORES_AND_DIGITS]);
+
+struct ExistingName {
+ interned: Symbol,
+ span: Span,
+ len: usize,
+ exemptions: &'static [&'static str],
+}
+
+struct SimilarNamesLocalVisitor<'a, 'tcx> {
+ names: Vec<ExistingName>,
+ cx: &'a EarlyContext<'tcx>,
+ lint: &'a NonExpressiveNames,
+
+ /// A stack of scopes containing the single-character bindings in each scope.
+ single_char_names: Vec<Vec<Ident>>,
+}
+
+impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn check_single_char_names(&self) {
+ let num_single_char_names = self.single_char_names.iter().flatten().count();
+ let threshold = self.lint.single_char_binding_names_threshold;
+ if num_single_char_names as u64 > threshold {
+ let span = self
+ .single_char_names
+ .iter()
+ .flatten()
+ .map(|ident| ident.span)
+ .collect::<Vec<_>>();
+ span_lint(
+ self.cx,
+ MANY_SINGLE_CHAR_NAMES,
+ span,
+ &format!(
+ "{} bindings with single-character names in scope",
+ num_single_char_names
+ ),
+ );
+ }
+ }
+}
+
+// this list contains lists of names that are allowed to be similar
+// the assumption is that no name is ever contained in multiple lists.
+#[rustfmt::skip]
+const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
+ &["parsed", "parser"],
+ &["lhs", "rhs"],
+ &["tx", "rx"],
+ &["set", "get"],
+ &["args", "arms"],
+ &["qpath", "path"],
+ &["lit", "lint"],
+ &["wparam", "lparam"],
+ &["iter", "item"],
+];
+
+struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
+
+impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn visit_pat(&mut self, pat: &'tcx Pat) {
+ match pat.kind {
+ PatKind::Ident(_, ident, _) => {
+ if !pat.span.from_expansion() {
+ self.check_ident(ident);
+ }
+ },
+ PatKind::Struct(_, _, ref fields, _) => {
+ for field in fields {
+ if !field.is_shorthand {
+ self.visit_pat(&field.pat);
+ }
+ }
+ },
+ // just go through the first pattern, as either all patterns
+ // bind the same bindings or rustc would have errored much earlier
+ PatKind::Or(ref pats) => self.visit_pat(&pats[0]),
+ _ => walk_pat(self, pat),
+ }
+ }
+}
+
+#[must_use]
+fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
+ for &list in ALLOWED_TO_BE_SIMILAR {
+ if allowed_to_be_similar(interned_name, list) {
+ return Some(list);
+ }
+ }
+ None
+}
+
+#[must_use]
+fn allowed_to_be_similar(interned_name: &str, list: &[&str]) -> bool {
+ list.iter()
+ .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name))
+}
+
+impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn check_short_ident(&mut self, ident: Ident) {
+ // Ignore shadowing
+ if self
+ .0
+ .single_char_names
+ .iter()
+ .flatten()
+ .any(|id| id.name == ident.name)
+ {
+ return;
+ }
+
+ if let Some(scope) = &mut self.0.single_char_names.last_mut() {
+ scope.push(ident);
+ }
+ }
+
++ #[expect(clippy::too_many_lines)]
+ fn check_ident(&mut self, ident: Ident) {
+ let interned_name = ident.name.as_str();
+ if interned_name.chars().any(char::is_uppercase) {
+ return;
+ }
+ if interned_name.chars().all(|c| c.is_ascii_digit() || c == '_') {
+ span_lint(
+ self.0.cx,
+ JUST_UNDERSCORES_AND_DIGITS,
+ ident.span,
+ "consider choosing a more descriptive name",
+ );
+ return;
+ }
+ if interned_name.starts_with('_') {
+ // these bindings are typically unused or represent an ignored portion of a destructuring pattern
+ return;
+ }
+ let count = interned_name.chars().count();
+ if count < 3 {
+ if count == 1 {
+ self.check_short_ident(ident);
+ }
+ return;
+ }
+ for existing_name in &self.0.names {
+ if allowed_to_be_similar(interned_name, existing_name.exemptions) {
+ continue;
+ }
+ match existing_name.len.cmp(&count) {
+ Ordering::Greater => {
+ if existing_name.len - count != 1
+ || levenstein_not_1(interned_name, existing_name.interned.as_str())
+ {
+ continue;
+ }
+ },
+ Ordering::Less => {
+ if count - existing_name.len != 1
+ || levenstein_not_1(existing_name.interned.as_str(), interned_name)
+ {
+ continue;
+ }
+ },
+ Ordering::Equal => {
+ let mut interned_chars = interned_name.chars();
+ let interned_str = existing_name.interned.as_str();
+ let mut existing_chars = interned_str.chars();
+ let first_i = interned_chars.next().expect("we know we have at least one char");
+ let first_e = existing_chars.next().expect("we know we have at least one char");
+ let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
+
+ if eq_or_numeric((first_i, first_e)) {
+ let last_i = interned_chars.next_back().expect("we know we have at least two chars");
+ let last_e = existing_chars.next_back().expect("we know we have at least two chars");
+ if eq_or_numeric((last_i, last_e)) {
+ if interned_chars
+ .zip(existing_chars)
+ .filter(|&ie| !eq_or_numeric(ie))
+ .count()
+ != 1
+ {
+ continue;
+ }
+ } else {
+ let second_last_i = interned_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ let second_last_e = existing_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ if !eq_or_numeric((second_last_i, second_last_e))
+ || second_last_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity foo_x, foo_y
+ // or too many chars differ (foo_x, boo_y) or (foox, booy)
+ continue;
+ }
+ }
+ } else {
+ let second_i = interned_chars.next().expect("we know we have at least two chars");
+ let second_e = existing_chars.next().expect("we know we have at least two chars");
+ if !eq_or_numeric((second_i, second_e))
+ || second_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity x_foo, y_foo
+ // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
+ continue;
+ }
+ }
+ },
+ }
+ span_lint_and_then(
+ self.0.cx,
+ SIMILAR_NAMES,
+ ident.span,
+ "binding's name is too similar to existing binding",
+ |diag| {
+ diag.span_note(existing_name.span, "existing binding defined here");
+ },
+ );
+ return;
+ }
+ self.0.names.push(ExistingName {
+ exemptions: get_exemptions(interned_name).unwrap_or(&[]),
+ interned: ident.name,
+ span: ident.span,
+ len: count,
+ });
+ }
+}
+
+impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {
+ /// ensure scoping rules work
+ fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
+ let n = self.names.len();
+ let single_char_count = self.single_char_names.len();
+ f(self);
+ self.names.truncate(n);
+ self.single_char_names.truncate(single_char_count);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn visit_local(&mut self, local: &'tcx Local) {
+ if let Some((init, els)) = &local.kind.init_else_opt() {
+ self.apply(|this| walk_expr(this, init));
+ if let Some(els) = els {
+ self.apply(|this| walk_block(this, els));
+ }
+ }
+ // add the pattern after the expression because the bindings aren't available
+ // yet in the init
+ // expression
+ SimilarNamesNameVisitor(self).visit_pat(&*local.pat);
+ }
+ fn visit_block(&mut self, blk: &'tcx Block) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| walk_block(this, blk));
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_arm(&mut self, arm: &'tcx Arm) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| {
+ SimilarNamesNameVisitor(this).visit_pat(&arm.pat);
+ this.apply(|this| walk_expr(this, &arm.body));
+ });
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_item(&mut self, _: &Item) {
+ // do not recurse into inner items
+ }
+}
+
+impl EarlyLintPass for NonExpressiveNames {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if in_external_macro(cx.sess(), item.span) {
+ return;
+ }
+
+ if let ItemKind::Fn(box ast::Fn {
+ ref sig,
+ body: Some(ref blk),
+ ..
+ }) = item.kind
+ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) {
+ if in_external_macro(cx.sess(), item.span) {
+ return;
+ }
+
+ if let AssocItemKind::Fn(box ast::Fn {
+ ref sig,
+ body: Some(ref blk),
+ ..
+ }) = item.kind
+ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+}
+
+fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) {
+ if !attrs.iter().any(|attr| attr.has_name(sym::test)) {
+ let mut visitor = SimilarNamesLocalVisitor {
+ names: Vec::new(),
+ cx,
+ lint,
+ single_char_names: vec![vec![]],
+ };
+
+ // initialize with function arguments
+ for arg in &decl.inputs {
+ SimilarNamesNameVisitor(&mut visitor).visit_pat(&arg.pat);
+ }
+ // walk all other bindings
+ walk_block(&mut visitor, blk);
+
+ visitor.check_single_char_names();
+ }
+}
+
+/// Precondition: `a_name.chars().count() < b_name.chars().count()`.
+#[must_use]
+fn levenstein_not_1(a_name: &str, b_name: &str) -> bool {
+ debug_assert!(a_name.chars().count() < b_name.chars().count());
+ let mut a_chars = a_name.chars();
+ let mut b_chars = b_name.chars();
+ while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) {
+ if a == b {
+ continue;
+ }
+ if let Some(b2) = b_chars.next() {
+ // check if there's just one character inserted
+ return a != b2 || a_chars.ne(b_chars);
+ }
+ // tuple
+ // ntuple
+ return true;
+ }
+ // for item in items
+ true
+}
--- /dev/null
- /// led to an **undefined behaviour** introduced with unsafe code.
+use std::cmp;
+use std::iter;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{is_self, is_self_ty};
+use if_chain::if_chain;
+use rustc_ast::attr;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_target::spec::Target;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by reference, where
+ /// the argument type is `Copy` and small enough to be more efficient to always
+ /// pass by value.
+ ///
+ /// ### Why is this bad?
+ /// In many calling conventions instances of structs will
+ /// be passed through registers if they fit into two or less general purpose
+ /// registers.
+ ///
+ /// ### Known problems
+ /// This lint is target register size dependent, it is
+ /// limited to 32-bit to try and reduce portability problems between 32 and
+ /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
+ /// will be different.
+ ///
+ /// The configuration option `trivial_copy_size_limit` can be set to override
+ /// this limit for a project.
+ ///
+ /// This lint attempts to allow passing arguments by reference if a reference
+ /// to that argument is returned. This is implemented by comparing the lifetime
+ /// of the argument and return value for equality. However, this can cause
+ /// false positives in cases involving multiple lifetimes that are bounded by
+ /// each other.
+ ///
+ /// Also, it does not take account of other similar cases where getting memory addresses
+ /// matters; namely, returning the pointer to the argument in question,
+ /// and passing the argument, as both references and pointers,
+ /// to a function that needs the memory address. For further details, refer to
+ /// [this issue](https://github.com/rust-lang/rust-clippy/issues/5953)
+ /// that explains a real case in which this false positive
- #[allow(clippy::integer_division)]
++ /// led to an **undefined behavior** introduced with unsafe code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// // Bad
+ /// fn foo(v: &u32) {}
+ /// ```
+ ///
+ /// ```rust
+ /// // Better
+ /// fn foo(v: u32) {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIALLY_COPY_PASS_BY_REF,
+ pedantic,
+ "functions taking small copyable arguments by reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, where
+ /// the argument type is `Copy` and large enough to be worth considering
+ /// passing by reference. Does not trigger if the function is being exported,
+ /// because that might induce API breakage, if the parameter is declared as mutable,
+ /// or if the argument is a `self`.
+ ///
+ /// ### Why is this bad?
+ /// Arguments passed by value might result in an unnecessary
+ /// shallow copy, taking up more space in the stack and requiring a call to
+ /// `memcpy`, which can be expensive.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[derive(Clone, Copy)]
+ /// struct TooLarge([u8; 2048]);
+ ///
+ /// // Bad
+ /// fn foo(v: TooLarge) {}
+ /// ```
+ /// ```rust
+ /// #[derive(Clone, Copy)]
+ /// struct TooLarge([u8; 2048]);
+ ///
+ /// // Good
+ /// fn foo(v: &TooLarge) {}
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub LARGE_TYPES_PASSED_BY_VALUE,
+ pedantic,
+ "functions taking large arguments by value"
+}
+
+#[derive(Copy, Clone)]
+pub struct PassByRefOrValue {
+ ref_min_size: u64,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl<'tcx> PassByRefOrValue {
+ pub fn new(
+ ref_min_size: Option<u64>,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+ target: &Target,
+ ) -> Self {
+ let ref_min_size = ref_min_size.unwrap_or_else(|| {
+ let bit_width = u64::from(target.pointer_width);
+ // Cap the calculated bit width at 32-bits to reduce
+ // portability problems between 32 and 64-bit targets
+ let bit_width = cmp::min(bit_width, 32);
++ #[expect(clippy::integer_division)]
+ let byte_width = bit_width / 8;
+ // Use a limit of 2 times the register byte width
+ byte_width * 2
+ });
+
+ Self {
+ ref_min_size,
+ value_max_size,
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
+ if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) {
+ return;
+ }
+
+ let fn_sig = cx.tcx.fn_sig(def_id);
+ let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
+
+ let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
+
+ for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
+ // All spans generated from a proc-macro invocation are the same...
+ match span {
+ Some(s) if s == input.span => return,
+ _ => (),
+ }
+
+ match ty.kind() {
+ ty::Ref(input_lt, ty, Mutability::Not) => {
+ // Use lifetimes to determine if we're returning a reference to the
+ // argument. In that case we can't switch to pass-by-value as the
+ // argument will not live long enough.
+ let output_lts = match *fn_sig.output().kind() {
+ ty::Ref(output_lt, _, _) => vec![output_lt],
+ ty::Adt(_, substs) => substs.regions().collect(),
+ _ => vec![],
+ };
+
+ if_chain! {
+ if !output_lts.contains(input_lt);
+ if is_copy(cx, *ty);
+ if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
+ if size <= self.ref_min_size;
+ if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
+ then {
+ let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
+ "self".into()
+ } else {
+ snippet(cx, decl_ty.span, "_").into()
+ };
+ span_lint_and_sugg(
+ cx,
+ TRIVIALLY_COPY_PASS_BY_REF,
+ input.span,
+ &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
+ "consider passing by value instead",
+ value_type,
+ Applicability::Unspecified,
+ );
+ }
+ }
+ },
+
+ ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
+ // if function has a body and parameter is annotated with mut, ignore
+ if let Some(param) = fn_body.and_then(|body| body.params.get(index)) {
+ match param.pat.kind {
+ PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {},
+ _ => continue,
+ }
+ }
+
+ if_chain! {
+ if is_copy(cx, ty);
+ if !is_self_ty(input);
+ if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
+ if size > self.value_max_size;
+ then {
+ span_lint_and_sugg(
+ cx,
+ LARGE_TYPES_PASSED_BY_VALUE,
+ input.span,
+ &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size),
+ "consider passing by reference instead",
+ format!("&{}", snippet(cx, input.span, "_")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+ }
+}
+
+impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
+ self.check_poly_fn(cx, item.def_id, &*method_sig.decl, None);
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ _body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ match kind {
+ FnKind::ItemFn(.., header) => {
+ if header.abi != Abi::Rust {
+ return;
+ }
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ for a in attrs {
+ if let Some(meta_items) = a.meta_item_list() {
+ if a.has_name(sym::proc_macro_derive)
+ || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))
+ {
+ return;
+ }
+ }
+ }
+ },
+ FnKind::Method(..) => (),
+ FnKind::Closure => return,
+ }
+
+ // Exclude non-inherent impls
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ self.check_poly_fn(cx, cx.tcx.hir().local_def_id(hir_id), decl, Some(span));
+ }
+}
--- /dev/null
- #[allow(rustc::usage_of_ty_tykind)]
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{
+ intravisit, Body, Expr, ExprKind, FnDecl, HirId, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for patterns that aren't exact representations of the types
+ /// they are applied to.
+ ///
+ /// To satisfy this lint, you will have to adjust either the expression that is matched
+ /// against or the pattern itself, as well as the bindings that are introduced by the
+ /// adjusted patterns. For matching you will have to either dereference the expression
+ /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
+ /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
+ /// to use the inverse. You can leave them as plain bindings if you wish for the value
+ /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
+ /// a reference into the matched structure.
+ ///
+ /// If you are looking for a way to learn about ownership semantics in more detail, it
+ /// is recommended to look at IDE options available to you to highlight types, lifetimes
+ /// and reference semantics in your code. The available tooling would expose these things
+ /// in a general way even outside of the various pattern matching mechanics. Of course
+ /// this lint can still be used to highlight areas of interest and ensure a good understanding
+ /// of ownership semantics.
+ ///
+ /// ### Why is this bad?
+ /// It isn't bad in general. But in some contexts it can be desirable
+ /// because it increases ownership hints in the code, and will guard against some changes
+ /// in ownership.
+ ///
+ /// ### Example
+ /// This example shows the basic adjustments necessary to satisfy the lint. Note how
+ /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
+ /// is bound to a shared borrow via `ref inner`.
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// let value = &Some(Box::new(23));
+ /// match value {
+ /// Some(inner) => println!("{}", inner),
+ /// None => println!("none"),
+ /// }
+ ///
+ /// // Good
+ /// let value = &Some(Box::new(23));
+ /// match *value {
+ /// Some(ref inner) => println!("{}", inner),
+ /// None => println!("none"),
+ /// }
+ /// ```
+ ///
+ /// The following example demonstrates one of the advantages of the more verbose style.
+ /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
+ /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
+ /// accidentally modify the wrong part of the structure.
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// let mut values = vec![(2, 3), (3, 4)];
+ /// for (a, b) in &mut values {
+ /// *a += *b;
+ /// }
+ ///
+ /// // Good
+ /// let mut values = vec![(2, 3), (3, 4)];
+ /// for &mut (ref mut a, b) in &mut values {
+ /// *a += b;
+ /// }
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub PATTERN_TYPE_MISMATCH,
+ restriction,
+ "type of pattern does not match the expression type"
+}
+
+declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
+
+impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if let StmtKind::Local(local) = stmt.kind {
+ if in_external_macro(cx.sess(), local.pat.span) {
+ return;
+ }
+ let deref_possible = match local.source {
+ LocalSource::Normal => DerefPossible::Possible,
+ _ => DerefPossible::Impossible,
+ };
+ apply_lint(cx, local.pat, deref_possible);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Match(_, arms, _) = expr.kind {
+ for arm in arms {
+ let pat = &arm.pat;
+ if apply_lint(cx, pat, DerefPossible::Possible) {
+ break;
+ }
+ }
+ }
+ if let ExprKind::Let(Let { pat, .. }) = expr.kind {
+ apply_lint(cx, pat, DerefPossible::Possible);
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ for param in body.params {
+ apply_lint(cx, param.pat, DerefPossible::Impossible);
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum DerefPossible {
+ Possible,
+ Impossible,
+}
+
+fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
+ let maybe_mismatch = find_first_mismatch(cx, pat);
+ if let Some((span, mutability, level)) = maybe_mismatch {
+ span_lint_and_help(
+ cx,
+ PATTERN_TYPE_MISMATCH,
+ span,
+ "type of pattern does not match the expression type",
+ None,
+ &format!(
+ "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
+ match (deref_possible, level) {
+ (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
+ _ => "",
+ },
+ match mutability {
+ Mutability::Mut => "&mut _",
+ Mutability::Not => "&_",
+ },
+ ),
+ );
+ true
+ } else {
+ false
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+enum Level {
+ Top,
+ Lower,
+}
+
+fn find_first_mismatch<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
+ let mut result = None;
+ pat.walk(|p| {
+ if result.is_some() {
+ return false;
+ }
+ if in_external_macro(cx.sess(), p.span) {
+ return true;
+ }
+ let adjust_pat = match p.kind {
+ PatKind::Or([p, ..]) => p,
+ _ => p,
+ };
+ if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
+ if let [first, ..] = **adjustments {
+ if let ty::Ref(.., mutability) = *first.kind() {
+ let level = if p.hir_id == pat.hir_id {
+ Level::Top
+ } else {
+ Level::Lower
+ };
+ result = Some((p.span, mutability, level));
+ }
+ }
+ }
+ result.is_none()
+ });
+ result
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+//! Checks for usage of `&Vec[_]` and `&String`.
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::expr_sig;
+use clippy_utils::visitors::contains_unsafe_block;
+use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
+use if_chain::if_chain;
+use rustc_errors::{Applicability, MultiSpan};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::HirIdMap;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{
+ self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg,
+ ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
+ TraitItem, TraitItemKind, TyKind, Unsafety,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::Symbol;
+use std::fmt;
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for function arguments of type `&String`, `&Vec`,
+ /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
+ /// with the appropriate `.to_owned()`/`to_string()` calls.
+ ///
+ /// ### Why is this bad?
+ /// Requiring the argument to be of the specific size
+ /// makes the function less useful for no benefit; slices in the form of `&[T]`
+ /// or `&str` usually suffice and can be obtained from other types, too.
+ ///
+ /// ### Known problems
+ /// There may be `fn(&Vec)`-typed references pointing to your function.
+ /// If you have them, you will get a compiler error after applying this lint's
+ /// suggestions. You then have the choice to undo your changes or change the
+ /// type of the reference.
+ ///
+ /// Note that if the function is part of your public interface, there may be
+ /// other crates referencing it, of which you may not be aware. Carefully
+ /// deprecate the function before applying the lint suggestions in this case.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// fn foo(&Vec<u32>) { .. }
+ ///
+ /// // Good
+ /// fn foo(&[u32]) { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PTR_ARG,
+ style,
+ "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for equality comparisons with `ptr::null`
+ ///
+ /// ### Why is this bad?
+ /// It's easier and more readable to use the inherent
+ /// `.is_null()`
+ /// method instead
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// if x == ptr::null {
+ /// ..
+ /// }
+ ///
+ /// // Good
+ /// if x.is_null() {
+ /// ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_NULL,
+ style,
+ "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for functions that take immutable references and return
+ /// mutable ones. This will not trigger if no unsafe code exists as there
+ /// are multiple safe functions which will do this transformation
+ ///
+ /// To be on the conservative side, if there's at least one mutable
+ /// reference with the output lifetime, this lint will not trigger.
+ ///
+ /// ### Why is this bad?
+ /// Creating a mutable reference which can be repeatably derived from an
+ /// immutable reference is unsound as it allows creating multiple live
+ /// mutable references to the same object.
+ ///
+ /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
+ /// lead to an interim Rust release 1.15.1.
+ ///
+ /// ### Known problems
+ /// This pattern is used by memory allocators to allow allocating multiple
+ /// objects while returning mutable references to each one. So long as
+ /// different mutable references are returned each time such a function may
+ /// be safe.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn foo(&Foo) -> &mut Bar { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUT_FROM_REF,
+ correctness,
+ "fns that create mutable refs from immutable ref args"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for invalid usages of `ptr::null`.
+ ///
+ /// ### Why is this bad?
+ /// This causes undefined behavior.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad. Undefined behavior
+ /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
+ /// ```
+ ///
+ /// ```ignore
+ /// // Good
+ /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub INVALID_NULL_PTR_USAGE,
+ correctness,
+ "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
+}
+
+declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
+
+impl<'tcx> LateLintPass<'tcx> for Ptr {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
+ if matches!(trait_method, TraitFn::Provided(_)) {
+ // Handled by check body.
+ return;
+ }
+
+ check_mut_from_ref(cx, sig, None);
+ for arg in check_fn_args(
+ cx,
+ cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
+ sig.decl.inputs,
+ &[],
+ )
+ .filter(|arg| arg.mutability() == Mutability::Not)
+ {
+ span_lint_and_sugg(
+ cx,
+ PTR_ARG,
+ arg.span,
+ &arg.build_msg(),
+ "change this to",
+ format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
+ Applicability::Unspecified,
+ );
+ }
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ let hir = cx.tcx.hir();
+ let mut parents = hir.parent_iter(body.value.hir_id);
+ let (item_id, sig, is_trait_item) = match parents.next() {
+ Some((_, Node::Item(i))) => {
+ if let ItemKind::Fn(sig, ..) = &i.kind {
+ (i.def_id, sig, false)
+ } else {
+ return;
+ }
+ },
+ Some((_, Node::ImplItem(i))) => {
+ if !matches!(parents.next(),
+ Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
+ ) {
+ return;
+ }
+ if let ImplItemKind::Fn(sig, _) = &i.kind {
+ (i.def_id, sig, false)
+ } else {
+ return;
+ }
+ },
+ Some((_, Node::TraitItem(i))) => {
+ if let TraitItemKind::Fn(sig, _) = &i.kind {
+ (i.def_id, sig, true)
+ } else {
+ return;
+ }
+ },
+ _ => return,
+ };
+
+ check_mut_from_ref(cx, sig, Some(body));
+ let decl = sig.decl;
+ let sig = cx.tcx.fn_sig(item_id).skip_binder();
+ let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
+ .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
+ .collect();
+ let results = check_ptr_arg_usage(cx, body, &lint_args);
+
+ for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
+ span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
+ diag.multipart_suggestion(
+ "change this to",
+ iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
+ .chain(result.replacements.iter().map(|r| {
+ (
+ r.expr_span,
+ format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
+ )
+ }))
+ .collect(),
+ Applicability::Unspecified,
+ );
+ });
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, l, r) = expr.kind {
+ if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
+ span_lint(
+ cx,
+ CMP_NULL,
+ expr.span,
+ "comparing with null is better expressed by the `.is_null()` method",
+ );
+ }
+ } else {
+ check_invalid_ptr_usage(cx, expr);
+ }
+ }
+}
+
+fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
+ const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
+ (&paths::SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_COPY, &[0, 1]),
+ (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_READ, &[0]),
+ (&paths::PTR_READ_UNALIGNED, &[0]),
+ (&paths::PTR_READ_VOLATILE, &[0]),
+ (&paths::PTR_REPLACE, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_SWAP, &[0, 1]),
+ (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_WRITE, &[0]),
+ (&paths::PTR_WRITE_UNALIGNED, &[0]),
+ (&paths::PTR_WRITE_VOLATILE, &[0]),
+ (&paths::PTR_WRITE_BYTES, &[0]),
+ ];
+
+ if_chain! {
+ if let ExprKind::Call(fun, 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();
+ let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
+ if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
+ .iter()
+ .find(|&&(fn_path, _)| fn_path == fun_def_path);
+ then {
+ for &arg_idx in arg_indices {
+ if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
+ span_lint_and_sugg(
+ cx,
+ INVALID_NULL_PTR_USAGE,
+ arg.span,
+ "pointer must be non-null",
+ "change this to",
+ "core::ptr::NonNull::dangling().as_ptr()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+struct PtrArgResult {
+ skip: bool,
+ replacements: Vec<PtrArgReplacement>,
+}
+
+struct PtrArgReplacement {
+ expr_span: Span,
+ self_span: Span,
+ replacement: &'static str,
+}
+
+struct PtrArg<'tcx> {
+ idx: usize,
+ span: Span,
+ ty_did: DefId,
+ ty_name: Symbol,
+ method_renames: &'static [(&'static str, &'static str)],
+ ref_prefix: RefPrefix,
+ deref_ty: DerefTy<'tcx>,
+}
+impl PtrArg<'_> {
+ fn build_msg(&self) -> String {
+ format!(
+ "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
+ self.ref_prefix.mutability.prefix_str(),
+ self.ty_name,
+ self.ref_prefix.mutability.prefix_str(),
+ self.deref_ty.argless_str(),
+ )
+ }
+
+ fn mutability(&self) -> Mutability {
+ self.ref_prefix.mutability
+ }
+}
+
+struct RefPrefix {
+ lt: LifetimeName,
+ mutability: Mutability,
+}
+impl fmt::Display for RefPrefix {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use fmt::Write;
+ f.write_char('&')?;
+ match self.lt {
+ LifetimeName::Param(ParamName::Plain(name)) => {
+ name.fmt(f)?;
+ f.write_char(' ')?;
+ },
+ LifetimeName::Underscore => f.write_str("'_ ")?,
+ LifetimeName::Static => f.write_str("'static ")?,
+ _ => (),
+ }
+ f.write_str(self.mutability.prefix_str())
+ }
+}
+
+struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
+impl fmt::Display for DerefTyDisplay<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use std::fmt::Write;
+ match self.1 {
+ DerefTy::Str => f.write_str("str"),
+ DerefTy::Path => f.write_str("Path"),
+ DerefTy::Slice(hir_ty, ty) => {
+ f.write_char('[')?;
+ match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
+ Some(s) => f.write_str(&s)?,
+ None => ty.fmt(f)?,
+ }
+ f.write_char(']')
+ },
+ }
+ }
+}
+
+enum DerefTy<'tcx> {
+ Str,
+ Path,
+ Slice(Option<Span>, Ty<'tcx>),
+}
+impl<'tcx> DerefTy<'tcx> {
+ fn argless_str(&self) -> &'static str {
+ match *self {
+ Self::Str => "str",
+ Self::Path => "Path",
+ Self::Slice(..) => "[_]",
+ }
+ }
+
+ fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
+ DerefTyDisplay(cx, self)
+ }
+}
+
+fn check_fn_args<'cx, 'tcx: 'cx>(
+ cx: &'cx LateContext<'tcx>,
+ tys: &'tcx [Ty<'_>],
+ hir_tys: &'tcx [hir::Ty<'_>],
+ params: &'tcx [Param<'_>],
+) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
+ tys.iter()
+ .zip(hir_tys.iter())
+ .enumerate()
+ .filter_map(|(i, (ty, hir_ty))| {
+ if_chain! {
+ if let ty::Ref(_, ty, mutability) = *ty.kind();
+ if let ty::Adt(adt, substs) = *ty.kind();
+
+ if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
+ if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
+
+ // Check that the name as typed matches the actual name of the type.
+ // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
+ if let [.., name] = path.segments;
+ if cx.tcx.item_name(adt.did()) == name.ident.name;
+
+ if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
+ if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
+
+ then {
+ let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
+ Some(sym::Vec) => (
+ [("clone", ".to_owned()")].as_slice(),
+ DerefTy::Slice(
+ name.args
+ .and_then(|args| args.args.first())
+ .and_then(|arg| if let GenericArg::Type(ty) = arg {
+ Some(ty.span)
+ } else {
+ None
+ }),
+ substs.type_at(0),
+ ),
+ ),
+ Some(sym::String) => (
+ [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
+ DerefTy::Str,
+ ),
+ Some(sym::PathBuf) => (
+ [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
+ DerefTy::Path,
+ ),
+ Some(sym::Cow) if mutability == Mutability::Not => {
+ let ty_name = name.args
+ .and_then(|args| {
+ args.args.iter().find_map(|a| match a {
+ GenericArg::Type(x) => Some(x),
+ _ => None,
+ })
+ })
+ .and_then(|arg| snippet_opt(cx, arg.span))
+ .unwrap_or_else(|| substs.type_at(1).to_string());
+ span_lint_and_sugg(
+ cx,
+ PTR_ARG,
+ hir_ty.span,
+ "using a reference to `Cow` is not recommended",
+ "change this to",
+ format!("&{}{}", mutability.prefix_str(), ty_name),
+ Applicability::Unspecified,
+ );
+ return None;
+ },
+ _ => return None,
+ };
+ return Some(PtrArg {
+ idx: i,
+ span: hir_ty.span,
+ ty_did: adt.did(),
+ ty_name: name.ident.name,
+ method_renames,
+ ref_prefix: RefPrefix {
+ lt: lt.name,
+ mutability,
+ },
+ deref_ty,
+ });
+ }
+ }
+ None
+ })
+}
+
+fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
+ if let FnRetTy::Return(ty) = sig.decl.output
+ && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
+ {
+ let args: Option<Vec<_>> = sig
+ .decl
+ .inputs
+ .iter()
+ .filter_map(get_rptr_lm)
+ .filter(|&(lt, _, _)| lt.name == out.name)
+ .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
+ .collect();
+ if let Some(args) = args
+ && !args.is_empty()
+ && body.map_or(true, |body| {
+ sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
+ })
+ {
+ span_lint_and_then(
+ cx,
+ MUT_FROM_REF,
+ ty.span,
+ "mutable borrow from immutable input(s)",
+ |diag| {
+ let ms = MultiSpan::from_spans(args);
+ diag.span_note(ms, "immutable borrow here");
+ },
+ );
+ }
+ }
+}
+
++#[expect(clippy::too_many_lines)]
+fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ /// Map from a local id to which argument it came from (index into `Self::args` and
+ /// `Self::results`)
+ bindings: HirIdMap<usize>,
+ /// The arguments being checked.
+ args: &'cx [PtrArg<'tcx>],
+ /// The results for each argument (len should match args.len)
+ results: Vec<PtrArgResult>,
+ /// The number of arguments which can't be linted. Used to return early.
+ skip_count: usize,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.skip_count == self.args.len() {
+ return;
+ }
+
+ // Check if this is local we care about
+ let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
+ Some(&i) => i,
+ None => return walk_expr(self, e),
+ };
+ let args = &self.args[args_idx];
+ let result = &mut self.results[args_idx];
+
+ // Helper function to handle early returns.
+ let mut set_skip_flag = || {
+ if !result.skip {
+ self.skip_count += 1;
+ }
+ result.skip = true;
+ };
+
+ match get_expr_use_or_unification_node(self.cx.tcx, e) {
+ Some((Node::Stmt(_), _)) => (),
+ Some((Node::Local(l), _)) => {
+ // Only trace simple bindings. e.g `let x = y;`
+ if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
+ self.bindings.insert(id, args_idx);
+ } else {
+ set_skip_flag();
+ }
+ },
+ Some((Node::Expr(e), child_id)) => match e.kind {
+ ExprKind::Call(f, expr_args) => {
+ let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
+ if expr_sig(self.cx, f)
+ .map(|sig| sig.input(i).skip_binder().peel_refs())
+ .map_or(true, |ty| match *ty.kind() {
+ ty::Param(_) => true,
+ ty::Adt(def, _) => def.did() == args.ty_did,
+ _ => false,
+ })
+ {
+ // Passed to a function taking the non-dereferenced type.
+ set_skip_flag();
+ }
+ },
+ ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
+ let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
+ if i == 0 {
+ // Check if the method can be renamed.
+ let name = name.ident.as_str();
+ if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
+ result.replacements.push(PtrArgReplacement {
+ expr_span: e.span,
+ self_span: self_arg.span,
+ replacement,
+ });
+ return;
+ }
+ }
+
+ let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
+ x
+ } else {
+ set_skip_flag();
+ return;
+ };
+
+ match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
+ ty::Param(_) => {
+ set_skip_flag();
+ },
+ // If the types match check for methods which exist on both types. e.g. `Vec::len` and
+ // `slice::len`
+ ty::Adt(def, _) if def.did() == args.ty_did => {
+ set_skip_flag();
+ },
+ _ => (),
+ }
+ },
+ // Indexing is fine for currently supported types.
+ ExprKind::Index(e, _) if e.hir_id == child_id => (),
+ _ => set_skip_flag(),
+ },
+ _ => set_skip_flag(),
+ }
+ }
+ }
+
+ let mut skip_count = 0;
+ let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
+ let mut v = V {
+ cx,
+ bindings: args
+ .iter()
+ .enumerate()
+ .filter_map(|(i, arg)| {
+ let param = &body.params[arg.idx];
+ match param.pat.kind {
+ PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
+ if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
+ {
+ Some((id, i))
+ },
+ _ => {
+ skip_count += 1;
+ results[i].skip = true;
+ None
+ },
+ }
+ })
+ .collect(),
+ args,
+ results,
+ skip_count,
+ };
+ v.visit_expr(&body.value);
+ v.results
+}
+
+fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
+ if let TyKind::Rptr(ref lt, ref m) = ty.kind {
+ Some((lt, m.mutbl, ty.span))
+ } else {
+ None
+ }
+}
+
+fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(pathexp, []) = expr.kind {
+ path_def_id(cx, pathexp).map_or(false, |id| {
+ matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))
+ })
+ } else {
+ false
+ }
+}
--- /dev/null
- if meets_msrv(self.msrv.as_ref(), &msrvs::RANGE_CONTAINS) {
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local};
+use clippy_utils::{higher, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, PathSegment, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{Span, Spanned};
+use rustc_span::sym;
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for zipping a collection with the range of
+ /// `0.._.len()`.
+ ///
+ /// ### Why is this bad?
+ /// The code is better expressed with `.enumerate()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = vec![1];
+ /// x.iter().zip(0..x.len());
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let x = vec![1];
+ /// x.iter().enumerate();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_ZIP_WITH_LEN,
+ complexity,
+ "zipping iterator with a range when `enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for exclusive ranges where 1 is added to the
+ /// upper bound, e.g., `x..(y+1)`.
+ ///
+ /// ### Why is this bad?
+ /// The code is more readable with an inclusive range
+ /// like `x..=y`.
+ ///
+ /// ### Known problems
+ /// Will add unnecessary pair of parentheses when the
+ /// expression is not wrapped in a pair but starts with an opening parenthesis
+ /// and ends with a closing one.
+ /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`.
+ ///
+ /// Also in many cases, inclusive ranges are still slower to run than
+ /// exclusive ranges, because they essentially add an extra branch that
+ /// LLVM may fail to hoist out of the loop.
+ ///
+ /// This will cause a warning that cannot be fixed if the consumer of the
+ /// range only accepts a specific range type, instead of the generic
+ /// `RangeBounds` trait
+ /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// for x..(y+1) { .. }
+ /// ```
+ /// Could be written as
+ /// ```rust,ignore
+ /// for x..=y { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_PLUS_ONE,
+ pedantic,
+ "`x..(y+1)` reads better as `x..=y`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for inclusive ranges where 1 is subtracted from
+ /// the upper bound, e.g., `x..=(y-1)`.
+ ///
+ /// ### Why is this bad?
+ /// The code is more readable with an exclusive range
+ /// like `x..y`.
+ ///
+ /// ### Known problems
+ /// This will cause a warning that cannot be fixed if
+ /// the consumer of the range only accepts a specific range type, instead of
+ /// the generic `RangeBounds` trait
+ /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// for x..=(y-1) { .. }
+ /// ```
+ /// Could be written as
+ /// ```rust,ignore
+ /// for x..y { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_MINUS_ONE,
+ pedantic,
+ "`x..=(y-1)` reads better as `x..y`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for range expressions `x..y` where both `x` and `y`
+ /// are constant and `x` is greater or equal to `y`.
+ ///
+ /// ### Why is this bad?
+ /// Empty ranges yield no values so iterating them is a no-op.
+ /// Moreover, trying to use a reversed range to index a slice will panic at run-time.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// fn main() {
+ /// (10..=0).for_each(|x| println!("{}", x));
+ ///
+ /// let arr = [1, 2, 3, 4, 5];
+ /// let sub = &arr[3..1];
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// (0..=10).rev().for_each(|x| println!("{}", x));
+ ///
+ /// let arr = [1, 2, 3, 4, 5];
+ /// let sub = &arr[1..3];
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub REVERSED_EMPTY_RANGES,
+ correctness,
+ "reversing the limits of range expressions, resulting in empty ranges"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions like `x >= 3 && x < 8` that could
+ /// be more readably expressed as `(3..8).contains(x)`.
+ ///
+ /// ### Why is this bad?
+ /// `contains` expresses the intent better and has less
+ /// failure modes (such as fencepost errors or using `||` instead of `&&`).
+ ///
+ /// ### Example
+ /// ```rust
+ /// // given
+ /// let x = 6;
+ ///
+ /// assert!(x >= 3 && x < 8);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ ///# let x = 6;
+ /// assert!((3..8).contains(&x));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MANUAL_RANGE_CONTAINS,
+ style,
+ "manually reimplementing {`Range`, `RangeInclusive`}`::contains`"
+}
+
+pub struct Ranges {
+ msrv: Option<RustcVersion>,
+}
+
+impl Ranges {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Ranges => [
+ RANGE_ZIP_WITH_LEN,
+ RANGE_PLUS_ONE,
+ RANGE_MINUS_ONE,
+ REVERSED_EMPTY_RANGES,
+ MANUAL_RANGE_CONTAINS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Ranges {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::MethodCall(path, args, _) => {
+ check_range_zip_with_len(cx, path, args, expr.span);
+ },
+ ExprKind::Binary(ref op, l, r) => {
- fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, expr: &Expr<'_>) {
++ if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
+ check_possible_range_contains(cx, op.node, l, r, expr);
+ }
+ },
+ _ => {},
+ }
+
+ check_exclusive_range_plus_one(cx, expr);
+ check_inclusive_range_minus_one(cx, expr);
+ check_reversed_empty_range(cx, expr);
+ }
+ extract_msrv_attr!(LateContext);
+}
+
- if let (Some((lval, lid, name_span, lval_span, lord, linc)), Some((rval, rid, _, rval_span, rord, rinc))) =
- (check_range_bounds(cx, l), check_range_bounds(cx, r))
- {
++fn check_possible_range_contains(
++ cx: &LateContext<'_>,
++ op: BinOpKind,
++ left: &Expr<'_>,
++ right: &Expr<'_>,
++ expr: &Expr<'_>,
++) {
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ let span = expr.span;
+ let combine_and = match op {
+ BinOpKind::And | BinOpKind::BitAnd => true,
+ BinOpKind::Or | BinOpKind::BitOr => false,
+ _ => return,
+ };
+ // value, name, order (higher/lower), inclusiveness
- if lid != rid || lord == rord {
++ if let (Some(l), Some(r)) = (check_range_bounds(cx, left), check_range_bounds(cx, right)) {
+ // we only lint comparisons on the same name and with different
+ // direction
- let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval);
- if combine_and && ord == Some(rord) {
++ if l.id != r.id || l.ord == r.ord {
+ return;
+ }
- let (l_span, u_span, l_inc, u_inc) = if rord == Ordering::Less {
- (lval_span, rval_span, linc, rinc)
++ let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l.expr), &l.val, &r.val);
++ if combine_and && ord == Some(r.ord) {
+ // order lower bound and upper bound
- (rval_span, lval_span, rinc, linc)
++ let (l_span, u_span, l_inc, u_inc) = if r.ord == Ordering::Less {
++ (l.val_span, r.val_span, l.inc, r.inc)
+ } else {
- let name = snippet_with_applicability(cx, name_span, "_", &mut applicability);
++ (r.val_span, l.val_span, r.inc, l.inc)
+ };
+ // we only lint inclusive lower bounds
+ if !l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("RangeInclusive", "..=")
+ } else {
+ ("Range", "..")
+ };
+ let mut applicability = Applicability::MachineApplicable;
- } else if !combine_and && ord == Some(lord) {
++ let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
+ &format!("manual `{}::contains` implementation", range_type),
+ "use",
+ format!("({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
+ applicability,
+ );
- let (l_span, u_span, l_inc, u_inc) = if lord == Ordering::Less {
- (lval_span, rval_span, linc, rinc)
++ } else if !combine_and && ord == Some(l.ord) {
+ // `!_.contains(_)`
+ // order lower bound and upper bound
- (rval_span, lval_span, rinc, linc)
++ let (l_span, u_span, l_inc, u_inc) = if l.ord == Ordering::Less {
++ (l.val_span, r.val_span, l.inc, r.inc)
+ } else {
- let name = snippet_with_applicability(cx, name_span, "_", &mut applicability);
++ (r.val_span, l.val_span, r.inc, l.inc)
+ };
+ if l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("Range", "..")
+ } else {
+ ("RangeInclusive", "..=")
+ };
+ let mut applicability = Applicability::MachineApplicable;
- fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, HirId, Span, Span, Ordering, bool)> {
++ let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
+ &format!("manual `!{}::contains` implementation", range_type),
+ "use",
+ format!("!({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
+ applicability,
+ );
+ }
+ }
+}
+
- return Some((c, id, l.span, r.span, ordering, inclusive));
++struct RangeBounds<'a> {
++ val: Constant,
++ expr: &'a Expr<'a>,
++ id: HirId,
++ name_span: Span,
++ val_span: Span,
++ ord: Ordering,
++ inc: bool,
++}
++
++// Takes a binary expression such as x <= 2 as input
++// Breaks apart into various pieces, such as the value of the number,
++// hir id of the variable, and direction/inclusiveness of the operator
++fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a>> {
+ if let ExprKind::Binary(ref op, l, r) = ex.kind {
+ let (inclusive, ordering) = match op.node {
+ BinOpKind::Gt => (false, Ordering::Greater),
+ BinOpKind::Ge => (true, Ordering::Greater),
+ BinOpKind::Lt => (false, Ordering::Less),
+ BinOpKind::Le => (true, Ordering::Less),
+ _ => return None,
+ };
+ if let Some(id) = path_to_local(l) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
- return Some((c, id, r.span, l.span, ordering.reverse(), inclusive));
++ return Some(RangeBounds {
++ val: c,
++ expr: r,
++ id,
++ name_span: l.span,
++ val_span: r.span,
++ ord: ordering,
++ inc: inclusive,
++ });
+ }
+ } else if let Some(id) = path_to_local(r) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
++ return Some(RangeBounds {
++ val: c,
++ expr: l,
++ id,
++ name_span: r.span,
++ val_span: l.span,
++ ord: ordering.reverse(),
++ inc: inclusive,
++ });
+ }
+ }
+ }
+ None
+}
+
+fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) {
+ if_chain! {
+ if path.ident.as_str() == "zip";
+ if let [iter, zip_arg] = args;
+ // `.iter()` call
+ if let ExprKind::MethodCall(iter_path, iter_args, _) = iter.kind;
+ if iter_path.ident.name == sym::iter;
+ // range expression in `.zip()` call: `0..x.len()`
+ if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg);
+ if is_integer_const(cx, start, 0);
+ // `.len()` call
+ if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
+ if len_path.ident.name == sym::len && len_args.len() == 1;
+ // `.iter()` and `.len()` called on same `Path`
+ if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
+ if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
+ if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
+ then {
+ span_lint(cx,
+ RANGE_ZIP_WITH_LEN,
+ span,
+ &format!("it is more idiomatic to use `{}.iter().enumerate()`",
+ snippet(cx, iter_args[0].span, "_"))
+ );
+ }
+ }
+}
+
+// exclusive range plus one: `x..(y+1)`
+fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range {
+ start,
+ end: Some(end),
+ limits: RangeLimits::HalfOpen
+ }) = higher::Range::hir(expr);
+ if let Some(y) = y_plus_one(cx, end);
+ then {
+ let span = if expr.span.from_expansion() {
+ expr.span
+ .ctxt()
+ .outer_expn_data()
+ .call_site
+ } else {
+ expr.span
+ };
+ span_lint_and_then(
+ cx,
+ RANGE_PLUS_ONE,
+ span,
+ "an inclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string());
+ let end = Sugg::hir(cx, y, "y").maybe_par();
+ if let Some(is_wrapped) = &snippet_opt(cx, span) {
+ if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') {
+ diag.span_suggestion(
+ span,
+ "use",
+ format!("({}..={})", start, end),
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ diag.span_suggestion(
+ span,
+ "use",
+ format!("{}..={}", start, end),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ },
+ );
+ }
+ }
+}
+
+// inclusive range minus one: `x..=(y-1)`
+fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::Range::hir(expr);
+ if let Some(y) = y_minus_one(cx, end);
+ then {
+ span_lint_and_then(
+ cx,
+ RANGE_MINUS_ONE,
+ expr.span,
+ "an exclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string());
+ let end = Sugg::hir(cx, y, "y").maybe_par();
+ diag.span_suggestion(
+ expr.span,
+ "use",
+ format!("{}..{}", start, end),
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+ }
+ }
+}
+
+fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ fn inside_indexing_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(
+ get_parent_expr(cx, expr),
+ Some(Expr {
+ kind: ExprKind::Index(..),
+ ..
+ })
+ )
+ }
+
+ fn is_for_loop_arg(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let mut cur_expr = expr;
+ while let Some(parent_expr) = get_parent_expr(cx, cur_expr) {
+ match higher::ForLoop::hir(parent_expr) {
+ Some(higher::ForLoop { arg, .. }) if arg.hir_id == expr.hir_id => return true,
+ _ => cur_expr = parent_expr,
+ }
+ }
+
+ false
+ }
+
+ fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool {
+ match limits {
+ RangeLimits::HalfOpen => ordering != Ordering::Less,
+ RangeLimits::Closed => ordering == Ordering::Greater,
+ }
+ }
+
+ if_chain! {
+ if let Some(higher::Range { start: Some(start), end: Some(end), limits }) = higher::Range::hir(expr);
+ let ty = cx.typeck_results().expr_ty(start);
+ if let ty::Int(_) | ty::Uint(_) = ty.kind();
+ if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start);
+ if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end);
+ if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx);
+ if is_empty_range(limits, ordering);
+ then {
+ if inside_indexing_expr(cx, expr) {
+ // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ...
+ if ordering != Ordering::Equal {
+ span_lint(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is reversed and using it to index a slice will panic at run-time",
+ );
+ }
+ // ... except in for loop arguments for backwards compatibility with `reverse_range_loop`
+ } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) {
+ span_lint_and_then(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is empty so it will yield no values",
+ |diag| {
+ if ordering != Ordering::Equal {
+ let start_snippet = snippet(cx, start.span, "_");
+ let end_snippet = snippet(cx, end.span, "_");
+ let dots = match limits {
+ RangeLimits::HalfOpen => "..",
+ RangeLimits::Closed => "..="
+ };
+
+ diag.span_suggestion(
+ expr.span,
+ "consider using the following if you are attempting to iterate over this \
+ range in reverse",
+ format!("({}{}{}).rev()", end_snippet, dots, start_snippet),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
+ match expr.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) => {
+ if is_integer_const(cx, lhs, 1) {
+ Some(rhs)
+ } else if is_integer_const(cx, rhs, 1) {
+ Some(lhs)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
+ match expr.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub, ..
+ },
+ lhs,
+ rhs,
+ ) if is_integer_const(cx, rhs, 1) => Some(lhs),
+ _ => None,
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::higher::VecArgs;
++use clippy_utils::last_path_segment;
++use clippy_utils::macros::root_macro_call_first_node;
++use clippy_utils::source::{indent_of, snippet};
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind, QPath, TyKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::{sym, Span, Symbol};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for `Arc::new` or `Rc::new` in `vec![elem; len]`
++ ///
++ /// ### Why is this bad?
++ /// This will create `elem` once and clone it `len` times - doing so with `Arc` or `Rc`
++ /// is a bit misleading, as it will create references to the same pointer, rather
++ /// than different instances.
++ ///
++ /// ### Example
++ /// ```rust
++ /// let v = vec![std::sync::Arc::new("some data".to_string()); 100];
++ /// // or
++ /// let v = vec![std::rc::Rc::new("some data".to_string()); 100];
++ /// ```
++ /// Use instead:
++ /// ```rust
++ ///
++ /// // Initialize each value separately:
++ /// let mut data = Vec::with_capacity(100);
++ /// for _ in 0..100 {
++ /// data.push(std::rc::Rc::new("some data".to_string()));
++ /// }
++ ///
++ /// // Or if you want clones of the same reference,
++ /// // Create the reference beforehand to clarify that
++ /// // it should be cloned for each value
++ /// let data = std::rc::Rc::new("some data".to_string());
++ /// let v = vec![data; 100];
++ /// ```
++ #[clippy::version = "1.62.0"]
++ pub RC_CLONE_IN_VEC_INIT,
++ suspicious,
++ "initializing `Arc` or `Rc` in `vec![elem; len]`"
++}
++declare_lint_pass!(RcCloneInVecInit => [RC_CLONE_IN_VEC_INIT]);
++
++impl LateLintPass<'_> for RcCloneInVecInit {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
++ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return; };
++ let Some(VecArgs::Repeat(elem, len)) = VecArgs::hir(cx, expr) else { return; };
++ let Some(symbol) = new_reference_call(cx, elem) else { return; };
++
++ emit_lint(cx, symbol, macro_call.span, elem, len);
++ }
++}
++
++fn elem_snippet(cx: &LateContext<'_>, elem: &Expr<'_>, symbol_name: &str) -> String {
++ let elem_snippet = snippet(cx, elem.span, "..").to_string();
++ if elem_snippet.contains('\n') {
++ // This string must be found in `elem_snippet`, otherwise we won't be constructing
++ // the snippet in the first place.
++ let reference_creation = format!("{symbol_name}::new");
++ let (code_until_reference_creation, _right) = elem_snippet.split_once(&reference_creation).unwrap();
++
++ return format!("{code_until_reference_creation}{reference_creation}(..)");
++ }
++
++ elem_snippet
++}
++
++fn loop_init_suggestion(elem: &str, len: &str, indent: &str) -> String {
++ format!(
++ r#"{{
++{indent} let mut v = Vec::with_capacity({len});
++{indent} (0..{len}).for_each(|_| v.push({elem}));
++{indent} v
++{indent}}}"#
++ )
++}
++
++fn extract_suggestion(elem: &str, len: &str, indent: &str) -> String {
++ format!(
++ "{{
++{indent} let data = {elem};
++{indent} vec![data; {len}]
++{indent}}}"
++ )
++}
++
++fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, elem: &Expr<'_>, len: &Expr<'_>) {
++ let symbol_name = symbol.as_str();
++
++ span_lint_and_then(
++ cx,
++ RC_CLONE_IN_VEC_INIT,
++ lint_span,
++ &format!("calling `{symbol_name}::new` in `vec![elem; len]`"),
++ |diag| {
++ let len_snippet = snippet(cx, len.span, "..");
++ let elem_snippet = elem_snippet(cx, elem, symbol_name);
++ let indentation = " ".repeat(indent_of(cx, lint_span).unwrap_or(0));
++ let loop_init_suggestion = loop_init_suggestion(&elem_snippet, len_snippet.as_ref(), &indentation);
++ let extract_suggestion = extract_suggestion(&elem_snippet, len_snippet.as_ref(), &indentation);
++
++ diag.note(format!("each element will point to the same `{symbol_name}` instance"));
++ diag.span_suggestion(
++ lint_span,
++ format!("consider initializing each `{symbol_name}` element individually"),
++ loop_init_suggestion,
++ Applicability::Unspecified,
++ );
++ diag.span_suggestion(
++ lint_span,
++ format!(
++ "or if this is intentional, consider extracting the `{symbol_name}` initialization to a variable"
++ ),
++ extract_suggestion,
++ Applicability::Unspecified,
++ );
++ },
++ );
++}
++
++/// Checks whether the given `expr` is a call to `Arc::new` or `Rc::new`
++fn new_reference_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
++ if_chain! {
++ if let ExprKind::Call(func, _args) = expr.kind;
++ if let ExprKind::Path(ref func_path @ QPath::TypeRelative(ty, _)) = func.kind;
++ if let TyKind::Path(ref ty_path) = ty.kind;
++ if let Some(def_id) = cx.qpath_res(ty_path, ty.hir_id).opt_def_id();
++ if last_path_segment(func_path).ident.name == sym::new;
++
++ then {
++ return cx.tcx.get_diagnostic_name(def_id).filter(|symbol| symbol == &sym::Arc || symbol == &sym::Rc);
++ }
++ }
++
++ None
++}
--- /dev/null
- use std::convert::TryFrom;
+use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
+use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{def_id, Body, FnDecl, HirId};
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::{
+ self, traversal,
+ visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
+ Mutability,
+};
+use rustc_middle::ty::{self, fold::TypeVisitor, Ty};
+use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis, ResultsCursor};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+use rustc_span::sym;
- #[allow(clippy::too_many_lines)]
+use std::ops::ControlFlow;
+
+macro_rules! unwrap_or_continue {
+ ($x:expr) => {
+ match $x {
+ Some(x) => x,
+ None => continue,
+ }
+ };
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a redundant `clone()` (and its relatives) which clones an owned
+ /// value that is going to be dropped without further use.
+ ///
+ /// ### Why is this bad?
+ /// It is not always possible for the compiler to eliminate useless
+ /// allocations and deallocations generated by redundant `clone()`s.
+ ///
+ /// ### Known problems
+ /// False-negatives: analysis performed by this lint is conservative and limited.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::path::Path;
+ /// # #[derive(Clone)]
+ /// # struct Foo;
+ /// # impl Foo {
+ /// # fn new() -> Self { Foo {} }
+ /// # }
+ /// # fn call(x: Foo) {}
+ /// {
+ /// let x = Foo::new();
+ /// call(x.clone());
+ /// call(x.clone()); // this can just pass `x`
+ /// }
+ ///
+ /// ["lorem", "ipsum"].join(" ").to_string();
+ ///
+ /// Path::new("/a/b").join("c").to_path_buf();
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub REDUNDANT_CLONE,
+ perf,
+ "`clone()` of an owned value that is going to be dropped immediately"
+}
+
+declare_lint_pass!(RedundantClone => [REDUNDANT_CLONE]);
+
+impl<'tcx> LateLintPass<'tcx> for RedundantClone {
- /// For exampel, `_1 = &mut _2` generate _1: {_2,...}
++ #[expect(clippy::too_many_lines)]
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ let def_id = cx.tcx.hir().body_owner_def_id(body.id());
+
+ // Building MIR for `fn`s with unsatisfiable preds results in ICE.
+ if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ let mir = cx.tcx.optimized_mir(def_id.to_def_id());
+
+ let possible_origin = {
+ let mut vis = PossibleOriginVisitor::new(mir);
+ vis.visit_body(mir);
+ vis.into_map(cx)
+ };
+ let maybe_storage_live_result = MaybeStorageLive
+ .into_engine(cx.tcx, mir)
+ .pass_name("redundant_clone")
+ .iterate_to_fixpoint()
+ .into_results_cursor(mir);
+ let mut possible_borrower = {
+ let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
+ vis.visit_body(mir);
+ vis.into_map(cx, maybe_storage_live_result)
+ };
+
+ for (bb, bbdata) in mir.basic_blocks().iter_enumerated() {
+ let terminator = bbdata.terminator();
+
+ if terminator.source_info.span.from_expansion() {
+ continue;
+ }
+
+ // Give up on loops
+ if terminator.successors().any(|s| s == bb) {
+ continue;
+ }
+
+ let (fn_def_id, arg, arg_ty, clone_ret) =
+ unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
+
+ let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD)
+ || match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD)
+ || (match_def_path(cx, fn_def_id, &paths::TO_STRING_METHOD)
+ && is_type_diagnostic_item(cx, arg_ty, sym::String));
+
+ let from_deref = !from_borrow
+ && (match_def_path(cx, fn_def_id, &paths::PATH_TO_PATH_BUF)
+ || match_def_path(cx, fn_def_id, &paths::OS_STR_TO_OS_STRING));
+
+ if !from_borrow && !from_deref {
+ continue;
+ }
+
+ if let ty::Adt(def, _) = arg_ty.kind() {
+ if def.is_manually_drop() {
+ continue;
+ }
+ }
+
+ // `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }`
+ let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(cx, mir, arg, from_borrow, bb));
+
+ let loc = mir::Location {
+ block: bb,
+ statement_index: bbdata.statements.len(),
+ };
+
+ // `Local` to be cloned, and a local of `clone` call's destination
+ let (local, ret_local) = if from_borrow {
+ // `res = clone(arg)` can be turned into `res = move arg;`
+ // if `arg` is the only borrow of `cloned` at this point.
+
+ if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) {
+ continue;
+ }
+
+ (cloned, clone_ret)
+ } else {
+ // `arg` is a reference as it is `.deref()`ed in the previous block.
+ // Look into the predecessor block and find out the source of deref.
+
+ let ps = &mir.predecessors()[bb];
+ if ps.len() != 1 {
+ continue;
+ }
+ let pred_terminator = mir[ps[0]].terminator();
+
+ // receiver of the `deref()` call
+ let (pred_arg, deref_clone_ret) = if_chain! {
+ if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) =
+ is_call_with_ref_arg(cx, mir, &pred_terminator.kind);
+ if res == cloned;
+ if cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id);
+ if is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf)
+ || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString);
+ then {
+ (pred_arg, res)
+ } else {
+ continue;
+ }
+ };
+
+ let (local, cannot_move_out) =
+ unwrap_or_continue!(find_stmt_assigns_to(cx, mir, pred_arg, true, ps[0]));
+ let loc = mir::Location {
+ block: bb,
+ statement_index: mir.basic_blocks()[bb].statements.len(),
+ };
+
+ // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed
+ // at the last statement:
+ //
+ // ```
+ // pred_arg = &local;
+ // cloned = deref(pred_arg);
+ // arg = &cloned;
+ // StorageDead(pred_arg);
+ // res = to_path_buf(cloned);
+ // ```
+ if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) {
+ continue;
+ }
+
+ (local, deref_clone_ret)
+ };
+
+ let clone_usage = if local == ret_local {
+ CloneUsage {
+ cloned_used: false,
+ cloned_consume_or_mutate_loc: None,
+ clone_consumed_or_mutated: true,
+ }
+ } else {
+ let clone_usage = visit_clone_usage(local, ret_local, mir, bb);
+ if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
+ // cloned value is used, and the clone is modified or moved
+ continue;
+ } else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc {
+ // cloned value is mutated, and the clone is alive.
+ if possible_borrower.local_is_alive_at(ret_local, loc) {
+ continue;
+ }
+ }
+ clone_usage
+ };
+
+ let span = terminator.source_info.span;
+ let scope = terminator.source_info.scope;
+ let node = mir.source_scopes[scope]
+ .local_data
+ .as_ref()
+ .assert_crate_local()
+ .lint_root;
+
+ if_chain! {
+ if let Some(snip) = snippet_opt(cx, span);
+ if let Some(dot) = snip.rfind('.');
+ then {
+ let sugg_span = span.with_lo(
+ span.lo() + BytePos(u32::try_from(dot).unwrap())
+ );
+ let mut app = Applicability::MaybeIncorrect;
+
+ let call_snip = &snip[dot + 1..];
+ // Machine applicable when `call_snip` looks like `foobar()`
+ if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
+ if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
+ app = Applicability::MachineApplicable;
+ }
+ }
+
+ span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
+ diag.span_suggestion(
+ sugg_span,
+ "remove this",
+ String::new(),
+ app,
+ );
+ if clone_usage.cloned_used {
+ diag.span_note(
+ span,
+ "cloned value is neither consumed nor mutated",
+ );
+ } else {
+ diag.span_note(
+ span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
+ "this value is dropped without further use",
+ );
+ }
+ });
+ } else {
+ span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
+ }
+ }
+ }
+ }
+}
+
+/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
+fn is_call_with_ref_arg<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &'tcx mir::Body<'tcx>,
+ kind: &'tcx mir::TerminatorKind<'tcx>,
+) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> {
+ if_chain! {
+ if let mir::TerminatorKind::Call { func, args, destination, .. } = kind;
+ if args.len() == 1;
+ if let mir::Operand::Move(mir::Place { local, .. }) = &args[0];
+ if let ty::FnDef(def_id, _) = *func.ty(&*mir, cx.tcx).kind();
+ if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
+ if !is_copy(cx, inner_ty);
+ then {
+ Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)?.as_local()?))
+ } else {
+ None
+ }
+ }
+}
+
+type CannotMoveOut = bool;
+
+/// Finds the first `to = (&)from`, and returns
+/// ``Some((from, whether `from` cannot be moved out))``.
+fn find_stmt_assigns_to<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &mir::Body<'tcx>,
+ to_local: mir::Local,
+ by_ref: bool,
+ bb: mir::BasicBlock,
+) -> Option<(mir::Local, CannotMoveOut)> {
+ let rvalue = mir.basic_blocks()[bb].statements.iter().rev().find_map(|stmt| {
+ if let mir::StatementKind::Assign(box (mir::Place { local, .. }, v)) = &stmt.kind {
+ return if *local == to_local { Some(v) } else { None };
+ }
+
+ None
+ })?;
+
+ match (by_ref, &*rvalue) {
+ (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => {
+ Some(base_local_and_movability(cx, mir, *place))
+ },
+ (false, mir::Rvalue::Ref(_, _, place)) => {
+ if let [mir::ProjectionElem::Deref] = place.as_ref().projection {
+ Some(base_local_and_movability(cx, mir, *place))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+/// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself
+/// if it is already a `Local`.
+///
+/// Also reports whether given `place` cannot be moved out.
+fn base_local_and_movability<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &mir::Body<'tcx>,
+ place: mir::Place<'tcx>,
+) -> (mir::Local, CannotMoveOut) {
+ use rustc_middle::mir::PlaceRef;
+
+ // Dereference. You cannot move things out from a borrowed value.
+ let mut deref = false;
+ // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
+ let mut field = false;
+ // If projection is a slice index then clone can be removed only if the
+ // underlying type implements Copy
+ let mut slice = false;
+
+ let PlaceRef { local, mut projection } = place.as_ref();
+ while let [base @ .., elem] = projection {
+ projection = base;
+ deref |= matches!(elem, mir::ProjectionElem::Deref);
+ field |= matches!(elem, mir::ProjectionElem::Field(..))
+ && has_drop(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
+ slice |= matches!(elem, mir::ProjectionElem::Index(..))
+ && !is_copy(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
+ }
+
+ (local, deref || field || slice)
+}
+
+#[derive(Default)]
+struct CloneUsage {
+ /// Whether the cloned value is used after the clone.
+ cloned_used: bool,
+ /// The first location where the cloned value is consumed or mutated, if any.
+ cloned_consume_or_mutate_loc: Option<mir::Location>,
+ /// Whether the clone value is mutated.
+ clone_consumed_or_mutated: bool,
+}
+fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
+ struct V {
+ cloned: mir::Local,
+ clone: mir::Local,
+ result: CloneUsage,
+ }
+ impl<'tcx> mir::visit::Visitor<'tcx> for V {
+ fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
+ let statements = &data.statements;
+ for (statement_index, statement) in statements.iter().enumerate() {
+ self.visit_statement(statement, mir::Location { block, statement_index });
+ }
+
+ self.visit_terminator(
+ data.terminator(),
+ mir::Location {
+ block,
+ statement_index: statements.len(),
+ },
+ );
+ }
+
+ fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
+ let local = place.local;
+
+ if local == self.cloned
+ && !matches!(
+ ctx,
+ PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
+ )
+ {
+ self.result.cloned_used = true;
+ self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
+ matches!(
+ ctx,
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
+ | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
+ )
+ .then(|| loc)
+ });
+ } else if local == self.clone {
+ match ctx {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
+ | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
+ self.result.clone_consumed_or_mutated = true;
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+
+ let init = CloneUsage {
+ cloned_used: false,
+ cloned_consume_or_mutate_loc: None,
+ // Consider non-temporary clones consumed.
+ // TODO: Actually check for mutation of non-temporaries.
+ clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp,
+ };
+ traversal::ReversePostorder::new(mir, bb)
+ .skip(1)
+ .fold(init, |usage, (tbb, tdata)| {
+ // Short-circuit
+ if (usage.cloned_used && usage.clone_consumed_or_mutated) ||
+ // Give up on loops
+ tdata.terminator().successors().any(|s| s == bb)
+ {
+ return CloneUsage {
+ cloned_used: true,
+ clone_consumed_or_mutated: true,
+ ..usage
+ };
+ }
+
+ let mut v = V {
+ cloned,
+ clone,
+ result: usage,
+ };
+ v.visit_basic_block_data(tbb, tdata);
+ v.result
+ })
+}
+
+/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
+#[derive(Copy, Clone)]
+struct MaybeStorageLive;
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
+ type Domain = BitSet<mir::Local>;
+ const NAME: &'static str = "maybe_storage_live";
+
+ fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = dead
+ BitSet::new_empty(body.local_decls.len())
+ }
+
+ fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ for arg in body.args_iter() {
+ state.insert(arg);
+ }
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
+ type Idx = mir::Local;
+
+ fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
+ match stmt.kind {
+ mir::StatementKind::StorageLive(l) => trans.gen(l),
+ mir::StatementKind::StorageDead(l) => trans.kill(l),
+ _ => (),
+ }
+ }
+
+ fn terminator_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ _loc: mir::Location,
+ ) {
+ }
+
+ fn call_return_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ _return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ // Nothing to do when a call returns successfully
+ }
+}
+
+/// Collects the possible borrowers of each local.
+/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
+/// possible borrowers of `a`.
+struct PossibleBorrowerVisitor<'a, 'tcx> {
+ possible_borrower: TransitiveRelation,
+ body: &'a mir::Body<'tcx>,
+ cx: &'a LateContext<'tcx>,
+ possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+}
+
+impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
+ fn new(
+ cx: &'a LateContext<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+ ) -> Self {
+ Self {
+ possible_borrower: TransitiveRelation::default(),
+ cx,
+ body,
+ possible_origin,
+ }
+ }
+
+ fn into_map(
+ self,
+ cx: &LateContext<'tcx>,
+ maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>,
+ ) -> PossibleBorrowerMap<'a, 'tcx> {
+ let mut map = FxHashMap::default();
+ for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
+ if is_copy(cx, self.body.local_decls[row].ty) {
+ continue;
+ }
+
+ let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
+ borrowers.remove(mir::Local::from_usize(0));
+ if !borrowers.is_empty() {
+ map.insert(row, borrowers);
+ }
+ }
+
+ let bs = BitSet::new_empty(self.body.local_decls.len());
+ PossibleBorrowerMap {
+ map,
+ maybe_live,
+ bitset: (bs.clone(), bs),
+ }
+ }
+}
+
+impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
+ fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
+ let lhs = place.local;
+ match rvalue {
+ mir::Rvalue::Ref(_, _, borrowed) => {
+ self.possible_borrower.add(borrowed.local, lhs);
+ },
+ other => {
+ if ContainsRegion
+ .visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
+ .is_continue()
+ {
+ return;
+ }
+ rvalue_locals(other, |rhs| {
+ if lhs != rhs {
+ self.possible_borrower.add(rhs, lhs);
+ }
+ });
+ },
+ }
+ }
+
+ fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
+ if let mir::TerminatorKind::Call {
+ args,
+ destination: Some((mir::Place { local: dest, .. }, _)),
+ ..
+ } = &terminator.kind
+ {
+ // TODO add doc
+ // If the call returns something with lifetimes,
+ // let's conservatively assume the returned value contains lifetime of all the arguments.
+ // For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
+
+ let mut immutable_borrowers = vec![];
+ let mut mutable_borrowers = vec![];
+
+ for op in args {
+ match op {
+ mir::Operand::Copy(p) | mir::Operand::Move(p) => {
+ if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
+ mutable_borrowers.push(p.local);
+ } else {
+ immutable_borrowers.push(p.local);
+ }
+ },
+ mir::Operand::Constant(..) => (),
+ }
+ }
+
+ let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
+ .iter()
+ .filter_map(|r| self.possible_origin.get(r))
+ .flat_map(HybridBitSet::iter)
+ .collect();
+
+ if ContainsRegion.visit_ty(self.body.local_decls[*dest].ty).is_break() {
+ mutable_variables.push(*dest);
+ }
+
+ for y in mutable_variables {
+ for x in &immutable_borrowers {
+ self.possible_borrower.add(*x, y);
+ }
+ for x in &mutable_borrowers {
+ self.possible_borrower.add(*x, y);
+ }
+ }
+ }
+ }
+}
+
+/// Collect possible borrowed for every `&mut` local.
++/// For example, `_1 = &mut _2` generate _1: {_2,...}
+/// Known Problems: not sure all borrowed are tracked
+struct PossibleOriginVisitor<'a, 'tcx> {
+ possible_origin: TransitiveRelation,
+ body: &'a mir::Body<'tcx>,
+}
+
+impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
+ fn new(body: &'a mir::Body<'tcx>) -> Self {
+ Self {
+ possible_origin: TransitiveRelation::default(),
+ body,
+ }
+ }
+
+ fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
+ let mut map = FxHashMap::default();
+ for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
+ if is_copy(cx, self.body.local_decls[row].ty) {
+ continue;
+ }
+
+ let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
+ borrowers.remove(mir::Local::from_usize(0));
+ if !borrowers.is_empty() {
+ map.insert(row, borrowers);
+ }
+ }
+ map
+ }
+}
+
+impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
+ fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
+ let lhs = place.local;
+ match rvalue {
+ // Only consider `&mut`, which can modify origin place
+ mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
+ // _2: &mut _;
+ // _3 = move _2
+ mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
+ // _3 = move _2 as &mut _;
+ mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
+ => {
+ self.possible_origin.add(lhs, borrowed.local);
+ },
+ _ => {},
+ }
+ }
+}
+
+struct ContainsRegion;
+
+impl TypeVisitor<'_> for ContainsRegion {
+ type BreakTy = ();
+
+ fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> {
+ ControlFlow::BREAK
+ }
+}
+
+fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
+ use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
+
+ let mut visit_op = |op: &mir::Operand<'_>| match op {
+ mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
+ mir::Operand::Constant(..) => (),
+ };
+
+ match rvalue {
+ Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
+ Aggregate(_, ops) => ops.iter().for_each(visit_op),
+ BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
+ visit_op(lhs);
+ visit_op(rhs);
+ },
+ _ => (),
+ }
+}
+
+/// Result of `PossibleBorrowerVisitor`.
+struct PossibleBorrowerMap<'a, 'tcx> {
+ /// Mapping `Local -> its possible borrowers`
+ map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+ maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>,
+ // Caches to avoid allocation of `BitSet` on every query
+ bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
+}
+
+impl PossibleBorrowerMap<'_, '_> {
+ /// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
+ fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
+ self.maybe_live.seek_after_primary_effect(at);
+
+ self.bitset.0.clear();
+ let maybe_live = &mut self.maybe_live;
+ if let Some(bitset) = self.map.get(&borrowed) {
+ for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
+ self.bitset.0.insert(b);
+ }
+ } else {
+ return false;
+ }
+
+ self.bitset.1.clear();
+ for b in borrowers {
+ self.bitset.1.insert(*b);
+ }
+
+ self.bitset.0 == self.bitset.1
+ }
+
+ fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
+ self.maybe_live.seek_after_primary_effect(at);
+ self.maybe_live.contains(local)
+ }
+}
+
+#[derive(Default)]
+struct TransitiveRelation {
+ relations: FxHashMap<mir::Local, Vec<mir::Local>>,
+}
+impl TransitiveRelation {
+ fn add(&mut self, a: mir::Local, b: mir::Local) {
+ self.relations.entry(a).or_default().push(b);
+ }
+
+ fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
+ let mut seen = HybridBitSet::new_empty(domain_size);
+ let mut stack = vec![a];
+ while let Some(u) = stack.pop() {
+ if let Some(edges) = self.relations.get(&u) {
+ for &v in edges {
+ if seen.insert(v) {
+ stack.push(v);
+ }
+ }
+ }
+ }
+ seen
+ }
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::FIELD_INIT_SHORTHAND) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for fields in struct literals where shorthands
+ /// could be used.
+ ///
+ /// ### Why is this bad?
+ /// If the field and variable names are the same,
+ /// the field name is redundant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let bar: u8 = 123;
+ ///
+ /// struct Foo {
+ /// bar: u8,
+ /// }
+ ///
+ /// let foo = Foo { bar: bar };
+ /// ```
+ /// the last line can be simplified to
+ /// ```ignore
+ /// let foo = Foo { bar };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_FIELD_NAMES,
+ style,
+ "checks for fields in struct literals where shorthands could be used"
+}
+
+pub struct RedundantFieldNames {
+ msrv: Option<RustcVersion>,
+}
+
+impl RedundantFieldNames {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]);
+
+impl EarlyLintPass for RedundantFieldNames {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
++ if !meets_msrv(self.msrv, msrvs::FIELD_INIT_SHORTHAND) {
+ return;
+ }
+
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ if let ExprKind::Struct(ref se) = expr.kind {
+ for field in &se.fields {
+ if field.is_shorthand {
+ continue;
+ }
+ if let ExprKind::Path(None, path) = &field.expr.kind {
+ if path.segments.len() == 1
+ && path.segments[0].ident == field.ident
+ && path.segments[0].args.is_none()
+ {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_FIELD_NAMES,
+ field.span,
+ "redundant field names in struct initialization",
+ "replace it with",
+ field.ident.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ }
+ extract_msrv_attr!(EarlyContext);
+}
--- /dev/null
- if !meets_msrv(self.msrv.as_ref(), &msrvs::STATIC_IN_CONST) {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{Item, ItemKind, Ty, TyKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for constants and statics with an explicit `'static` lifetime.
+ ///
+ /// ### Why is this bad?
+ /// Adding `'static` to every reference can create very
+ /// complicated types.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// ```
+ /// This code can be rewritten as
+ /// ```ignore
+ /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub REDUNDANT_STATIC_LIFETIMES,
+ style,
+ "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them."
+}
+
+pub struct RedundantStaticLifetimes {
+ msrv: Option<RustcVersion>,
+}
+
+impl RedundantStaticLifetimes {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]);
+
+impl RedundantStaticLifetimes {
+ // Recursively visit types
+ fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
+ match ty.kind {
+ // Be careful of nested structures (arrays and tuples)
+ TyKind::Array(ref ty, _) => {
+ self.visit_type(&*ty, cx, reason);
+ },
+ TyKind::Tup(ref tup) => {
+ for tup_ty in tup {
+ self.visit_type(&*tup_ty, cx, reason);
+ }
+ },
+ // This is what we are looking for !
+ TyKind::Rptr(ref optional_lifetime, ref borrow_type) => {
+ // Match the 'static lifetime
+ if let Some(lifetime) = *optional_lifetime {
+ match borrow_type.ty.kind {
+ TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => {
+ if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime {
+ let snip = snippet(cx, borrow_type.ty.span, "<type>");
+ let sugg = format!("&{}", snip);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_STATIC_LIFETIMES,
+ lifetime.ident.span,
+ reason,
+ |diag| {
+ diag.span_suggestion(
+ ty.span,
+ "consider removing `'static`",
+ sugg,
+ Applicability::MachineApplicable, //snippet
+ );
+ },
+ );
+ }
+ },
+ _ => {},
+ }
+ }
+ self.visit_type(&*borrow_type.ty, cx, reason);
+ },
+ TyKind::Slice(ref ty) => {
+ self.visit_type(ty, cx, reason);
+ },
+ _ => {},
+ }
+ }
+}
+
+impl EarlyLintPass for RedundantStaticLifetimes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
++ if !meets_msrv(self.msrv, msrvs::STATIC_IN_CONST) {
+ return;
+ }
+
+ if !item.span.from_expansion() {
+ if let ItemKind::Const(_, ref var_type, _) = item.kind {
+ self.visit_type(var_type, cx, "constants have by default a `'static` lifetime");
+ // Don't check associated consts because `'static` cannot be elided on those (issue
+ // #2438)
+ }
+
+ if let ItemKind::Static(ref var_type, _, _) = item.kind {
+ self.visit_type(var_type, cx, "statics have by default a `'static` lifetime");
+ }
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
--- /dev/null
- #[allow(clippy::option_if_let_else)]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::BytePos;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `*&` and `*&mut` in expressions.
+ ///
+ /// ### Why is this bad?
+ /// Immediately dereferencing a reference is no-op and
+ /// makes the code less clear.
+ ///
+ /// ### Known problems
+ /// Multiple dereference/addrof pairs are not handled so
+ /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// let a = f(*&mut b);
+ /// let c = *&d;
+ ///
+ /// // Good
+ /// let a = f(b);
+ /// let c = d;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DEREF_ADDROF,
+ complexity,
+ "use of `*&` or `*&mut` in an expression"
+}
+
+declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);
+
+fn without_parens(mut e: &Expr) -> &Expr {
+ while let ExprKind::Paren(ref child_e) = e.kind {
+ e = child_e;
+ }
+ e
+}
+
+impl EarlyLintPass for DerefAddrOf {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
+ if_chain! {
+ if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind;
+ if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind;
+ if deref_target.span.ctxt() == e.span.ctxt();
+ if !addrof_target.span.from_expansion();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = if e.span.from_expansion() {
- #[allow(clippy::cast_possible_truncation)]
+ if let Some(macro_source) = snippet_opt(cx, e.span) {
+ // Remove leading whitespace from the given span
+ // e.g: ` $visitor` turns into `$visitor`
+ let trim_leading_whitespaces = |span| {
+ snippet_opt(cx, span).and_then(|snip| {
- #[allow(clippy::cast_possible_truncation)]
++ #[expect(clippy::cast_possible_truncation)]
+ snip.find(|c: char| !c.is_whitespace()).map(|pos| {
+ span.lo() + BytePos(pos as u32)
+ })
+ }).map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
+ };
+
+ let mut generate_snippet = |pattern: &str| {
++ #[expect(clippy::cast_possible_truncation)]
+ macro_source.rfind(pattern).map(|pattern_pos| {
+ let rpos = pattern_pos + pattern.len();
+ let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
+ let span = trim_leading_whitespaces(span_after_ref);
+ snippet_with_applicability(cx, span, "_", &mut applicability)
+ })
+ };
+
+ if *mutability == Mutability::Mut {
+ generate_snippet("mut")
+ } else {
+ generate_snippet("&")
+ }
+ } else {
+ Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
+ }
+ } else {
+ Some(snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability))
+ };
+ if let Some(sugg) = sugg {
+ span_lint_and_sugg(
+ cx,
+ DEREF_ADDROF,
+ e.span,
+ "immediately dereferencing a reference",
+ "try this",
+ sugg.to_string(),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use std::convert::TryFrom;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::{LitKind, StrStyle};
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
- #[allow(clippy::cast_possible_truncation)] // truncation very unlikely here
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks [regex](https://crates.io/crates/regex) creation
+ /// (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`) for correct
+ /// regex syntax.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to a runtime panic.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("|")
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INVALID_REGEX,
+ correctness,
+ "invalid regular expressions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for trivial [regex](https://crates.io/crates/regex)
+ /// creation (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`).
+ ///
+ /// ### Why is this bad?
+ /// Matching the regex can likely be replaced by `==` or
+ /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str`
+ /// methods.
+ ///
+ /// ### Known problems
+ /// If the same regex is going to be applied to multiple
+ /// inputs, the precomputations done by `Regex` construction can give
+ /// significantly better performance than any of the `str`-based methods.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("^foobar")
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIAL_REGEX,
+ nursery,
+ "trivial regular expressions"
+}
+
+declare_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
+
+impl<'tcx> LateLintPass<'tcx> for Regex {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if args.len() == 1;
+ if let Some(def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ then {
+ if match_def_path(cx, def_id, &paths::REGEX_NEW) ||
+ match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) {
+ check_regex(cx, &args[0], true);
+ } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) ||
+ match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) {
+ check_regex(cx, &args[0], false);
+ } else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) {
+ check_set(cx, &args[0], true);
+ } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) {
+ check_set(cx, &args[0], false);
+ }
+ }
+ }
+ }
+}
+
+#[must_use]
+fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u8) -> Span {
+ let offset = u32::from(offset);
+ let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset);
+ let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset);
+ assert!(start <= end);
+ Span::new(start, end, base.ctxt(), base.parent())
+}
+
+fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> {
+ constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c {
+ Constant::Str(s) => Some(s),
+ _ => None,
+ })
+}
+
+fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> {
+ use regex_syntax::hir::Anchor::{EndText, StartText};
+ use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal};
+
+ let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| matches!(*e.kind(), Literal(_)));
+
+ match *s.kind() {
+ Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"),
+ Literal(_) => Some("consider using `str::contains`"),
+ Alternation(ref exprs) => {
+ if exprs.iter().all(|e| e.kind().is_empty()) {
+ Some("the regex is unlikely to be useful as it is")
+ } else {
+ None
+ }
+ },
+ Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) {
+ (&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => {
+ Some("consider using `str::is_empty`")
+ },
+ (&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `==` on `str`s")
+ },
+ (&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"),
+ (&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `str::ends_with`")
+ },
+ _ if is_literal(exprs) => Some("consider using `str::contains`"),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+fn check_set<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
+ if let ExprKind::Array(exprs) = expr.kind;
+ then {
+ for expr in exprs {
+ check_regex(cx, expr, utf8);
+ }
+ }
+ }
+}
+
+fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ let mut parser = regex_syntax::ParserBuilder::new()
+ .unicode(true)
+ .allow_invalid_utf8(!utf8)
+ .build();
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(ref r, style) = lit.node {
+ let r = r.as_str();
+ let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 };
+ match parser.parse(r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+ } else if let Some(r) = const_str(cx, expr) {
+ match parser.parse(&r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+}
--- /dev/null
+// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.
+
+#[rustfmt::skip]
+pub static RENAMED_LINTS: &[(&str, &str)] = &[
+ ("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"),
+ ("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"),
+ ("clippy::box_vec", "clippy::box_collection"),
+ ("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"),
+ ("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"),
+ ("clippy::disallowed_method", "clippy::disallowed_methods"),
+ ("clippy::disallowed_type", "clippy::disallowed_types"),
++ ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
+ ("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"),
+ ("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"),
+ ("clippy::identity_conversion", "clippy::useless_conversion"),
+ ("clippy::if_let_some_result", "clippy::match_result_ok"),
+ ("clippy::new_without_default_derive", "clippy::new_without_default"),
+ ("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
+ ("clippy::option_expect_used", "clippy::expect_used"),
+ ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"),
+ ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"),
+ ("clippy::option_unwrap_used", "clippy::unwrap_used"),
+ ("clippy::ref_in_deref", "clippy::needless_borrow"),
+ ("clippy::result_expect_used", "clippy::expect_used"),
+ ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
+ ("clippy::result_unwrap_used", "clippy::unwrap_used"),
+ ("clippy::single_char_push_str", "clippy::single_char_add_str"),
+ ("clippy::stutter", "clippy::module_name_repetitions"),
+ ("clippy::to_string_in_display", "clippy::recursive_format_impl"),
+ ("clippy::zero_width_space", "clippy::invisible_characters"),
+ ("clippy::drop_bounds", "drop_bounds"),
+ ("clippy::into_iter_on_array", "array_into_iter"),
+ ("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
+ ("clippy::invalid_ref", "invalid_value"),
+ ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
+ ("clippy::panic_params", "non_fmt_panics"),
+ ("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"),
+ ("clippy::unknown_clippy_lints", "unknown_lints"),
+ ("clippy::unused_label", "unused_labels"),
+];
--- /dev/null
- /// ```rust
+use crate::FxHashSet;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::get_attr;
+use clippy_utils::source::{indent_of, snippet};
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{Ty, TypeAndMut};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for temporaries returned from function calls in a match scrutinee that have the
+ /// `clippy::has_significant_drop` attribute.
+ ///
+ /// ### Why is this bad?
+ /// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have
+ /// an important side-effect, such as unlocking a mutex, making it important for users to be
+ /// able to accurately understand their lifetimes. When a temporary is returned in a function
+ /// call in a match scrutinee, its lifetime lasts until the end of the match block, which may
+ /// be surprising.
+ ///
+ /// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a
+ /// function call that returns a `MutexGuard` and then tries to lock again in one of the match
+ /// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of
+ /// the match block and thus will not unlock.
+ ///
+ /// ### Example
- nursery,
++ /// ```rust.ignore
+ /// # use std::sync::Mutex;
+ ///
+ /// # struct State {}
+ ///
+ /// # impl State {
+ /// # fn foo(&self) -> bool {
+ /// # true
+ /// # }
+ ///
+ /// # fn bar(&self) {}
+ /// # }
+ ///
+ ///
+ /// let mutex = Mutex::new(State {});
+ ///
+ /// match mutex.lock().unwrap().foo() {
+ /// true => {
+ /// mutex.lock().unwrap().bar(); // Deadlock!
+ /// }
+ /// false => {}
+ /// };
+ ///
+ /// println!("All done!");
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::sync::Mutex;
+ ///
+ /// # struct State {}
+ ///
+ /// # impl State {
+ /// # fn foo(&self) -> bool {
+ /// # true
+ /// # }
+ ///
+ /// # fn bar(&self) {}
+ /// # }
+ ///
+ /// let mutex = Mutex::new(State {});
+ ///
+ /// let is_foo = mutex.lock().unwrap().foo();
+ /// match is_foo {
+ /// true => {
+ /// mutex.lock().unwrap().bar();
+ /// }
+ /// false => {}
+ /// };
+ ///
+ /// println!("All done!");
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub SIGNIFICANT_DROP_IN_SCRUTINEE,
- )
++ suspicious,
+ "warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime"
+}
+
+declare_lint_pass!(SignificantDropInScrutinee => [SIGNIFICANT_DROP_IN_SCRUTINEE]);
+
+impl<'tcx> LateLintPass<'tcx> for SignificantDropInScrutinee {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if let Some(suggestions) = has_significant_drop_in_scrutinee(cx, expr) {
+ for found in suggestions {
+ span_lint_and_then(
+ cx,
+ SIGNIFICANT_DROP_IN_SCRUTINEE,
+ found.found_span,
+ "temporary with significant drop in match scrutinee",
+ |diag| set_diagnostic(diag, cx, expr, found),
- /// If the expression is an ExprKind::Match, check if the scrutinee has a significant drop that may
- /// have a surprising lifetime.
++ );
+ }
+ }
+ }
+}
+
+fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
+ if found.lint_suggestion == LintSuggestion::MoveAndClone {
+ // If our suggestion is to move and clone, then we want to leave it to the user to
+ // decide how to address this lint, since it may be that cloning is inappropriate.
+ // Therefore, we won't to emit a suggestion.
+ return;
+ }
+
+ let original = snippet(cx, found.found_span, "..");
+ let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0));
+
+ let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy {
+ format!("let value = *{};\n{}", original, trailing_indent)
+ } else if found.is_unit_return_val {
+ // If the return value of the expression to be moved is unit, then we don't need to
+ // capture the result in a temporary -- we can just replace it completely with `()`.
+ format!("{};\n{}", original, trailing_indent)
+ } else {
+ format!("let value = {};\n{}", original, trailing_indent)
+ };
+
+ let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly {
+ "try moving the temporary above the match"
+ } else {
+ "try moving the temporary above the match and create a copy"
+ };
+
+ let scrutinee_replacement = if found.is_unit_return_val {
+ "()".to_owned()
+ } else {
+ "value".to_owned()
+ };
+
+ diag.multipart_suggestion(
+ suggestion_message,
+ vec![
+ (expr.span.shrink_to_lo(), replacement),
+ (found.found_span, scrutinee_replacement),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+}
+
- /// later). If allow_move_and_clone is false, the suggestion *won't* be set -- this gives us
++/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
++/// may have a surprising lifetime.
+fn has_significant_drop_in_scrutinee<'tcx, 'a>(
+ cx: &'a LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+) -> Option<Vec<FoundSigDrop>> {
+ let mut helper = SigDropHelper::new(cx);
+ match expr.kind {
+ ExprKind::Match(match_expr, _, _) => helper.find_sig_drop(match_expr),
+ _ => None,
+ }
+}
+
+struct SigDropHelper<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_chain_end: bool,
+ seen_types: FxHashSet<Ty<'tcx>>,
+ has_significant_drop: bool,
+ current_sig_drop: Option<FoundSigDrop>,
+ sig_drop_spans: Option<Vec<FoundSigDrop>>,
+ special_handling_for_binary_op: bool,
+}
+
++#[expect(clippy::enum_variant_names)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum LintSuggestion {
+ MoveOnly,
+ MoveAndDerefToCopy,
+ MoveAndClone,
+}
+
+#[derive(Clone, Copy)]
+struct FoundSigDrop {
+ found_span: Span,
+ is_unit_return_val: bool,
+ lint_suggestion: LintSuggestion,
+}
+
+impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> {
+ SigDropHelper {
+ cx,
+ is_chain_end: true,
+ seen_types: FxHashSet::default(),
+ has_significant_drop: false,
+ current_sig_drop: None,
+ sig_drop_spans: None,
+ special_handling_for_binary_op: false,
+ }
+ }
+
+ fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option<Vec<FoundSigDrop>> {
+ self.visit_expr(match_expr);
+
+ // If sig drop spans is empty but we found a significant drop, it means that we didn't find
+ // a type that was trivially copyable as we moved up the chain after finding a significant
+ // drop, so move the entire scrutinee.
+ if self.has_significant_drop && self.sig_drop_spans.is_none() {
+ self.try_setting_current_suggestion(match_expr, true);
+ self.move_current_suggestion();
+ }
+
+ self.sig_drop_spans.take()
+ }
+
+ /// This will try to set the current suggestion (so it can be moved into the suggestions vec
- rustc_middle::ty::Array(ty, _) => self.has_sig_drop_attr(cx, *ty),
- rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) => self.has_sig_drop_attr(cx, *ty),
- rustc_middle::ty::Ref(_, ty, _) => self.has_sig_drop_attr(cx, *ty),
- rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
++ /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us
+ /// an opportunity to look for another type in the chain that will be trivially copyable.
+ /// However, if we are at the the end of the chain, we want to accept whatever is there. (The
+ /// suggestion won't actually be output, but the diagnostic message will be output, so the user
+ /// can determine the best way to handle the lint.)
+ fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) {
+ if self.current_sig_drop.is_some() {
+ return;
+ }
+ let ty = self.get_type(expr);
+ if ty.is_ref() {
+ // We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
+ // but let's avoid any chance of an ICE
+ if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) {
+ if ty.is_trivially_pure_clone_copy() {
+ self.current_sig_drop.replace(FoundSigDrop {
+ found_span: expr.span,
+ is_unit_return_val: false,
+ lint_suggestion: LintSuggestion::MoveAndDerefToCopy,
+ });
+ } else if allow_move_and_clone {
+ self.current_sig_drop.replace(FoundSigDrop {
+ found_span: expr.span,
+ is_unit_return_val: false,
+ lint_suggestion: LintSuggestion::MoveAndClone,
+ });
+ }
+ }
+ } else if ty.is_trivially_pure_clone_copy() {
+ self.current_sig_drop.replace(FoundSigDrop {
+ found_span: expr.span,
+ is_unit_return_val: false,
+ lint_suggestion: LintSuggestion::MoveOnly,
+ });
+ }
+ }
+
+ fn move_current_suggestion(&mut self) {
+ if let Some(current) = self.current_sig_drop.take() {
+ self.sig_drop_spans.get_or_insert_with(Vec::new).push(current);
+ }
+ }
+
+ fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
+ self.cx.typeck_results().expr_ty(ex)
+ }
+
+ fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
+ !self.seen_types.insert(ty)
+ }
+
+ fn visit_exprs_for_binary_ops(
+ &mut self,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+ is_unit_return_val: bool,
+ span: Span,
+ ) {
+ self.special_handling_for_binary_op = true;
+ self.visit_expr(left);
+ self.visit_expr(right);
+
+ // If either side had a significant drop, suggest moving the entire scrutinee to avoid
+ // unnecessary copies and to simplify cases where both sides have significant drops.
+ if self.has_significant_drop {
+ self.current_sig_drop.replace(FoundSigDrop {
+ found_span: span,
+ is_unit_return_val,
+ lint_suggestion: LintSuggestion::MoveOnly,
+ });
+ }
+
+ self.special_handling_for_binary_op = false;
+ }
+
+ fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if let Some(adt) = ty.ty_adt_def() {
+ if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
+ return true;
+ }
+ }
+
+ match ty.kind() {
+ rustc_middle::ty::Adt(a, b) => {
+ for f in a.all_fields() {
+ let ty = f.ty(cx.tcx, b);
+ if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
+ return true;
+ }
+ }
+
+ for generic_arg in b.iter() {
+ if let GenericArgKind::Type(ty) = generic_arg.unpack() {
+ if self.has_sig_drop_attr(cx, ty) {
+ return true;
+ }
+ }
+ }
+ false
+ },
- self.visit_expr(expr)
++ rustc_middle::ty::Array(ty, _)
++ | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
++ | rustc_middle::ty::Ref(_, ty, _)
++ | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
+ _ => false,
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) {
+ self.has_significant_drop = true;
+ return;
+ }
+ self.is_chain_end = false;
+
+ match ex.kind {
+ ExprKind::MethodCall(_, [ref expr, ..], _) => {
- ExprKind::Assign(left, right, _) => {
- self.visit_exprs_for_binary_ops(left, right, true, ex.span);
- }
- ExprKind::AssignOp(_, left, right) => {
++ self.visit_expr(expr);
+ }
+ ExprKind::Binary(_, left, right) => {
+ self.visit_exprs_for_binary_ops(left, right, false, ex.span);
+ }
++ ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => {
+ self.visit_exprs_for_binary_ops(left, right, true, ex.span);
+ }
+ ExprKind::Tup(exprs) => {
+ for expr in exprs {
+ self.visit_expr(expr);
+ if self.has_significant_drop {
+ // We may have not have set current_sig_drop if all the suggestions were
+ // MoveAndClone, so add this tuple item's full expression in that case.
+ if self.current_sig_drop.is_none() {
+ self.try_setting_current_suggestion(expr, true);
+ }
+
+ // Now we are guaranteed to have something, so add it to the final vec.
+ self.move_current_suggestion();
+ }
+ // Reset `has_significant_drop` after each tuple expression so we can look for
+ // additional cases.
+ self.has_significant_drop = false;
+ }
+ if self.sig_drop_spans.is_some() {
+ self.has_significant_drop = true;
+ }
+ }
+ ExprKind::Box(..) |
+ ExprKind::Array(..) |
+ ExprKind::Call(..) |
+ ExprKind::Unary(..) |
+ ExprKind::If(..) |
+ ExprKind::Match(..) |
+ ExprKind::Field(..) |
+ ExprKind::Index(..) |
+ ExprKind::Ret(..) |
+ ExprKind::Repeat(..) |
+ ExprKind::Yield(..) |
+ ExprKind::MethodCall(..) => walk_expr(self, ex),
+ ExprKind::AddrOf(_, _, _) |
+ ExprKind::Block(_, _) |
+ ExprKind::Break(_, _) |
+ ExprKind::Cast(_, _) |
+ // Don't want to check the closure itself, only invocation, which is covered by MethodCall
+ ExprKind::Closure(_, _, _, _, _) |
+ ExprKind::ConstBlock(_) |
+ ExprKind::Continue(_) |
+ ExprKind::DropTemps(_) |
+ ExprKind::Err |
+ ExprKind::InlineAsm(_) |
+ ExprKind::Let(_) |
+ ExprKind::Lit(_) |
+ ExprKind::Loop(_, _, _, _) |
+ ExprKind::Path(_) |
+ ExprKind::Struct(_, _, _) |
+ ExprKind::Type(_, _) => {
+ return;
+ }
+ }
+
+ // Once a significant temporary has been found, we need to go back up at least 1 level to
+ // find the span to extract for replacement, so the temporary gets dropped. However, for
+ // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to
+ // simplify cases where both sides have significant drops.
+ if self.has_significant_drop && !self.special_handling_for_binary_op {
+ self.try_setting_current_suggestion(ex, false);
+ }
+ }
+}
--- /dev/null
- use std::convert::TryFrom;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks doc comments for usage of tab characters.
+ ///
+ /// ### Why is this bad?
+ /// The rust style-guide promotes spaces instead of tabs for indentation.
+ /// To keep a consistent view on the source, also doc comments should not have tabs.
+ /// Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when the
+ /// display settings of the author and reader differ.
+ ///
+ /// ### Example
+ /// ```rust
+ /// ///
+ /// /// Struct to hold two strings:
+ /// /// - first one
+ /// /// - second one
+ /// pub struct DoubleString {
+ /// ///
+ /// /// - First String:
+ /// /// - needs to be inside here
+ /// first_string: String,
+ /// ///
+ /// /// - Second String:
+ /// /// - needs to be inside here
+ /// second_string: String,
+ ///}
+ /// ```
+ ///
+ /// Will be converted to:
+ /// ```rust
+ /// ///
+ /// /// Struct to hold two strings:
+ /// /// - first one
+ /// /// - second one
+ /// pub struct DoubleString {
+ /// ///
+ /// /// - First String:
+ /// /// - needs to be inside here
+ /// first_string: String,
+ /// ///
+ /// /// - Second String:
+ /// /// - needs to be inside here
+ /// second_string: String,
+ ///}
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub TABS_IN_DOC_COMMENTS,
+ style,
+ "using tabs in doc comments is not recommended"
+}
+
+declare_lint_pass!(TabsInDocComments => [TABS_IN_DOC_COMMENTS]);
+
+impl TabsInDocComments {
+ fn warn_if_tabs_in_doc(cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+ if let ast::AttrKind::DocComment(_, comment) = attr.kind {
+ let comment = comment.as_str();
+
+ for (lo, hi) in get_chunks_of_tabs(comment) {
+ // +3 skips the opening delimiter
+ let new_span = Span::new(
+ attr.span.lo() + BytePos(3 + lo),
+ attr.span.lo() + BytePos(3 + hi),
+ attr.span.ctxt(),
+ attr.span.parent(),
+ );
+ span_lint_and_sugg(
+ cx,
+ TABS_IN_DOC_COMMENTS,
+ new_span,
+ "using tabs in doc comments is not recommended",
+ "consider using four spaces per tab",
+ " ".repeat((hi - lo) as usize),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
+
+impl EarlyLintPass for TabsInDocComments {
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attribute: &ast::Attribute) {
+ Self::warn_if_tabs_in_doc(cx, attribute);
+ }
+}
+
+///
+/// scans the string for groups of tabs and returns the start(inclusive) and end positions
+/// (exclusive) of all groups
+/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as
+/// 012 3456 7 89
+/// ^-^ ^---^
+fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
+ let line_length_way_to_long = "doc comment longer than 2^32 chars";
+ let mut spans: Vec<(u32, u32)> = vec![];
+ let mut current_start: u32 = 0;
+
+ // tracker to decide if the last group of tabs is not closed by a non-tab character
+ let mut is_active = false;
+
+ // Note that we specifically need the char _byte_ indices here, not the positional indexes
+ // within the char array to deal with multi-byte characters properly. `char_indices` does
+ // exactly that. It provides an iterator over tuples of the form `(byte position, char)`.
+ let char_indices: Vec<_> = the_str.char_indices().collect();
+
+ if let [(_, '\t')] = char_indices.as_slice() {
+ return vec![(0, 1)];
+ }
+
+ for entry in char_indices.windows(2) {
+ match entry {
+ [(_, '\t'), (_, '\t')] => {
+ // either string starts with double tab, then we have to set it active,
+ // otherwise is_active is true anyway
+ is_active = true;
+ },
+ [(_, _), (index_b, '\t')] => {
+ // as ['\t', '\t'] is excluded, this has to be a start of a tab group,
+ // set indices accordingly
+ is_active = true;
+ current_start = u32::try_from(*index_b).unwrap();
+ },
+ [(_, '\t'), (index_b, _)] => {
+ // this now has to be an end of the group, hence we have to push a new tuple
+ is_active = false;
+ spans.push((current_start, u32::try_from(*index_b).unwrap()));
+ },
+ _ => {},
+ }
+ }
+
+ // only possible when tabs are at the end, insert last group
+ if is_active {
+ spans.push((
+ current_start,
+ u32::try_from(char_indices.last().unwrap().0 + 1).expect(line_length_way_to_long),
+ ));
+ }
+
+ spans
+}
+
+#[cfg(test)]
+mod tests_for_get_chunks_of_tabs {
+ use super::get_chunks_of_tabs;
+
+ #[test]
+ fn test_unicode_han_string() {
+ let res = get_chunks_of_tabs(" \u{4f4d}\t");
+
+ assert_eq!(res, vec![(4, 5)]);
+ }
+
+ #[test]
+ fn test_empty_string() {
+ let res = get_chunks_of_tabs("");
+
+ assert_eq!(res, vec![]);
+ }
+
+ #[test]
+ fn test_simple() {
+ let res = get_chunks_of_tabs("sd\t\t\taa");
+
+ assert_eq!(res, vec![(2, 5)]);
+ }
+
+ #[test]
+ fn test_only_t() {
+ let res = get_chunks_of_tabs("\t\t");
+
+ assert_eq!(res, vec![(0, 2)]);
+ }
+
+ #[test]
+ fn test_only_one_t() {
+ let res = get_chunks_of_tabs("\t");
+
+ assert_eq!(res, vec![(0, 1)]);
+ }
+
+ #[test]
+ fn test_double() {
+ let res = get_chunks_of_tabs("sd\tasd\t\taa");
+
+ assert_eq!(res, vec![(2, 3), (6, 8)]);
+ }
+
+ #[test]
+ fn test_start() {
+ let res = get_chunks_of_tabs("\t\taa");
+
+ assert_eq!(res, vec![(0, 2)]);
+ }
+
+ #[test]
+ fn test_end() {
+ let res = get_chunks_of_tabs("aa\t\t");
+
+ assert_eq!(res, vec![(2, 4)]);
+ }
+
+ #[test]
+ fn test_start_single() {
+ let res = get_chunks_of_tabs("\taa");
+
+ assert_eq!(res, vec![(0, 1)]);
+ }
+
+ #[test]
+ fn test_end_single() {
+ let res = get_chunks_of_tabs("aa\t");
+
+ assert_eq!(res, vec![(2, 3)]);
+ }
+
+ #[test]
+ fn test_no_tabs() {
+ let res = get_chunks_of_tabs("dsfs");
+
+ assert_eq!(res, vec![]);
+ }
+}
--- /dev/null
- /// It's basically guaranteed to be undefined behaviour.
+mod crosspointer_transmute;
+mod transmute_float_to_int;
+mod transmute_int_to_bool;
+mod transmute_int_to_char;
+mod transmute_int_to_float;
+mod transmute_num_to_bytes;
+mod transmute_ptr_to_ptr;
+mod transmute_ptr_to_ref;
+mod transmute_ref_to_ref;
+mod transmute_undefined_repr;
+mod transmutes_expressible_as_ptr_casts;
+mod unsound_collection_transmute;
+mod useless_transmute;
+mod utils;
+mod wrong_transmute;
+
+use clippy_utils::in_constant;
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes that can't ever be correct on any
+ /// architecture.
+ ///
+ /// ### Why is this bad?
- "transmutes that are confusing at best, undefined behaviour at worst and always useless"
++ /// It's basically guaranteed to be undefined behavior.
+ ///
+ /// ### Known problems
+ /// When accessing C, users might want to store pointer
+ /// sized objects in `extradata` arguments to save an allocation.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let ptr: *const T = core::intrinsics::transmute('x')
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRONG_TRANSMUTE,
+ correctness,
++ "transmutes that are confusing at best, undefined behavior at worst and always useless"
+}
+
+// FIXME: Move this to `complexity` again, after #5343 is fixed
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes to the original type of the object
+ /// and transmutes that could be a cast.
+ ///
+ /// ### Why is this bad?
+ /// Readability. The code tricks people into thinking that
+ /// something complex is going on.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_TRANSMUTE,
+ nursery,
+ "transmutes that have the same to and from types or could be a cast/coercion"
+}
+
+// FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery.
+declare_clippy_lint! {
+ /// ### What it does
+ ///Checks for transmutes that could be a pointer cast.
+ ///
+ /// ### Why is this bad?
+ /// Readability. The code tricks people into thinking that
+ /// something complex is going on.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # let p: *const [i32] = &[];
+ /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let p: *const [i32] = &[];
+ /// p as *const [u16];
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ complexity,
+ "transmutes that could be a pointer cast"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes between a type `T` and `*T`.
+ ///
+ /// ### Why is this bad?
+ /// It's easy to mistakenly transmute between a type and a
+ /// pointer to that type.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// core::intrinsics::transmute(t) // where the result type is the same as
+ /// // `*t` or `&t`'s
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CROSSPOINTER_TRANSMUTE,
+ complexity,
+ "transmutes that have to or from types that are a pointer to the other"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a pointer to a reference.
+ ///
+ /// ### Why is this bad?
+ /// This can always be rewritten with `&` and `*`.
+ ///
+ /// ### Known problems
+ /// - `mem::transmute` in statics and constants is stable from Rust 1.46.0,
+ /// while dereferencing raw pointer is not stable yet.
+ /// If you need to do this in those places,
+ /// you would have to use `transmute` instead.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// unsafe {
+ /// let _: &T = std::mem::transmute(p); // where p: *const T
+ /// }
+ ///
+ /// // can be written:
+ /// let _: &T = &*p;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_PTR_TO_REF,
+ complexity,
+ "transmutes from a pointer to a reference type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a `char`.
+ ///
+ /// ### Why is this bad?
+ /// Not every integer is a Unicode scalar value.
+ ///
+ /// ### Known problems
+ /// - [`from_u32`] which this lint suggests using is slower than `transmute`
+ /// as it needs to validate the input.
+ /// If you are certain that the input is always a valid Unicode scalar value,
+ /// use [`from_u32_unchecked`] which is as fast as `transmute`
+ /// but has a semantically meaningful name.
+ /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`.
+ ///
+ /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html
+ /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1_u32;
+ /// unsafe {
+ /// let _: char = std::mem::transmute(x); // where x: u32
+ /// }
+ ///
+ /// // should be:
+ /// let _ = std::char::from_u32(x).unwrap();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_CHAR,
+ complexity,
+ "transmutes from an integer to a `char`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a `&[u8]` to a `&str`.
+ ///
+ /// ### Why is this bad?
+ /// Not every byte slice is a valid UTF-8 string.
+ ///
+ /// ### Known problems
+ /// - [`from_utf8`] which this lint suggests using is slower than `transmute`
+ /// as it needs to validate the input.
+ /// If you are certain that the input is always a valid UTF-8,
+ /// use [`from_utf8_unchecked`] which is as fast as `transmute`
+ /// but has a semantically meaningful name.
+ /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`.
+ ///
+ /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html
+ /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
+ ///
+ /// ### Example
+ /// ```rust
+ /// let b: &[u8] = &[1_u8, 2_u8];
+ /// unsafe {
+ /// let _: &str = std::mem::transmute(b); // where b: &[u8]
+ /// }
+ ///
+ /// // should be:
+ /// let _ = std::str::from_utf8(b).unwrap();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_BYTES_TO_STR,
+ complexity,
+ "transmutes from a `&[u8]` to a `&str`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a `bool`.
+ ///
+ /// ### Why is this bad?
+ /// This might result in an invalid in-memory representation of a `bool`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1_u8;
+ /// unsafe {
+ /// let _: bool = std::mem::transmute(x); // where x: u8
+ /// }
+ ///
+ /// // should be:
+ /// let _: bool = x != 0;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_BOOL,
+ complexity,
+ "transmutes from an integer to a `bool`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a float.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive
+ /// and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let _: f32 = std::mem::transmute(1_u32); // where x: u32
+ /// }
+ ///
+ /// // should be:
+ /// let _: f32 = f32::from_bits(1_u32);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_FLOAT,
+ complexity,
+ "transmutes from an integer to a float"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a float to an integer.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive
+ /// and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let _: u32 = std::mem::transmute(1f32);
+ /// }
+ ///
+ /// // should be:
+ /// let _: u32 = 1f32.to_bits();
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub TRANSMUTE_FLOAT_TO_INT,
+ complexity,
+ "transmutes from a float to an integer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a number to an array of `u8`
+ ///
+ /// ### Why this is bad?
+ /// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
+ /// is intuitive and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let x: [u8; 8] = std::mem::transmute(1i64);
+ /// }
+ ///
+ /// // should be
+ /// let x: [u8; 8] = 0i64.to_ne_bytes();
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub TRANSMUTE_NUM_TO_BYTES,
+ complexity,
+ "transmutes from a number to an array of `u8`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a pointer to a pointer, or
+ /// from a reference to a reference.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous, and these can instead be
+ /// written as casts.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let ptr = &1u32 as *const u32;
+ /// unsafe {
+ /// // pointer-to-pointer transmute
+ /// let _: *const f32 = std::mem::transmute(ptr);
+ /// // ref-ref transmute
+ /// let _: &f32 = std::mem::transmute(&1u32);
+ /// }
+ /// // These can be respectively written:
+ /// let _ = ptr as *const f32;
+ /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_PTR_TO_PTR,
+ pedantic,
+ "transmutes from a pointer to a pointer / a reference to a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes between collections whose
+ /// types have different ABI, size or alignment.
+ ///
+ /// ### Why is this bad?
+ /// This is undefined behavior.
+ ///
+ /// ### Known problems
+ /// Currently, we cannot know whether a type is a
+ /// collection, so we just lint the ones that come with `std`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // different size, therefore likely out-of-bounds memory access
+ /// // You absolutely do not want this in your code!
+ /// unsafe {
+ /// std::mem::transmute::<_, Vec<u32>>(vec![2_u16])
+ /// };
+ /// ```
+ ///
+ /// You must always iterate, map and collect the values:
+ ///
+ /// ```rust
+ /// vec![2_u16].into_iter().map(u32::from).collect::<Vec<_>>();
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub UNSOUND_COLLECTION_TRANSMUTE,
+ correctness,
+ "transmute between collections of layout-incompatible types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes between types which do not have a representation defined relative to
+ /// each other.
+ ///
+ /// ### Why is this bad?
+ /// The results of such a transmute are not defined.
+ ///
+ /// ### Known problems
+ /// This lint has had multiple problems in the past and was moved to `nursery`. See issue
+ /// [#8496](https://github.com/rust-lang/rust-clippy/issues/8496) for more details.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo<T>(u32, T);
+ /// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// struct Foo<T>(u32, T);
+ /// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub TRANSMUTE_UNDEFINED_REPR,
+ nursery,
+ "transmute to or from a type with an undefined representation"
+}
+
+declare_lint_pass!(Transmute => [
+ CROSSPOINTER_TRANSMUTE,
+ TRANSMUTE_PTR_TO_REF,
+ TRANSMUTE_PTR_TO_PTR,
+ USELESS_TRANSMUTE,
+ WRONG_TRANSMUTE,
+ TRANSMUTE_INT_TO_CHAR,
+ TRANSMUTE_BYTES_TO_STR,
+ TRANSMUTE_INT_TO_BOOL,
+ TRANSMUTE_INT_TO_FLOAT,
+ TRANSMUTE_FLOAT_TO_INT,
+ TRANSMUTE_NUM_TO_BYTES,
+ UNSOUND_COLLECTION_TRANSMUTE,
+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ TRANSMUTE_UNDEFINED_REPR,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Transmute {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(path_expr, [arg]) = e.kind;
+ if let ExprKind::Path(ref qpath) = path_expr.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
+ if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
+ then {
+ // Avoid suggesting non-const operations in const contexts:
+ // - from/to bits (https://github.com/rust-lang/rust/issues/73736)
+ // - dereferencing raw pointers (https://github.com/rust-lang/rust/issues/51911)
+ // - char conversions (https://github.com/rust-lang/rust/issues/89259)
+ let const_context = in_constant(cx, e.hir_id);
+
+ let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
+ // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them.
+ let to_ty = cx.typeck_results().expr_ty(e);
+
+ // If useless_transmute is triggered, the other lints can be skipped.
+ if useless_transmute::check(cx, e, from_ty, to_ty, arg) {
+ return;
+ }
+
+ let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
+ | crosspointer_transmute::check(cx, e, from_ty, to_ty)
+ | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
+ | transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
+ | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
+ | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
+ | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
+ | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
+ | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
+ | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
+ | (
+ unsound_collection_transmute::check(cx, e, from_ty, to_ty)
+ || transmute_undefined_repr::check(cx, e, from_ty, to_ty)
+ );
+
+ if !linted {
+ transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
+ }
+ }
+ }
+ }
+}
--- /dev/null
- // Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
- if matches!(ty.kind, TyKind::TraitObject(..)) {
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::{path_def_id, qpath_generic_tys};
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
++use rustc_typeck::hir_ty_to_ty;
+
+use super::{utils, REDUNDANT_ALLOCATION};
+
+pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
+ let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() {
+ "Box"
+ } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
+ "Rc"
+ } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) {
+ "Arc"
+ } else {
+ return false;
+ };
+
+ if let Some(span) = utils::match_borrows_parameter(cx, qpath) {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let generic_snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{}<{}>`", outer_sym, generic_snippet),
+ |diag| {
+ diag.span_suggestion(hir_ty.span, "try", format!("{}", generic_snippet), applicability);
+ diag.note(&format!(
+ "`{generic}` is already a pointer, `{outer}<{generic}>` allocates a pointer on the heap",
+ outer = outer_sym,
+ generic = generic_snippet
+ ));
+ },
+ );
+ return true;
+ }
+
+ let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
+ let Some(id) = path_def_id(cx, ty) else { return false };
+ let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) {
+ Some(sym::Arc) => ("Arc", ty),
+ Some(sym::Rc) => ("Rc", ty),
+ _ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty),
+ _ => return false,
+ };
+
+ let inner_qpath = match &ty.kind {
+ TyKind::Path(inner_qpath) => inner_qpath,
+ _ => return false,
+ };
+ let inner_span = match qpath_generic_tys(inner_qpath).next() {
+ Some(ty) => {
++ // Reallocation of a fat pointer causes it to become thin. `hir_ty_to_ty` is safe to use
++ // here because `mod.rs` guarantees this lint is only run on types outside of bodies and
++ // is not run on locals.
++ if !hir_ty_to_ty(cx.tcx, ty).is_sized(cx.tcx.at(ty.span), cx.param_env) {
+ return false;
+ }
+ ty.span
+ },
+ None => return false,
+ };
+ if inner_sym == outer_sym {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let generic_snippet = snippet_with_applicability(cx, inner_span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{}<{}<{}>>`", outer_sym, inner_sym, generic_snippet),
+ |diag| {
+ diag.span_suggestion(
+ hir_ty.span,
+ "try",
+ format!("{}<{}>", outer_sym, generic_snippet),
+ applicability,
+ );
+ diag.note(&format!(
+ "`{inner}<{generic}>` is already on the heap, `{outer}<{inner}<{generic}>>` makes an extra allocation",
+ outer = outer_sym,
+ inner = inner_sym,
+ generic = generic_snippet
+ ));
+ },
+ );
+ } else {
+ let generic_snippet = snippet(cx, inner_span, "..");
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
+ &format!("usage of `{}<{}<{}>>`", outer_sym, inner_sym, generic_snippet),
+ |diag| {
+ diag.note(&format!(
+ "`{inner}<{generic}>` is already on the heap, `{outer}<{inner}<{generic}>>` makes an extra allocation",
+ outer = outer_sym,
+ inner = inner_sym,
+ generic = generic_snippet
+ ));
+ diag.help(&format!(
+ "consider using just `{outer}<{generic}>` or `{inner}<{generic}>`",
+ outer = outer_sym,
+ inner = inner_sym,
+ generic = generic_snippet
+ ));
+ },
+ );
+ }
+ true
+}
--- /dev/null
- use clippy_utils::is_lint_allowed;
+use clippy_utils::diagnostics::span_lint_and_help;
- use rustc_hir::{Block, BlockCheckMode, UnsafeSource};
+use clippy_utils::source::walk_span_to_context;
++use clippy_utils::{get_parent_node, is_lint_allowed};
+use rustc_data_structures::sync::Lrc;
- use rustc_span::{BytePos, Pos, SyntaxContext};
++use rustc_hir as hir;
++use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- /// Checks for `unsafe` blocks without a `// SAFETY: ` comment
++use rustc_span::{BytePos, Pos, Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
- /// Undocumented unsafe blocks can make it difficult to
++ /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
+ /// explaining why the unsafe operations performed inside
+ /// the block are safe.
+ ///
+ /// Note the comment must appear on the line(s) preceding the unsafe block
+ /// with nothing appearing in between. The following is ok:
+ /// ```ignore
+ /// foo(
+ /// // SAFETY:
+ /// // This is a valid safety comment
+ /// unsafe { *x }
+ /// )
+ /// ```
+ /// But neither of these are:
+ /// ```ignore
+ /// // SAFETY:
+ /// // This is not a valid safety comment
+ /// foo(
+ /// /* SAFETY: Neither is this */ unsafe { *x },
+ /// );
+ /// ```
+ ///
+ /// ### Why is this bad?
- && !is_unsafe_from_proc_macro(cx, block)
++ /// Undocumented unsafe blocks and impls can make it difficult to
+ /// read and maintain code, as well as uncover unsoundness
+ /// and bugs.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// // SAFETY: references are guaranteed to be non-null.
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNDOCUMENTED_UNSAFE_BLOCKS,
+ restriction,
+ "creating an unsafe block without explaining why it is safe"
+}
+
+declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
+
+impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
+ fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
+ if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
+ && !in_external_macro(cx.tcx.sess, block.span)
+ && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
- fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
++ && !is_unsafe_from_proc_macro(cx, block.span)
+ && !block_has_safety_comment(cx, block)
+ {
+ let source_map = cx.tcx.sess.source_map();
+ let span = if source_map.is_multiline(block.span) {
+ source_map.span_until_char(block.span, '\n')
+ } else {
+ block.span
+ };
+
+ span_lint_and_help(
+ cx,
+ UNDOCUMENTED_UNSAFE_BLOCKS,
+ span,
+ "unsafe block missing a safety comment",
+ None,
+ "consider adding a safety comment on the preceding line",
+ );
+ }
+ }
++
++ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
++ if let hir::ItemKind::Impl(imple) = item.kind
++ && imple.unsafety == hir::Unsafety::Unsafe
++ && !in_external_macro(cx.tcx.sess, item.span)
++ && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
++ && !is_unsafe_from_proc_macro(cx, item.span)
++ && !item_has_safety_comment(cx, item)
++ {
++ let source_map = cx.tcx.sess.source_map();
++ let span = if source_map.is_multiline(item.span) {
++ source_map.span_until_char(item.span, '\n')
++ } else {
++ item.span
++ };
++
++ span_lint_and_help(
++ cx,
++ UNDOCUMENTED_UNSAFE_BLOCKS,
++ span,
++ "unsafe impl missing a safety comment",
++ None,
++ "consider adding a safety comment on the preceding line",
++ );
++ }
++ }
+}
+
- let file_pos = source_map.lookup_byte_offset(block.span.lo());
++fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
+ let source_map = cx.sess().source_map();
- fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
++ let file_pos = source_map.lookup_byte_offset(span.lo());
+ file_pos
+ .sf
+ .src
+ .as_deref()
+ .and_then(|src| src.get(file_pos.pos.to_usize()..))
+ .map_or(true, |src| !src.starts_with("unsafe"))
+}
+
+/// Checks if the lines immediately preceding the block contain a safety comment.
- let ctxt = block.span.ctxt();
- if ctxt != SyntaxContext::root() {
- // From a macro expansion. Get the text from the start of the macro declaration to start of the unsafe block.
++fn block_has_safety_comment(cx: &LateContext<'_>, block: &hir::Block<'_>) -> bool {
+ // This intentionally ignores text before the start of a function so something like:
+ // ```
+ // // SAFETY: reason
+ // fn foo() { unsafe { .. } }
+ // ```
+ // won't work. This is to avoid dealing with where such a comment should be place relative to
+ // attributes and doc comments.
+
++ span_from_macro_expansion_has_safety_comment(cx, block.span) || span_in_body_has_safety_comment(cx, block.span)
++}
++
++/// Checks if the lines immediately preceding the item contain a safety comment.
++#[allow(clippy::collapsible_match)]
++fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> bool {
++ if span_from_macro_expansion_has_safety_comment(cx, item.span) {
++ return true;
++ }
++
++ if item.span.ctxt() == SyntaxContext::root() {
++ if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
++ let comment_start = match parent_node {
++ Node::Crate(parent_mod) => {
++ comment_start_before_impl_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
++ },
++ Node::Item(parent_item) => {
++ if let ItemKind::Mod(parent_mod) = &parent_item.kind {
++ comment_start_before_impl_in_mod(cx, parent_mod, parent_item.span, item)
++ } else {
++ // Doesn't support impls in this position. Pretend a comment was found.
++ return true;
++ }
++ },
++ Node::Stmt(stmt) => {
++ if let Some(stmt_parent) = get_parent_node(cx.tcx, stmt.hir_id) {
++ match stmt_parent {
++ Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
++ _ => {
++ // Doesn't support impls in this position. Pretend a comment was found.
++ return true;
++ },
++ }
++ } else {
++ // Problem getting the parent node. Pretend a comment was found.
++ return true;
++ }
++ },
++ _ => {
++ // Doesn't support impls in this position. Pretend a comment was found.
++ return true;
++ },
++ };
++
++ let source_map = cx.sess().source_map();
++ if let Some(comment_start) = comment_start
++ && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
++ && let Ok(comment_start_line) = source_map.lookup_line(comment_start)
++ && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
++ && let Some(src) = unsafe_line.sf.src.as_deref()
++ {
++ comment_start_line.line < unsafe_line.line && text_has_safety_comment(
++ src,
++ &unsafe_line.sf.lines[comment_start_line.line + 1..=unsafe_line.line],
++ unsafe_line.sf.start_pos.to_usize(),
++ )
++ } else {
++ // Problem getting source text. Pretend a comment was found.
++ true
++ }
++ } else {
++ // No parent node. Pretend a comment was found.
++ true
++ }
++ } else {
++ false
++ }
++}
++
++fn comment_start_before_impl_in_mod(
++ cx: &LateContext<'_>,
++ parent_mod: &hir::Mod<'_>,
++ parent_mod_span: Span,
++ imple: &hir::Item<'_>,
++) -> Option<BytePos> {
++ parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
++ if *item_id == imple.item_id() {
++ if idx == 0 {
++ // mod A { /* comment */ unsafe impl T {} ... }
++ // ^------------------------------------------^ returns the start of this span
++ // ^---------------------^ finally checks comments in this range
++ if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
++ return Some(sp.lo());
++ }
++ } else {
++ // some_item /* comment */ unsafe impl T {}
++ // ^-------^ returns the end of this span
++ // ^---------------^ finally checks comments in this range
++ let prev_item = cx.tcx.hir().item(parent_mod.item_ids[idx - 1]);
++ if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
++ return Some(sp.hi());
++ }
++ }
++ }
++ None
++ })
++}
++
++fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
+ let source_map = cx.sess().source_map();
- if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
++ let ctxt = span.ctxt();
++ if ctxt == SyntaxContext::root() {
++ false
++ } else {
++ // From a macro expansion. Get the text from the start of the macro declaration to start of the
++ // unsafe block.
+ // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
+ // ^--------------------------------------------^
- } else if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
++ if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
+ && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo())
+ && Lrc::ptr_eq(&unsafe_line.sf, ¯o_line.sf)
+ && let Some(src) = unsafe_line.sf.src.as_deref()
+ {
+ macro_line.line < unsafe_line.line && text_has_safety_comment(
+ src,
+ &unsafe_line.sf.lines[macro_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos.to_usize(),
+ )
+ } else {
+ // Problem getting source text. Pretend a comment was found.
+ true
+ }
- && let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
- && let Ok(body_line) = source_map.lookup_line(body_span.lo())
- && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
- && let Some(src) = unsafe_line.sf.src.as_deref()
++ }
++}
++
++fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
++ let source_map = cx.sess().source_map();
++ let ctxt = span.ctxt();
++ if ctxt == SyntaxContext::root()
+ && let Some(body) = cx.enclosing_body
- // Get the text from the start of function body to the unsafe block.
- // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
- // ^-------------^
- body_line.line < unsafe_line.line && text_has_safety_comment(
- src,
- &unsafe_line.sf.lines[body_line.line + 1..=unsafe_line.line],
- unsafe_line.sf.start_pos.to_usize(),
- )
+ {
- // Problem getting source text. Pretend a comment was found.
- true
++ if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
++ && let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
++ && let Ok(body_line) = source_map.lookup_line(body_span.lo())
++ && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
++ && let Some(src) = unsafe_line.sf.src.as_deref()
++ {
++ // Get the text from the start of function body to the unsafe block.
++ // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
++ // ^-------------^
++ body_line.line < unsafe_line.line && text_has_safety_comment(
++ src,
++ &unsafe_line.sf.lines[body_line.line + 1..=unsafe_line.line],
++ unsafe_line.sf.start_pos.to_usize(),
++ )
++ } else {
++ // Problem getting source text. Pretend a comment was found.
++ true
++ }
+ } else {
++ false
+ }
+}
+
+/// Checks if the given text has a safety comment for the immediately proceeding line.
+fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> bool {
+ let mut lines = line_starts
+ .array_windows::<2>()
+ .rev()
+ .map_while(|[start, end]| {
+ let start = start.to_usize() - offset;
+ let end = end.to_usize() - offset;
+ src.get(start..end).map(|text| (start, text.trim_start()))
+ })
+ .filter(|(_, text)| !text.is_empty());
+
+ let Some((line_start, line)) = lines.next() else {
+ return false;
+ };
+ // Check for a sequence of line comments.
+ if line.starts_with("//") {
+ let mut line = line;
+ loop {
+ if line.to_ascii_uppercase().contains("SAFETY:") {
+ return true;
+ }
+ match lines.next() {
+ Some((_, x)) if x.starts_with("//") => line = x,
+ _ => return false,
+ }
+ }
+ }
+ // No line comments; look for the start of a block comment.
+ // This will only find them if they are at the start of a line.
+ let (mut line_start, mut line) = (line_start, line);
+ loop {
+ if line.starts_with("/*") {
+ let src = src[line_start..line_starts.last().unwrap().to_usize() - offset].trim_start();
+ let mut tokens = tokenize(src);
+ return src[..tokens.next().unwrap().len]
+ .to_ascii_uppercase()
+ .contains("SAFETY:")
+ && tokens.all(|t| t.kind == TokenKind::Whitespace);
+ }
+ match lines.next() {
+ Some(x) => (line_start, line) = x,
+ None => return false,
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::collapsible_span_lint_calls)]
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
+use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
+use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
+use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
+ /// This is commonly caused by calling `set_len()` right after allocating or
+ /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
+ ///
+ /// ### Why is this bad?
+ /// It creates a `Vec` with uninitialized data, which leads to
+ /// undefined behavior with most safe operations. Notably, uninitialized
+ /// `Vec<u8>` must not be used with generic `Read`.
+ ///
+ /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
+ /// creates out-of-bound values that lead to heap memory corruption when used.
+ ///
+ /// ### Known Problems
+ /// This lint only checks directly adjacent statements.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// unsafe { vec.set_len(1000); }
+ /// reader.read(&mut vec); // undefined behavior!
+ /// ```
+ ///
+ /// ### How to fix?
+ /// 1. Use an initialized buffer:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = vec![0; 1000];
+ /// reader.read(&mut vec);
+ /// ```
+ /// 2. Wrap the content in `MaybeUninit`:
+ /// ```rust,ignore
+ /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
+ /// vec.set_len(1000); // `MaybeUninit` can be uninitialized
+ /// ```
+ /// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
+ /// // perform initialization with `remaining`
+ /// vec.set_len(...); // Safe to call `set_len()` on initialized part
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNINIT_VEC,
+ correctness,
+ "Vec with uninitialized data"
+}
+
+declare_lint_pass!(UninitVec => [UNINIT_VEC]);
+
+// FIXME: update to a visitor-based implementation.
+// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
+impl<'tcx> LateLintPass<'tcx> for UninitVec {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ if !in_external_macro(cx.tcx.sess, block.span) {
+ for w in block.stmts.windows(2) {
+ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
+ handle_uninit_vec_pair(cx, &w[0], expr);
+ }
+ }
+
+ if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
+ handle_uninit_vec_pair(cx, stmt, expr);
+ }
+ }
+ }
+}
+
+fn handle_uninit_vec_pair<'tcx>(
+ cx: &LateContext<'tcx>,
+ maybe_init_or_reserve: &'tcx Stmt<'tcx>,
+ maybe_set_len: &'tcx Expr<'tcx>,
+) {
+ if_chain! {
+ if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
+ if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
+ if vec.location.eq_expr(cx, set_len_self);
+ if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
+ if let ty::Adt(_, substs) = vec_ty.kind();
+ // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
+ if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
+ then {
+ if vec.has_capacity() {
+ // with_capacity / reserve -> set_len
+
+ // Check T of Vec<T>
+ if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
+ // FIXME: #7698, false positive of the internal lints
++ #[expect(clippy::collapsible_span_lint_calls)]
+ span_lint_and_then(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` immediately after reserving a buffer creates uninitialized values",
+ |diag| {
+ diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
+ },
+ );
+ }
+ } else {
+ // new / default -> set_len
+ span_lint(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` on empty `Vec` creates out-of-bound values",
+ );
+ }
+ }
+ }
+}
+
+/// The target `Vec` that is initialized or reserved
+#[derive(Clone, Copy)]
+struct TargetVec<'tcx> {
+ location: VecLocation<'tcx>,
+ /// `None` if `reserve()`
+ init_kind: Option<VecInitKind>,
+}
+
+impl TargetVec<'_> {
+ pub fn has_capacity(self) -> bool {
+ !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
+ }
+}
+
+#[derive(Clone, Copy)]
+enum VecLocation<'tcx> {
+ Local(HirId),
+ Expr(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> VecLocation<'tcx> {
+ pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ match self {
+ VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
+ VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
+ }
+ }
+}
+
+/// Finds the target location where the result of `Vec` initialization is stored
+/// or `self` expression for `Vec::reserve()`.
+fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if_chain! {
+ if let Some(init_expr) = local.init;
+ if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
+ if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
+ then {
+ return Some(TargetVec {
+ location: VecLocation::Local(hir_id),
+ init_kind: Some(init_kind),
+ })
+ }
+ }
+ },
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ ExprKind::Assign(lhs, rhs, _span) => {
+ if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
+ return Some(TargetVec {
+ location: VecLocation::Expr(lhs),
+ init_kind: Some(init_kind),
+ });
+ }
+ },
+ ExprKind::MethodCall(path, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
+ return Some(TargetVec {
+ location: VecLocation::Expr(self_expr),
+ init_kind: None,
+ });
+ },
+ _ => (),
+ },
+ StmtKind::Item(_) => (),
+ }
+ None
+}
+
+fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
+ && path.ident.name.as_str() == "reserve"
+}
+
+/// Returns self if the expression is `Vec::set_len()`
+fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
+ // peel unsafe blocks in `unsafe { vec.set_len() }`
+ let expr = peel_hir_expr_while(expr, |e| {
+ if let ExprKind::Block(block, _) = e.kind {
+ // Extract the first statement/expression
+ match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
+ (None, Some(expr)) => Some(expr),
+ (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ });
+ match expr.kind {
+ ExprKind::MethodCall(path, [self_expr, _], _) => {
+ let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
+ if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
+ Some((self_expr, expr.span))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
--- /dev/null
- ) => cx.qpath_res(path, *hir_id).opt_def_id(),
- ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(e.hir_id),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::visitors::for_each_value_source;
+use core::ops::ControlFlow;
+use rustc_errors::Applicability;
++use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty, TypeFoldable, TypeVisitor};
+
+use super::LET_UNIT_VALUE;
+
+pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
+ if let StmtKind::Local(local) = stmt.kind
+ && let Some(init) = local.init
+ && !local.pat.span.from_expansion()
+ && !in_external_macro(cx.sess(), stmt.span)
+ && cx.typeck_results().pat_ty(local.pat).is_unit()
+ {
+ let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
+ ControlFlow::Continue(())
+ } else {
+ ControlFlow::Break(())
+ }).is_continue();
+
+ if needs_inferred {
+ if !matches!(local.pat.kind, PatKind::Wild) {
+ span_lint_and_then(
+ cx,
+ LET_UNIT_VALUE,
+ stmt.span,
+ "this let-binding has unit value",
+ |diag| {
+ diag.span_suggestion(
+ local.pat.span,
+ "use a wild (`_`) binding",
+ "_",
+ Applicability::MaybeIncorrect, // snippet
+ );
+ },
+ );
+ }
+ } else {
+ span_lint_and_then(
+ cx,
+ LET_UNIT_VALUE,
+ stmt.span,
+ "this let-binding has unit value",
+ |diag| {
+ if let Some(expr) = &local.init {
+ let snip = snippet_with_macro_callsite(cx, expr.span, "()");
+ diag.span_suggestion(
+ stmt.span,
+ "omit the `let` binding",
+ format!("{};", snip),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ },
+ );
+ }
+ }
+}
+
+fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let id = match e.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(ref path),
+ hir_id,
+ ..
+ },
+ _,
- if let Some(id) = id
- && let sig = cx.tcx.fn_sig(id).skip_binder()
- && let ty::Param(output_ty) = *sig.output().kind()
- {
++ ) => match cx.qpath_res(path, *hir_id) {
++ Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id,
++ _ => return false,
++ },
++ ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
++ Some(id) => id,
++ None => return false,
++ },
+ _ => return false,
+ };
++ let sig = cx.tcx.fn_sig(id).skip_binder();
++ if let ty::Param(output_ty) = *sig.output().kind() {
+ sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
+ } else {
+ false
+ }
+}
+
+fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
+ struct Visitor(u32);
+ impl<'tcx> TypeVisitor<'tcx> for Visitor {
+ type BreakTy = ();
+ fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+ if let ty::Param(ty) = *ty.kind() {
+ if ty.index == self.0 {
+ ControlFlow::BREAK
+ } else {
+ ControlFlow::CONTINUE
+ }
+ } else {
+ ty.super_visit_with(self)
+ }
+ }
+ }
+ ty.visit_with(&mut Visitor(index)).is_break()
+}
--- /dev/null
- if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
+#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use clippy_utils::ast_utils::{eq_field_pat, eq_id, eq_maybe_qself, eq_pat, eq_path};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{meets_msrv, msrvs, over};
+use rustc_ast::mut_visit::*;
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Mutability, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
+use rustc_ast_pretty::pprust;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::DUMMY_SP;
+
+use std::cell::Cell;
+use std::mem;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and
+ /// suggests replacing the pattern with a nested one, `Some(0 | 2)`.
+ ///
+ /// Another way to think of this is that it rewrites patterns in
+ /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*.
+ ///
+ /// ### Why is this bad?
+ /// In the example above, `Some` is repeated, which unnecessarily complicates the pattern.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// if let Some(0) | Some(2) = Some(0) {}
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// if let Some(0 | 2) = Some(0) {}
+ /// }
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub UNNESTED_OR_PATTERNS,
+ pedantic,
+ "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
+}
+
+#[derive(Clone, Copy)]
+pub struct UnnestedOrPatterns {
+ msrv: Option<RustcVersion>,
+}
+
+impl UnnestedOrPatterns {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]);
+
+impl EarlyLintPass for UnnestedOrPatterns {
+ fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) {
- if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
++ if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &a.pat);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
- if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
++ if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
+ if let ast::ExprKind::Let(pat, _, _) = &e.kind {
+ lint_unnested_or_patterns(cx, pat);
+ }
+ }
+ }
+
+ fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) {
- if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
++ if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &p.pat);
+ }
+ }
+
+ fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) {
++ if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &l.pat);
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) {
+ if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind {
+ // This is a leaf pattern, so cloning is unprofitable.
+ return;
+ }
+
+ let mut pat = P(pat.clone());
+
+ // Nix all the paren patterns everywhere so that they aren't in our way.
+ remove_all_parens(&mut pat);
+
+ // Transform all unnested or-patterns into nested ones, and if there were none, quit.
+ if !unnest_or_patterns(&mut pat) {
+ return;
+ }
+
+ span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| {
+ insert_necessary_parens(&mut pat);
+ db.span_suggestion_verbose(
+ pat.span,
+ "nest the patterns",
+ pprust::pat_to_string(&pat),
+ Applicability::MachineApplicable,
+ );
+ });
+}
+
+/// Remove all `(p)` patterns in `pat`.
+fn remove_all_parens(pat: &mut P<Pat>) {
+ struct Visitor;
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, pat: &mut P<Pat>) {
+ noop_visit_pat(pat, self);
+ let inner = match &mut pat.kind {
+ Paren(i) => mem::replace(&mut i.kind, Wild),
+ _ => return,
+ };
+ pat.kind = inner;
+ }
+ }
+ Visitor.visit_pat(pat);
+}
+
+/// Insert parens where necessary according to Rust's precedence rules for patterns.
+fn insert_necessary_parens(pat: &mut P<Pat>) {
+ struct Visitor;
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, pat: &mut P<Pat>) {
+ use ast::{BindingMode::*, Mutability::*};
+ noop_visit_pat(pat, self);
+ let target = match &mut pat.kind {
+ // `i @ a | b`, `box a | b`, and `& mut? a | b`.
+ Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p,
+ Ref(p, Not) if matches!(p.kind, Ident(ByValue(Mut), ..)) => p, // `&(mut x)`
+ _ => return,
+ };
+ target.kind = Paren(P(take_pat(target)));
+ }
+ }
+ Visitor.visit_pat(pat);
+}
+
+/// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`.
+/// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`.
+fn unnest_or_patterns(pat: &mut P<Pat>) -> bool {
+ struct Visitor {
+ changed: bool,
+ }
+ impl MutVisitor for Visitor {
+ fn visit_pat(&mut self, p: &mut P<Pat>) {
+ // This is a bottom up transformation, so recurse first.
+ noop_visit_pat(p, self);
+
+ // Don't have an or-pattern? Just quit early on.
+ let alternatives = match &mut p.kind {
+ Or(ps) => ps,
+ _ => return,
+ };
+
+ // Collapse or-patterns directly nested in or-patterns.
+ let mut idx = 0;
+ let mut this_level_changed = false;
+ while idx < alternatives.len() {
+ let inner = if let Or(ps) = &mut alternatives[idx].kind {
+ mem::take(ps)
+ } else {
+ idx += 1;
+ continue;
+ };
+ this_level_changed = true;
+ alternatives.splice(idx..=idx, inner);
+ }
+
+ // Focus on `p_n` and then try to transform all `p_i` where `i > n`.
+ let mut focus_idx = 0;
+ while focus_idx < alternatives.len() {
+ this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx);
+ focus_idx += 1;
+ }
+ self.changed |= this_level_changed;
+
+ // Deal with `Some(Some(0)) | Some(Some(1))`.
+ if this_level_changed {
+ noop_visit_pat(p, self);
+ }
+ }
+ }
+
+ let mut visitor = Visitor { changed: false };
+ visitor.visit_pat(pat);
+ visitor.changed
+}
+
+/// Match `$scrutinee` against `$pat` and extract `$then` from it.
+/// Panics if there is no match.
+macro_rules! always_pat {
+ ($scrutinee:expr, $pat:pat => $then:expr) => {
+ match $scrutinee {
+ $pat => $then,
+ _ => unreachable!(),
+ }
+ };
+}
+
+/// Focus on `focus_idx` in `alternatives`,
+/// attempting to extend it with elements of the same constructor `C`
+/// in `alternatives[focus_idx + 1..]`.
+fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize) -> bool {
+ // Extract the kind; we'll need to make some changes in it.
+ let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild);
+ // We'll focus on `alternatives[focus_idx]`,
+ // so we're draining from `alternatives[focus_idx + 1..]`.
+ let start = focus_idx + 1;
+
+ // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`.
+ let changed = match &mut focus_kind {
+ // These pattern forms are "leafs" and do not have sub-patterns.
+ // Therefore they are not some form of constructor `C`,
+ // with which a pattern `C(p_0)` may be formed,
+ // which we would want to join with other `C(p_j)`s.
+ Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_)
+ // Skip immutable refs, as grouping them saves few characters,
+ // and almost always requires adding parens (increasing noisiness).
+ // In the case of only two patterns, replacement adds net characters.
+ | Ref(_, Mutability::Not)
+ // Dealt with elsewhere.
+ | Or(_) | Paren(_) => false,
+ // Transform `box x | ... | box y` into `box (x | y)`.
+ //
+ // The cases below until `Slice(...)` deal with *singleton* products.
+ // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`.
+ Box(target) => extend_with_matching(
+ target, start, alternatives,
+ |k| matches!(k, Box(_)),
+ |k| always_pat!(k, Box(p) => p),
+ ),
+ // Transform `&mut x | ... | &mut y` into `&mut (x | y)`.
+ Ref(target, Mutability::Mut) => extend_with_matching(
+ target, start, alternatives,
+ |k| matches!(k, Ref(_, Mutability::Mut)),
+ |k| always_pat!(k, Ref(p, _) => p),
+ ),
+ // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`.
+ Ident(b1, i1, Some(target)) => extend_with_matching(
+ target, start, alternatives,
+ // Binding names must match.
+ |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)),
+ |k| always_pat!(k, Ident(_, _, Some(p)) => p),
+ ),
+ // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`.
+ Slice(ps1) => extend_with_matching_product(
+ ps1, start, alternatives,
+ |k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)),
+ |k| always_pat!(k, Slice(ps) => ps),
+ ),
+ // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post)`.
+ Tuple(ps1) => extend_with_matching_product(
+ ps1, start, alternatives,
+ |k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)),
+ |k| always_pat!(k, Tuple(ps) => ps),
+ ),
+ // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post)`.
+ TupleStruct(qself1, path1, ps1) => extend_with_matching_product(
+ ps1, start, alternatives,
+ |k, ps1, idx| matches!(
+ k,
+ TupleStruct(qself2, path2, ps2)
+ if eq_maybe_qself(qself1, qself2) && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx)
+ ),
+ |k| always_pat!(k, TupleStruct(_, _, ps) => ps),
+ ),
+ // Transform a record pattern `S { fp_0, ..., fp_n }`.
+ Struct(qself1, path1, fps1, rest1) => extend_with_struct_pat(qself1, path1, fps1, *rest1, start, alternatives),
+ };
+
+ alternatives[focus_idx].kind = focus_kind;
+ changed
+}
+
+/// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`.
+/// In particular, for a record pattern, the order in which the field patterns is irrelevant.
+/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
+/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
+fn extend_with_struct_pat(
+ qself1: &Option<ast::QSelf>,
+ path1: &ast::Path,
+ fps1: &mut [ast::PatField],
+ rest1: bool,
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+) -> bool {
+ (0..fps1.len()).any(|idx| {
+ let pos_in_2 = Cell::new(None); // The element `k`.
+ let tail_or = drain_matching(
+ start,
+ alternatives,
+ |k| {
+ matches!(k, Struct(qself2, path2, fps2, rest2)
+ if rest1 == *rest2 // If one struct pattern has `..` so must the other.
+ && eq_maybe_qself(qself1, qself2)
+ && eq_path(path1, path2)
+ && fps1.len() == fps2.len()
+ && fps1.iter().enumerate().all(|(idx_1, fp1)| {
+ if idx_1 == idx {
+ // In the case of `k`, we merely require identical field names
+ // so that we will transform into `ident_k: p1_k | p2_k`.
+ let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident));
+ pos_in_2.set(pos);
+ pos.is_some()
+ } else {
+ fps2.iter().any(|fp2| eq_field_pat(fp1, fp2))
+ }
+ }))
+ },
+ // Extract `p2_k`.
+ |k| always_pat!(k, Struct(_, _, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat),
+ );
+ extend_with_tail_or(&mut fps1[idx].pat, tail_or)
+ })
+}
+
+/// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`.
+/// Here, the idea is that we fixate on some `p_k` in `C`,
+/// allowing it to vary between two `targets` and `ps2` (returned by `extract`),
+/// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post),
+/// where `~` denotes semantic equality.
+fn extend_with_matching_product(
+ targets: &mut [P<Pat>],
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+ predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool,
+ extract: impl Fn(PatKind) -> Vec<P<Pat>>,
+) -> bool {
+ (0..targets.len()).any(|idx| {
+ let tail_or = drain_matching(
+ start,
+ alternatives,
+ |k| predicate(k, targets, idx),
+ |k| extract(k).swap_remove(idx),
+ );
+ extend_with_tail_or(&mut targets[idx], tail_or)
+ })
+}
+
+/// Extract the pattern from the given one and replace it with `Wild`.
+/// This is meant for temporarily swapping out the pattern for manipulation.
+fn take_pat(from: &mut Pat) -> Pat {
+ let dummy = Pat {
+ id: DUMMY_NODE_ID,
+ kind: Wild,
+ span: DUMMY_SP,
+ tokens: None,
+ };
+ mem::replace(from, dummy)
+}
+
+/// Extend `target` as an or-pattern with the alternatives
+/// in `tail_or` if there are any and return if there were.
+fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<P<Pat>>) -> bool {
+ fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) {
+ match target {
+ // On an existing or-pattern in the target, append to it.
+ Pat { kind: Or(ps), .. } => ps.append(&mut tail_or),
+ // Otherwise convert the target to an or-pattern.
+ target => {
+ let mut init_or = vec![P(take_pat(target))];
+ init_or.append(&mut tail_or);
+ target.kind = Or(init_or);
+ },
+ }
+ }
+
+ let changed = !tail_or.is_empty();
+ if changed {
+ // Extend the target.
+ extend(target, tail_or);
+ }
+ changed
+}
+
+// Extract all inner patterns in `alternatives` matching our `predicate`.
+// Only elements beginning with `start` are considered for extraction.
+fn drain_matching(
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+ predicate: impl Fn(&PatKind) -> bool,
+ extract: impl Fn(PatKind) -> P<Pat>,
+) -> Vec<P<Pat>> {
+ let mut tail_or = vec![];
+ let mut idx = 0;
+ for pat in alternatives.drain_filter(|p| {
+ // Check if we should extract, but only if `idx >= start`.
+ idx += 1;
+ idx > start && predicate(&p.kind)
+ }) {
+ tail_or.push(extract(pat.into_inner().kind));
+ }
+ tail_or
+}
+
+fn extend_with_matching(
+ target: &mut Pat,
+ start: usize,
+ alternatives: &mut Vec<P<Pat>>,
+ predicate: impl Fn(&PatKind) -> bool,
+ extract: impl Fn(PatKind) -> P<Pat>,
+) -> bool {
+ extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract))
+}
+
+/// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`?
+fn eq_pre_post(ps1: &[P<Pat>], ps2: &[P<Pat>], idx: usize) -> bool {
+ ps1.len() == ps2.len()
+ && ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
+ && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r))
+ && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r))
+}
--- /dev/null
- #[allow(clippy::cast_possible_truncation)]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{position_before_rarrow, snippet_opt};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit::FnKind;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unit (`()`) expressions that can be removed.
+ ///
+ /// ### Why is this bad?
+ /// Such expressions add no value, but can make the code
+ /// less readable. Depending on formatting they can make a `break` or `return`
+ /// statement look like a function call.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ /// is equivalent to
+ /// ```rust
+ /// fn return_unit() {}
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
+
+impl EarlyLintPass for UnusedUnit {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
+ lint_unneeded_unit_return(cx, ty, span);
+ }
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if_chain! {
+ if let Some(stmt) = block.stmts.last();
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind;
+ if is_unit_expr(expr);
+ let ctxt = block.span.ctxt();
+ if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt;
+ then {
+ let sp = expr.span;
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ sp,
+ "unneeded unit expression",
+ "remove the final `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
+ if is_unit_expr(expr) && !expr.span.from_expansion() {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ expr.span,
+ "unneeded `()`",
+ "remove the `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
+ let segments = &poly.trait_ref.path.segments;
+
+ if_chain! {
+ if segments.len() == 1;
+ if ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str());
+ if let Some(args) = &segments[0].args;
+ if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
+ if let ast::FnRetTy::Ty(ty) = &generic_args.output;
+ if ty.kind.is_unit();
+ then {
+ lint_unneeded_unit_return(cx, ty, generic_args.span);
+ }
+ }
+ }
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<Span> {
+ if span.from_expansion() {
+ Some(span.ctxt().outer_expn_data().def_site)
+ } else {
+ None
+ }
+}
+
+// is this expr a `()` unit?
+fn is_unit_expr(expr: &ast::Expr) -> bool {
+ if let ast::ExprKind::Tup(ref vals) = expr.kind {
+ vals.is_empty()
+ } else {
+ false
+ }
+}
+
+fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
+ let (ret_span, appl) =
+ snippet_opt(cx, span.with_hi(ty.span.hi())).map_or((ty.span, Applicability::MaybeIncorrect), |fn_source| {
+ position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+ (
++ #[expect(clippy::cast_possible_truncation)]
+ ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+ Applicability::MachineApplicable,
+ )
+ })
+ });
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ ret_span,
+ "unneeded unit return type",
+ "remove the `-> ()`",
+ String::new(),
+ appl,
+ );
+}
--- /dev/null
- if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::same_type_and_consts;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ self as hir,
+ def::{CtorOf, DefKind, Res},
+ def_id::LocalDefId,
+ intravisit::{walk_inf, walk_ty, Visitor},
+ Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
+ TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary repetition of structure name when a
+ /// replacement with `Self` is applicable.
+ ///
+ /// ### Why is this bad?
+ /// Unnecessary repetition. Mixed use of `Self` and struct
+ /// name
+ /// feels inconsistent.
+ ///
+ /// ### Known problems
+ /// - Unaddressed false negative in fn bodies of trait implementations
+ /// - False positive with associated types in traits (#4140)
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo;
+ /// impl Foo {
+ /// fn new() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
+ /// could be
+ /// ```rust
+ /// struct Foo;
+ /// impl Foo {
+ /// fn new() -> Self {
+ /// Self {}
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USE_SELF,
+ nursery,
+ "unnecessary structure name repetition whereas `Self` is applicable"
+}
+
+#[derive(Default)]
+pub struct UseSelf {
+ msrv: Option<RustcVersion>,
+ stack: Vec<StackItem>,
+}
+
+impl UseSelf {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Self::default()
+ }
+ }
+}
+
+#[derive(Debug)]
+enum StackItem {
+ Check {
+ impl_id: LocalDefId,
+ in_body: u32,
+ types_to_skip: FxHashSet<HirId>,
+ },
+ NoCheck,
+}
+
+impl_lint_pass!(UseSelf => [USE_SELF]);
+
+const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
+
+impl<'tcx> LateLintPass<'tcx> for UseSelf {
+ fn check_item(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
+ if matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ // skip over `ItemKind::OpaqueTy` in order to lint `foo() -> impl <..>`
+ return;
+ }
+ // 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
+ let stack_item = if_chain! {
+ if let ItemKind::Impl(Impl { self_ty, .. }) = item.kind;
+ if let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind;
+ let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
+ if parameters.as_ref().map_or(true, |params| {
+ !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
+ });
+ then {
+ StackItem::Check {
+ impl_id: item.def_id,
+ in_body: 0,
+ types_to_skip: std::iter::once(self_ty.hir_id).collect(),
+ }
+ } else {
+ StackItem::NoCheck
+ }
+ };
+ self.stack.push(stack_item);
+ }
+
+ fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
+ if !matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ self.stack.pop();
+ }
+ }
+
+ 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_id,
+ ref mut types_to_skip,
+ ..
+ }) = self.stack.last_mut();
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_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_item(impl_item.def_id)
+ .trait_item_def_id
+ .expect("impl method matches a trait method");
+ let trait_method_sig = cx.tcx.fn_sig(trait_method);
+ let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
+
+ // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the
+ // implementation of the trait.
+ let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output {
+ Some(&**ty)
+ } else {
+ None
+ };
+ let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty);
+
+ // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
+ //
+ // `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the
+ // trait declaration. This is used to check if `Self` was used in the trait
+ // declaration.
+ //
+ // If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed
+ // to `Self`), we want to skip linting that type and all subtypes of it. This
+ // avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`, in an `impl Trait
+ // for u8`, when the trait always uses `Vec<u8>`.
+ //
+ // See also https://github.com/rust-lang/rust-clippy/issues/2894.
+ for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) {
+ if trait_sem_ty.walk().any(|inner| inner == self_ty.into()) {
+ let mut visitor = SkipTyCollector::default();
+ visitor.visit_ty(impl_hir_ty);
+ types_to_skip.extend(visitor.types_to_skip);
+ }
+ }
+ }
+ }
+ }
+
+ fn check_body(&mut self, _: &LateContext<'_>, _: &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.
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_add(1);
+ }
+ }
+
+ fn check_body_post(&mut self, _: &LateContext<'_>, _: &hir::Body<'_>) {
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_sub(1);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
+ if_chain! {
+ if !hir_ty.span.from_expansion();
- if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
++ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check {
+ impl_id,
+ in_body,
+ ref types_to_skip,
+ }) = self.stack.last();
+ if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
+ if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _));
+ if !types_to_skip.contains(&hir_ty.hir_id);
+ let ty = if in_body > 0 {
+ cx.typeck_results().node_type(hir_ty.hir_id)
+ } else {
+ hir_ty_to_ty(cx.tcx, hir_ty)
+ };
+ if same_type_and_consts(ty, cx.tcx.type_of(impl_id));
+ let hir = cx.tcx.hir();
+ // prevents false positive on `#[derive(serde::Deserialize)]`
+ if !hir.span(hir.get_parent_node(hir_ty.hir_id)).in_derive_expansion();
+ then {
+ span_lint(cx, hir_ty.span);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
- if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
++ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+ if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id);
+ then {} else { return; }
+ }
+ match expr.kind {
+ ExprKind::Struct(QPath::Resolved(_, path), ..) => match path.res {
+ Res::SelfTy { .. } => (),
+ Res::Def(DefKind::Variant, _) => lint_path_to_variant(cx, path),
+ _ => span_lint(cx, path.span),
+ },
+ // tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`)
+ ExprKind::Call(fun, _) => {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind {
+ if let Res::Def(DefKind::Ctor(ctor_of, _), ..) = path.res {
+ match ctor_of {
+ CtorOf::Variant => lint_path_to_variant(cx, path),
+ CtorOf::Struct => span_lint(cx, path.span),
+ }
+ }
+ }
+ },
+ // unit enum variants (`Enum::A`)
+ ExprKind::Path(QPath::Resolved(_, path)) => lint_path_to_variant(cx, path),
+ _ => (),
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
+ if_chain! {
+ if !pat.span.from_expansion();
++ if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+ if let PatKind::Path(QPath::Resolved(_, path)) = pat.kind;
+ if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _));
+ if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id);
+ if let [first, ..] = path.segments;
+ if let Some(hir_id) = first.hir_id;
+ then {
+ span_lint(cx, cx.tcx.hir().span(hir_id));
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[derive(Default)]
+struct SkipTyCollector {
+ types_to_skip: Vec<HirId>,
+}
+
+impl<'tcx> Visitor<'tcx> for SkipTyCollector {
+ fn visit_infer(&mut self, inf: &hir::InferArg) {
+ self.types_to_skip.push(inf.hir_id);
+
+ walk_inf(self, inf);
+ }
+ fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) {
+ self.types_to_skip.push(hir_ty.hir_id);
+
+ walk_ty(self, hir_ty);
+ }
+}
+
+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,
+ );
+}
+
+fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
+ if let [.., self_seg, _variant] = path.segments {
+ let span = path
+ .span
+ .with_hi(self_seg.args().span_ext().unwrap_or(self_seg.ident.span).hi());
+ span_lint(cx, span);
+ }
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
+use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, HirId, MatchSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls
+ /// which uselessly convert to the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// // format!() returns a `String`
+ /// let s: String = format!("hello").into();
+ ///
+ /// // Good
+ /// let s: String = format!("hello");
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub USELESS_CONVERSION,
+ complexity,
+ "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type"
+}
+
+#[derive(Default)]
+pub struct UselessConversion {
+ try_desugar_arm: Vec<HirId>,
+}
+
+impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
+
++#[expect(clippy::too_many_lines)]
+impl<'tcx> LateLintPass<'tcx> for UselessConversion {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Match(_, arms, MatchSource::TryDesugar) => {
+ let e = match arms[0].body.kind {
+ ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e,
+ _ => return,
+ };
+ if let ExprKind::Call(_, args) = e.kind {
+ self.try_desugar_arm.push(args[0].hir_id);
+ }
+ },
+
+ ExprKind::MethodCall(name, .., args, _) => {
+ if is_trait_method(cx, e, sym::Into) && name.ident.as_str() == "into" {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet_with_macro_callsite(cx, args[0].span, "<expr>").to_string();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ "consider removing `.into()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if is_trait_method(cx, e, sym::IntoIterator) && name.ident.name == sym::into_iter {
+ if let Some(parent_expr) = get_parent_expr(cx, e) {
+ if let ExprKind::MethodCall(parent_name, ..) = parent_expr.kind {
+ if parent_name.ident.name != sym::into_iter {
+ return;
+ }
+ }
+ }
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet(cx, args[0].span, "<expr>").into_owned();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ "consider removing `.into_iter()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if_chain! {
+ if is_trait_method(cx, e, sym::TryInto) && name.ident.name == sym::try_into;
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ None,
+ "consider removing `.try_into()`",
+ );
+ }
+ }
+ },
+
+ ExprKind::Call(path, args) => {
+ if_chain! {
+ if args.len() == 1;
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ then {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if_chain! {
+ if match_def_path(cx, def_id, &paths::TRY_FROM);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from"));
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ None,
+ &hint,
+ );
+ }
+ }
+
+ if_chain! {
+ if match_def_path(cx, def_id, &paths::FROM_FROM);
+ if same_type_and_consts(a, b);
+
+ then {
+ let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "<expr>").maybe_par();
+ let sugg_msg =
+ format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ &sugg_msg,
+ sugg.to_string(),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ self.try_desugar_arm.pop();
+ }
+ }
+}
--- /dev/null
- use std::{env, fmt, fs, io};
+//! Read configurations files.
+
+#![allow(clippy::module_name_repetitions)]
+
+use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
+use serde::Deserialize;
+use std::error::Error;
+use std::path::{Path, PathBuf};
- pub errors: Vec<String>,
++use std::str::FromStr;
++use std::{cmp, env, fmt, fs, io, iter};
+
+/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
+#[derive(Clone, Debug, Deserialize)]
+pub struct Rename {
+ pub path: String,
+ pub rename: String,
+}
+
+/// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum DisallowedMethod {
+ Simple(String),
+ WithReason { path: String, reason: Option<String> },
+}
+
+impl DisallowedMethod {
+ pub fn path(&self) -> &str {
+ let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
+
+ path
+ }
+}
+
+/// A single disallowed type, used by the `DISALLOWED_TYPES` lint.
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum DisallowedType {
+ Simple(String),
+ WithReason { path: String, reason: Option<String> },
+}
+
+/// Conf with parse errors
+#[derive(Default)]
+pub struct TryConf {
+ pub conf: Conf,
- fn from_error(error: impl Error) -> Self {
++ pub errors: Vec<Box<dyn Error>>,
+}
+
+impl TryConf {
- errors: vec![error.to_string()],
++ fn from_error(error: impl Error + 'static) -> Self {
+ Self {
+ conf: Conf::default(),
- $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
++ errors: vec![Box::new(error)],
+ }
+ }
+}
+
++#[derive(Debug)]
++struct ConfError(String);
++
++impl fmt::Display for ConfError {
++ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
++ <String as fmt::Display>::fmt(&self.0, f)
++ }
++}
++
++impl Error for ConfError {}
++
++fn conf_error(s: String) -> Box<dyn Error> {
++ Box::new(ConfError(s))
++}
++
+macro_rules! define_Conf {
+ ($(
+ $(#[doc = $doc:literal])+
+ $(#[conf_deprecated($dep:literal)])?
+ ($name:ident: $ty:ty = $default:expr),
+ )*) => {
+ /// Clippy lint configuration
+ pub struct Conf {
+ $($(#[doc = $doc])+ pub $name: $ty,)*
+ }
+
+ mod defaults {
+ $(pub fn $name() -> $ty { $default })*
+ }
+
+ impl Default for Conf {
+ fn default() -> Self {
+ Self { $($name: defaults::$name(),)* }
+ }
+ }
+
+ impl<'de> Deserialize<'de> for TryConf {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
+ deserializer.deserialize_map(ConfVisitor)
+ }
+ }
+
+ #[derive(Deserialize)]
+ #[serde(field_identifier, rename_all = "kebab-case")]
+ #[allow(non_camel_case_types)]
+ enum Field { $($name,)* third_party, }
+
+ struct ConfVisitor;
+
+ impl<'de> Visitor<'de> for ConfVisitor {
+ type Value = TryConf;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("Conf")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
+ let mut errors = Vec::new();
+ $(let mut $name = None;)*
+ // could get `Field` here directly, but get `str` first for diagnostics
+ while let Some(name) = map.next_key::<&str>()? {
+ match Field::deserialize(name.into_deserializer())? {
+ $(Field::$name => {
- Err(e) => errors.push(e.to_string()),
++ $(errors.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)?
+ match map.next_value() {
- Some(_) => errors.push(format!("duplicate field `{}`", name)),
++ Err(e) => errors.push(conf_error(e.to_string())),
+ Ok(value) => match $name {
++ Some(_) => errors.push(conf_error(format!("duplicate field `{}`", name))),
+ None => $name = Some(value),
+ }
+ }
+ })*
+ // white-listed; ignore
+ Field::third_party => drop(map.next_value::<IgnoredAny>())
+ }
+ }
+ let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
+ Ok(TryConf { conf, errors })
+ }
+ }
+
+ #[cfg(feature = "internal")]
+ pub mod metadata {
+ use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
+
+ macro_rules! wrap_option {
+ () => (None);
+ ($x:literal) => (Some($x));
+ }
+
+ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
+ vec![
+ $(
+ {
+ let deprecation_reason = wrap_option!($($dep)?);
+
+ ClippyConfiguration::new(
+ stringify!($name),
+ stringify!($ty),
+ format!("{:?}", super::defaults::$name()),
+ concat!($($doc, '\n',)*),
+ deprecation_reason,
+ )
+ },
+ )+
+ ]
+ }
+ }
+ };
+}
+
+define_Conf! {
+ /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
+ ///
+ /// Suppress lints whenever the suggested change would cause breakage for other crates.
+ (avoid_breaking_exported_api: bool = true),
+ /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
+ ///
+ /// The minimum rust version that the project supports
+ (msrv: Option<String> = None),
+ /// Lint: BLACKLISTED_NAME.
+ ///
+ /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
+ (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
+ /// Lint: COGNITIVE_COMPLEXITY.
+ ///
+ /// The maximum cognitive complexity a function can have
+ (cognitive_complexity_threshold: u64 = 25),
+ /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
+ ///
+ /// Use the Cognitive Complexity lint instead.
+ #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
+ (cyclomatic_complexity_threshold: Option<u64> = None),
+ /// Lint: DOC_MARKDOWN.
+ ///
+ /// The list of words this lint should not consider as identifiers needing ticks
+ (doc_valid_idents: Vec<String> = [
+ "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", "FreeBSD",
+ "TeX", "LaTeX", "BibTeX", "BibLaTeX",
+ "MinGW",
+ "CamelCase",
+ ].iter().map(ToString::to_string).collect()),
+ /// Lint: TOO_MANY_ARGUMENTS.
+ ///
+ /// The maximum number of argument a function or method can have
+ (too_many_arguments_threshold: u64 = 7),
+ /// Lint: TYPE_COMPLEXITY.
+ ///
+ /// The maximum complexity a type can have
+ (type_complexity_threshold: u64 = 250),
+ /// Lint: MANY_SINGLE_CHAR_NAMES.
+ ///
+ /// The maximum number of single char bindings a scope may have
+ (single_char_binding_names_threshold: u64 = 4),
+ /// Lint: BOXED_LOCAL, USELESS_VEC.
+ ///
+ /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
+ (too_large_for_stack: u64 = 200),
+ /// Lint: ENUM_VARIANT_NAMES.
+ ///
+ /// The minimum number of enum variants for the lints about variant names to trigger
+ (enum_variant_name_threshold: u64 = 3),
+ /// Lint: LARGE_ENUM_VARIANT.
+ ///
+ /// The maximum size of an enum's variant to avoid box suggestion
+ (enum_variant_size_threshold: u64 = 200),
+ /// Lint: VERBOSE_BIT_MASK.
+ ///
+ /// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
+ (verbose_bit_mask_threshold: u64 = 1),
+ /// Lint: DECIMAL_LITERAL_REPRESENTATION.
+ ///
+ /// The lower bound for linting decimal literals
+ (literal_representation_threshold: u64 = 16384),
+ /// Lint: TRIVIALLY_COPY_PASS_BY_REF.
+ ///
+ /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
+ (trivial_copy_size_limit: Option<u64> = None),
+ /// Lint: LARGE_TYPE_PASS_BY_MOVE.
+ ///
+ /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
+ (pass_by_value_size_limit: u64 = 256),
+ /// Lint: TOO_MANY_LINES.
+ ///
+ /// The maximum number of lines a function or method can have
+ (too_many_lines_threshold: u64 = 100),
+ /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS.
+ ///
+ /// The maximum allowed size for arrays on the stack
+ (array_size_threshold: u64 = 512_000),
+ /// Lint: VEC_BOX.
+ ///
+ /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
+ (vec_box_size_threshold: u64 = 4096),
+ /// Lint: TYPE_REPETITION_IN_BOUNDS.
+ ///
+ /// The maximum number of bounds a trait can have to be linted
+ (max_trait_bounds: u64 = 3),
+ /// Lint: STRUCT_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool fields a struct can have
+ (max_struct_bools: u64 = 3),
+ /// Lint: FN_PARAMS_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool parameters a function can have
+ (max_fn_params_bools: u64 = 3),
+ /// Lint: WILDCARD_IMPORTS.
+ ///
+ /// Whether to allow certain wildcard imports (prelude, super in tests).
+ (warn_on_all_wildcard_imports: bool = false),
+ /// Lint: DISALLOWED_METHODS.
+ ///
+ /// The list of disallowed methods, written as fully qualified paths.
+ (disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()),
+ /// Lint: DISALLOWED_TYPES.
+ ///
+ /// The list of disallowed types, written as fully qualified paths.
+ (disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
+ /// Lint: UNREADABLE_LITERAL.
+ ///
+ /// Should the fraction of a decimal be linted to include separators.
+ (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: bool = false),
+ /// Lint: _CARGO_COMMON_METADATA.
+ ///
+ /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
+ (cargo_ignore_publish: bool = false),
+ /// Lint: NONSTANDARD_MACRO_BRACES.
+ ///
+ /// Enforce the named macros always use the braces specified.
+ ///
+ /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
+ /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
+ /// `crate_name::macro_name` and one with just the macro name.
+ (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
+ /// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
+ ///
+ /// The list of imports to always rename, a fully qualified path followed by the rename.
+ (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()),
+ /// Lint: DISALLOWED_SCRIPT_IDENTS.
+ ///
+ /// The list of unicode scripts allowed to be used in the scope.
+ (allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()),
+ /// Lint: NON_SEND_FIELDS_IN_SEND_TY.
+ ///
+ /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
+ (enable_raw_pointer_heuristic_for_send: bool = true),
+ /// Lint: INDEX_REFUTABLE_SLICE.
+ ///
+ /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
+ /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
+ /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
+ (max_suggested_slice_pattern_length: u64 = 3),
+ /// Lint: AWAIT_HOLDING_INVALID_TYPE
+ (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
+ /// Lint: LARGE_INCLUDE_FILE.
+ ///
+ /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
+ (max_include_file_size: u64 = 1_000_000),
++ /// Lint: EXPECT_USED.
++ ///
++ /// Whether `expect` should be allowed in test functions
++ (allow_expect_in_tests: bool = false),
++ /// Lint: UNWRAP_USED.
++ ///
++ /// Whether `unwrap` should be allowed in test functions
++ (allow_unwrap_in_tests: bool = false),
+}
+
+/// Search for the configuration file.
+pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
+ /// Possible filename to search for.
+ const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
+
+ // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
+ // If neither of those exist, use ".".
+ let mut current = env::var_os("CLIPPY_CONF_DIR")
+ .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
+ .map_or_else(|| PathBuf::from("."), PathBuf::from);
+
+ let mut found_config: Option<PathBuf> = None;
+
+ loop {
+ for config_file_name in &CONFIG_FILE_NAMES {
+ if let Ok(config_file) = current.join(config_file_name).canonicalize() {
+ match fs::metadata(&config_file) {
+ Err(e) if e.kind() == io::ErrorKind::NotFound => {},
+ Err(e) => return Err(e),
+ Ok(md) if md.is_dir() => {},
+ Ok(_) => {
+ // warn if we happen to find two config files #8323
+ if let Some(ref found_config_) = found_config {
+ eprintln!(
+ "Using config file `{}`\nWarning: `{}` will be ignored.",
+ found_config_.display(),
+ config_file.display(),
+ );
+ } else {
+ found_config = Some(config_file);
+ }
+ },
+ }
+ }
+ }
+
+ if found_config.is_some() {
+ return Ok(found_config);
+ }
+
+ // If the current directory has no parent, we're done searching.
+ if !current.pop() {
+ return Ok(None);
+ }
+ }
+}
+
+/// Read the `toml` configuration file.
+///
+/// In case of error, the function tries to continue as much as possible.
+pub fn read(path: &Path) -> TryConf {
+ let content = match fs::read_to_string(path) {
+ Err(e) => return TryConf::from_error(e),
+ Ok(content) => content,
+ };
+ toml::from_str(&content).unwrap_or_else(TryConf::from_error)
+}
++
++const SEPARATOR_WIDTH: usize = 4;
++
++// Check whether the error is "unknown field" and, if so, list the available fields sorted and at
++// least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it.
++pub fn format_error(error: Box<dyn Error>) -> String {
++ let s = error.to_string();
++
++ if_chain! {
++ if error.downcast::<toml::de::Error>().is_ok();
++ if let Some((prefix, mut fields, suffix)) = parse_unknown_field_message(&s);
++ then {
++ use fmt::Write;
++
++ fields.sort_unstable();
++
++ let (rows, column_widths) = calculate_dimensions(&fields);
++
++ let mut msg = String::from(prefix);
++ for row in 0..rows {
++ write!(msg, "\n").unwrap();
++ for (column, column_width) in column_widths.iter().copied().enumerate() {
++ let index = column * rows + row;
++ let field = fields.get(index).copied().unwrap_or_default();
++ write!(
++ msg,
++ "{:separator_width$}{:field_width$}",
++ " ",
++ field,
++ separator_width = SEPARATOR_WIDTH,
++ field_width = column_width
++ )
++ .unwrap();
++ }
++ }
++ write!(msg, "\n{}", suffix).unwrap();
++ msg
++ } else {
++ s
++ }
++ }
++}
++
++// `parse_unknown_field_message` will become unnecessary if
++// https://github.com/alexcrichton/toml-rs/pull/364 is merged.
++fn parse_unknown_field_message(s: &str) -> Option<(&str, Vec<&str>, &str)> {
++ // An "unknown field" message has the following form:
++ // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y
++ // ^^ ^^^^ ^^
++ if_chain! {
++ if s.starts_with("unknown field");
++ let slices = s.split("`, `").collect::<Vec<_>>();
++ let n = slices.len();
++ if n >= 2;
++ if let Some((prefix, first_field)) = slices[0].rsplit_once(" `");
++ if let Some((last_field, suffix)) = slices[n - 1].split_once("` ");
++ then {
++ let fields = iter::once(first_field)
++ .chain(slices[1..n - 1].iter().copied())
++ .chain(iter::once(last_field))
++ .collect::<Vec<_>>();
++ Some((prefix, fields, suffix))
++ } else {
++ None
++ }
++ }
++}
++
++fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
++ let columns = env::var("CLIPPY_TERMINAL_WIDTH")
++ .ok()
++ .and_then(|s| <usize as FromStr>::from_str(&s).ok())
++ .map_or(1, |terminal_width| {
++ let max_field_width = fields.iter().map(|field| field.len()).max().unwrap();
++ cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
++ });
++
++ let rows = (fields.len() + (columns - 1)) / columns;
++
++ let column_widths = (0..columns)
++ .map(|column| {
++ if column < columns - 1 {
++ (0..rows)
++ .map(|row| {
++ let index = column * rows + row;
++ let field = fields.get(index).copied().unwrap_or_default();
++ field.len()
++ })
++ .max()
++ .unwrap()
++ } else {
++ // Avoid adding extra space to the last column.
++ 0
++ }
++ })
++ .collect::<Vec<_>>();
++
++ (rows, column_widths)
++}
--- /dev/null
- #[allow(clippy::module_name_repetitions)]
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_copy;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
- #[allow(clippy::cast_possible_truncation)]
++#[expect(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct UselessVec {
+ pub too_large_for_stack: u64,
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `&vec![..]` when using `&[..]` would
+ /// be possible.
+ ///
+ /// ### Why is this bad?
+ /// This is less efficient.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn foo(my_vec: &[u8]) {}
+ ///
+ /// // Bad
+ /// foo(&vec![1, 2]);
+ ///
+ /// // Good
+ /// foo(&[1, 2]);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_VEC,
+ perf,
+ "useless `vec!`"
+}
+
+impl_lint_pass!(UselessVec => [USELESS_VEC]);
+
+impl<'tcx> LateLintPass<'tcx> for UselessVec {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // search for `&vec![_]` expressions where the adjusted type is `&[_]`
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
+ if let ty::Slice(..) = ty.kind();
+ if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
+ if let Some(vec_args) = higher::VecArgs::hir(cx, addressee);
+ then {
+ self.check_vec_macro(cx, &vec_args, mutability, expr.span);
+ }
+ }
+
+ // search for `for _ in vec![…]`
+ if_chain! {
+ if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr);
+ if let Some(vec_args) = higher::VecArgs::hir(cx, arg);
+ if is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(arg)));
+ then {
+ // report the error around the `vec!` not inside `<std macros>:`
+ let span = arg.span.ctxt().outer_expn_data().call_site;
+ self.check_vec_macro(cx, &vec_args, Mutability::Not, span);
+ }
+ }
+ }
+}
+
+impl UselessVec {
+ fn check_vec_macro<'tcx>(
+ self,
+ cx: &LateContext<'tcx>,
+ vec_args: &higher::VecArgs<'tcx>,
+ mutability: Mutability,
+ span: Span,
+ ) {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = match *vec_args {
+ higher::VecArgs::Repeat(elem, len) => {
+ if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
- #[allow(clippy::cast_possible_truncation)]
++ #[expect(clippy::cast_possible_truncation)]
+ if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
+ return;
+ }
+
+ match mutability {
+ Mutability::Mut => {
+ format!(
+ "&mut [{}; {}]",
+ snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
+ snippet_with_applicability(cx, len.span, "len", &mut applicability)
+ )
+ },
+ Mutability::Not => {
+ format!(
+ "&[{}; {}]",
+ snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
+ snippet_with_applicability(cx, len.span, "len", &mut applicability)
+ )
+ },
+ }
+ } else {
+ return;
+ }
+ },
+ higher::VecArgs::Vec(args) => {
+ if let Some(last) = args.iter().last() {
+ if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
+ return;
+ }
+ let span = args[0].span.to(last.span);
+
+ match mutability {
+ Mutability::Mut => {
+ format!(
+ "&mut [{}]",
+ snippet_with_applicability(cx, span, "..", &mut applicability)
+ )
+ },
+ Mutability::Not => {
+ format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
+ },
+ }
+ } else {
+ match mutability {
+ Mutability::Mut => "&mut []".into(),
+ Mutability::Not => "&[]".into(),
+ }
+ }
+ },
+ };
+
+ span_lint_and_sugg(
+ cx,
+ USELESS_VEC,
+ span,
+ "useless use of `vec!`",
+ "you can use a slice directly",
+ snippet,
+ applicability,
+ );
+ }
+}
+
+fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
+ let ty = cx.typeck_results().expr_ty_adjusted(expr);
+ cx.layout_of(ty).map_or(0, |l| l.size.bytes())
+}
+
+/// Returns the item type of the vector (i.e., the `T` in `Vec<T>`).
+fn vec_type(ty: Ty<'_>) -> Ty<'_> {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.type_at(0)
+ } else {
+ panic!("The type of `vec!` is a not a struct?");
+ }
+}
--- /dev/null
- use clippy_utils::{path_to_local, path_to_local_id};
- use if_chain::if_chain;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
+use clippy_utils::source::snippet;
- use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
++use clippy_utils::visitors::for_each_local_use_after_expr;
++use clippy_utils::{get_parent_expr, path_to_local_id};
++use core::ops::ControlFlow;
+use rustc_errors::Applicability;
- use rustc_span::Span;
++use rustc_hir::def::Res;
++use rustc_hir::{
++ BindingAnnotation, Block, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, Stmt, StmtKind, UnOp,
++};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
- lhs_is_local: bool,
- lhs_span: Span,
++use rustc_span::{Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `push` immediately after creating a new `Vec`.
+ ///
++ /// If the `Vec` is created using `with_capacity` this will only lint if the capacity is a
++ /// constant and the number of pushes is greater than or equal to the initial capacity.
++ ///
++ /// If the `Vec` is extended after the initial sequence of pushes and it was default initialized
++ /// then this will only lint after there were at least four pushes. This number may change in
++ /// the future.
++ ///
+ /// ### Why is this bad?
+ /// The `vec![]` macro is both more performant and easier to read than
+ /// multiple `push` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut v = Vec::new();
+ /// v.push(0);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let v = vec![0];
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub VEC_INIT_THEN_PUSH,
+ perf,
+ "`push` immediately after `Vec` creation"
+}
+
+impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]);
+
+#[derive(Default)]
+pub struct VecInitThenPush {
+ searcher: Option<VecPushSearcher>,
+}
+
+struct VecPushSearcher {
+ local_id: HirId,
+ init: VecInitKind,
- found: u64,
++ lhs_is_let: bool,
++ let_ty_span: Option<Span>,
++ name: Symbol,
+ err_span: Span,
- match self.init {
++ found: u128,
++ last_push_expr: HirId,
+}
+impl VecPushSearcher {
+ fn display_err(&self, cx: &LateContext<'_>) {
- VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
++ let required_pushes_before_extension = match self.init {
+ _ if self.found == 0 => return,
- _ => (),
++ VecInitKind::WithConstCapacity(x) if x > self.found => return,
++ VecInitKind::WithConstCapacity(x) => x,
+ VecInitKind::WithExprCapacity(_) => return,
- let mut s = if self.lhs_is_local {
++ _ => 3,
+ };
+
- s.push_str(&snippet(cx, self.lhs_span, ".."));
++ let mut needs_mut = false;
++ let res = for_each_local_use_after_expr(cx, self.local_id, self.last_push_expr, |e| {
++ let Some(parent) = get_parent_expr(cx, e) else {
++ return ControlFlow::Continue(())
++ };
++ let adjusted_ty = cx.typeck_results().expr_ty_adjusted(e);
++ let adjusted_mut = adjusted_ty.ref_mutability().unwrap_or(Mutability::Not);
++ needs_mut |= adjusted_mut == Mutability::Mut;
++ match parent.kind {
++ ExprKind::AddrOf(_, Mutability::Mut, _) => {
++ needs_mut = true;
++ return ControlFlow::Break(true);
++ },
++ ExprKind::Unary(UnOp::Deref, _) | ExprKind::Index(..) if !needs_mut => {
++ let mut last_place = parent;
++ while let Some(parent) = get_parent_expr(cx, parent) {
++ if matches!(parent.kind, ExprKind::Unary(UnOp::Deref, _) | ExprKind::Field(..))
++ || matches!(parent.kind, ExprKind::Index(e, _) if e.hir_id == last_place.hir_id)
++ {
++ last_place = parent;
++ } else {
++ break;
++ }
++ }
++ needs_mut |= cx.typeck_results().expr_ty_adjusted(last_place).ref_mutability()
++ == Some(Mutability::Mut)
++ || get_parent_expr(cx, last_place)
++ .map_or(false, |e| matches!(e.kind, ExprKind::AddrOf(_, Mutability::Mut, _)));
++ },
++ ExprKind::MethodCall(_, [recv, ..], _)
++ if recv.hir_id == e.hir_id
++ && adjusted_mut == Mutability::Mut
++ && !adjusted_ty.peel_refs().is_slice() =>
++ {
++ // No need to set `needs_mut` to true. The receiver will be either explicitly borrowed, or it will
++ // be implicitly borrowed via an adjustment. Both of these cases are already handled by this point.
++ return ControlFlow::Break(true);
++ },
++ ExprKind::Assign(lhs, ..) if e.hir_id == lhs.hir_id => {
++ needs_mut = true;
++ return ControlFlow::Break(false);
++ },
++ _ => (),
++ }
++ ControlFlow::Continue(())
++ });
++
++ // Avoid allocating small `Vec`s when they'll be extended right after.
++ if res == ControlFlow::Break(true) && self.found <= required_pushes_before_extension {
++ return;
++ }
++
++ let mut s = if self.lhs_is_let {
+ String::from("let ")
+ } else {
+ String::new()
+ };
- if_chain! {
- if !in_external_macro(cx.sess(), local.span);
- if let Some(init) = local.init;
- 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,
- lhs_is_local: true,
- lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)),
- err_span: local.span,
- found: 0,
- });
- }
++ if needs_mut {
++ s.push_str("mut ");
++ }
++ s.push_str(self.name.as_str());
++ if let Some(span) = self.let_ty_span {
++ s.push_str(": ");
++ s.push_str(&snippet(cx, span, "_"));
++ }
+ s.push_str(" = vec![..];");
+
+ span_lint_and_sugg(
+ cx,
+ VEC_INIT_THEN_PUSH,
+ self.err_span,
+ "calls to `push` immediately after creation",
+ "consider using the `vec![]` macro",
+ s,
+ Applicability::HasPlaceholders,
+ );
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
+ 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 self.searcher.is_none();
- if !in_external_macro(cx.sess(), expr.span);
- if let ExprKind::Assign(left, right, _) = expr.kind;
- 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,
- lhs_is_local: false,
- lhs_span: left.span,
- err_span: expr.span,
- found: 0,
- });
- }
++ if let Some(init_expr) = local.init
++ && let PatKind::Binding(BindingAnnotation::Mutable, id, name, None) = local.pat.kind
++ && !in_external_macro(cx.sess(), local.span)
++ && let Some(init) = get_vec_init_kind(cx, init_expr)
++ && !matches!(init, VecInitKind::WithExprCapacity(_))
++ {
++ self.searcher = Some(VecPushSearcher {
++ local_id: id,
++ init,
++ lhs_is_let: true,
++ name: name.name,
++ let_ty_span: local.ty.map(|ty| ty.span),
++ err_span: local.span,
++ found: 0,
++ last_push_expr: init_expr.hir_id,
++ });
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- 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";
- then {
- self.searcher = Some(VecPushSearcher {
- found: searcher.found + 1,
- err_span: searcher.err_span.to(stmt.span),
- .. searcher
- });
- } else {
- searcher.display_err(cx);
- }
++ if self.searcher.is_none()
++ && let ExprKind::Assign(left, right, _) = expr.kind
++ && let ExprKind::Path(QPath::Resolved(None, path)) = left.kind
++ && let [name] = &path.segments
++ && let Res::Local(id) = path.res
++ && !in_external_macro(cx.sess(), expr.span)
++ && let Some(init) = get_vec_init_kind(cx, right)
++ && !matches!(init, VecInitKind::WithExprCapacity(_))
++ {
++ self.searcher = Some(VecPushSearcher {
++ local_id: id,
++ init,
++ lhs_is_let: false,
++ let_ty_span: None,
++ name: name.ident.name,
++ err_span: expr.span,
++ found: 0,
++ last_push_expr: expr.hir_id,
++ });
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if let Some(searcher) = self.searcher.take() {
++ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind
++ && let ExprKind::MethodCall(name, [self_arg, _], _) = expr.kind
++ && path_to_local_id(self_arg, searcher.local_id)
++ && name.ident.as_str() == "push"
++ {
++ self.searcher = Some(VecPushSearcher {
++ found: searcher.found + 1,
++ err_span: searcher.err_span.to(stmt.span),
++ last_push_expr: expr.hir_id,
++ .. searcher
++ });
++ } else {
++ searcher.display_err(cx);
+ }
+ }
+ }
+
+ fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
+ if let Some(searcher) = self.searcher.take() {
+ searcher.display_err(cx);
+ }
+ }
+}
--- /dev/null
- // FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed
- #[allow(clippy::option_if_let_else)]
+use std::borrow::Cow;
+use std::iter;
+use std::ops::{Deref, Range};
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
+use rustc_ast::token::{self, LitKind};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_lexer::unescape::{self, EscapeError};
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_parse::parser;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{sym, BytePos, InnerSpan, Span, DUMMY_SP};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `println!("")` to
+ /// print a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `println!()`, which is simpler.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// println!("");
+ ///
+ /// // Good
+ /// println!();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINTLN_EMPTY_STRING,
+ style,
+ "using `println!(\"\")` with an empty string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `print!()` with a format
+ /// string that ends in a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `println!()` instead, which appends the
+ /// newline.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "World";
+ /// print!("Hello {}!\n", name);
+ /// ```
+ /// use println!() instead
+ /// ```rust
+ /// # let name = "World";
+ /// println!("Hello {}!", name);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_WITH_NEWLINE,
+ style,
+ "using `print!()` with a format string that ends in a single newline"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for printing on *stdout*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// People often print on *stdout* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// ### Known problems
+ /// * Only catches `print!` and `println!` calls.
+ /// * The lint level is unaffected by crate attributes. The level can still
+ /// be set for functions, modules and other items. To change the level for
+ /// the entire crate, please use command line flags. More information and a
+ /// configuration example can be found in [clippy#6610].
+ ///
+ /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("Hello world!");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_STDOUT,
+ restriction,
+ "printing on stdout"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for printing on *stderr*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// People often print on *stderr* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// ### Known problems
+ /// * Only catches `eprint!` and `eprintln!` calls.
+ /// * The lint level is unaffected by crate attributes. The level can still
+ /// be set for functions, modules and other items. To change the level for
+ /// the entire crate, please use command line flags. More information and a
+ /// configuration example can be found in [clippy#6610].
+ ///
+ /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
+ ///
+ /// ### Example
+ /// ```rust
+ /// eprintln!("Hello world!");
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub PRINT_STDERR,
+ restriction,
+ "printing on stderr"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Debug` formatting. The purpose of this
+ /// lint is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// The purpose of the `Debug` trait is to facilitate
+ /// debugging Rust code. It should not be used in user-facing output.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = "bar";
+ /// println!("{:?}", foo);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USE_DEBUG,
+ restriction,
+ "use of `Debug`-based formatting"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about the use of literals as `print!`/`println!` args.
+ ///
+ /// ### Why is this bad?
+ /// Using literals as `println!` args is inefficient
+ /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
+ /// (i.e., just put the literal in the format string)
+ ///
+ /// ### Known problems
+ /// Will also warn with macro calls as arguments that expand to literals
+ /// -- e.g., `println!("{}", env!("FOO"))`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("{}", "foo");
+ /// ```
+ /// use the literal without formatting:
+ /// ```rust
+ /// println!("foo");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_LITERAL,
+ style,
+ "printing a literal with a format string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `writeln!(buf, "")` to
+ /// print a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `writeln!(buf)`, which is simpler.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// // Bad
+ /// writeln!(buf, "");
+ ///
+ /// // Good
+ /// writeln!(buf);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITELN_EMPTY_STRING,
+ style,
+ "using `writeln!(buf, \"\")` with an empty string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `write!()` with a format
+ /// string that
+ /// ends in a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `writeln!()` instead, which appends the
+ /// newline.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// # let name = "World";
+ /// // Bad
+ /// write!(buf, "Hello {}!\n", name);
+ ///
+ /// // Good
+ /// writeln!(buf, "Hello {}!", name);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITE_WITH_NEWLINE,
+ style,
+ "using `write!()` with a format string that ends in a single newline"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about the use of literals as `write!`/`writeln!` args.
+ ///
+ /// ### Why is this bad?
+ /// Using literals as `writeln!` args is inefficient
+ /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
+ /// (i.e., just put the literal in the format string)
+ ///
+ /// ### Known problems
+ /// Will also warn with macro calls as arguments that expand to literals
+ /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// // Bad
+ /// writeln!(buf, "{}", "foo");
+ ///
+ /// // Good
+ /// writeln!(buf, "foo");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRITE_LITERAL,
+ style,
+ "writing a literal with a format string"
+}
+
+#[derive(Default)]
+pub struct Write {
+ in_debug_impl: bool,
+}
+
+impl_lint_pass!(Write => [
+ PRINT_WITH_NEWLINE,
+ PRINTLN_EMPTY_STRING,
+ PRINT_STDOUT,
+ PRINT_STDERR,
+ USE_DEBUG,
+ PRINT_LITERAL,
+ WRITE_WITH_NEWLINE,
+ WRITELN_EMPTY_STRING,
+ WRITE_LITERAL
+]);
+
+impl EarlyLintPass for Write {
+ fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
+ if let ItemKind::Impl(box Impl {
+ of_trait: Some(trait_ref),
+ ..
+ }) = &item.kind
+ {
+ let trait_name = trait_ref
+ .path
+ .segments
+ .iter()
+ .last()
+ .expect("path has at least one segment")
+ .ident
+ .name;
+ if trait_name == sym::Debug {
+ self.in_debug_impl = true;
+ }
+ }
+ }
+
+ fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) {
+ self.in_debug_impl = false;
+ }
+
+ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ fn is_build_script(cx: &EarlyContext<'_>) -> bool {
+ // Cargo sets the crate name for build scripts to `build_script_build`
+ cx.sess()
+ .opts
+ .crate_name
+ .as_ref()
+ .map_or(false, |crate_name| crate_name == "build_script_build")
+ }
+
+ if mac.path == sym!(print) {
+ if !is_build_script(cx) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
+ }
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(println) {
+ if !is_build_script(cx) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
+ }
+ self.lint_println_empty_string(cx, mac);
+ } else if mac.path == sym!(eprint) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`");
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(eprintln) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`");
+ self.lint_println_empty_string(cx, mac);
+ } else if mac.path == sym!(write) {
+ if let (Some(fmt_str), dest) = self.check_tts(cx, mac.args.inner_tokens(), true) {
+ if check_newlines(&fmt_str) {
+ let (nl_span, only_nl) = newline_span(&fmt_str);
+ let nl_span = match (dest, only_nl) {
+ // Special case of `write!(buf, "\n")`: Mark everything from the end of
+ // `buf` for removal so no trailing comma [`writeln!(buf, )`] remains.
+ (Some(dest_expr), true) => nl_span.with_lo(dest_expr.span.hi()),
+ _ => nl_span,
+ };
+ span_lint_and_then(
+ cx,
+ WRITE_WITH_NEWLINE,
+ mac.span(),
+ "using `write!()` with a format string that ends in a single newline",
+ |err| {
+ err.multipart_suggestion(
+ "use `writeln!()` instead",
+ vec![(mac.path.span, String::from("writeln")), (nl_span, String::new())],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ } else if mac.path == sym!(writeln) {
+ if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
+ if fmt_str.symbol == kw::Empty {
+ let mut applicability = Applicability::MachineApplicable;
- #[allow(clippy::too_many_lines)]
+ let suggestion = if let Some(e) = expr {
+ snippet_with_applicability(cx, e.span, "v", &mut applicability)
+ } else {
+ applicability = Applicability::HasPlaceholders;
+ Cow::Borrowed("v")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ WRITELN_EMPTY_STRING,
+ mac.span(),
+ format!("using `writeln!({}, \"\")`", suggestion).as_str(),
+ "replace it with",
+ format!("writeln!({})", suggestion),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Given a format string that ends in a newline and its span, calculates the span of the
+/// newline, or the format string itself if the format string consists solely of a newline.
+/// Return this and a boolean indicating whether it only consisted of a newline.
+fn newline_span(fmtstr: &StrLit) -> (Span, bool) {
+ let sp = fmtstr.span;
+ let contents = fmtstr.symbol.as_str();
+
+ if contents == r"\n" {
+ return (sp, true);
+ }
+
+ let newline_sp_hi = sp.hi()
+ - match fmtstr.style {
+ StrStyle::Cooked => BytePos(1),
+ StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
+ };
+
+ let newline_sp_len = if contents.ends_with('\n') {
+ BytePos(1)
+ } else if contents.ends_with(r"\n") {
+ BytePos(2)
+ } else {
+ panic!("expected format string to contain a newline");
+ };
+
+ (sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi), false)
+}
+
+/// Stores a list of replacement spans for each argument, but only if all the replacements used an
+/// empty format string.
+#[derive(Default)]
+struct SimpleFormatArgs {
+ unnamed: Vec<Vec<Span>>,
+ named: Vec<(Symbol, Vec<Span>)>,
+}
+impl SimpleFormatArgs {
+ fn get_unnamed(&self) -> impl Iterator<Item = &[Span]> {
+ self.unnamed.iter().map(|x| match x.as_slice() {
+ // Ignore the dummy span added from out of order format arguments.
+ [DUMMY_SP] => &[],
+ x => x,
+ })
+ }
+
+ fn get_named(&self, n: &Path) -> &[Span] {
+ self.named.iter().find(|x| *n == x.0).map_or(&[], |x| x.1.as_slice())
+ }
+
+ fn push(&mut self, arg: rustc_parse_format::Argument<'_>, span: Span) {
+ use rustc_parse_format::{
+ AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec,
+ };
+
+ const SIMPLE: FormatSpec<'_> = FormatSpec {
+ fill: None,
+ align: AlignUnknown,
+ flags: 0,
+ precision: CountImplied,
+ precision_span: None,
+ width: CountImplied,
+ width_span: None,
+ ty: "",
+ ty_span: None,
+ };
+
+ match arg.position {
+ ArgumentIs(n) | ArgumentImplicitlyIs(n) => {
+ if self.unnamed.len() <= n {
+ // Use a dummy span to mark all unseen arguments.
+ self.unnamed.resize_with(n, || vec![DUMMY_SP]);
+ if arg.format == SIMPLE {
+ self.unnamed.push(vec![span]);
+ } else {
+ self.unnamed.push(Vec::new());
+ }
+ } else {
+ let args = &mut self.unnamed[n];
+ match (args.as_mut_slice(), arg.format == SIMPLE) {
+ // A non-empty format string has been seen already.
+ ([], _) => (),
+ // Replace the dummy span, if it exists.
+ ([dummy @ DUMMY_SP], true) => *dummy = span,
+ ([_, ..], true) => args.push(span),
+ ([_, ..], false) => *args = Vec::new(),
+ }
+ }
+ },
+ ArgumentNamed(n, _) => {
+ let n = Symbol::intern(n);
+ if let Some(x) = self.named.iter_mut().find(|x| x.0 == n) {
+ match x.1.as_slice() {
+ // A non-empty format string has been seen already.
+ [] => (),
+ [_, ..] if arg.format == SIMPLE => x.1.push(span),
+ [_, ..] => x.1 = Vec::new(),
+ }
+ } else if arg.format == SIMPLE {
+ self.named.push((n, vec![span]));
+ } else {
+ self.named.push((n, Vec::new()));
+ }
+ },
+ };
+ }
+}
+
+impl Write {
+ /// Parses a format string into a collection of spans for each argument. This only keeps track
+ /// of empty format arguments. Will also lint usages of debug format strings outside of debug
+ /// impls.
+ fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str_lit: &StrLit) -> Option<SimpleFormatArgs> {
+ use rustc_parse_format::{ParseMode, Parser, Piece};
+
+ let str_sym = str_lit.symbol_unescaped.as_str();
+ let style = match str_lit.style {
+ StrStyle::Cooked => None,
+ StrStyle::Raw(n) => Some(n as usize),
+ };
+
+ let mut parser = Parser::new(str_sym, style, snippet_opt(cx, str_lit.span), false, ParseMode::Format);
+ let mut args = SimpleFormatArgs::default();
+
+ while let Some(arg) = parser.next() {
+ let arg = match arg {
+ Piece::String(_) => continue,
+ Piece::NextArgument(arg) => arg,
+ };
+ let span = parser
+ .arg_places
+ .last()
+ .map_or(DUMMY_SP, |&x| str_lit.span.from_inner(InnerSpan::new(x.start, x.end)));
+
+ if !self.in_debug_impl && arg.format.ty == "?" {
+ // FIXME: modify rustc's fmt string parser to give us the current span
+ span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting");
+ }
+
+ args.push(arg, span);
+ }
+
+ parser.errors.is_empty().then(move || args)
+ }
+
+ /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
+ /// `format_str` should be written to.
+ ///
+ /// Example:
+ ///
+ /// Calling this function on
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// # let something = "something";
+ /// writeln!(buf, "string to write: {}", something);
+ /// ```
+ /// will return
+ /// ```rust,ignore
+ /// (Some("string to write: {}"), Some(buf))
+ /// ```
+ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) {
+ let mut parser = parser::Parser::new(&cx.sess().parse_sess, tts, false, None);
+ let expr = if is_write {
+ match parser
+ .parse_expr()
+ .map(rustc_ast::ptr::P::into_inner)
+ .map_err(DiagnosticBuilder::cancel)
+ {
+ // write!(e, ...)
+ Ok(p) if parser.eat(&token::Comma) => Some(p),
+ // write!(e) or error
+ e => return (None, e.ok()),
+ }
+ } else {
+ None
+ };
+
+ let fmtstr = match parser.parse_str_lit() {
+ Ok(fmtstr) => fmtstr,
+ Err(_) => return (None, expr),
+ };
+
+ let args = match self.parse_fmt_string(cx, &fmtstr) {
+ Some(args) => args,
+ None => return (Some(fmtstr), expr),
+ };
+
+ let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
+ let mut unnamed_args = args.get_unnamed();
+ loop {
+ if !parser.eat(&token::Comma) {
+ return (Some(fmtstr), expr);
+ }
+
+ let comma_span = parser.prev_token.span;
+ let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) {
+ expr
+ } else {
+ return (Some(fmtstr), None);
+ };
+ let (fmt_spans, lit) = match &token_expr.kind {
+ ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit),
+ ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) {
+ (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
+ _ => continue,
+ },
+ _ => {
+ unnamed_args.next();
+ continue;
+ },
+ };
+
+ let replacement: String = match lit.token.kind {
+ LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
+ lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
+ },
+ LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
+ lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
+ },
+ LitKind::StrRaw(_)
+ | LitKind::Str
+ | LitKind::ByteStrRaw(_)
+ | LitKind::ByteStr
+ | LitKind::Integer
+ | LitKind::Float
+ | LitKind::Err => continue,
+ LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() {
+ "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
+ "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
+ "\\\\" if matches!(fmtstr.style, StrStyle::Raw(_)) => "\\",
+ "\\'" => "'",
+ "{" => "{{",
+ "}" => "}}",
+ x if matches!(fmtstr.style, StrStyle::Raw(_)) && x.starts_with('\\') => continue,
+ x => x,
+ }
+ .into(),
+ LitKind::Bool => lit.token.symbol.as_str().deref().into(),
+ };
+
+ if !fmt_spans.is_empty() {
+ span_lint_and_then(
+ cx,
+ lint,
+ token_expr.span,
+ "literal with an empty format string",
+ |diag| {
+ diag.multipart_suggestion(
+ "try this",
+ iter::once((comma_span.to(token_expr.span), String::new()))
+ .chain(fmt_spans.iter().copied().zip(iter::repeat(replacement)))
+ .collect(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
+
+ fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if fmt_str.symbol == kw::Empty {
+ let name = mac.path.segments[0].ident.name;
+ span_lint_and_sugg(
+ cx,
+ PRINTLN_EMPTY_STRING,
+ mac.span(),
+ &format!("using `{}!(\"\")`", name),
+ "replace it with",
+ format!("{}!()", name),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if check_newlines(&fmt_str) {
+ let name = mac.path.segments[0].ident.name;
+ let suggested = format!("{}ln", name);
+ span_lint_and_then(
+ cx,
+ PRINT_WITH_NEWLINE,
+ mac.span(),
+ &format!("using `{}!()` with a format string that ends in a single newline", name),
+ |err| {
+ err.multipart_suggestion(
+ &format!("use `{}!` instead", suggested),
+ vec![(mac.path.span, suggested), (newline_span(&fmt_str).0, String::new())],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+/// Checks if the format string contains a single newline that terminates it.
+///
+/// Literal and escaped newlines are both checked (only literal for raw strings).
+fn check_newlines(fmtstr: &StrLit) -> bool {
+ let mut has_internal_newline = false;
+ let mut last_was_cr = false;
+ let mut should_lint = false;
+
+ let contents = fmtstr.symbol.as_str();
+
+ let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| {
+ let c = c.unwrap();
+
+ if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline {
+ should_lint = true;
+ } else {
+ last_was_cr = c == '\r';
+ if c == '\n' {
+ has_internal_newline = true;
+ }
+ }
+ };
+
+ match fmtstr.style {
+ StrStyle::Cooked => unescape::unescape_literal(contents, unescape::Mode::Str, &mut cb),
+ StrStyle::Raw(_) => unescape::unescape_literal(contents, unescape::Mode::RawStr, &mut cb),
+ }
+
+ should_lint
+}
--- /dev/null
- version = "0.1.62"
+[package]
+name = "clippy_utils"
++version = "0.1.63"
+edition = "2021"
+publish = false
+
+[dependencies]
+arrayvec = { version = "0.7", default-features = false }
+if_chain = "1.0"
+rustc-semver = "1.1"
+
+[features]
+deny-warnings = []
+internal = []
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- #[allow(clippy::too_many_lines)] // Just a big match statement
+//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
+//!
+//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
+
+#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use crate::{both, over};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, *};
+use rustc_span::symbol::Ident;
+use std::mem;
+
+pub mod ident_iter;
+pub use ident_iter::IdentIter;
+
+pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
+ use BinOpKind::*;
+ matches!(
+ kind,
+ Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
+ )
+}
+
+/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
+pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
+}
+
+pub fn eq_id(l: Ident, r: Ident) -> bool {
+ l.name == r.name
+}
+
+pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
+ use PatKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_pat(l, r),
+ (_, Paren(r)) => eq_pat(l, r),
+ (Wild, Wild) | (Rest, Rest) => true,
+ (Lit(l), Lit(r)) => eq_expr(l, r),
+ (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
+ (Range(lf, lt, le), Range(rf, rt, re)) => {
+ eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
+ },
+ (Box(l), Box(r))
+ | (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
+ | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
+ (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => {
+ eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r))
+ },
+ (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
+ lr == rr && eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat)
+ },
+ (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
+ match (l, r) {
+ (RangeEnd::Excluded, RangeEnd::Excluded) => true,
+ (RangeEnd::Included(l), RangeEnd::Included(r)) => {
+ matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_pat(&l.pat, &r.pat)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
+ l.position == r.position && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_maybe_qself(l: &Option<QSelf>, r: &Option<QSelf>) -> bool {
+ match (l, r) {
+ (Some(l), Some(r)) => eq_qself(l, r),
+ (None, None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_path(l: &Path, r: &Path) -> bool {
+ over(&l.segments, &r.segments, eq_path_seg)
+}
+
+pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
+ eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
+}
+
+pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
+ match (l, r) {
+ (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => over(&l.args, &r.args, eq_angle_arg),
+ (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
+ over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
+ match (l, r) {
+ (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
+ (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
+ match (l, r) {
+ (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
+ (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
+ (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
+ _ => false,
+ }
+}
+
+pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
+ both(l, r, |l, r| eq_expr(l, r))
+}
+
+pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
+ match (l, r) {
+ (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
+ (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
+ use ExprKind::*;
+ if !over(&l.attrs, &r.attrs, eq_attr) {
+ return false;
+ }
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_expr(l, r),
+ (_, Paren(r)) => eq_expr(l, r),
+ (Err, Err) => true,
+ (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
+ (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
+ (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
+ (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
+ (MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
+ (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
+ (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
+ (Lit(l), Lit(r)) => l.kind == r.kind,
+ (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
+ (Let(lp, le, _), Let(rp, re, _)) => eq_pat(lp, rp) && eq_expr(le, re),
+ (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
+ (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
+ (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
+ eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
+ },
+ (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
+ (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
+ (TryBlock(l), TryBlock(r)) => eq_block(l, r),
+ (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
+ (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
+ (Continue(ll), Continue(rl)) => eq_label(ll, rl),
+ (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
+ (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
+ (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
+ (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm),
+ (Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
+ lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
+ },
+ (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
+ (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
+ (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (Struct(lse), Struct(rse)) => {
+ eq_maybe_qself(&lse.qself, &rse.qself)
+ && eq_path(&lse.path, &rse.path)
+ && eq_struct_rest(&lse.rest, &rse.rest)
+ && unordered_over(&lse.fields, &rse.fields, eq_field)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_expr(&l.expr, &r.expr)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_expr(&l.body, &r.body)
+ && eq_expr_opt(&l.guard, &r.guard)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
+ both(l, r, |l, r| eq_id(l.ident, r.ident))
+}
+
+pub fn eq_block(l: &Block, r: &Block) -> bool {
+ l.rules == r.rules && over(&l.stmts, &r.stmts, eq_stmt)
+}
+
+pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
+ use StmtKind::*;
+ match (&l.kind, &r.kind) {
+ (Local(l), Local(r)) => {
+ eq_pat(&l.pat, &r.pat)
+ && both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
+ && eq_local_kind(&l.kind, &r.kind)
+ && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ (Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
+ (Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
+ (Empty, Empty) => true,
+ (MacCall(l), MacCall(r)) => {
+ l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool {
+ use LocalKind::*;
+ match (l, r) {
+ (Decl, Decl) => true,
+ (Init(l), Init(r)) => eq_expr(l, r),
+ (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re),
+ _ => false,
+ }
+}
+
+pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
+ eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
+}
+
++#[expect(clippy::too_many_lines)] // Just a big match statement
+pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
+ use ItemKind::*;
+ match (l, r) {
+ (ExternCrate(l), ExternCrate(r)) => l == r,
+ (Use(l), Use(r)) => eq_use_tree(l, r),
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (Mod(lu, lmk), Mod(ru, rmk)) => {
+ lu == ru
+ && match (lmk, rmk) {
+ (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
+ linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
+ },
+ (ModKind::Unloaded, ModKind::Unloaded) => true,
+ _ => false,
+ }
+ },
+ (ForeignMod(l), ForeignMod(r)) => {
+ both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
+ },
+ (
+ TyAlias(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ TyAlias(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (Enum(le, lg), Enum(re, rg)) => over(&le.variants, &re.variants, eq_variant) && eq_generics(lg, rg),
+ (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
+ eq_variant_data(lv, rv) && eq_generics(lg, rg)
+ },
+ (
+ Trait(box ast::Trait {
+ is_auto: la,
+ unsafety: lu,
+ generics: lg,
+ bounds: lb,
+ items: li,
+ }),
+ Trait(box ast::Trait {
+ is_auto: ra,
+ unsafety: ru,
+ generics: rg,
+ bounds: rb,
+ items: ri,
+ }),
+ ) => {
+ la == ra
+ && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, eq_generic_bound),
+ (
+ Impl(box ast::Impl {
+ unsafety: lu,
+ polarity: lp,
+ defaultness: ld,
+ constness: lc,
+ generics: lg,
+ of_trait: lot,
+ self_ty: lst,
+ items: li,
+ }),
+ Impl(box ast::Impl {
+ unsafety: ru,
+ polarity: rp,
+ defaultness: rd,
+ constness: rc,
+ generics: rg,
+ of_trait: rot,
+ self_ty: rst,
+ items: ri,
+ }),
+ ) => {
+ matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
+ && eq_defaultness(*ld, *rd)
+ && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
+ && eq_generics(lg, rg)
+ && both(lot, rot, |l, r| eq_path(&l.path, &r.path))
+ && eq_ty(lst, rst)
+ && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
+ _ => false,
+ }
+}
+
+pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
+ use ForeignItemKind::*;
+ match (l, r) {
+ (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (
+ TyAlias(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ TyAlias(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
+ use AssocItemKind::*;
+ match (l, r) {
+ (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ (
+ Fn(box ast::Fn {
+ defaultness: ld,
+ sig: lf,
+ generics: lg,
+ body: lb,
+ }),
+ Fn(box ast::Fn {
+ defaultness: rd,
+ sig: rf,
+ generics: rg,
+ body: rb,
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (
+ TyAlias(box ast::TyAlias {
+ defaultness: ld,
+ generics: lg,
+ bounds: lb,
+ ty: lt,
+ ..
+ }),
+ TyAlias(box ast::TyAlias {
+ defaultness: rd,
+ generics: rg,
+ bounds: rb,
+ ty: rt,
+ ..
+ }),
+ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, eq_attr)
+ && eq_vis(&l.vis, &r.vis)
+ && eq_id(l.ident, r.ident)
+ && eq_variant_data(&l.data, &r.data)
+ && both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
+}
+
+pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
+ use VariantData::*;
+ match (l, r) {
+ (Unit(_), Unit(_)) => true,
+ (Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, eq_struct_field),
+ _ => false,
+ }
+}
+
+pub fn eq_struct_field(l: &FieldDef, r: &FieldDef) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, eq_attr)
+ && eq_vis(&l.vis, &r.vis)
+ && both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
+ && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
+ eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
+}
+
+pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
+ matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
+ && l.asyncness.is_async() == r.asyncness.is_async()
+ && matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
+ && eq_ext(&l.ext, &r.ext)
+}
+
+pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
+ over(&l.params, &r.params, eq_generic_param)
+ && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
+ eq_where_predicate(l, r)
+ })
+}
+
+pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
+ use WherePredicate::*;
+ match (l, r) {
+ (BoundPredicate(l), BoundPredicate(r)) => {
+ over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ }) && eq_ty(&l.bounded_ty, &r.bounded_ty)
+ && over(&l.bounds, &r.bounds, eq_generic_bound)
+ },
+ (RegionPredicate(l), RegionPredicate(r)) => {
+ eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound)
+ },
+ (EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
+ _ => false,
+ }
+}
+
+pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
+ eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
+}
+
+pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
+ eq_expr(&l.value, &r.value)
+}
+
+pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
+ use UseTreeKind::*;
+ match (l, r) {
+ (Glob, Glob) => true,
+ (Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
+ (Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
+ _ => false,
+ }
+}
+
+pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
+ matches!(
+ (l, r),
+ (Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
+ )
+}
+
+pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
+ use VisibilityKind::*;
+ match (&l.kind, &r.kind) {
+ (Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
+ (Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
+ eq_fn_ret_ty(&l.output, &r.output)
+ && over(&l.inputs, &r.inputs, |l, r| {
+ l.is_placeholder == r.is_placeholder
+ && eq_pat(&l.pat, &r.pat)
+ && eq_ty(&l.ty, &r.ty)
+ && over(&l.attrs, &r.attrs, eq_attr)
+ })
+}
+
+pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
+ match (l, r) {
+ (FnRetTy::Default(_), FnRetTy::Default(_)) => true,
+ (FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
+ use TyKind::*;
+ match (&l.kind, &r.kind) {
+ (Paren(l), _) => eq_ty(l, r),
+ (_, Paren(r)) => eq_ty(l, r),
+ (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
+ (Slice(l), Slice(r)) => eq_ty(l, r),
+ (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
+ (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
+ (Rptr(ll, l), Rptr(rl, r)) => {
+ both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
+ },
+ (BareFn(l), BareFn(r)) => {
+ l.unsafety == r.unsafety
+ && eq_ext(&l.ext, &r.ext)
+ && over(&l.generic_params, &r.generic_params, eq_generic_param)
+ && eq_fn_decl(&l.decl, &r.decl)
+ },
+ (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
+ (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp),
+ (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound),
+ (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound),
+ (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
+ use Extern::*;
+ match (l, r) {
+ (None, None) | (Implicit, Implicit) => true,
+ (Explicit(l), Explicit(r)) => eq_str_lit(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
+ l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
+}
+
+pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
+ eq_path(&l.trait_ref.path, &r.trait_ref.path)
+ && over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
+ eq_generic_param(l, r)
+ })
+}
+
+pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
+ use GenericParamKind::*;
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && over(&l.bounds, &r.bounds, eq_generic_bound)
+ && match (&l.kind, &r.kind) {
+ (Lifetime, Lifetime) => true,
+ (Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
+ (
+ Const {
+ ty: lt,
+ kw_span: _,
+ default: ld,
+ },
+ Const {
+ ty: rt,
+ kw_span: _,
+ default: rd,
+ },
+ ) => eq_ty(lt, rt) && both(ld, rd, eq_anon_const),
+ _ => false,
+ }
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
+ use GenericBound::*;
+ match (l, r) {
+ (Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
+ (Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
+ _ => false,
+ }
+}
+
+fn eq_term(l: &Term, r: &Term) -> bool {
+ match (l, r) {
+ (Term::Ty(l), Term::Ty(r)) => eq_ty(l, r),
+ (Term::Const(l), Term::Const(r)) => eq_anon_const(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_assoc_constraint(l: &AssocConstraint, r: &AssocConstraint) -> bool {
+ use AssocConstraintKind::*;
+ eq_id(l.ident, r.ident)
+ && match (&l.kind, &r.kind) {
+ (Equality { term: l }, Equality { term: r }) => eq_term(l, r),
+ (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, eq_generic_bound),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
+ eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
+}
+
+pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
+ use AttrKind::*;
+ l.style == r.style
+ && match (&l.kind, &r.kind) {
+ (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
+ (Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
+ _ => false,
+ }
+}
+
+pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
+ use MacArgs::*;
+ match (l, r) {
+ (Empty, Empty) => true,
+ (Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
+ (Eq(_, MacArgsEq::Ast(le)), Eq(_, MacArgsEq::Ast(re))) => eq_expr(le, re),
+ (Eq(_, MacArgsEq::Hir(ll)), Eq(_, MacArgsEq::Hir(rl))) => ll.kind == rl.kind,
+ _ => false,
+ }
+}
--- /dev/null
- use rustc_ast::attr;
+use rustc_ast::ast;
++use rustc_ast::attr;
+use rustc_errors::Applicability;
+use rustc_session::Session;
- #[allow(dead_code)]
+use rustc_span::sym;
+use std::str::FromStr;
+
+/// Deprecation status of attributes known by Clippy.
-
+pub enum DeprecationStatus {
+ /// Attribute is deprecated
+ Deprecated,
+ /// Attribute is deprecated and was replaced by the named attribute
+ Replaced(&'static str),
+ None,
+}
+
+#[rustfmt::skip]
+pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
+ ("author", DeprecationStatus::None),
+ ("version", DeprecationStatus::None),
+ ("cognitive_complexity", DeprecationStatus::None),
+ ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
+ ("dump", DeprecationStatus::None),
+ ("msrv", DeprecationStatus::None),
+ ("has_significant_drop", DeprecationStatus::None),
+];
+
+pub struct LimitStack {
+ stack: Vec<u64>,
+}
+
+impl Drop for LimitStack {
+ fn drop(&mut self) {
+ assert_eq!(self.stack.len(), 1);
+ }
+}
+
+impl LimitStack {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self { stack: vec![limit] }
+ }
+ pub fn limit(&self) -> u64 {
+ *self.stack.last().expect("there should always be a value in the stack")
+ }
+ pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| stack.push(val));
+ }
+ pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
+ }
+}
+
+pub fn get_attr<'a>(
+ sess: &'a Session,
+ attrs: &'a [ast::Attribute],
+ name: &'static str,
+) -> impl Iterator<Item = &'a ast::Attribute> {
+ attrs.iter().filter(move |attr| {
+ let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr
+ } else {
+ return false;
+ };
+ let attr_segments = &attr.path.segments;
+ if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
+ BUILTIN_ATTRIBUTES
+ .iter()
+ .find_map(|&(builtin_name, ref deprecation_status)| {
+ if attr_segments[1].ident.name.as_str() == builtin_name {
+ Some(deprecation_status)
+ } else {
+ None
+ }
+ })
+ .map_or_else(
+ || {
+ sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
+ false
+ },
+ |deprecation_status| {
+ let mut diag =
+ sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
+ match *deprecation_status {
+ DeprecationStatus::Deprecated => {
+ diag.emit();
+ false
+ },
+ DeprecationStatus::Replaced(new_name) => {
+ diag.span_suggestion(
+ attr_segments[1].ident.span,
+ "consider using",
+ new_name.to_string(),
+ Applicability::MachineApplicable,
+ );
+ diag.emit();
+ false
+ },
+ DeprecationStatus::None => {
+ diag.cancel();
+ attr_segments[1].ident.name.as_str() == name
+ },
+ }
+ },
+ )
+ } else {
+ false
+ }
+ })
+}
+
+fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
+ for attr in get_attr(sess, attrs, name) {
+ if let Some(ref value) = attr.value_str() {
+ if let Ok(value) = FromStr::from_str(value.as_str()) {
+ f(value);
+ } else {
+ sess.span_err(attr.span, "not a number");
+ }
+ } else {
+ sess.span_err(attr.span, "bad clippy attribute");
+ }
+ }
+}
+
+pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
+ let mut unique_attr = None;
+ for attr in get_attr(sess, attrs, name) {
+ match attr.style {
+ ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
+ ast::AttrStyle::Inner => {
+ sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
+ .span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
+ .emit();
+ },
+ ast::AttrStyle::Outer => {
+ sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
+ },
+ }
+ }
+ unique_attr
+}
+
+/// Return true if the attributes contain any of `proc_macro`,
+/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
+pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
+}
+
+/// Return true if the attributes contain `#[doc(hidden)]`
+pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
+ attrs
+ .iter()
+ .filter(|attr| attr.has_name(sym::doc))
+ .filter_map(ast::Attribute::meta_item_list)
+ .any(|l| attr::list_contains_name(&l, sym::hidden))
+}
--- /dev/null
- use std::convert::TryInto;
+#![allow(clippy::float_cmp)]
+
+use crate::{clip, is_direct_expn_of, 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, Item, ItemKind, Node, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::mir::interpret::Scalar;
+use rustc_middle::ty::subst::{Subst, SubstsRef};
+use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt};
+use rustc_middle::{bug, span_bug};
+use rustc_span::symbol::Symbol;
+use std::cmp::Ordering::{self, Equal};
- (&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))
- }
+use std::hash::{Hash, Hasher};
+use std::iter;
+
+/// 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)),
- #[allow(clippy::cast_possible_wrap)]
++ (&Self::Int(l), &Self::Int(r)) => match *cmp_type.kind() {
++ ty::Int(int_ty) => Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))),
++ ty::Uint(_) => Some(l.cmp(&r)),
++ _ => bug!("Not an int type"),
+ },
+ (&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)) => iter::zip(l, r)
+ .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,
+ }
+ }
+
+ /// Returns the integer value or `None` if `self` or `val_type` is not integer type.
+ pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option<FullInt> {
+ if let Constant::Int(const_int) = *self {
+ match *val_type.kind() {
+ ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))),
+ ty::Uint(_) => Some(FullInt::U(const_int)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn peel_refs(mut self) -> Self {
+ while let Constant::Ref(r) = self {
+ self = *r;
+ }
+ self
+ }
+}
+
+/// Parses a `LitKind` to a `Constant`.
+pub fn lit_to_mir_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) })
+}
+
+pub fn constant_full_int<'tcx>(
+ lcx: &LateContext<'tcx>,
+ typeck_results: &ty::TypeckResults<'tcx>,
+ e: &Expr<'_>,
+) -> Option<FullInt> {
+ constant_simple(lcx, typeck_results, e)?.int_value(lcx, typeck_results.expr_ty(e))
+}
+
+#[derive(Copy, Clone, Debug, Eq)]
+pub enum FullInt {
+ S(i128),
+ U(u128),
+}
+
+impl PartialEq for FullInt {
+ #[must_use]
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+impl PartialOrd for FullInt {
+ #[must_use]
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for FullInt {
+ #[must_use]
+ fn cmp(&self, other: &Self) -> Ordering {
+ use FullInt::{S, U};
+
+ fn cmp_s_u(s: i128, u: u128) -> Ordering {
+ u128::try_from(s).map_or(Ordering::Less, |x| x.cmp(&u))
+ }
+
+ match (*self, *other) {
+ (S(s), S(o)) => s.cmp(&o),
+ (U(s), U(o)) => s.cmp(&o),
+ (S(s), U(o)) => cmp_s_u(s, o),
+ (U(s), S(o)) => cmp_s_u(o, s).reverse(),
+ }
+ }
+}
+
+/// 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(block, _) => self.block(block),
+ ExprKind::Lit(ref lit) => {
+ if is_direct_expn_of(e.span, "cfg").is_some() {
+ None
+ } else {
+ Some(lit_to_mir_constant(&lit.node, self.typeck_results.expr_ty_opt(e)))
+ }
+ },
+ ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec),
+ ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple),
+ ExprKind::Repeat(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, 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(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
+ ExprKind::Binary(op, left, right) => self.binop(op, left, right),
+ ExprKind::Call(callee, 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 = self.lcx.get_def_path(def_id);
+ let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).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(arr, index) => self.index(arr, index),
+ ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
+ // TODO: add other expressions.
+ _ => None,
+ }
+ }
+
++ #[expect(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 an `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) => {
+ // Check if this constant is based on `cfg!(..)`,
+ // which is NOT constant for our purposes.
+ if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) &&
+ let Node::Item(&Item {
+ kind: ItemKind::Const(_, body_id),
+ ..
+ }) = node &&
+ let Node::Expr(&Expr {
+ kind: ExprKind::Lit(_),
+ span,
+ ..
+ }) = self.lcx.tcx.hir().get(body_id.hir_id) &&
+ is_direct_expn_of(span, "cfg").is_some() {
+ return None;
+ }
+
+ let substs = self.typeck_results.node_substs(id);
+ let substs = if self.substs.is_empty() {
+ substs
+ } else {
+ EarlyBinder(substs).subst(self.lcx.tcx, self.substs)
+ };
+
+ let result = self
+ .lcx
+ .tcx
+ .const_eval_resolve(
+ self.param_env,
+ ty::Unevaluated::new(ty::WithOptConstParam::unknown(def_id), substs),
+ 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.inner()
+ .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
+ .inner()
+ .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
+ .inner()
+ .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
+ .to_owned()
+ .chunks(8)
+ .map(|chunk| {
+ Some(Constant::F64(f64::from_le_bytes(
+ chunk.try_into().expect("this shouldn't happen"),
+ )))
+ })
+ .collect::<Option<Vec<Constant>>>()
+ .map(Constant::Vec),
+ _ => None,
+ },
+ // FIXME: implement other array type conversions.
+ _ => None,
+ },
+ _ => None,
+ },
+ // FIXME: implement other conversions.
+ _ => None,
+ }
+}
--- /dev/null
- span: Span,
+//! Clippy wrappers around rustc's diagnostic functions.
+//!
+//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding
+//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in
+//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added
+//! or renamed.
+//!
+//! Thank you!
+//! ~The `INTERNAL_METADATA_COLLECTOR` lint
+
+use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic, MultiSpan};
+use rustc_hir::HirId;
+use rustc_lint::{LateContext, Lint, LintContext};
+use rustc_span::source_map::Span;
+use std::env;
+
+fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
+ if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
+ if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
+ diag.help(&format!(
+ "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
+ &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
+ // extract just major + minor version and ignore patch versions
+ format!("rust-{}", n.rsplit_once('.').unwrap().1)
+ }),
+ lint
+ ));
+ }
+ }
+}
+
+/// Emit a basic lint message with a `msg` and a `span`.
+///
+/// This is the most primitive of our lint emission methods and can
+/// be a good way to get a new lint started.
+///
+/// Usually it's nicer to provide more context for lint messages.
+/// Be sure the output is understandable when you use this method.
+///
+/// # Example
+///
+/// ```ignore
+/// error: usage of mem::forget on Drop type
+/// --> $DIR/mem_forget.rs:17:5
+/// |
+/// 17 | std::mem::forget(seven);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Same as `span_lint` but with an extra `help` message.
+///
+/// Use this if you want to provide some general help but
+/// can't provide a specific machine applicable suggestion.
+///
+/// The `help` message can be optionally attached to a `Span`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: constant division of 0.0 with 0.0 will always result in NaN
+/// --> $DIR/zero_div_zero.rs:6:25
+/// |
+/// 6 | let other_f64_nan = 0.0f64 / 0.0;
+/// | ^^^^^^^^^^^^
+/// |
+/// = help: consider using `f64::NAN` if you would like a constant representing NaN
+/// ```
+pub fn span_lint_and_help<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
++ span: impl Into<MultiSpan>,
+ msg: &str,
+ help_span: Option<Span>,
+ help: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(help_span) = help_span {
+ diag.span_help(help_span, help);
+ } else {
+ diag.help(help);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but with a `note` section instead of a `help` message.
+///
+/// The `note` message is presented separately from the main lint message
+/// and is attached to a specific span:
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
+/// --> $DIR/drop_forget_ref.rs:10:5
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^^^^^^^^^
+/// |
+/// = note: `-D clippy::forget-ref` implied by `-D warnings`
+/// note: argument has type &SomeStruct
+/// --> $DIR/drop_forget_ref.rs:10:12
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^
+/// ```
+pub fn span_lint_and_note<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ span: impl Into<MultiSpan>,
+ msg: &str,
+ note_span: Option<Span>,
+ note: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(note_span) = note_span {
+ diag.span_note(note_span, note);
+ } else {
+ diag.note(note);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
+///
+/// If you need to customize your lint output a lot, use this function.
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+pub fn span_lint_and_then<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F)
+where
+ C: LintContext,
+ S: Into<MultiSpan>,
+ F: FnOnce(&mut Diagnostic),
+{
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ hir_id: HirId,
+ sp: Span,
+ msg: &str,
+) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir_and_then(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ hir_id: HirId,
+ sp: impl Into<MultiSpan>,
+ msg: &str,
+ f: impl FnOnce(&mut Diagnostic),
+) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Add a span lint with a suggestion on how to fix it.
+///
+/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
+/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
+/// 2)"`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```text
+/// error: This `.fold` can be more succinctly expressed as `.any`
+/// --> $DIR/methods.rs:390:13
+/// |
+/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
+/// |
+/// = note: `-D fold-any` implied by `-D warnings`
+/// ```
+#[cfg_attr(feature = "internal", allow(clippy::collapsible_span_lint_calls))]
+pub fn span_lint_and_sugg<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ help: &str,
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ diag.span_suggestion(sp, help, sugg, applicability);
+ });
+}
+
+/// Like [`span_lint_and_sugg`] with a focus on the edges. The output will either
+/// emit single span or multispan suggestion depending on the number of its lines.
+///
+/// If the given suggestion string has more lines than the maximum display length defined by
+/// [`MAX_SUGGESTION_HIGHLIGHT_LINES`][`rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES`],
+/// this function will split the suggestion and span to showcase the change for the top and
+/// bottom edge of the code. For normal suggestions, in one display window, the help message
+/// will be combined with a colon.
+///
+/// Multipart suggestions like the one being created here currently cannot be
+/// applied by rustfix (See [rustfix#141](https://github.com/rust-lang/rustfix/issues/141)).
+/// Testing rustfix with this lint emission function might require a file with
+/// suggestions that can be fixed and those that can't. See
+/// [clippy#8520](https://github.com/rust-lang/rust-clippy/pull/8520/files) for
+/// an example and of this.
+///
+/// # Example for a long suggestion
+///
+/// ```text
+/// error: called `map(..).flatten()` on `Option`
+/// --> $DIR/map_flatten.rs:8:10
+/// |
+/// LL | .map(|x| {
+/// | __________^
+/// LL | | if x <= 5 {
+/// LL | | Some(x)
+/// LL | | } else {
+/// ... |
+/// LL | | })
+/// LL | | .flatten();
+/// | |__________________^
+/// |
+/// = note: `-D clippy::map-flatten` implied by `-D warnings`
+/// help: try replacing `map` with `and_then`
+/// |
+/// LL ~ .and_then(|x| {
+/// LL + if x <= 5 {
+/// LL + Some(x)
+/// |
+/// help: and remove the `.flatten()`
+/// |
+/// LL + None
+/// LL + }
+/// LL ~ });
+/// |
+/// ```
+pub fn span_lint_and_sugg_for_edges(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ helps: &[&str; 2],
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ let sugg_lines_count = sugg.lines().count();
+ if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES {
+ let sm = cx.sess().source_map();
+ if let (Ok(line_upper), Ok(line_bottom)) =
+ (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi()))
+ {
+ let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2;
+ let span_upper = sm.span_until_char(
+ sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]),
+ '\n',
+ );
+ let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]);
+
+ let sugg_lines_vec = sugg.lines().collect::<Vec<&str>>();
+ let sugg_upper = sugg_lines_vec[..split_idx].join("\n");
+ let sugg_bottom = sugg_lines_vec[sugg_lines_count - split_idx..].join("\n");
+
+ diag.span_suggestion(span_upper, helps[0], sugg_upper, applicability);
+ diag.span_suggestion(span_bottom, helps[1], sugg_bottom, applicability);
+
+ return;
+ }
+ }
+ diag.span_suggestion_with_style(
+ sp,
+ &helps.join(", "),
+ sugg,
+ applicability,
+ rustc_errors::SuggestionStyle::ShowAlways,
+ );
+ });
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// Note: in the JSON format (used by `compiletest_rs`), the help message will
+/// appear once per
+/// replacement. In human-readable format though, it only appears once before
+/// the whole suggestion.
+pub fn multispan_sugg<I>(diag: &mut Diagnostic, help_msg: &str, sugg: I)
+where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg);
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// rustfix currently doesn't support the automatic application of suggestions with
+/// multiple spans. This is tracked in issue [rustfix#141](https://github.com/rust-lang/rustfix/issues/141).
+/// Suggestions with multiple spans will be silently ignored.
+pub fn multispan_sugg_with_applicability<I>(
+ diag: &mut Diagnostic,
+ help_msg: &str,
+ applicability: Applicability,
+ sugg: I,
+) where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
+}
--- /dev/null
- #[allow(clippy::too_many_lines)]
+//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
+//!
+//! Things to consider:
+//! - has the expression side-effects?
+//! - is the expression computationally expensive?
+//!
+//! See lints:
+//! - unnecessary-lazy-evaluations
+//! - or-fun-call
+//! - option-if-let-else
+
+use crate::ty::{all_predicates_of, is_copy};
+use crate::visitors::is_const_evaluatable;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, PredicateKind};
+use rustc_span::{sym, Symbol};
+use std::cmp;
+use std::ops;
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+enum EagernessSuggestion {
+ // The expression is cheap and should be evaluated eagerly
+ Eager,
+ // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
+ // eager evaluation.
+ NoChange,
+ // The expression is likely expensive and should be evaluated lazily.
+ Lazy,
+ // The expression cannot be placed into a closure.
+ ForceNoChange,
+}
+impl ops::BitOr for EagernessSuggestion {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self {
+ cmp::max(self, rhs)
+ }
+}
+impl ops::BitOrAssign for EagernessSuggestion {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs;
+ }
+}
+
+/// Determine the eagerness of the given function call.
+fn fn_eagerness<'tcx>(
+ cx: &LateContext<'tcx>,
+ fn_id: DefId,
+ name: Symbol,
+ args: &'tcx [Expr<'_>],
+) -> EagernessSuggestion {
+ use EagernessSuggestion::{Eager, Lazy, NoChange};
+ let name = name.as_str();
+
+ let ty = match cx.tcx.impl_of_method(fn_id) {
+ Some(id) => cx.tcx.type_of(id),
+ None => return Lazy,
+ };
+
+ if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
+ if matches!(
+ cx.tcx.crate_name(fn_id.krate),
+ sym::std | sym::core | sym::alloc | sym::proc_macro
+ ) {
+ Eager
+ } else {
+ NoChange
+ }
+ } else if let ty::Adt(def, subs) = ty.kind() {
+ // Types where the only fields are generic types (or references to) with no trait bounds other
+ // than marker traits.
+ // Due to the limited operations on these types functions should be fairly cheap.
+ if def
+ .variants()
+ .iter()
+ .flat_map(|v| v.fields.iter())
+ .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
+ && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
+ PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
+ _ => true,
+ })
+ && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
+ {
+ // Limit the function to either `(self) -> bool` or `(&self) -> bool`
+ match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output {
+ [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
+ _ => Lazy,
+ }
+ } else {
+ Lazy
+ }
+ } else {
+ Lazy
+ }
+}
+
++#[expect(clippy::too_many_lines)]
+fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ eagerness: EagernessSuggestion,
+ }
+
+ impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
+ if self.eagerness == ForceNoChange {
+ return;
+ }
+ match e.kind {
+ ExprKind::Call(
+ &Expr {
+ kind: ExprKind::Path(ref path),
+ hir_id,
+ ..
+ },
+ args,
+ ) => match self.cx.qpath_res(path, hir_id) {
+ Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (),
+ Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
+ // No need to walk the arguments here, `is_const_evaluatable` already did
+ Res::Def(..) if is_const_evaluatable(self.cx, e) => {
+ self.eagerness |= NoChange;
+ return;
+ },
+ Res::Def(_, id) => match path {
+ QPath::Resolved(_, p) => {
+ self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args);
+ },
+ QPath::TypeRelative(_, name) => {
+ self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
+ },
+ QPath::LangItem(..) => self.eagerness = Lazy,
+ },
+ _ => self.eagerness = Lazy,
+ },
+ // No need to walk the arguments here, `is_const_evaluatable` already did
+ ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
+ self.eagerness |= NoChange;
+ return;
+ },
+ ExprKind::MethodCall(name, args, _) => {
+ self.eagerness |= self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
+ },
+ ExprKind::Index(_, e) => {
+ let ty = self.cx.typeck_results().expr_ty_adjusted(e);
+ if is_copy(self.cx, ty) && !ty.is_ref() {
+ self.eagerness |= NoChange;
+ } else {
+ self.eagerness = Lazy;
+ }
+ },
+
+ // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
+ ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (),
+ ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
+
+ ExprKind::Unary(_, e)
+ if matches!(
+ self.cx.typeck_results().expr_ty(e).kind(),
+ ty::Bool | ty::Int(_) | ty::Uint(_),
+ ) => {},
+ ExprKind::Binary(_, lhs, rhs)
+ if self.cx.typeck_results().expr_ty(lhs).is_primitive()
+ && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
+
+ // Can't be moved into a closure
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::Yield(..)
+ | ExprKind::Err => {
+ self.eagerness = ForceNoChange;
+ return;
+ },
+
+ // Memory allocation, custom operator, loop, or call to an unknown function
+ ExprKind::Box(_)
+ | ExprKind::Unary(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Call(..) => self.eagerness = Lazy,
+
+ ExprKind::ConstBlock(_)
+ | ExprKind::Array(_)
+ | ExprKind::Tup(_)
+ | ExprKind::Lit(_)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::DropTemps(_)
+ | ExprKind::Let(..)
+ | ExprKind::If(..)
+ | ExprKind::Match(..)
+ | ExprKind::Closure(..)
+ | ExprKind::Field(..)
+ | ExprKind::Path(_)
+ | ExprKind::AddrOf(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Block(Block { stmts: [], .. }, _) => (),
+
+ // Assignment might be to a local defined earlier, so don't eagerly evaluate.
+ // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
+ // TODO: Actually check if either of these are true here.
+ ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
+ }
+ walk_expr(self, e);
+ }
+ }
+
+ let mut v = V {
+ cx,
+ eagerness: EagernessSuggestion::Eager,
+ };
+ v.visit_expr(e);
+ v.eagerness
+}
+
+/// Whether the given expression should be changed to evaluate eagerly
+pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ expr_eagerness(cx, expr) == EagernessSuggestion::Eager
+}
+
+/// Whether the given expression should be changed to evaluate lazily
+pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
+}
--- /dev/null
- use rustc_ast::ast::{self, LitKind};
+//! This module contains functions that retrieve specific elements.
+
+#![deny(clippy::missing_docs_in_private_items)]
+
++use crate::consts::{constant_simple, Constant};
+use crate::ty::is_type_diagnostic_item;
+use crate::{is_expn_of, match_def_path, paths};
+use if_chain::if_chain;
- WithLiteralCapacity(u64),
++use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath};
+use rustc_lint::LateContext;
+use rustc_span::{sym, symbol, Span};
+
+/// The essential nodes of a desugared for loop as well as the entire span:
+/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
+pub struct ForLoop<'tcx> {
+ /// `for` loop item
+ pub pat: &'tcx hir::Pat<'tcx>,
+ /// `IntoIterator` argument
+ pub arg: &'tcx hir::Expr<'tcx>,
+ /// `for` loop body
+ pub body: &'tcx hir::Expr<'tcx>,
+ /// Compare this against `hir::Destination.target`
+ pub loop_id: HirId,
+ /// entire `for` loop span
+ pub span: Span,
+}
+
+impl<'tcx> ForLoop<'tcx> {
+ /// Parses a desugared `for` loop
+ pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
+ if_chain! {
+ if let hir::ExprKind::DropTemps(e) = expr.kind;
+ if let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind;
+ if let hir::ExprKind::Call(_, [arg]) = iterexpr.kind;
+ if let hir::ExprKind::Loop(block, ..) = arm.body.kind;
+ if let [stmt] = &*block.stmts;
+ if let hir::StmtKind::Expr(e) = stmt.kind;
+ if let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind;
+ if let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind;
+ then {
+ return Some(Self {
+ pat: field.pat,
+ arg,
+ body: some_arm.body,
+ loop_id: arm.body.hir_id,
+ span: expr.span.ctxt().outer_expn_data().call_site,
+ });
+ }
+ }
+ None
+ }
+}
+
+/// An `if` expression without `DropTemps`
+pub struct If<'hir> {
+ /// `if` condition
+ pub cond: &'hir Expr<'hir>,
+ /// `if` then expression
+ pub then: &'hir Expr<'hir>,
+ /// `else` expression
+ pub r#else: Option<&'hir Expr<'hir>>,
+}
+
+impl<'hir> If<'hir> {
+ #[inline]
+ /// Parses an `if` expression
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(
+ Expr {
+ kind: ExprKind::DropTemps(cond),
+ ..
+ },
+ then,
+ r#else,
+ ) = expr.kind
+ {
+ Some(Self { cond, then, r#else })
+ } else {
+ None
+ }
+ }
+}
+
+/// An `if let` expression
+pub struct IfLet<'hir> {
+ /// `if let` pattern
+ pub let_pat: &'hir Pat<'hir>,
+ /// `if let` scrutinee
+ pub let_expr: &'hir Expr<'hir>,
+ /// `if let` then expression
+ pub if_then: &'hir Expr<'hir>,
+ /// `if let` else expression
+ pub if_else: Option<&'hir Expr<'hir>>,
+}
+
+impl<'hir> IfLet<'hir> {
+ /// Parses an `if let` expression
+ pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(
+ Expr {
+ kind:
+ ExprKind::Let(hir::Let {
+ pat: let_pat,
+ init: let_expr,
+ ..
+ }),
+ ..
+ },
+ if_then,
+ if_else,
+ ) = expr.kind
+ {
+ let mut iter = cx.tcx.hir().parent_iter(expr.hir_id);
+ if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() {
+ if let Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::Loop(_, _, LoopSource::While, _),
+ ..
+ }),
+ )) = iter.next()
+ {
+ // while loop desugar
+ return None;
+ }
+ }
+ return Some(Self {
+ let_pat,
+ let_expr,
+ if_then,
+ if_else,
+ });
+ }
+ None
+ }
+}
+
+/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
+pub enum IfLetOrMatch<'hir> {
+ /// Any `match` expression
+ Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
+ /// scrutinee, pattern, then block, else block
+ IfLet(
+ &'hir Expr<'hir>,
+ &'hir Pat<'hir>,
+ &'hir Expr<'hir>,
+ Option<&'hir Expr<'hir>>,
+ ),
+}
+
+impl<'hir> IfLetOrMatch<'hir> {
+ /// Parses an `if let` or `match` expression
+ pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
+ match expr.kind {
+ ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
+ _ => IfLet::hir(cx, expr).map(
+ |IfLet {
+ let_expr,
+ let_pat,
+ if_then,
+ if_else,
+ }| { Self::IfLet(let_expr, let_pat, if_then, if_else) },
+ ),
+ }
+ }
+}
+
+/// An `if` or `if let` expression
+pub struct IfOrIfLet<'hir> {
+ /// `if` condition that is maybe a `let` expression
+ pub cond: &'hir Expr<'hir>,
+ /// `if` then expression
+ pub then: &'hir Expr<'hir>,
+ /// `else` expression
+ pub r#else: Option<&'hir Expr<'hir>>,
+}
+
+impl<'hir> IfOrIfLet<'hir> {
+ #[inline]
+ /// Parses an `if` or `if let` expression
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(cond, then, r#else) = expr.kind {
+ if let ExprKind::DropTemps(new_cond) = cond.kind {
+ return Some(Self {
+ cond: new_cond,
+ r#else,
+ then,
+ });
+ }
+ if let ExprKind::Let(..) = cond.kind {
+ return Some(Self { cond, then, r#else });
+ }
+ }
+ None
+ }
+}
+
+/// Represent a range akin to `ast::ExprKind::Range`.
+#[derive(Debug, Copy, Clone)]
+pub struct Range<'a> {
+ /// The lower bound of the range, or `None` for ranges such as `..X`.
+ pub start: Option<&'a hir::Expr<'a>>,
+ /// The upper bound of the range, or `None` for ranges such as `X..`.
+ pub end: Option<&'a hir::Expr<'a>>,
+ /// Whether the interval is open or closed.
+ pub limits: ast::RangeLimits,
+}
+
+impl<'a> Range<'a> {
+ /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
+ pub fn hir(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
+ /// Finds the field named `name` in the field. Always return `Some` for
+ /// convenience.
+ fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c hir::Expr<'c>> {
+ let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
+ Some(expr)
+ }
+
+ match expr.kind {
+ hir::ExprKind::Call(path, args)
+ if matches!(
+ path.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..))
+ ) =>
+ {
+ Some(Range {
+ start: Some(&args[0]),
+ end: Some(&args[1]),
+ limits: ast::RangeLimits::Closed,
+ })
+ },
+ hir::ExprKind::Struct(path, fields, None) => match &path {
+ hir::QPath::LangItem(hir::LangItem::RangeFull, ..) => Some(Range {
+ start: None,
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeFrom, ..) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::Range, ..) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeToInclusive, ..) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::Closed,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeTo, ..) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+}
+
+/// Represent the pre-expansion arguments of a `vec!` invocation.
+pub enum VecArgs<'a> {
+ /// `vec![elem; len]`
+ Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
+ /// `vec![a, b, c]`
+ Vec(&'a [hir::Expr<'a>]),
+}
+
+impl<'a> VecArgs<'a> {
+ /// Returns the arguments of the `vec!` macro if this expression was expanded
+ /// from `vec!`.
+ pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> {
+ if_chain! {
+ if let hir::ExprKind::Call(fun, args) = expr.kind;
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if is_expn_of(fun.span, "vec").is_some();
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ then {
+ return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
+ // `vec![elem; size]` case
+ Some(VecArgs::Repeat(&args[0], &args[1]))
+ } else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
+ // `vec![a, b, c]` case
+ if_chain! {
+ if let hir::ExprKind::Box(boxed) = args[0].kind;
+ if let hir::ExprKind::Array(args) = boxed.kind;
+ then {
+ return Some(VecArgs::Vec(args));
+ }
+ }
+
+ None
+ } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
+ Some(VecArgs::Vec(&[]))
+ } else {
+ None
+ };
+ }
+ }
+
+ None
+ }
+}
+
+/// A desugared `while` loop
+pub struct While<'hir> {
+ /// `while` loop condition
+ pub condition: &'hir Expr<'hir>,
+ /// `while` loop body
+ pub body: &'hir Expr<'hir>,
+}
+
+impl<'hir> While<'hir> {
+ #[inline]
+ /// Parses a desugared `while` loop
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::Loop(
+ Block {
+ expr:
+ Some(Expr {
+ kind:
+ ExprKind::If(
+ Expr {
+ kind: ExprKind::DropTemps(condition),
+ ..
+ },
+ body,
+ _,
+ ),
+ ..
+ }),
+ ..
+ },
+ _,
+ LoopSource::While,
+ _,
+ ) = expr.kind
+ {
+ return Some(Self { condition, body });
+ }
+ None
+ }
+}
+
+/// A desugared `while let` loop
+pub struct WhileLet<'hir> {
+ /// `while let` loop item pattern
+ pub let_pat: &'hir Pat<'hir>,
+ /// `while let` loop scrutinee
+ pub let_expr: &'hir Expr<'hir>,
+ /// `while let` loop body
+ pub if_then: &'hir Expr<'hir>,
+}
+
+impl<'hir> WhileLet<'hir> {
+ #[inline]
+ /// Parses a desugared `while let` loop
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::Loop(
+ Block {
+ expr:
+ Some(Expr {
+ kind:
+ ExprKind::If(
+ Expr {
+ kind:
+ ExprKind::Let(hir::Let {
+ pat: let_pat,
+ init: let_expr,
+ ..
+ }),
+ ..
+ },
+ if_then,
+ _,
+ ),
+ ..
+ }),
+ ..
+ },
+ _,
+ LoopSource::While,
+ _,
+ ) = expr.kind
+ {
+ return Some(Self {
+ let_pat,
+ let_expr,
+ if_then,
+ });
+ }
+ None
+ }
+}
+
+/// Converts a hir binary operator to the corresponding `ast` type.
+#[must_use]
+pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
+ match op {
+ hir::BinOpKind::Eq => ast::BinOpKind::Eq,
+ hir::BinOpKind::Ge => ast::BinOpKind::Ge,
+ hir::BinOpKind::Gt => ast::BinOpKind::Gt,
+ hir::BinOpKind::Le => ast::BinOpKind::Le,
+ hir::BinOpKind::Lt => ast::BinOpKind::Lt,
+ hir::BinOpKind::Ne => ast::BinOpKind::Ne,
+ hir::BinOpKind::Or => ast::BinOpKind::Or,
+ hir::BinOpKind::Add => ast::BinOpKind::Add,
+ hir::BinOpKind::And => ast::BinOpKind::And,
+ hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
+ hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
+ hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
+ hir::BinOpKind::Div => ast::BinOpKind::Div,
+ hir::BinOpKind::Mul => ast::BinOpKind::Mul,
+ hir::BinOpKind::Rem => ast::BinOpKind::Rem,
+ hir::BinOpKind::Shl => ast::BinOpKind::Shl,
+ hir::BinOpKind::Shr => ast::BinOpKind::Shr,
+ hir::BinOpKind::Sub => ast::BinOpKind::Sub,
+ }
+}
+
+/// A parsed `Vec` initialization expression
+#[derive(Clone, Copy)]
+pub enum VecInitKind {
+ /// `Vec::new()`
+ New,
+ /// `Vec::default()` or `Default::default()`
+ Default,
+ /// `Vec::with_capacity(123)`
- if_chain! {
- if let ExprKind::Lit(lit) = &arg.kind;
- if let LitKind::Int(num, _) = lit.node;
- then {
- return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?));
- }
- }
- return Some(VecInitKind::WithExprCapacity(arg.hir_id));
- }
++ WithConstCapacity(u128),
+ /// `Vec::with_capacity(slice.len())`
+ WithExprCapacity(HirId),
+}
+
+/// Checks if given expression is an initialization of `Vec` and returns its kind.
+pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
+ if let ExprKind::Call(func, args) = expr.kind {
+ match func.kind {
+ ExprKind::Path(QPath::TypeRelative(ty, name))
+ if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
+ {
+ if name.ident.name == sym::new {
+ return Some(VecInitKind::New);
+ } else if name.ident.name == symbol::kw::Default {
+ return Some(VecInitKind::Default);
+ } else if name.ident.name.as_str() == "with_capacity" {
+ let arg = args.get(0)?;
++ return match constant_simple(cx, cx.typeck_results(), arg) {
++ Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
++ _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
++ };
++ };
+ },
+ ExprKind::Path(QPath::Resolved(_, path))
+ if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
+ {
+ return Some(VecInitKind::Default);
+ },
+ _ => (),
+ }
+ }
+ None
+}
--- /dev/null
- #[allow(dead_code)]
+use crate::consts::constant_simple;
+use crate::source::snippet_opt;
+use rustc_ast::ast::InlineAsmTemplatePiece;
+use rustc_data_structures::fx::FxHasher;
+use rustc_hir::def::Res;
+use rustc_hir::HirIdMap;
+use rustc_hir::{
+ ArrayLen, BinOpKind, Block, BodyId, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, HirId,
+ InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath, Stmt,
+ StmtKind, Ty, TyKind, TypeBinding,
+};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::TypeckResults;
+use rustc_span::Symbol;
+use std::hash::{Hash, Hasher};
+
+/// Type used to check whether two ast are the same. This is different from the
+/// operator `==` on ast types as this operator would compare true equality with
+/// ID and span.
+///
+/// Note that some expressions kinds are not considered but could be added.
+pub struct SpanlessEq<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<(&'tcx TypeckResults<'tcx>, &'tcx TypeckResults<'tcx>)>,
+ allow_side_effects: bool,
+ expr_fallback: Option<Box<dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
+}
+
+impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results().map(|x| (x, x)),
+ allow_side_effects: true,
+ expr_fallback: None,
+ }
+ }
+
+ /// Consider expressions containing potential side effects as not equal.
+ #[must_use]
+ pub fn deny_side_effects(self) -> Self {
+ Self {
+ allow_side_effects: false,
+ ..self
+ }
+ }
+
+ #[must_use]
+ pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
+ Self {
+ expr_fallback: Some(Box::new(expr_fallback)),
+ ..self
+ }
+ }
+
+ /// Use this method to wrap comparisons that may involve inter-expression context.
+ /// See `self.locals`.
+ pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
+ HirEqInterExpr {
+ inner: self,
+ locals: HirIdMap::default(),
+ }
+ }
+
- #[allow(clippy::similar_names)]
+ 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(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+ self.inter_expr().eq_path(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 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: HirIdMap<HirId>,
+}
+
+impl HirEqInterExpr<'_, '_, '_> {
+ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&StmtKind::Local(l), &StmtKind::Local(r)) => {
+ // This additional check ensures that the type of the locals are equivalent even if the init
+ // expression or type have some inferred parts.
+ if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
+ let l_ty = typeck_lhs.pat_ty(l.pat);
+ let r_ty = typeck_rhs.pat_ty(r.pat);
+ if l_ty != r_ty {
+ return false;
+ }
+ }
+
+ // eq_pat adds the HirIds to the locals map. We therefor call it last to make sure that
+ // these only get added if the init and type is equal.
+ both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
+ && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
+ && self.eq_pat(l.pat, r.pat)
+ },
+ (&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r),
+ _ => false,
+ }
+ }
+
+ /// Checks whether two blocks are the same.
+ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ match (left.stmts, left.expr, right.stmts, right.expr) {
+ ([], None, [], None) => {
+ // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
+ // expanded to nothing, or the cfg attribute was used.
+ let (left, right) = match (
+ snippet_opt(self.inner.cx, left.span),
+ snippet_opt(self.inner.cx, right.span),
+ ) {
+ (Some(left), Some(right)) => (left, right),
+ _ => return true,
+ };
+ let mut left_pos = 0;
+ let left = tokenize(&left)
+ .map(|t| {
+ let end = left_pos + t.len;
+ let s = &left[left_pos..end];
+ left_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ let mut right_pos = 0;
+ let right = tokenize(&right)
+ .map(|t| {
+ let end = right_pos + t.len;
+ let s = &right[right_pos..end];
+ right_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ left.eq(right)
+ },
+ _ => {
+ over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r))
+ && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
+ },
+ }
+ }
+
+ pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool {
+ match (left, right) {
+ (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true,
+ (ArrayLen::Body(l_ct), ArrayLen::Body(r_ct)) => self.eq_body(l_ct.body, r_ct.body),
+ (_, _) => false,
+ }
+ }
+
+ pub fn eq_body(&mut self, left: BodyId, right: BodyId) -> bool {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.inner.maybe_typeck_results.replace((
+ self.inner.cx.tcx.typeck_body(left),
+ self.inner.cx.tcx.typeck_body(right),
+ ));
+ let res = self.eq_expr(
+ &self.inner.cx.tcx.hir().body(left).value,
+ &self.inner.cx.tcx.hir().body(right).value,
+ );
+ self.inner.maybe_typeck_results = old_maybe_typeck_results;
+ res
+ }
+
- #[allow(clippy::similar_names)]
++ #[expect(clippy::similar_names)]
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
+ return false;
+ }
+
+ if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
+ if let (Some(l), Some(r)) = (
+ constant_simple(self.inner.cx, typeck_lhs, left),
+ constant_simple(self.inner.cx, typeck_rhs, right),
+ ) {
+ if l == r {
+ return true;
+ }
+ }
+ }
+
+ let is_eq = match (
+ reduce_exprkind(self.inner.cx, &left.kind),
+ reduce_exprkind(self.inner.cx, &right.kind),
+ ) {
+ (&ExprKind::AddrOf(lb, l_mut, le), &ExprKind::AddrOf(rb, r_mut, re)) => {
+ lb == rb && l_mut == r_mut && self.eq_expr(le, re)
+ },
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Assign(ll, lr, _), &ExprKind::Assign(rl, rr, _)) => {
+ self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::AssignOp(ref lo, ll, lr), &ExprKind::AssignOp(ref ro, rl, rr)) => {
+ self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::Block(l, _), &ExprKind::Block(r, _)) => self.eq_block(l, r),
+ (&ExprKind::Binary(l_op, ll, lr), &ExprKind::Binary(r_op, rl, rr)) => {
+ l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
+ l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ })
+ },
+ (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Box(l), &ExprKind::Box(r)) => self.eq_expr(l, r),
+ (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
+ self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Cast(lx, lt), &ExprKind::Cast(rx, rt)) | (&ExprKind::Type(lx, lt), &ExprKind::Type(rx, rt)) => {
+ self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
+ },
+ (&ExprKind::Field(l_f_exp, ref l_f_ident), &ExprKind::Field(r_f_exp, ref r_f_ident)) => {
+ l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
+ },
+ (&ExprKind::Index(la, li), &ExprKind::Index(ra, ri)) => self.eq_expr(la, ra) && self.eq_expr(li, ri),
+ (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => {
+ self.eq_expr(lc, rc) && self.eq_expr(lt, rt) && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Let(l), &ExprKind::Let(r)) => {
+ self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init)
+ },
+ (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
+ (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => {
+ lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Match(le, la, ref ls), &ExprKind::Match(re, ra, ref rs)) => {
+ ls == rs
+ && self.eq_expr(le, re)
+ && over(la, ra, |l, r| {
+ self.eq_pat(l.pat, r.pat)
+ && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
+ && self.eq_expr(l.body, r.body)
+ })
+ },
+ (&ExprKind::MethodCall(l_path, l_args, _), &ExprKind::MethodCall(r_path, r_args, _)) => {
+ self.inner.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Repeat(le, ll), &ExprKind::Repeat(re, rl)) => {
+ self.eq_expr(le, re) && self.eq_array_length(ll, rl)
+ },
+ (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
+ (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&ExprKind::Struct(l_path, lf, ref lo), &ExprKind::Struct(r_path, rf, ref ro)) => {
+ self.eq_qpath(l_path, r_path)
+ && both(lo, ro, |l, r| self.eq_expr(l, r))
+ && over(lf, rf, |l, r| self.eq_expr_field(l, r))
+ },
+ (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
+ (&ExprKind::Unary(l_op, le), &ExprKind::Unary(r_op, re)) => l_op == r_op && self.eq_expr(le, re),
+ (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
+ (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re),
+ _ => false,
+ };
+ is_eq || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
+ }
+
+ fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
+ over(left, right, |l, r| self.eq_expr(l, r))
+ }
+
+ fn eq_expr_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_expr(left.expr, right.expr)
+ }
+
+ fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
+ match (left, right) {
+ (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
+ (Guard::IfLet(l), Guard::IfLet(r)) => {
+ self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init)
+ },
+ _ => false,
+ }
+ }
+
+ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
+ match (left, right) {
+ (GenericArg::Const(l), GenericArg::Const(r)) => self.eq_body(l.value.body, r.value.body),
+ (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
+ (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
+ (GenericArg::Infer(l_inf), GenericArg::Infer(r_inf)) => self.eq_ty(&l_inf.to_ty(), &r_inf.to_ty()),
+ _ => false,
+ }
+ }
+
+ fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
+ left.name == right.name
+ }
+
+ fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
+ let (PatField { ident: li, pat: lp, .. }, PatField { ident: ri, pat: rp, .. }) = (&left, &right);
+ li.name == ri.name && self.eq_pat(lp, rp)
+ }
+
+ /// Checks whether two patterns are the same.
+ fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&PatKind::Box(l), &PatKind::Box(r)) => self.eq_pat(l, r),
+ (&PatKind::Struct(ref lp, la, ..), &PatKind::Struct(ref rp, ra, ..)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat_field(l, r))
+ },
+ (&PatKind::TupleStruct(ref lp, la, ls), &PatKind::TupleStruct(ref rp, ra, rs)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
+ },
+ (&PatKind::Binding(lb, li, _, ref lp), &PatKind::Binding(rb, ri, _, ref rp)) => {
+ let eq = lb == rb && both(lp, rp, |l, r| self.eq_pat(l, r));
+ if eq {
+ self.locals.insert(li, ri);
+ }
+ eq
+ },
+ (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&PatKind::Lit(l), &PatKind::Lit(r)) => self.eq_expr(l, r),
+ (&PatKind::Tuple(l, ls), &PatKind::Tuple(r, rs)) => ls == rs && over(l, r, |l, r| self.eq_pat(l, r)),
+ (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
+ both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
+ },
+ (&PatKind::Ref(le, ref lm), &PatKind::Ref(re, ref rm)) => lm == rm && self.eq_pat(le, re),
+ (&PatKind::Slice(ls, ref li, le), &PatKind::Slice(rs, ref ri, re)) => {
+ over(ls, rs, |l, r| self.eq_pat(l, r))
+ && over(le, re, |l, r| self.eq_pat(l, r))
+ && both(li, ri, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Wild, &PatKind::Wild) => true,
+ _ => false,
+ }
+ }
+
- #[allow(clippy::similar_names)]
++ #[expect(clippy::similar_names)]
+ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
+ match (left, right) {
+ (&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => {
+ both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
+ },
+ (&QPath::TypeRelative(lty, lseg), &QPath::TypeRelative(rty, rseg)) => {
+ self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
+ },
+ (&QPath::LangItem(llang_item, ..), &QPath::LangItem(rlang_item, ..)) => llang_item == rlang_item,
+ _ => false,
+ }
+ }
+
+ pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+ match (left.res, right.res) {
+ (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
+ (Res::Local(_), _) | (_, Res::Local(_)) => false,
+ _ => over(left.segments, right.segments, |l, r| self.eq_path_segment(l, r)),
+ }
+ }
+
+ fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
+ if !(left.parenthesized || right.parenthesized) {
+ over(left.args, right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
+ && over(left.bindings, right.bindings, |l, r| self.eq_type_binding(l, r))
+ } else if left.parenthesized && right.parenthesized {
+ over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
+ && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
+ self.eq_ty(l, r)
+ })
+ } else {
+ false
+ }
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
+ }
+
- #[allow(clippy::too_many_lines)]
+ pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&TyKind::Slice(l_vec), &TyKind::Slice(r_vec)) => self.eq_ty(l_vec, r_vec),
+ (&TyKind::Array(lt, ll), &TyKind::Array(rt, rl)) => self.eq_ty(lt, rt) && self.eq_array_length(ll, rl),
+ (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
+ l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
+ },
+ (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
+ },
+ (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&TyKind::Tup(l), &TyKind::Tup(r)) => over(l, r, |l, r| self.eq_ty(l, r)),
+ (&TyKind::Infer, &TyKind::Infer) => true,
+ _ => false,
+ }
+ }
+
+ fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_ty(left.ty(), right.ty())
+ }
+}
+
+/// Some simple reductions like `{ return }` => `return`
+fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> {
+ if let ExprKind::Block(block, _) = kind {
+ match (block.stmts, block.expr) {
+ // From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty
+ // block with an empty span.
+ ([], None) if block.span.is_empty() => &ExprKind::Tup(&[]),
+ // `{}` => `()`
+ ([], None) => match snippet_opt(cx, block.span) {
+ // Don't reduce if there are any tokens contained in the braces
+ Some(snip)
+ if tokenize(&snip)
+ .map(|t| t.kind)
+ .filter(|t| {
+ !matches!(
+ t,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) =>
+ {
+ kind
+ },
+ _ => &ExprKind::Tup(&[]),
+ },
+ ([], Some(expr)) => match expr.kind {
+ // `{ return .. }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ ([stmt], None) => match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ // `{ return ..; }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ _ => kind,
+ },
+ _ => kind,
+ }
+ } else {
+ kind
+ }
+}
+
+fn swap_binop<'a>(
+ binop: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
+ match binop {
+ BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
+ Some((binop, rhs, lhs))
+ },
+ BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
+ BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
+ BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
+ BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
+ BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Rem
+ | BinOpKind::Sub
+ | BinOpKind::Div
+ | BinOpKind::And
+ | BinOpKind::Or => None,
+ }
+}
+
+/// Checks if the two `Option`s are both `None` or some equal values as per
+/// `eq_fn`.
+pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ l.as_ref()
+ .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
+}
+
+/// Checks if two slices are equal as per `eq_fn`.
+pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
+}
+
+/// Counts how many elements of the slices are equal as per `eq_fn`.
+pub fn count_eq<X: Sized>(
+ left: &mut dyn Iterator<Item = X>,
+ right: &mut dyn Iterator<Item = X>,
+ mut eq_fn: impl FnMut(&X, &X) -> bool,
+) -> usize {
+ left.zip(right).take_while(|(l, r)| eq_fn(l, r)).count()
+}
+
+/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
+pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
+}
+
+/// Type used to hash an ast element. This is different from the `Hash` trait
+/// on ast types as this
+/// trait would consider IDs and spans.
+///
+/// All expressions kind are hashed, but some might have a weaker hash.
+pub struct SpanlessHash<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ s: FxHasher,
+}
+
+impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ s: FxHasher::default(),
+ }
+ }
+
+ pub fn finish(self) -> u64 {
+ self.s.finish()
+ }
+
+ pub fn hash_block(&mut self, b: &Block<'_>) {
+ for s in b.stmts {
+ self.hash_stmt(s);
+ }
+
+ if let Some(e) = b.expr {
+ self.hash_expr(e);
+ }
+
+ std::mem::discriminant(&b.rules).hash(&mut self.s);
+ }
+
++ #[expect(clippy::too_many_lines)]
+ pub fn hash_expr(&mut self, e: &Expr<'_>) {
+ let simple_const = self
+ .maybe_typeck_results
+ .and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
+
+ // const hashing may result in the same hash as some unrelated node, so add a sort of
+ // discriminant depending on which path we're choosing next
+ simple_const.hash(&mut self.s);
+ if simple_const.is_some() {
+ return;
+ }
+
+ std::mem::discriminant(&e.kind).hash(&mut self.s);
+
+ match e.kind {
+ ExprKind::AddrOf(kind, m, e) => {
+ std::mem::discriminant(&kind).hash(&mut self.s);
+ m.hash(&mut self.s);
+ self.hash_expr(e);
+ },
+ ExprKind::Continue(i) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Assign(l, r, _) => {
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::AssignOp(ref o, l, r) => {
+ std::mem::discriminant(&o.node).hash(&mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Block(b, _) => {
+ self.hash_block(b);
+ },
+ ExprKind::Binary(op, l, r) => {
+ std::mem::discriminant(&op.node).hash(&mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Break(i, ref j) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ if let Some(j) = *j {
+ self.hash_expr(&*j);
+ }
+ },
+ ExprKind::Box(e) | ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
+ self.hash_expr(e);
+ },
+ ExprKind::Call(fun, args) => {
+ self.hash_expr(fun);
+ self.hash_exprs(args);
+ },
+ ExprKind::Cast(e, ty) | ExprKind::Type(e, ty) => {
+ self.hash_expr(e);
+ self.hash_ty(ty);
+ },
+ ExprKind::Closure(cap, _, eid, _, _) => {
+ std::mem::discriminant(&cap).hash(&mut self.s);
+ // closures inherit TypeckResults
+ self.hash_expr(&self.cx.tcx.hir().body(eid).value);
+ },
+ ExprKind::Field(e, ref f) => {
+ self.hash_expr(e);
+ self.hash_name(f.name);
+ },
+ ExprKind::Index(a, i) => {
+ self.hash_expr(a);
+ self.hash_expr(i);
+ },
+ ExprKind::InlineAsm(asm) => {
+ for piece in asm.template {
+ match piece {
+ InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
+ InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span: _,
+ } => {
+ operand_idx.hash(&mut self.s);
+ modifier.hash(&mut self.s);
+ },
+ }
+ }
+ asm.options.hash(&mut self.s);
+ for (op, _op_sp) in asm.operands {
+ match op {
+ InlineAsmOperand::In { reg, expr } => {
+ reg.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::Out { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ if let Some(expr) = expr {
+ self.hash_expr(expr);
+ }
+ },
+ InlineAsmOperand::InOut { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::SplitInOut {
+ reg,
+ late,
+ in_expr,
+ out_expr,
+ } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(in_expr);
+ if let Some(out_expr) = out_expr {
+ self.hash_expr(out_expr);
+ }
+ },
+ InlineAsmOperand::Const { anon_const } | InlineAsmOperand::SymFn { anon_const } => {
+ self.hash_body(anon_const.body);
+ },
+ InlineAsmOperand::SymStatic { path, def_id: _ } => self.hash_qpath(path),
+ }
+ }
+ },
+ ExprKind::Let(Let { pat, init, ty, .. }) => {
+ self.hash_expr(init);
+ if let Some(ty) = ty {
+ self.hash_ty(ty);
+ }
+ self.hash_pat(pat);
+ },
+ ExprKind::Err => {},
+ ExprKind::Lit(ref l) => {
+ l.node.hash(&mut self.s);
+ },
+ ExprKind::Loop(b, ref i, ..) => {
+ self.hash_block(b);
+ if let Some(i) = *i {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::If(cond, then, ref else_opt) => {
+ self.hash_expr(cond);
+ self.hash_expr(then);
+ if let Some(e) = *else_opt {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Match(e, arms, ref s) => {
+ self.hash_expr(e);
+
+ for arm in arms {
+ self.hash_pat(arm.pat);
+ if let Some(ref e) = arm.guard {
+ self.hash_guard(e);
+ }
+ self.hash_expr(arm.body);
+ }
+
+ s.hash(&mut self.s);
+ },
+ ExprKind::MethodCall(path, args, ref _fn_span) => {
+ self.hash_name(path.ident.name);
+ self.hash_exprs(args);
+ },
+ ExprKind::ConstBlock(ref l_id) => {
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Repeat(e, len) => {
+ self.hash_expr(e);
+ self.hash_array_length(len);
+ },
+ ExprKind::Ret(ref e) => {
+ if let Some(e) = *e {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
+ },
+ ExprKind::Struct(path, fields, ref expr) => {
+ self.hash_qpath(path);
+
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_expr(f.expr);
+ }
+
+ if let Some(e) = *expr {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Tup(tup) => {
+ self.hash_exprs(tup);
+ },
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
+ ExprKind::Unary(lop, le) => {
+ std::mem::discriminant(&lop).hash(&mut self.s);
+ self.hash_expr(le);
+ },
+ }
+ }
+
+ pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
+ for e in e {
+ self.hash_expr(e);
+ }
+ }
+
+ pub fn hash_name(&mut self, n: Symbol) {
+ n.hash(&mut self.s);
+ }
+
+ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
+ match *p {
+ QPath::Resolved(_, path) => {
+ self.hash_path(path);
+ },
+ QPath::TypeRelative(_, path) => {
+ self.hash_name(path.ident.name);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ std::mem::discriminant(&lang_item).hash(&mut self.s);
+ },
+ }
+ // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
+ }
+
+ pub fn hash_pat(&mut self, pat: &Pat<'_>) {
+ std::mem::discriminant(&pat.kind).hash(&mut self.s);
+ match pat.kind {
+ PatKind::Binding(ann, _, _, pat) => {
+ std::mem::discriminant(&ann).hash(&mut self.s);
+ if let Some(pat) = pat {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Box(pat) => self.hash_pat(pat),
+ PatKind::Lit(expr) => self.hash_expr(expr),
+ PatKind::Or(pats) => {
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Path(ref qpath) => self.hash_qpath(qpath),
+ PatKind::Range(s, e, i) => {
+ if let Some(s) = s {
+ self.hash_expr(s);
+ }
+ if let Some(e) = e {
+ self.hash_expr(e);
+ }
+ std::mem::discriminant(&i).hash(&mut self.s);
+ },
+ PatKind::Ref(pat, mu) => {
+ self.hash_pat(pat);
+ std::mem::discriminant(&mu).hash(&mut self.s);
+ },
+ PatKind::Slice(l, m, r) => {
+ for pat in l {
+ self.hash_pat(pat);
+ }
+ if let Some(pat) = m {
+ self.hash_pat(pat);
+ }
+ for pat in r {
+ self.hash_pat(pat);
+ }
+ },
+ PatKind::Struct(ref qpath, fields, e) => {
+ self.hash_qpath(qpath);
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_pat(f.pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::Tuple(pats, e) => {
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::TupleStruct(ref qpath, pats, e) => {
+ self.hash_qpath(qpath);
+ for pat in pats {
+ self.hash_pat(pat);
+ }
+ e.hash(&mut self.s);
+ },
+ PatKind::Wild => {},
+ }
+ }
+
+ pub fn hash_path(&mut self, path: &Path<'_>) {
+ match path.res {
+ // constant hash since equality is dependant on inter-expression context
+ // e.g. The expressions `if let Some(x) = foo() {}` and `if let Some(y) = foo() {}` are considered equal
+ // even though the binding names are different and they have different `HirId`s.
+ Res::Local(_) => 1_usize.hash(&mut self.s),
+ _ => {
+ for seg in path.segments {
+ self.hash_name(seg.ident.name);
+ self.hash_generic_args(seg.args().args);
+ }
+ },
+ }
+ }
+
+ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
+ std::mem::discriminant(&b.kind).hash(&mut self.s);
+
+ match &b.kind {
+ StmtKind::Local(local) => {
+ self.hash_pat(local.pat);
+ if let Some(init) = local.init {
+ self.hash_expr(init);
+ }
+ },
+ StmtKind::Item(..) => {},
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_guard(&mut self, g: &Guard<'_>) {
+ match g {
+ Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_lifetime(&mut self, lifetime: Lifetime) {
+ std::mem::discriminant(&lifetime.name).hash(&mut self.s);
+ if let LifetimeName::Param(ref name) = lifetime.name {
+ std::mem::discriminant(name).hash(&mut self.s);
+ match name {
+ ParamName::Plain(ref ident) => {
+ ident.name.hash(&mut self.s);
+ },
+ ParamName::Fresh(ref size) => {
+ size.hash(&mut self.s);
+ },
+ ParamName::Error => {},
+ }
+ }
+ }
+
+ pub fn hash_ty(&mut self, ty: &Ty<'_>) {
+ std::mem::discriminant(&ty.kind).hash(&mut self.s);
+ self.hash_tykind(&ty.kind);
+ }
+
+ pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
+ match ty {
+ TyKind::Slice(ty) => {
+ self.hash_ty(ty);
+ },
+ &TyKind::Array(ty, len) => {
+ self.hash_ty(ty);
+ self.hash_array_length(len);
+ },
+ TyKind::Ptr(ref mut_ty) => {
+ self.hash_ty(mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::Rptr(lifetime, ref mut_ty) => {
+ self.hash_lifetime(*lifetime);
+ self.hash_ty(mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::BareFn(bfn) => {
+ bfn.unsafety.hash(&mut self.s);
+ bfn.abi.hash(&mut self.s);
+ for arg in bfn.decl.inputs {
+ self.hash_ty(arg);
+ }
+ std::mem::discriminant(&bfn.decl.output).hash(&mut self.s);
+ match bfn.decl.output {
+ FnRetTy::DefaultReturn(_) => {},
+ FnRetTy::Return(ty) => {
+ self.hash_ty(ty);
+ },
+ }
+ bfn.decl.c_variadic.hash(&mut self.s);
+ },
+ TyKind::Tup(ty_list) => {
+ for ty in *ty_list {
+ self.hash_ty(ty);
+ }
+ },
+ TyKind::Path(ref qpath) => self.hash_qpath(qpath),
+ TyKind::OpaqueDef(_, arg_list) => {
+ self.hash_generic_args(arg_list);
+ },
+ TyKind::TraitObject(_, lifetime, _) => {
+ self.hash_lifetime(*lifetime);
+ },
+ TyKind::Typeof(anon_const) => {
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Err | TyKind::Infer | TyKind::Never => {},
+ }
+ }
+
+ pub fn hash_array_length(&mut self, length: ArrayLen) {
+ match length {
+ ArrayLen::Infer(..) => {},
+ ArrayLen::Body(anon_const) => self.hash_body(anon_const.body),
+ }
+ }
+
+ pub fn hash_body(&mut self, body_id: BodyId) {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
+ self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
+ for arg in arg_list {
+ match *arg {
+ GenericArg::Lifetime(l) => self.hash_lifetime(l),
+ GenericArg::Type(ref ty) => self.hash_ty(ty),
+ GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
+ GenericArg::Infer(ref inf) => self.hash_ty(&inf.to_ty()),
+ }
+ }
+ }
+}
--- /dev/null
- #[allow(clippy::module_name_repetitions)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(let_else)]
+#![feature(let_chains)]
++#![feature(lint_reasons)]
+#![feature(once_cell)]
+#![feature(rustc_private)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
+// warn on the same lints as `clippy_lints`
+#![warn(trivial_casts, trivial_numeric_casts)]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// 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_attr;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+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;
+
- HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource,
- Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind,
- TraitRef, TyKind, UnOp,
+pub mod ast_utils;
+pub mod attrs;
+pub mod comparisons;
+pub mod consts;
+pub mod diagnostics;
+pub mod eager_or_lazy;
+pub mod higher;
+mod hir_utils;
+pub mod macros;
+pub mod msrvs;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod qualify_min_const_fn;
+pub mod source;
+pub mod str_utils;
+pub mod sugg;
+pub mod ty;
+pub mod usage;
+pub mod visitors;
+
+pub use self::attrs::*;
+pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, SpanlessHash};
+
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
+use std::lazy::SyncOnceCell;
+use std::sync::{Mutex, MutexGuard};
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, LitKind};
++use rustc_ast::Attribute;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::unhash::UnhashMap;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, CRATE_DEF_ID};
+use rustc_hir::hir_id::{HirIdMap, HirIdSet};
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
+use rustc_hir::{
+ def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, ExprKind, FnDecl,
- pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
- msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
++ HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node,
++ Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
++ UnOp,
+};
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::ty as rustc_ty;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::binding::BindingMode;
+use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{
+ ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType,
+ PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
+};
+use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
+use rustc_middle::ty::{FloatTy, IntTy, UintTy};
+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::{Span, DUMMY_SP};
+use rustc_target::abi::Integer;
+
+use crate::consts::{constant, Constant};
+use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
+use crate::visitors::expr_visitor_no_bodies;
+
+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
+}
+
- #[allow(clippy::cast_possible_wrap)]
++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 {
+ ($context:ident) => {
+ fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
+ let sess = rustc_lint::LintContext::sess(cx);
+ match $crate::get_unique_inner_attr(sess, attrs, "msrv") {
+ Some(msrv_attr) => {
+ if let Some(msrv) = msrv_attr.value_str() {
+ self.msrv = $crate::parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
+ } else {
+ sess.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+ },
+ _ => (),
+ }
+ }
+ };
+}
+
+/// If the given expression is a local binding, find the initializer expression.
+/// If that initializer expression is another local binding, find its initializer again.
+/// This process repeats as long as possible (but usually no more than once). Initializer
+/// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`]
+/// instead.
+///
+/// Examples:
+/// ```
+/// let abc = 1;
+/// // ^ output
+/// let def = abc;
+/// dbg!(def);
+/// // ^^^ input
+///
+/// // or...
+/// let abc = 1;
+/// let def = abc + 2;
+/// // ^^^^^^^ output
+/// dbg!(def);
+/// // ^^^ input
+/// ```
+pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
+ while let Some(init) = path_to_local(expr)
+ .and_then(|id| find_binding_init(cx, id))
+ .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
+ {
+ expr = init;
+ }
+ expr
+}
+
+/// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable.
+/// By only considering immutable bindings, we guarantee that the returned expression represents the
+/// value of the binding wherever it is referenced.
+///
+/// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned.
+/// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the
+/// canonical binding `HirId`.
+pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
+ let hir = cx.tcx.hir();
+ if_chain! {
+ if let Some(Node::Binding(pat)) = hir.find(hir_id);
+ if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..));
+ let parent = hir.get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = hir.find(parent);
+ then {
+ return local.init;
+ }
+ }
+ None
+}
+
+/// 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_by_def_id(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,
+ }
+}
+
+/// Checks if a `QPath` resolves to a constructor of a `LangItem`.
+/// For example, use this to check whether a function call or a pattern is `Some(..)`.
+pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool {
+ if let QPath::Resolved(_, path) = qpath {
+ if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res {
+ if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) {
+ return cx.tcx.parent(ctor_id) == item_id;
+ }
+ }
+ }
+ false
+}
+
+pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
+ matches!(
+ expr.kind,
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: None,
+ ..
+ },
+ _
+ ) | ExprKind::Tup([])
+ )
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild(pat: &Pat<'_>) -> bool {
+ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+/// This is a deprecated function, consider using [`is_trait_method`].
+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 a method is defined in an impl of a diagnostic item
+pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
+ return cx.tcx.is_diagnostic_item(diag_item, adt.did());
+ }
+ }
+ false
+}
+
+/// Checks if a method is in a diagnostic item trait
+pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
+ return cx.tcx.is_diagnostic_item(diag_item, trait_did);
+ }
+ false
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ cx.typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
+}
+
+/// Checks if the given expression is a path referring an item on the trait
+/// that is marked with the given diagnostic item.
+///
+/// For checking method call expressions instead of path expressions, use
+/// [`is_trait_method`].
+///
+/// For example, this can be used to find if an expression like `u64::default`
+/// refers to an item of the trait `Default`, which is associated with the
+/// `diag_item` of `sym::Default`.
+pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ if let hir::ExprKind::Path(ref qpath) = expr.kind {
+ cx.qpath_res(qpath, expr.hir_id)
+ .opt_def_id()
+ .map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item))
+ } else {
+ false
+ }
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, seg) => seg,
+ QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
+ }
+}
+
+pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
+ last_path_segment(qpath)
+ .args
+ .map_or(&[][..], |a| a.args)
+ .iter()
+ .filter_map(|a| match a {
+ hir::GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// 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(_, path) => match_path(path, segments),
+ QPath::TypeRelative(ty, 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,
+ }
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
+///
+/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item.
+pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
+ path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
+/// diagnostic item.
+pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// 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)
+}
+
+/// 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, 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)
+}
+
+pub trait MaybePath<'hir> {
+ fn hir_id(&self) -> HirId;
+ fn qpath_opt(&self) -> Option<&QPath<'hir>>;
+}
+
+macro_rules! maybe_path {
+ ($ty:ident, $kind:ident) => {
+ impl<'hir> MaybePath<'hir> for hir::$ty<'hir> {
+ fn hir_id(&self) -> HirId {
+ self.hir_id
+ }
+ fn qpath_opt(&self) -> Option<&QPath<'hir>> {
+ match &self.kind {
+ hir::$kind::Path(qpath) => Some(qpath),
+ _ => None,
+ }
+ }
+ }
+ };
+}
+maybe_path!(Expr, ExprKind);
+maybe_path!(Pat, PatKind);
+maybe_path!(Ty, TyKind);
+
+/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err`
+pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res {
+ match maybe_path.qpath_opt() {
+ None => Res::Err,
+ Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()),
+ }
+}
+
+/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID
+pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<DefId> {
+ path_res(cx, maybe_path).opt_def_id()
+}
+
+/// Resolves a def path like `std::vec::Vec`.
+/// This function is expensive and should be used sparingly.
+pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
+ fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str) -> Option<Res> {
+ match tcx.def_kind(def_id) {
+ DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx
+ .module_children(def_id)
+ .iter()
+ .find(|item| item.ident.name.as_str() == name)
+ .map(|child| child.res.expect_non_local()),
+ DefKind::Impl => tcx
+ .associated_item_def_ids(def_id)
+ .iter()
+ .copied()
+ .find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name)
+ .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)),
+ _ => None,
+ }
+ }
+ fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
+ let single = |ty| tcx.incoherent_impls(ty).iter().copied();
+ let empty = || [].iter().copied();
+ match name {
+ "bool" => single(BoolSimplifiedType),
+ "char" => single(CharSimplifiedType),
+ "str" => single(StrSimplifiedType),
+ "array" => single(ArraySimplifiedType),
+ "slice" => single(SliceSimplifiedType),
+ // FIXME: rustdoc documents these two using just `pointer`.
+ //
+ // Maybe this is something we should do here too.
+ "const_ptr" => single(PtrSimplifiedType(Mutability::Not)),
+ "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)),
+ "isize" => single(IntSimplifiedType(IntTy::Isize)),
+ "i8" => single(IntSimplifiedType(IntTy::I8)),
+ "i16" => single(IntSimplifiedType(IntTy::I16)),
+ "i32" => single(IntSimplifiedType(IntTy::I32)),
+ "i64" => single(IntSimplifiedType(IntTy::I64)),
+ "i128" => single(IntSimplifiedType(IntTy::I128)),
+ "usize" => single(UintSimplifiedType(UintTy::Usize)),
+ "u8" => single(UintSimplifiedType(UintTy::U8)),
+ "u16" => single(UintSimplifiedType(UintTy::U16)),
+ "u32" => single(UintSimplifiedType(UintTy::U32)),
+ "u64" => single(UintSimplifiedType(UintTy::U64)),
+ "u128" => single(UintSimplifiedType(UintTy::U128)),
+ "f32" => single(FloatSimplifiedType(FloatTy::F32)),
+ "f64" => single(FloatSimplifiedType(FloatTy::F64)),
+ _ => empty(),
+ }
+ }
+ fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
+ tcx.crates(())
+ .iter()
+ .copied()
+ .find(|&num| tcx.crate_name(num).as_str() == name)
+ .map(CrateNum::as_def_id)
+ }
+
+ let (base, first, path) = match *path {
+ [base, first, ref path @ ..] => (base, first, path),
+ [primitive] => {
+ return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
+ },
+ _ => return Res::Err,
+ };
+ let tcx = cx.tcx;
+ let starts = find_primitive(tcx, base)
+ .chain(find_crate(tcx, base))
+ .filter_map(|id| item_child_by_name(tcx, id, first));
+
+ for first in starts {
+ let last = path
+ .iter()
+ .copied()
+ // for each segment, find the child item
+ .try_fold(first, |res, segment| {
+ let def_id = res.def_id();
+ if let Some(item) = item_child_by_name(tcx, def_id, segment) {
+ Some(item)
+ } else if matches!(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
+ }
+ });
+
+ if let Some(last) = last {
+ return last;
+ }
+ }
+
+ Res::Err
+}
+
+/// 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 def_path_res(cx, path) {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
+ }
+}
+
+/// 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>, def_id: LocalDefId) -> Option<&'tcx TraitRef<'tcx>> {
+ // Get the implemented trait for the current function
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+ let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
+ if_chain! {
+ if parent_impl != CRATE_DEF_ID;
+ if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl);
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ then {
+ return impl_.of_trait.as_ref();
+ }
+ }
+ None
+}
+
+/// This method will return tuple of projection stack and root of the expression,
+/// used in `can_mut_borrow_both`.
+///
+/// For example, if `e` represents the `v[0].a.b[x]`
+/// this method will return a tuple, composed of a `Vec`
+/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
+/// and an `Expr` for root of them, `v`
+fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
+ let mut result = vec![];
+ let root = loop {
+ match e.kind {
+ ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
+ result.push(e);
+ e = ep;
+ },
+ _ => break e,
+ };
+ };
+ result.reverse();
+ (result, root)
+}
+
+/// Gets the mutability of the custom deref adjustment, if any.
+pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
+ cx.typeck_results()
+ .expr_adjustments(e)
+ .iter()
+ .find_map(|a| match a.kind {
+ Adjust::Deref(Some(d)) => Some(Some(d.mutbl)),
+ Adjust::Deref(None) => None,
+ _ => Some(None),
+ })
+ .and_then(|x| x)
+}
+
+/// Checks if two expressions can be mutably borrowed simultaneously
+/// and they aren't dependent on borrowing same thing twice
+pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
+ let (s1, r1) = projection_stack(e1);
+ let (s2, r2) = projection_stack(e2);
+ if !eq_expr_value(cx, r1, r2) {
+ return true;
+ }
+ if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
+ return false;
+ }
+
+ for (x1, x2) in s1.iter().zip(s2.iter()) {
+ if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
+ return false;
+ }
+
+ match (&x1.kind, &x2.kind) {
+ (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
+ if i1 != i2 {
+ return true;
+ }
+ },
+ (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
+ if !eq_expr_value(cx, i1, i2) {
+ return false;
+ }
+ },
+ _ => return false,
+ }
+ }
+ false
+}
+
+/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
+/// constructor from the std library
+fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
+ let std_types_symbols = &[
+ sym::String,
+ sym::Vec,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::HashMap,
+ sym::BTreeMap,
+ sym::HashSet,
+ sym::BTreeSet,
+ sym::BinaryHeap,
+ ];
+
+ if let QPath::TypeRelative(_, method) = path {
+ if method.ident.name == sym::new {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
+ return std_types_symbols
+ .iter()
+ .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did()));
+ }
+ }
+ }
+ }
+ false
+}
+
+/// Return true if the expr is equal to `Default::default` when evaluated.
+pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ if is_diag_trait_item(cx, repl_def_id, sym::Default)
+ || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
+ then { true } else { false }
+ }
+}
+
+/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
+/// It doesn't cover all cases, for example indirect function calls (some of std
+/// functions are supported) but it is the best we have.
+pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(false) | LitKind::Int(0, _) => true,
+ LitKind::Str(s, _) => s.is_empty(),
+ _ => false,
+ },
+ ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
+ ExprKind::Repeat(x, ArrayLen::Body(len)) => if_chain! {
+ if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind;
+ if let LitKind::Int(v, _) = const_lit.node;
+ if v <= 32 && is_default_equivalent(cx, x);
+ then {
+ true
+ }
+ else {
+ false
+ }
+ },
+ ExprKind::Call(repl_func, _) => is_default_equivalent_call(cx, repl_func),
+ ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
+ _ => false,
+ }
+}
+
+/// Checks if the top level expression can be moved into a closure as is.
+/// Currently checks for:
+/// * Break/Continue outside the given loop HIR ids.
+/// * Yield/Return statements.
+/// * Inline assembly.
+/// * Usages of a field of a local where the type of the local can be partially moved.
+///
+/// For example, given the following function:
+///
+/// ```
+/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) {
+/// for item in iter {
+/// let s = item.1;
+/// if item.0 > 10 {
+/// continue;
+/// } else {
+/// s.clear();
+/// }
+/// }
+/// }
+/// ```
+///
+/// When called on the expression `item.0` this will return false unless the local `item` is in the
+/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it
+/// isn't always safe to move into a closure when only a single field is needed.
+///
+/// When called on the `continue` expression this will return false unless the outer loop expression
+/// is in the `loop_ids` set.
+///
+/// Note that this check is not recursive, so passing the `if` expression will always return true
+/// even though sub-expressions might return false.
+pub fn can_move_expr_to_closure_no_visit<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ loop_ids: &[HirId],
+ ignore_locals: &HirIdSet,
+) -> bool {
+ match expr.kind {
+ ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
+ | ExprKind::Continue(Destination { target_id: Ok(id), .. })
+ if loop_ids.contains(&id) =>
+ {
+ true
+ },
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Yield(..)
+ | ExprKind::InlineAsm(_) => false,
+ // Accessing a field of a local value can only be done if the type isn't
+ // partially moved.
+ ExprKind::Field(
+ &Expr {
+ hir_id,
+ kind:
+ ExprKind::Path(QPath::Resolved(
+ _,
+ Path {
+ res: Res::Local(local_id),
+ ..
+ },
+ )),
+ ..
+ },
+ _,
+ ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
+ // TODO: check if the local has been partially moved. Assume it has for now.
+ false
+ },
+ _ => true,
+ }
+}
+
+/// How a local is captured by a closure
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum CaptureKind {
+ Value,
+ Ref(Mutability),
+}
+impl CaptureKind {
+ pub fn is_imm_ref(self) -> bool {
+ self == Self::Ref(Mutability::Not)
+ }
+}
+impl std::ops::BitOr for CaptureKind {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self::Output {
+ match (self, rhs) {
+ (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
+ (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
+ | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
+ (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
+ }
+ }
+}
+impl std::ops::BitOrAssign for CaptureKind {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs;
+ }
+}
+
+/// Given an expression referencing a local, determines how it would be captured in a closure.
+/// Note as this will walk up to parent expressions until the capture can be determined it should
+/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
+/// function argument (other than a receiver).
+pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
+ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
+ let mut capture = CaptureKind::Ref(Mutability::Not);
+ pat.each_binding_or_first(&mut |_, id, span, _| match cx
+ .typeck_results()
+ .extract_binding_mode(cx.sess(), id, span)
+ .unwrap()
+ {
+ BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
+ capture = CaptureKind::Value;
+ },
+ BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
+ capture = CaptureKind::Ref(Mutability::Mut);
+ },
+ _ => (),
+ });
+ capture
+ }
+
+ debug_assert!(matches!(
+ e.kind,
+ ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
+ ));
+
+ let mut child_id = e.hir_id;
+ let mut capture = CaptureKind::Value;
+ let mut capture_expr_ty = e;
+
+ for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) {
+ if let [
+ Adjustment {
+ kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
+ target,
+ },
+ ref adjust @ ..,
+ ] = *cx
+ .typeck_results()
+ .adjustments()
+ .get(child_id)
+ .map_or(&[][..], |x| &**x)
+ {
+ if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
+ *adjust.last().map_or(target, |a| a.target).kind()
+ {
+ return CaptureKind::Ref(mutability);
+ }
+ }
+
+ match parent {
+ Node::Expr(e) => match e.kind {
+ ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
+ ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
+ ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
+ return CaptureKind::Ref(Mutability::Mut);
+ },
+ ExprKind::Field(..) => {
+ if capture == CaptureKind::Value {
+ capture_expr_ty = e;
+ }
+ },
+ ExprKind::Let(let_expr) => {
+ let mutability = match pat_capture_kind(cx, let_expr.pat) {
+ CaptureKind::Value => Mutability::Not,
+ CaptureKind::Ref(m) => m,
+ };
+ return CaptureKind::Ref(mutability);
+ },
+ ExprKind::Match(_, arms, _) => {
+ let mut mutability = Mutability::Not;
+ for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
+ match capture {
+ CaptureKind::Value => break,
+ CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
+ CaptureKind::Ref(Mutability::Not) => (),
+ }
+ }
+ return CaptureKind::Ref(mutability);
+ },
+ _ => break,
+ },
+ Node::Local(l) => match pat_capture_kind(cx, l.pat) {
+ CaptureKind::Value => break,
+ capture @ CaptureKind::Ref(_) => return capture,
+ },
+ _ => break,
+ }
+
+ child_id = parent_id;
+ }
+
+ if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
+ // Copy types are never automatically captured by value.
+ CaptureKind::Ref(Mutability::Not)
+ } else {
+ capture
+ }
+}
+
+/// Checks if the expression can be moved into a closure as is. This will return a list of captures
+/// if so, otherwise, `None`.
+pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ // Stack of potential break targets contained in the expression.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+ /// Whether this expression can be turned into a closure.
+ allow_closure: bool,
+ /// Locals which need to be captured, and whether they need to be by value, reference, or
+ /// mutable reference.
+ captures: HirIdMap<CaptureKind>,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.allow_closure {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
+ if !self.locals.contains(&l) {
+ let cap = capture_local_usage(self.cx, e);
+ self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
+ }
+ },
+ ExprKind::Closure(..) => {
+ let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id();
+ for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
+ let local_id = match capture.place.base {
+ PlaceBase::Local(id) => id,
+ PlaceBase::Upvar(var) => var.var_path.hir_id,
+ _ => continue,
+ };
+ if !self.locals.contains(&local_id) {
+ let capture = match capture.info.capture_kind {
+ UpvarCapture::ByValue => CaptureKind::Value,
+ UpvarCapture::ByRef(kind) => match kind {
+ BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
+ BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
+ CaptureKind::Ref(Mutability::Mut)
+ },
+ },
+ };
+ self.captures
+ .entry(local_id)
+ .and_modify(|e| *e |= capture)
+ .or_insert(capture);
+ }
+ }
+ },
+ ExprKind::Loop(b, ..) => {
+ self.loops.push(e.hir_id);
+ self.visit_block(b);
+ self.loops.pop();
+ },
+ _ => {
+ self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
+ walk_expr(self, e);
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+ }
+
+ let mut v = V {
+ cx,
+ allow_closure: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ captures: HirIdMap::default(),
+ };
+ v.visit_expr(expr);
+ v.allow_closure.then(|| v.captures)
+}
+
+/// 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, 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(path.ident.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(path, 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(())
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_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);
+ Some(parent.to_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_by_def_id(parent_id) {
+ Some(
+ Node::Item(Item { ident, .. })
+ | Node::TraitItem(TraitItem { ident, .. })
+ | Node::ImplItem(ImplItem { ident, .. }),
+ ) => Some(ident.name),
+ _ => None,
+ }
+}
+
+pub struct ContainsName {
+ pub name: Symbol,
+ pub result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ fn visit_name(&mut self, _: Span, name: Symbol) {
+ if self.name == name {
+ self.result = true;
+ }
+ }
+}
+
+/// 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 {
+ let mut found = false;
+ expr_visitor_no_bodies(|expr| {
+ if !found {
+ if let hir::ExprKind::Ret(..) = &expr.kind {
+ found = true;
+ }
+ }
+ !found
+ })
+ .visit_expr(expr);
+ found
+}
+
+/// Extends the span to the beginning of the spans line, incl. whitespaces.
+///
+/// ```rust
+/// 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.with_lo(line_start)
+}
+
+/// Gets the parent node, if any.
+pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
+ tcx.hir().parent_iter(id).next().map(|(_, node)| node)
+}
+
+/// 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>> {
+ get_parent_expr_for_hir(cx, e.hir_id)
+}
+
+/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
+/// constraint lints
+pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
+ match get_parent_node(cx.tcx, hir_id) {
+ Some(Node::Expr(parent)) => Some(parent),
+ _ => 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(block, _) => Some(block),
+ _ => None,
+ },
+ _ => None,
+ })
+}
+
+/// Gets the loop or closure enclosing the given expression, if any.
+pub fn get_enclosing_loop_or_closure<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ for (_, node) in tcx.hir().parent_iter(expr.hir_id) {
+ match node {
+ Node::Expr(
+ e @ Expr {
+ kind: ExprKind::Loop(..) | ExprKind::Closure(..),
+ ..
+ },
+ ) => return Some(e),
+ Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
+ _ => break,
+ }
+ }
+ None
+}
+
+/// Gets the parent node if it's an impl block.
+pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
+ match tcx.hir().parent_iter(id).next() {
+ Some((
+ _,
+ Node::Item(Item {
+ kind: ItemKind::Impl(imp),
+ ..
+ }),
+ )) => Some(imp),
+ _ => None,
+ }
+}
+
+/// Removes blocks around an expression, only if the block contains just one expression
+/// and no statements. Unsafe blocks are not removed.
+///
+/// Examples:
+/// * `{}` -> `{}`
+/// * `{ x }` -> `x`
+/// * `{{ x }}` -> `x`
+/// * `{ x; }` -> `{ x; }`
+/// * `{ x; y }` -> `{ x; y }`
+/// * `{ unsafe { x } }` -> `unsafe { x }`
+pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
+ while let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(inner),
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ },
+ _,
+ ) = expr.kind
+ {
+ expr = inner;
+ }
+ expr
+}
+
+/// Removes blocks around an expression, only if the block contains just one expression
+/// or just one expression statement with a semicolon. Unsafe blocks are not removed.
+///
+/// Examples:
+/// * `{}` -> `{}`
+/// * `{ x }` -> `x`
+/// * `{ x; }` -> `x`
+/// * `{{ x; }}` -> `x`
+/// * `{ x; y }` -> `{ x; y }`
+/// * `{ unsafe { x } }` -> `unsafe { x }`
+pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
+ while let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(inner),
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ }
+ | Block {
+ stmts:
+ [
+ Stmt {
+ kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
+ ..
+ },
+ ],
+ expr: None,
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ },
+ _,
+ ) = expr.kind
+ {
+ expr = inner;
+ }
+ expr
+}
+
+/// Checks if the given expression is the else clause of either an `if` or `if let` expression.
+pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let mut iter = tcx.hir().parent_iter(expr.hir_id);
+ match iter.next() {
+ Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::If(_, _, Some(else_expr)),
+ ..
+ }),
+ )) => else_expr.hir_id == expr.hir_id,
+ _ => false,
+ }
+}
+
+/// 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 enclosing_body = cx.tcx.hir().local_def_id(cx.tcx.hir().enclosing_body_owner(e.hir_id));
+ if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
+ return value == v;
+ }
+ 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 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
+/// # macro_rules! foo { ($name:tt!$args:tt) => { $name!$args } }
+/// # macro_rules! bar { ($e:expr) => { $e } }
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// from `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)
+}
+
+/// Convenience function to get the nth argument type of a function.
+pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId, nth: usize) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let arg = cx.tcx.fn_sig(fn_def_id).input(nth);
+ cx.tcx.erase_late_bound_regions(arg)
+}
+
+/// 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(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: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
+ i.into_iter().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(pat) | PatKind::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(pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats)
+ },
+ PatKind::Tuple(pats, _) => are_refutable(cx, pats),
+ PatKind::Struct(ref qpath, fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats),
+ PatKind::Slice(head, middle, tail) => {
+ match &cx.typeck_results().node_type(pat.hir_id).kind() {
+ rustc_ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call
+/// the function once on the given pattern.
+pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
+ if let PatKind::Or(pats) = pat.kind {
+ pats.iter().for_each(f);
+ } else {
+ f(pat);
+ }
+}
+
+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 let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
+ if let Res::SelfTy { .. } = path.res {
+ 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>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, pat, None) = arm.pat.kind;
+ if is_lang_ctor(cx, path, ResultOk);
+ 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(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ is_lang_ctor(cx, path, ResultErr)
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if *source == MatchSource::TryDesugar {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &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_lint_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: rustc_ty::IntTy) -> u64 {
+ Integer::from_int_ty(&tcx, ity).size().bits()
+}
+
- #[allow(clippy::cast_sign_loss)]
++#[expect(clippy::cast_possible_wrap)]
+/// Turn a constant int byte representation into an i128
+pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::IntTy) -> i128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as i128) << amt) >> amt
+}
+
++#[expect(clippy::cast_sign_loss)]
+/// clip unused bytes
+pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: rustc_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: rustc_ty::UintTy) -> u128 {
+ let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
+pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool {
+ attrs.iter().any(|attr| attr.has_name(symbol))
+}
+
+pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
+ if has_attr(map.attrs(enclosing_node), symbol) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.local_def_id_to_hir_id(map.get_parent_item(enclosing_node));
+ }
+
+ false
+}
+
+pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
+ any_parent_has_attr(tcx, node, sym::automatically_derived)
+}
+
+/// 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(fun, 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 the given `DefId` matches any of the paths. Returns the index of matching path, if
+/// any.
+///
+/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items.
+pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
+ let search_path = cx.get_def_path(did);
+ paths
+ .iter()
+ .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
+}
+
+/// Checks if the given `DefId` matches the path.
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+ // We should probably move to Symbols in Clippy as well rather than interning every time.
+ let path = cx.get_def_path(did);
+ syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
+}
+
+/// Checks if the given `DefId` matches the `libc` item.
+pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
+ let path = cx.get_def_path(did);
+ // libc is meant to be used as a flat list of names, but they're all actually defined in different
+ // modules based on the target platform. Ignore everything but crate name and the item name.
+ path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name)
+}
+
+/// 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>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
+ let mut conds = Vec::new();
+ let mut blocks: Vec<&Block<'_>> = Vec::new();
+
+ while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
+ conds.push(&*cond);
+ if let ExprKind::Block(block, _) = then.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(else_expr) = r#else {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(block, _) = expr.kind {
+ blocks.push(block);
+ }
+ }
+
+ (conds, blocks)
+}
+
+/// Checks if the given function kind is an async function.
+pub fn is_async_fn(kind: FnKind<'_>) -> bool {
+ matches!(kind, FnKind::ItemFn(_, _, header) if header.asyncness == IsAsync::Async)
+}
+
+/// Peels away all the compiler generated code surrounding the body of an async function,
+pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Call(
+ _,
+ &[
+ Expr {
+ kind: ExprKind::Closure(_, _, body, _, _),
+ ..
+ },
+ ],
+ ) = body.value.kind
+ {
+ if let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr:
+ Some(Expr {
+ kind: ExprKind::DropTemps(expr),
+ ..
+ }),
+ ..
+ },
+ _,
+ ) = tcx.hir().body(body).value.kind
+ {
+ return Some(expr);
+ }
+ };
+ None
+}
+
+// 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(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| cx.tcx.has_attr(did, sym::must_use))
+}
+
+/// Checks if an expression represents the identity function
+/// Only examines closures and `std::convert::identity`
+pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Checks if a function's body represents the identity function. Looks for bodies of the form:
+ /// * `|x| x`
+ /// * `|x| return x`
+ /// * `|x| { return x }`
+ /// * `|x| { return x; }`
+ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
+ let id = if_chain! {
+ if let [param] = func.params;
+ if let PatKind::Binding(_, id, _, _) = param.pat.kind;
+ then {
+ id
+ } else {
+ return false;
+ }
+ };
+
+ let mut expr = &func.value;
+ loop {
+ match expr.kind {
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
+ | ExprKind::Ret(Some(e)) => expr = e,
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
+ if_chain! {
+ if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
+ if let ExprKind::Ret(Some(ret_val)) = e.kind;
+ then {
+ expr = ret_val;
+ } else {
+ return false;
+ }
+ }
+ },
+ _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
+ }
+ }
+ }
+
+ match expr.kind {
+ ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
+ _ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)),
+ }
+}
+
+/// Gets the node where an expression is either used, or it's type is unified with another branch.
+/// Returns both the node and the `HirId` of the closest child node.
+pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
+ let mut child_id = expr.hir_id;
+ let mut iter = tcx.hir().parent_iter(child_id);
+ loop {
+ match iter.next() {
+ None => break None,
+ Some((id, Node::Block(_))) => child_id = id,
+ Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
+ Some((_, Node::Expr(expr))) => match expr.kind {
+ ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
+ ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
+ ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
+ _ => break Some((Node::Expr(expr), child_id)),
+ },
+ Some((_, node)) => break Some((node, child_id)),
+ }
+ }
+}
+
+/// Checks if the result of an expression is used, or it's type is unified with another branch.
+pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ !matches!(
+ get_expr_use_or_unification_node(tcx, expr),
+ None | Some((
+ Node::Stmt(Stmt {
+ kind: StmtKind::Expr(_)
+ | StmtKind::Semi(_)
+ | StmtKind::Local(Local {
+ pat: Pat {
+ kind: PatKind::Wild,
+ ..
+ },
+ ..
+ }),
+ ..
+ }),
+ _
+ ))
+ )
+}
+
+/// Checks if the expression is the final expression returned from a block.
+pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
+}
+
+pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
+ if !is_no_std_crate(cx) {
+ Some("std")
+ } else if !is_no_core_crate(cx) {
+ Some("core")
+ } else {
+ None
+ }
+}
+
+pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr.path == sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
+pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr.path == sym::no_core
+ } else {
+ false
+ }
+ })
+}
+
+/// Check if parent of a hir node is a trait implementation block.
+/// For example, `f` in
+/// ```rust
+/// # struct S;
+/// # trait Trait { fn f(); }
+/// 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,
+ }
+}
+
+/// 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 {
+ rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
+ rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
+ if let rustc_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() {
+ rustc_ty::Slice(..) => return Some("slice".into()),
+ rustc_ty::Array(..) => return Some("array".into()),
+ rustc_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,
+{
+ match exprs {
+ [a, b] if eq(a, b) => return vec![(a, b)],
+ _ if exprs.len() <= 2 => return vec![],
+ _ => {},
+ }
+
+ let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+ let mut map: UnhashMap<u64, Vec<&_>> =
+ UnhashMap::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<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+ fn peel<'a>(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 of expressions while the given closure returns `Some`.
+pub fn peel_hir_expr_while<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+ mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
+) -> &'tcx Expr<'tcx> {
+ while let Some(e) = f(expr) {
+ expr = e;
+ }
+ expr
+}
+
+/// 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<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ let mut remaining = count;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
+ remaining -= 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count - remaining)
+}
+
+/// Peels off all references on the expression. Returns the underlying expression and the number of
+/// references removed.
+pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
+ let mut count = 0;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
+ count += 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count)
+}
+
+/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
+/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
+pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+ loop {
+ match expr.kind {
+ ExprKind::AddrOf(_, _, e) => expr = e,
+ ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ if let Res::Def(_, def_id) = path.res {
+ return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
+ }
+ }
+ false
+}
+
+static TEST_ITEM_NAMES_CACHE: SyncOnceCell<Mutex<FxHashMap<LocalDefId, Vec<Symbol>>>> = SyncOnceCell::new();
+
+fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool {
+ let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
+ let mut map: MutexGuard<'_, FxHashMap<LocalDefId, Vec<Symbol>>> = cache.lock().unwrap();
+ match map.entry(module) {
+ Entry::Occupied(entry) => f(entry.get()),
+ Entry::Vacant(entry) => {
+ let mut names = Vec::new();
+ for id in tcx.hir().module_items(module) {
+ if matches!(tcx.def_kind(id.def_id), DefKind::Const)
+ && let item = tcx.hir().item(id)
+ && let ItemKind::Const(ty, _body) = item.kind {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ // We could also check for the type name `test::TestDescAndFn`
+ if let Res::Def(DefKind::Struct, _) = path.res {
+ let has_test_marker = tcx
+ .hir()
+ .attrs(item.hir_id())
+ .iter()
+ .any(|a| a.has_name(sym::rustc_test_marker));
+ if has_test_marker {
+ names.push(item.ident.name);
+ }
+ }
+ }
+ }
+ }
+ names.sort_unstable();
+ f(&*entry.insert(names))
+ },
+ }
+}
+
+/// Checks if the function containing the given `HirId` is a `#[test]` function
+///
+/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
+pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
+ with_test_item_names(tcx, tcx.parent_module(id), |names| {
+ tcx.hir()
+ .parent_iter(id)
+ // Since you can nest functions we need to collect all until we leave
+ // function scope
+ .any(|(_id, node)| {
+ if let Node::Item(item) = node {
+ if let ItemKind::Fn(_, _, _) = item.kind {
+ // Note that we have sorted the item names in the visitor,
+ // so the binary_search gets the same as `contains`, but faster.
+ return names.binary_search(&item.ident.name).is_ok();
+ }
+ }
+ false
+ })
+ })
+}
+
++/// Checks if the item containing the given `HirId` has `#[cfg(test)]` attribute applied
++///
++/// Note: Add `// compile-flags: --test` to UI tests with a `#[cfg(test)]` function
++pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
++ fn is_cfg_test(attr: &Attribute) -> bool {
++ if attr.has_name(sym::cfg)
++ && let Some(items) = attr.meta_item_list()
++ && let [item] = &*items
++ && item.has_name(sym::test)
++ {
++ true
++ } else {
++ false
++ }
++ }
++ tcx.hir()
++ .parent_iter(id)
++ .flat_map(|(parent_id, _)| tcx.hir().attrs(parent_id))
++ .any(is_cfg_test)
++}
++
+/// Checks whether item either has `test` attribute applied, or
+/// is a module with `test` in its name.
+///
+/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
+pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
+ is_in_test_function(tcx, item.hir_id())
+ || matches!(item.kind, ItemKind::Mod(..))
+ && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
+}
+
+macro_rules! op_utils {
+ ($($name:ident $assign:ident)*) => {
+ /// Binary operation traits like `LangItem::Add`
+ pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
+
+ /// Operator-Assign traits like `LangItem::AddAssign`
+ pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
+
+ /// Converts `BinOpKind::Add` to `(LangItem::Add, LangItem::AddAssign)`, for example
+ pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
+ match kind {
+ $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
+ _ => None,
+ }
+ }
+ };
+}
+
+op_utils! {
+ Add AddAssign
+ Sub SubAssign
+ Mul MulAssign
+ Div DivAssign
+ Rem RemAssign
+ BitXor BitXorAssign
+ BitAnd BitAndAssign
+ BitOr BitOrAssign
+ Shl ShlAssign
+ Shr ShrAssign
+}
--- /dev/null
- #[derive(Debug, PartialEq, Copy, Clone)]
+use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
+use std::iter;
+
++#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+pub enum Radix {
+ Binary,
+ Octal,
+ Decimal,
+ Hexadecimal,
+}
+
+impl Radix {
+ /// Returns a reasonable digit group size for this radix.
+ #[must_use]
+ fn suggest_grouping(self) -> usize {
+ match self {
+ Self::Binary | Self::Hexadecimal => 4,
+ Self::Octal | Self::Decimal => 3,
+ }
+ }
+}
+
+/// A helper method to format numeric literals with digit grouping.
+/// `lit` must be a valid numeric literal without suffix.
+pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
+ NumericLiteral::new(lit, type_suffix, float).format()
+}
+
+#[derive(Debug)]
+pub struct NumericLiteral<'a> {
+ /// Which radix the literal was represented in.
+ pub radix: Radix,
+ /// The radix prefix, if present.
+ pub prefix: Option<&'a str>,
+
+ /// The integer part of the number.
+ pub integer: &'a str,
+ /// The fraction part of the number.
+ pub fraction: Option<&'a str>,
+ /// The exponent separator (b'e' or b'E') including preceding underscore if present
+ /// and the exponent part.
+ pub exponent: Option<(&'a str, &'a str)>,
+
+ /// The type suffix, including preceding underscore if present.
+ pub suffix: Option<&'a str>,
+}
+
+impl<'a> NumericLiteral<'a> {
+ pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
+ NumericLiteral::from_lit_kind(src, &lit.kind)
+ }
+
+ pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
+ let unsigned_src = src.strip_prefix('-').map_or(src, |s| s);
+ if lit_kind.is_numeric()
+ && unsigned_src
+ .trim_start()
+ .chars()
+ .next()
+ .map_or(false, |c| c.is_ascii_digit())
+ {
+ let (unsuffixed, suffix) = split_suffix(src, lit_kind);
+ let float = matches!(lit_kind, LitKind::Float(..));
+ Some(NumericLiteral::new(unsuffixed, suffix, float))
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
+ // Determine delimiter for radix prefix, if present, and radix.
+ let radix = if lit.starts_with("0x") {
+ Radix::Hexadecimal
+ } else if lit.starts_with("0b") {
+ Radix::Binary
+ } else if lit.starts_with("0o") {
+ Radix::Octal
+ } else {
+ Radix::Decimal
+ };
+
+ // Grab part of the literal after prefix, if present.
+ let (prefix, mut sans_prefix) = if radix == Radix::Decimal {
+ (None, lit)
+ } else {
+ let (p, s) = lit.split_at(2);
+ (Some(p), s)
+ };
+
+ if suffix.is_some() && sans_prefix.ends_with('_') {
+ // The '_' before the suffix isn't part of the digits
+ sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
+ }
+
+ let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
+
+ Self {
+ radix,
+ prefix,
+ integer,
+ fraction,
+ exponent,
+ suffix,
+ }
+ }
+
+ pub fn is_decimal(&self) -> bool {
+ self.radix == Radix::Decimal
+ }
+
+ pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) {
+ let mut integer = digits;
+ let mut fraction = None;
+ let mut exponent = None;
+
+ if float {
+ for (i, c) in digits.char_indices() {
+ match c {
+ '.' => {
+ integer = &digits[..i];
+ fraction = Some(&digits[i + 1..]);
+ },
+ 'e' | 'E' => {
+ let exp_start = if digits[..i].ends_with('_') { i - 1 } else { i };
+
+ if integer.len() > exp_start {
+ integer = &digits[..exp_start];
+ } else {
+ fraction = Some(&digits[integer.len() + 1..exp_start]);
+ };
+ exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
+ break;
+ },
+ _ => {},
+ }
+ }
+ }
+
+ (integer, fraction, exponent)
+ }
+
+ /// Returns literal formatted in a sensible way.
+ pub fn format(&self) -> String {
+ let mut output = String::new();
+
+ if let Some(prefix) = self.prefix {
+ output.push_str(prefix);
+ }
+
+ let group_size = self.radix.suggest_grouping();
+
+ Self::group_digits(
+ &mut output,
+ self.integer,
+ group_size,
+ true,
+ self.radix == Radix::Hexadecimal,
+ );
+
+ if let Some(fraction) = self.fraction {
+ output.push('.');
+ Self::group_digits(&mut output, fraction, group_size, false, false);
+ }
+
+ if let Some((separator, exponent)) = self.exponent {
+ if exponent != "0" {
+ output.push_str(separator);
+ Self::group_digits(&mut output, exponent, group_size, true, false);
+ }
+ }
+
+ if let Some(suffix) = self.suffix {
+ if output.ends_with('.') {
+ output.push('0');
+ }
+ output.push('_');
+ output.push_str(suffix);
+ }
+
+ output
+ }
+
+ pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
+ debug_assert!(group_size > 0);
+
+ let mut digits = input.chars().filter(|&c| c != '_');
+
+ // The exponent may have a sign, output it early, otherwise it will be
+ // treated as a digit
+ if digits.clone().next() == Some('-') {
+ let _ = digits.next();
+ output.push('-');
+ }
+
+ let first_group_size;
+
+ if partial_group_first {
+ first_group_size = (digits.clone().count() - 1) % group_size + 1;
+ if pad {
+ for _ in 0..group_size - first_group_size {
+ output.push('0');
+ }
+ }
+ } else {
+ first_group_size = group_size;
+ }
+
+ for _ in 0..first_group_size {
+ if let Some(digit) = digits.next() {
+ output.push(digit);
+ }
+ }
+
+ for (c, i) in iter::zip(digits, (0..group_size).cycle()) {
+ if i == 0 {
+ output.push('_');
+ }
+ output.push(c);
+ }
+ }
+}
+
+fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
+ debug_assert!(lit_kind.is_numeric());
+ lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
+ let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
+ (unsuffixed, Some(suffix))
+ })
+}
+
+fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
+ debug_assert!(lit_kind.is_numeric());
+ let suffix = match lit_kind {
+ LitKind::Int(_, int_lit_kind) => match int_lit_kind {
+ LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
+ LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
+ LitIntType::Unsuffixed => None,
+ },
+ LitKind::Float(_, float_lit_kind) => match float_lit_kind {
+ LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
+ LitFloatType::Unsuffixed => None,
+ },
+ _ => None,
+ };
+
+ suffix.map(str::len)
+}
--- /dev/null
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+//! This module contains paths to types and functions Clippy needs to know
+//! about.
+//!
+//! Whenever possible, please consider diagnostic items over hardcoded paths.
+//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
+
+#[cfg(feature = "internal")]
+pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
+#[cfg(feature = "internal")]
+pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
+ ["rustc_lint_defs", "Applicability", "Unspecified"],
+ ["rustc_lint_defs", "Applicability", "HasPlaceholders"],
+ ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
+ ["rustc_lint_defs", "Applicability", "MachineApplicable"],
+];
+#[cfg(feature = "internal")]
+pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
+pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
+pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
+pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
+pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
+pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
+pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
+/// Preferably use the diagnostic item `sym::deref_method` where possible
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
+pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
+#[cfg(feature = "internal")]
+pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+#[cfg(feature = "internal")]
+pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
+pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
+pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
+pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+#[cfg(feature = "internal")]
+pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
+#[cfg(feature = "internal")]
+pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
+pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
+pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
+pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
+pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
+pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
+pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
+#[cfg(feature = "internal")]
+pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
+#[cfg(feature = "internal")]
+pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
+#[cfg(feature = "internal")]
+pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
+#[cfg(feature = "internal")]
+pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
+pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+/// Preferably use the diagnostic item `sym::Option` where possible
+pub const OPTION: [&str; 3] = ["core", "option", "Option"];
+pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
+pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
+pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
+pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
+pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
+pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"];
+pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"];
+pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
+pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
+pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
+pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
+pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
+#[cfg_attr(not(unix), allow(clippy::invalid_paths))]
+pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
+pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
+pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
+pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
+pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
+pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
+pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
+pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
+pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
+pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
+pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
+pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
+pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
+pub const PTR_UNALIGNED_VOLATILE_LOAD: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_load"];
+pub const PTR_UNALIGNED_VOLATILE_STORE: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_store"];
+pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
+pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
+pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
+pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
+pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+/// Preferably use the diagnostic item `sym::Result` where possible
+pub const RESULT: [&str; 3] = ["core", "result", "Result"];
+pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
+pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+#[cfg(feature = "internal")]
+pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
+pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
+pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
+pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
+pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
+pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
+pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
+pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"];
+pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"];
+pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
+pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
+pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
+pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
+#[cfg(feature = "internal")]
+pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
+#[cfg(feature = "internal")]
+pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
+#[cfg(feature = "internal")]
+pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
+#[cfg(feature = "internal")]
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
- #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
++#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
+pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
+pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
+pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
+pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
+pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
+pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];
--- /dev/null
- pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&RustcVersion>) -> McfResult {
+// This code used to be a part of `rustc` but moved to Clippy as a result of
+// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some
+// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
+// differ from the time of `rustc` even if the name stays the same.
+
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_middle::mir::{
+ Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
+ TerminatorKind,
+};
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use std::borrow::Cow;
+
+type McfResult = Result<(), (Span, Cow<'static, str>)>;
+
- msrv: Option<&RustcVersion>,
++pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<RustcVersion>) -> McfResult {
+ let def_id = body.source.def_id();
+ let mut current = def_id;
+ loop {
+ let predicates = tcx.predicates_of(current);
+ for (predicate, _) in predicates.predicates {
+ match predicate.kind().skip_binder() {
+ ty::PredicateKind::RegionOutlives(_)
+ | ty::PredicateKind::TypeOutlives(_)
+ | ty::PredicateKind::WellFormed(_)
+ | ty::PredicateKind::Projection(_)
+ | ty::PredicateKind::ConstEvaluatable(..)
+ | ty::PredicateKind::ConstEquate(..)
+ | ty::PredicateKind::Trait(..)
+ | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
+ ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
+ ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
+ ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
+ ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate),
+ }
+ }
+ match predicates.parent {
+ Some(parent) => current = parent,
+ None => break,
+ }
+ }
+
+ for local in &body.local_decls {
+ check_ty(tcx, local.ty, local.source_info.span)?;
+ }
+ // impl trait is gone in MIR, so check the return type manually
+ check_ty(
+ tcx,
+ tcx.fn_sig(def_id).output().skip_binder(),
+ body.local_decls.iter().next().unwrap().source_info.span,
+ )?;
+
+ for bb in body.basic_blocks() {
+ check_terminator(tcx, body, bb.terminator(), msrv)?;
+ for stmt in &bb.statements {
+ check_statement(tcx, body, def_id, stmt)?;
+ }
+ }
+ Ok(())
+}
+
+fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
+ for arg in ty.walk() {
+ let ty = match arg.unpack() {
+ GenericArgKind::Type(ty) => ty,
+
+ // No constraints on lifetimes or constants, except potentially
+ // constants' types, but `walk` will get to them as well.
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
+ };
+
+ match ty.kind() {
+ ty::Ref(_, _, hir::Mutability::Mut) => {
+ return Err((span, "mutable references in const fn are unstable".into()));
+ },
+ ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
+ ty::FnPtr(..) => {
+ return Err((span, "function pointers in const fn are unstable".into()));
+ },
+ ty::Dynamic(preds, _) => {
+ for pred in preds.iter() {
+ match pred.skip_binder() {
+ ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ },
+ ty::ExistentialPredicate::Trait(trait_ref) => {
+ if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
+ return Err((
+ span,
+ "trait bounds other than `Sized` \
+ on const fn parameters are unstable"
+ .into(),
+ ));
+ }
+ },
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ Ok(())
+}
+
+fn check_rvalue<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ def_id: DefId,
+ rvalue: &Rvalue<'tcx>,
+ span: Span,
+) -> McfResult {
+ match rvalue {
+ Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
+ Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => check_operand(tcx, operand, span, body),
+ Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+ check_place(tcx, *place, span, body)
+ },
+ Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
+ use rustc_middle::ty::cast::CastTy;
+ let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
+ let cast_out = CastTy::from_ty(*cast_ty).expect("bad output type for cast");
+ match (cast_in, cast_out) {
+ (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
+ Err((span, "casting pointers to ints is unstable in const fn".into()))
+ },
+ _ => check_operand(tcx, operand, span, body),
+ }
+ },
+ Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _) => {
+ check_operand(tcx, operand, span, body)
+ },
+ Rvalue::Cast(
+ CastKind::Pointer(
+ PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
+ ),
+ _,
+ _,
+ ) => Err((span, "function pointer casts are not allowed in const fn".into())),
+ Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
+ let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
+ deref_ty.ty
+ } else {
+ // We cannot allow this for now.
+ return Err((span, "unsizing casts are only allowed for references right now".into()));
+ };
+ let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
+ if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
+ check_operand(tcx, op, span, body)?;
+ // Casting/coercing things to slices is fine.
+ Ok(())
+ } else {
+ // We just can't allow trait objects until we have figured out trait method calls.
+ Err((span, "unsizing casts are not allowed in const fn".into()))
+ }
+ },
+ // binops are fine on integers
+ Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
+ check_operand(tcx, lhs, span, body)?;
+ check_operand(tcx, rhs, span, body)?;
+ let ty = lhs.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() || ty.is_char() {
+ Ok(())
+ } else {
+ Err((
+ span,
+ "only int, `bool` and `char` operations are stable in const fn".into(),
+ ))
+ }
+ },
+ Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()),
+ Rvalue::UnaryOp(_, operand) => {
+ let ty = operand.ty(body, tcx);
+ if ty.is_integral() || ty.is_bool() {
+ check_operand(tcx, operand, span, body)
+ } else {
+ Err((span, "only int and `bool` operations are stable in const fn".into()))
+ }
+ },
+ Rvalue::Aggregate(_, operands) => {
+ for operand in operands {
+ check_operand(tcx, operand, span, body)?;
+ }
+ Ok(())
+ },
+ }
+}
+
+fn check_statement<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ def_id: DefId,
+ statement: &Statement<'tcx>,
+) -> McfResult {
+ let span = statement.source_info.span;
+ match &statement.kind {
+ StatementKind::Assign(box (place, rval)) => {
+ check_place(tcx, *place, span, body)?;
+ check_rvalue(tcx, body, def_id, rval, span)
+ },
+
+ StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body),
+ // just an assignment
+ StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
+ check_place(tcx, **place, span, body)
+ },
+
+ StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping { dst, src, count }) => {
+ check_operand(tcx, dst, span, body)?;
+ check_operand(tcx, src, span, body)?;
+ check_operand(tcx, count, span, body)
+ },
+ // These are all NOPs
+ StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Retag { .. }
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Coverage(..)
+ | StatementKind::Nop => Ok(()),
+ }
+}
+
+fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ match operand {
+ Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
+ Operand::Constant(c) => match c.check_static_ptr(tcx) {
+ Some(_) => Err((span, "cannot access `static` items in const fn".into())),
+ None => Ok(()),
+ },
+ }
+}
+
+fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
+ let mut cursor = place.projection.as_ref();
+ while let [ref proj_base @ .., elem] = *cursor {
+ cursor = proj_base;
+ match elem {
+ ProjectionElem::Field(..) => {
+ let base_ty = Place::ty_from(place.local, proj_base, body, tcx).ty;
+ if let Some(def) = base_ty.ty_adt_def() {
+ // No union field accesses in `const fn`
+ if def.is_union() {
+ return Err((span, "accessing union fields is unstable".into()));
+ }
+ }
+ },
+ ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Downcast(..)
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Deref
+ | ProjectionElem::Index(_) => {},
+ }
+ }
+
+ Ok(())
+}
+
+fn check_terminator<'a, 'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ terminator: &Terminator<'tcx>,
- fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<&RustcVersion>) -> bool {
++ msrv: Option<RustcVersion>,
+) -> McfResult {
+ let span = terminator.source_info.span;
+ match &terminator.kind {
+ TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::Return
+ | TerminatorKind::Resume
+ | TerminatorKind::Unreachable => Ok(()),
+
+ TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
+ TerminatorKind::DropAndReplace { place, value, .. } => {
+ check_place(tcx, *place, span, body)?;
+ check_operand(tcx, value, span, body)
+ },
+
+ TerminatorKind::SwitchInt {
+ discr,
+ switch_ty: _,
+ targets: _,
+ } => check_operand(tcx, discr, span, body),
+
+ TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
+ TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
+ Err((span, "const fn generators are unstable".into()))
+ },
+
+ TerminatorKind::Call {
+ func,
+ args,
+ from_hir_call: _,
+ destination: _,
+ cleanup: _,
+ fn_span: _,
+ } => {
+ let fn_ty = func.ty(body, tcx);
+ if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
+ if !is_const_fn(tcx, fn_def_id, msrv) {
+ return Err((
+ span,
+ format!(
+ "can only call other `const fn` within a `const fn`, \
+ but `{:?}` is not stable as `const fn`",
+ func,
+ )
+ .into(),
+ ));
+ }
+
+ // HACK: This is to "unstabilize" the `transmute` intrinsic
+ // within const fns. `transmute` is allowed in all other const contexts.
+ // This won't really scale to more intrinsics or functions. Let's allow const
+ // transmutes in const fn before we add more hacks to this.
+ if tcx.is_intrinsic(fn_def_id) && tcx.item_name(fn_def_id) == sym::transmute {
+ return Err((
+ span,
+ "can only call `transmute` from const items, not `const fn`".into(),
+ ));
+ }
+
+ check_operand(tcx, func, span, body)?;
+
+ for arg in args {
+ check_operand(tcx, arg, span, body)?;
+ }
+ Ok(())
+ } else {
+ Err((span, "can only call other const fns within const fn".into()))
+ }
+ },
+
+ TerminatorKind::Assert {
+ cond,
+ expected: _,
+ msg: _,
+ target: _,
+ cleanup: _,
+ } => check_operand(tcx, cond, span, body),
+
+ TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
+ }
+}
+
- &RustcVersion::parse(since.as_str())
++fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bool {
+ tcx.is_const_fn(def_id)
+ && tcx.lookup_const_stability(def_id).map_or(true, |const_stab| {
+ if let rustc_attr::StabilityLevel::Stable { since } = const_stab.level {
+ // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
+ // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
+ // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
+ crate::meets_msrv(
+ msrv,
++ RustcVersion::parse(since.as_str())
+ .expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"),
+ )
+ } else {
+ // Unstable const fn with the feature enabled.
+ msrv.is_none()
+ }
+ })
+}
--- /dev/null
- #[allow(clippy::needless_pass_by_value)]
+//! Utils for extracting, inspecting or transforming source code
+
+#![allow(clippy::module_name_repetitions)]
+
+use crate::line_span;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_span::hygiene;
+use rustc_span::source_map::SourceMap;
+use rustc_span::{BytePos, Pos, Span, SyntaxContext};
+use std::borrow::Cow;
+
+/// Checks if the span starts with the given text. This will return false if the span crosses
+/// multiple files or if source is not available.
+///
+/// This is used to check for proc macros giving unhelpful spans to things.
+pub fn span_starts_with<T: LintContext>(cx: &T, span: Span, text: &str) -> bool {
+ fn helper(sm: &SourceMap, span: Span, text: &str) -> bool {
+ let pos = sm.lookup_byte_offset(span.lo());
+ let Some(ref src) = pos.sf.src else {
+ return false;
+ };
+ let end = span.hi() - pos.sf.start_pos;
+ src.get(pos.pos.0 as usize..end.0 as usize)
+ // Expression spans can include wrapping parenthesis. Remove them first.
+ .map_or(false, |s| s.trim_start_matches('(').starts_with(text))
+ }
+ helper(cx.sess().source_map(), span, text)
+}
+
+/// 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))
+ }
+}
+
+/// 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()))
+}
+
+/// Gets a snippet of the indentation of the line of a span
+pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ snippet_opt(cx, line_span(cx, span)).map(|mut s| {
+ let len = s.len() - s.trim_start().len();
+ s.truncate(len);
+ s
+ })
+}
+
+// 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
+}
+
+/// Returns the position 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
+ })
+}
+
+/// Reindent a multiline string with possibility of ignoring the first line.
++#[expect(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(usize::from(ignore_first))
+ .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")
+}
+
+/// Converts a span to a code snippet if available, otherwise returns the 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`. To create suggestions consider using
+/// [`snippet_with_applicability`] to ensure that the applicability stays correct.
+///
+/// # Example
+/// ```rust,ignore
+/// // Given two spans one for `value` and one for the `init` expression.
+/// let value = Vec::new();
+/// // ^^^^^ ^^^^^^^^^^
+/// // span1 span2
+///
+/// // The snipped call would return the corresponding code snippet
+/// snippet(cx, span1, "..") // -> "value"
+/// snippet(cx, span2, "..") // -> "Vec::new()"
+/// ```
+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)
+}
+
+/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
+/// will result in the macro call, rather then the expansion, if the span is from a child context.
+/// If the span is not from a child context, it will be used directly instead.
+///
+/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
+/// would result in `box []`. If given the context of the address of expression, this function will
+/// correctly get a snippet of `vec![]`.
+///
+/// This will also return whether or not the snippet is a macro call.
+pub fn snippet_with_context<'a>(
+ cx: &LateContext<'_>,
+ span: Span,
+ outer: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> (Cow<'a, str>, bool) {
+ let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
+ || {
+ // The span is from a macro argument, and the outer context is the macro using the argument
+ if *applicability != Applicability::Unspecified {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ // TODO: get the argument span.
+ (span, false)
+ },
+ |outer_span| (outer_span, span.ctxt() != outer),
+ );
+
+ (
+ snippet_with_applicability(cx, span, default, applicability),
+ is_macro_call,
+ )
+}
+
+/// Walks the span up to the target context, thereby returning the macro call site if the span is
+/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
+/// case of the span being in a macro expansion, but the target context is from expanding a macro
+/// argument.
+///
+/// Given the following
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { f($e) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
+/// containing `0` as the context is the same as the outer context.
+///
+/// This will traverse through multiple macro calls. Given the following:
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
+/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`.
+pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
+ let outer_span = hygiene::walk_chain(span, outer);
+ (outer_span.ctxt() == outer).then(|| outer_span)
+}
+
+/// 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
+}
+
+#[cfg(test)]
+mod test {
+ use super::{reindent_multiline, without_block_comments};
+
+ #[test]
+ fn test_reindent_multiline_single_line() {
+ assert_eq!("", reindent_multiline("".into(), false, None));
+ assert_eq!("...", reindent_multiline("...".into(), false, None));
+ assert_eq!("...", reindent_multiline(" ...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", reindent_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_lines_deeper() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline("\
+ if x {
+ y
+ } else {
+ z
+ }".into(), true, Some(8)));
+ }
+
+ #[test]
+ fn test_without_block_comments_lines_without_block_comments() {
+ let result = without_block_comments(vec!["/*", "", "*/"]);
+ println!("result: {:?}", result);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
+ assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
+
+ let result = without_block_comments(vec!["/* rust", "", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* one-line comment */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["foo", "bar", "baz"]);
+ assert_eq!(result, vec!["foo", "bar", "baz"]);
+ }
+}
--- /dev/null
- use std::convert::TryInto;
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite};
+use crate::{get_parent_expr_for_hir, higher};
+use rustc_ast::util::parser::AssocOp;
+use rustc_ast::{ast, token};
+use rustc_ast_pretty::pprust::token_kind_to_string;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{ExprKind, HirId, MutTy, TyKind};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{EarlyContext, LateContext, LintContext};
+use rustc_middle::hir::place::ProjectionKind;
+use rustc_middle::mir::{FakeReadCause, Mutability};
+use rustc_middle::ty;
+use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
+use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use std::borrow::Cow;
- #[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
+use std::fmt::{Display, Write as _};
+use std::iter;
+use std::ops::{Add, Neg, Not, Sub};
+
+/// A helper type to build suggestion correctly handling parentheses.
+#[derive(Clone, PartialEq)]
+pub enum Sugg<'a> {
+ /// An expression that never needs parentheses such as `1337` or `[0; 42]`.
+ NonParen(Cow<'a, str>),
+ /// An expression that does not fit in other variants.
+ MaybeParen(Cow<'a, str>),
+ /// A binary operator expression, including `as`-casts and explicit type
+ /// coercion.
+ BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
+}
+
+/// Literal constant `0`, for convenience.
+pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
+/// Literal constant `1`, for convenience.
+pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
+/// a constant represents an empty string, for convenience.
+pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
+
+impl Display for Sugg<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ match *self {
+ Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
+ Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
+ }
+ }
+}
+
- #[allow(dead_code)]
++#[expect(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
+impl<'a> Sugg<'a> {
+ /// Prepare a suggestion from an expression.
+ pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
+ let get_snippet = |span| snippet(cx, span, "");
+ snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet))
+ }
+
+ /// Convenience function around `hir_opt` for suggestions with a default
+ /// text.
+ pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
+ }
+
+ /// Same as `hir`, but it adapts the applicability level by following rules:
+ ///
+ /// - Applicability level `Unspecified` will never be changed.
+ /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+ /// - If the default value is used and the applicability level is `MachineApplicable`, change it
+ /// to
+ /// `HasPlaceholders`
+ pub fn hir_with_applicability(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ Self::hir_opt(cx, expr).unwrap_or_else(|| {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Sugg::NonParen(Cow::Borrowed(default))
+ })
+ }
+
+ /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
+ pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
+ let get_snippet = |span| snippet_with_macro_callsite(cx, span, default);
+ Self::hir_from_snippet(expr, get_snippet)
+ }
+
+ /// Same as `hir`, but first walks the span up to the given context. This will result in the
+ /// macro call, rather then the expansion, if the span is from a child context. If the span is
+ /// not from a child context, it will be used directly instead.
+ ///
+ /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR
+ /// node would result in `box []`. If given the context of the address of expression, this
+ /// function will correctly get a snippet of `vec![]`.
+ pub fn hir_with_context(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ ctxt: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ if expr.span.ctxt() == ctxt {
+ Self::hir_from_snippet(expr, |span| snippet(cx, span, default))
+ } else {
+ let snip = snippet_with_applicability(cx, expr.span, default, applicability);
+ Sugg::NonParen(snip)
+ }
+ }
+
+ /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
+ /// function variants of `Sugg`, since these use different snippet functions.
+ fn hir_from_snippet(expr: &hir::Expr<'_>, get_snippet: impl Fn(Span) -> Cow<'a, str>) -> Self {
+ if let Some(range) = higher::Range::hir(expr) {
+ let op = match range.limits {
+ ast::RangeLimits::HalfOpen => AssocOp::DotDot,
+ ast::RangeLimits::Closed => AssocOp::DotDotEq,
+ };
+ let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
+ let end = range.end.map_or("".into(), |expr| get_snippet(expr.span));
+
+ return Sugg::BinOp(op, start, end);
+ }
+
+ match expr.kind {
+ hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Let(..)
+ | hir::ExprKind::Closure(..)
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Match(..) => Sugg::MaybeParen(get_snippet(expr.span)),
+ hir::ExprKind::Continue(..)
+ | hir::ExprKind::Yield(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Call(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Index(..)
+ | hir::ExprKind::InlineAsm(..)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Lit(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Path(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::DropTemps(_)
+ | hir::ExprKind::Err => Sugg::NonParen(get_snippet(expr.span)),
+ hir::ExprKind::Assign(lhs, rhs, _) => {
+ Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span))
+ },
+ hir::ExprKind::AssignOp(op, lhs, rhs) => {
+ Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span))
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp(
+ AssocOp::from_ast_binop(op.node.into()),
+ get_snippet(lhs.span),
+ get_snippet(rhs.span),
+ ),
+ hir::ExprKind::Cast(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)),
+ hir::ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::Colon, get_snippet(lhs.span), get_snippet(ty.span)),
+ }
+ }
+
+ /// Prepare a suggestion from an expression.
+ pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
+ use rustc_ast::ast::RangeLimits;
+
+ let get_whole_snippet = || {
+ if expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, expr.span, default)
+ } else {
+ snippet(cx, expr.span, default)
+ }
+ };
+
+ match expr.kind {
+ ast::ExprKind::AddrOf(..)
+ | ast::ExprKind::Box(..)
+ | ast::ExprKind::Closure(..)
+ | ast::ExprKind::If(..)
+ | ast::ExprKind::Let(..)
+ | ast::ExprKind::Unary(..)
+ | ast::ExprKind::Match(..) => Sugg::MaybeParen(get_whole_snippet()),
+ ast::ExprKind::Async(..)
+ | ast::ExprKind::Block(..)
+ | ast::ExprKind::Break(..)
+ | ast::ExprKind::Call(..)
+ | ast::ExprKind::Continue(..)
+ | ast::ExprKind::Yield(..)
+ | ast::ExprKind::Field(..)
+ | ast::ExprKind::ForLoop(..)
+ | ast::ExprKind::Index(..)
+ | ast::ExprKind::InlineAsm(..)
+ | ast::ExprKind::ConstBlock(..)
+ | ast::ExprKind::Lit(..)
+ | ast::ExprKind::Loop(..)
+ | ast::ExprKind::MacCall(..)
+ | ast::ExprKind::MethodCall(..)
+ | ast::ExprKind::Paren(..)
+ | ast::ExprKind::Underscore
+ | ast::ExprKind::Path(..)
+ | ast::ExprKind::Repeat(..)
+ | ast::ExprKind::Ret(..)
+ | ast::ExprKind::Yeet(..)
+ | ast::ExprKind::Struct(..)
+ | ast::ExprKind::Try(..)
+ | ast::ExprKind::TryBlock(..)
+ | ast::ExprKind::Tup(..)
+ | ast::ExprKind::Array(..)
+ | ast::ExprKind::While(..)
+ | ast::ExprKind::Await(..)
+ | ast::ExprKind::Err => Sugg::NonParen(get_whole_snippet()),
+ ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
+ AssocOp::DotDot,
+ lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)),
+ rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)),
+ ),
+ ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
+ AssocOp::DotDotEq,
+ lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)),
+ rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)),
+ ),
+ ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
+ AssocOp::Assign,
+ snippet(cx, lhs.span, default),
+ snippet(cx, rhs.span, default),
+ ),
+ ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
+ astbinop2assignop(op),
+ snippet(cx, lhs.span, default),
+ snippet(cx, rhs.span, default),
+ ),
+ ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
+ AssocOp::from_ast_binop(op.node),
+ snippet(cx, lhs.span, default),
+ snippet(cx, rhs.span, default),
+ ),
+ ast::ExprKind::Cast(ref lhs, ref ty) => Sugg::BinOp(
+ AssocOp::As,
+ snippet(cx, lhs.span, default),
+ snippet(cx, ty.span, default),
+ ),
+ ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
+ AssocOp::Colon,
+ snippet(cx, lhs.span, default),
+ snippet(cx, ty.span, default),
+ ),
+ }
+ }
+
+ /// Convenience method to create the `<lhs> && <rhs>` suggestion.
+ pub fn and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::And, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> & <rhs>` suggestion.
+ pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::BitAnd, &self, rhs)
+ }
+
+ /// Convenience method to create the `<lhs> as <rhs>` suggestion.
+ pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
+ make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
+ }
+
+ /// Convenience method to create the `&<expr>` suggestion.
+ pub fn addr(self) -> Sugg<'static> {
+ make_unop("&", self)
+ }
+
+ /// Convenience method to create the `&mut <expr>` suggestion.
+ pub fn mut_addr(self) -> Sugg<'static> {
+ make_unop("&mut ", self)
+ }
+
+ /// Convenience method to create the `*<expr>` suggestion.
+ pub fn deref(self) -> Sugg<'static> {
+ make_unop("*", self)
+ }
+
+ /// Convenience method to create the `&*<expr>` suggestion. Currently this
+ /// is needed because `sugg.deref().addr()` produces an unnecessary set of
+ /// parentheses around the deref.
+ pub fn addr_deref(self) -> Sugg<'static> {
+ make_unop("&*", self)
+ }
+
+ /// Convenience method to create the `&mut *<expr>` suggestion. Currently
+ /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
+ /// set of parentheses around the deref.
+ pub fn mut_addr_deref(self) -> Sugg<'static> {
+ make_unop("&mut *", self)
+ }
+
+ /// Convenience method to transform suggestion into a return call
+ pub fn make_return(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("return {}", self)))
+ }
+
+ /// Convenience method to transform suggestion into a block
+ /// where the suggestion is a trailing expression
+ pub fn blockify(self) -> Sugg<'static> {
+ Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
+ }
+
+ /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
+ /// suggestion.
- #[allow(clippy::too_many_lines)]
+ pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
+ match limit {
+ ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
+ ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
+ }
+ }
+
+ /// Adds parentheses to any expression that might need them. Suitable to the
+ /// `self` argument of a method call
+ /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
+ #[must_use]
+ pub fn maybe_par(self) -> Self {
+ match self {
+ Sugg::NonParen(..) => self,
+ // `(x)` and `(x).y()` both don't need additional parens.
+ Sugg::MaybeParen(sugg) => {
+ if has_enclosing_paren(&sugg) {
+ Sugg::MaybeParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({})", sugg).into())
+ }
+ },
+ Sugg::BinOp(op, lhs, rhs) => {
+ let sugg = binop_to_string(op, &lhs, &rhs);
+ Sugg::NonParen(format!("({})", sugg).into())
+ },
+ }
+ }
+}
+
+/// Generates a string from the operator and both sides.
+fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
+ match op {
+ AssocOp::Add
+ | AssocOp::Subtract
+ | AssocOp::Multiply
+ | AssocOp::Divide
+ | AssocOp::Modulus
+ | AssocOp::LAnd
+ | AssocOp::LOr
+ | AssocOp::BitXor
+ | AssocOp::BitAnd
+ | AssocOp::BitOr
+ | AssocOp::ShiftLeft
+ | AssocOp::ShiftRight
+ | AssocOp::Equal
+ | AssocOp::Less
+ | AssocOp::LessEqual
+ | AssocOp::NotEqual
+ | AssocOp::Greater
+ | AssocOp::GreaterEqual => format!(
+ "{} {} {}",
+ lhs,
+ op.to_ast_binop().expect("Those are AST ops").to_string(),
+ rhs
+ ),
+ AssocOp::Assign => format!("{} = {}", lhs, rhs),
+ AssocOp::AssignOp(op) => {
+ format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs)
+ },
+ AssocOp::As => format!("{} as {}", lhs, rhs),
+ AssocOp::DotDot => format!("{}..{}", lhs, rhs),
+ AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
+ AssocOp::Colon => format!("{}: {}", lhs, rhs),
+ }
+}
+
+/// Return `true` if `sugg` is enclosed in parenthesis.
+pub fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
+ let mut chars = sugg.as_ref().chars();
+ if chars.next() == Some('(') {
+ let mut depth = 1;
+ for c in &mut chars {
+ if c == '(' {
+ depth += 1;
+ } else if c == ')' {
+ depth -= 1;
+ }
+ if depth == 0 {
+ break;
+ }
+ }
+ chars.next().is_none()
+ } else {
+ false
+ }
+}
+
+/// Copied from the rust standard library, and then edited
+macro_rules! forward_binop_impls_to_ref {
+ (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
+ impl $imp<$t> for &$t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(self, &other)
+ }
+ }
+
+ impl $imp<&$t> for $t {
+ type Output = $o;
+
+ fn $method(self, other: &$t) -> $o {
+ $imp::$method(&self, other)
+ }
+ }
+
+ impl $imp for $t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(&self, &other)
+ }
+ }
+ };
+}
+
+impl Add for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Add, self, rhs)
+ }
+}
+
+impl Sub for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Sub, self, rhs)
+ }
+}
+
+forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
+forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
+
+impl Neg for Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn neg(self) -> Sugg<'static> {
+ make_unop("-", self)
+ }
+}
+
+impl<'a> Not for Sugg<'a> {
+ type Output = Sugg<'a>;
+ fn not(self) -> Sugg<'a> {
+ use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
+
+ if let Sugg::BinOp(op, lhs, rhs) = self {
+ let to_op = match op {
+ Equal => NotEqual,
+ NotEqual => Equal,
+ Less => GreaterEqual,
+ GreaterEqual => Less,
+ Greater => LessEqual,
+ LessEqual => Greater,
+ _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
+ };
+ Sugg::BinOp(to_op, lhs, rhs)
+ } else {
+ make_unop("!", self)
+ }
+ }
+}
+
+/// Helper type to display either `foo` or `(foo)`.
+struct ParenHelper<T> {
+ /// `true` if parentheses are needed.
+ paren: bool,
+ /// The main thing to display.
+ wrapped: T,
+}
+
+impl<T> ParenHelper<T> {
+ /// Builds a `ParenHelper`.
+ fn new(paren: bool, wrapped: T) -> Self {
+ Self { paren, wrapped }
+ }
+}
+
+impl<T: Display> Display for ParenHelper<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ if self.paren {
+ write!(f, "({})", self.wrapped)
+ } else {
+ self.wrapped.fmt(f)
+ }
+ }
+}
+
+/// Builds the string for `<op><expr>` adding parenthesis when necessary.
+///
+/// For convenience, the operator is taken as a string because all unary
+/// operators have the same
+/// precedence.
+pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
+ Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
+}
+
+/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
+///
+/// Precedence of shift operator relative to other arithmetic operation is
+/// often confusing so
+/// parenthesis will always be added for a mix of these.
+pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ /// Returns `true` if the operator is a shift operator `<<` or `>>`.
+ fn is_shift(op: AssocOp) -> bool {
+ matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+ }
+
+ /// Returns `true` if the operator is an arithmetic operator
+ /// (i.e., `+`, `-`, `*`, `/`, `%`).
+ fn is_arith(op: AssocOp) -> bool {
+ matches!(
+ op,
+ AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+ )
+ }
+
+ /// Returns `true` if the operator `op` needs parenthesis with the operator
+ /// `other` in the direction `dir`.
+ fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
+ other.precedence() < op.precedence()
+ || (other.precedence() == op.precedence()
+ && ((op != other && associativity(op) != dir)
+ || (op == other && associativity(op) != Associativity::Both)))
+ || is_shift(op) && is_arith(other)
+ || is_shift(other) && is_arith(op)
+ }
+
+ let lhs_paren = if let Sugg::BinOp(lop, _, _) = *lhs {
+ needs_paren(op, lop, Associativity::Left)
+ } else {
+ false
+ };
+
+ let rhs_paren = if let Sugg::BinOp(rop, _, _) = *rhs {
+ needs_paren(op, rop, Associativity::Right)
+ } else {
+ false
+ };
+
+ let lhs = ParenHelper::new(lhs_paren, lhs).to_string();
+ let rhs = ParenHelper::new(rhs_paren, rhs).to_string();
+ Sugg::BinOp(op, lhs.into(), rhs.into())
+}
+
+/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
+pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+/// Operator associativity.
+enum Associativity {
+ /// The operator is both left-associative and right-associative.
+ Both,
+ /// The operator is left-associative.
+ Left,
+ /// The operator is not associative.
+ None,
+ /// The operator is right-associative.
+ Right,
+}
+
+/// Returns the associativity/fixity of an operator. The difference with
+/// `AssocOp::fixity` is that an operator can be both left and right associative
+/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
+///
+/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
+/// they are considered
+/// associative.
+#[must_use]
+fn associativity(op: AssocOp) -> Associativity {
+ use rustc_ast::util::parser::AssocOp::{
+ Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
+ GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+ };
+
+ match op {
+ Assign | AssignOp(_) => Associativity::Right,
+ Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
+ Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
+ | Subtract => Associativity::Left,
+ DotDot | DotDotEq => Associativity::None,
+ }
+}
+
+/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
+fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
+ use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
+
+ AssocOp::AssignOp(match op.node {
+ hir::BinOpKind::Add => Plus,
+ hir::BinOpKind::BitAnd => And,
+ hir::BinOpKind::BitOr => Or,
+ hir::BinOpKind::BitXor => Caret,
+ hir::BinOpKind::Div => Slash,
+ hir::BinOpKind::Mul => Star,
+ hir::BinOpKind::Rem => Percent,
+ hir::BinOpKind::Shl => Shl,
+ hir::BinOpKind::Shr => Shr,
+ hir::BinOpKind::Sub => Minus,
+
+ hir::BinOpKind::And
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
+fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
+ use rustc_ast::ast::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ use rustc_ast::token::BinOpToken;
+
+ AssocOp::AssignOp(match op.node {
+ Add => BinOpToken::Plus,
+ BitAnd => BinOpToken::And,
+ BitOr => BinOpToken::Or,
+ BitXor => BinOpToken::Caret,
+ Div => BinOpToken::Slash,
+ Mul => BinOpToken::Star,
+ Rem => BinOpToken::Percent,
+ Shl => BinOpToken::Shl,
+ Shr => BinOpToken::Shr,
+ Sub => BinOpToken::Minus,
+ And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Returns the indentation before `span` if there are nothing but `[ \t]`
+/// before it on its line.
+fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ let lo = cx.sess().source_map().lookup_char_pos(span.lo());
+ lo.file
+ .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
+ .and_then(|line| {
+ if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
+ // We can mix char and byte positions here because we only consider `[ \t]`.
+ if lo.col == CharPos(pos) {
+ Some(line[..pos].into())
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+}
+
+/// Convenience extension trait for `Diagnostic`.
+pub trait DiagnosticExt<T: LintContext> {
+ /// Suggests to add an attribute to an item.
+ ///
+ /// Correctly handles indentation of the attribute and item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
+ /// ```
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ );
+
+ /// Suggest to add an item before another.
+ ///
+ /// The item should not be indented (except for inner indentation).
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_prepend_item(cx, item,
+ /// "fn foo() {
+ /// bar();
+ /// }");
+ /// ```
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
+
+ /// Suggest to completely remove an item.
+ ///
+ /// This will remove an item and all following whitespace until the next non-whitespace
+ /// character. This should work correctly if item is on the same indentation level as the
+ /// following item.
+ ///
+ /// # Example
+ ///
+ /// ```rust,ignore
+ /// diag.suggest_remove_item(cx, item, "remove this")
+ /// ```
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
+}
+
+impl<T: LintContext> DiagnosticExt<T> for rustc_errors::Diagnostic {
+ fn suggest_item_with_attr<D: Display + ?Sized>(
+ &mut self,
+ cx: &T,
+ item: Span,
+ msg: &str,
+ attr: &D,
+ applicability: Applicability,
+ ) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
+ }
+ }
+
+ fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
+ if let Some(indent) = indentation(cx, item) {
+ let span = item.with_hi(item.lo());
+
+ let mut first = true;
+ let new_item = new_item
+ .lines()
+ .map(|l| {
+ if first {
+ first = false;
+ format!("{}\n", l)
+ } else {
+ format!("{}{}\n", indent, l)
+ }
+ })
+ .collect::<String>();
+
+ self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
+ }
+ }
+
+ fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
+ let mut remove_span = item;
+ let hi = cx.sess().source_map().next_point(remove_span).hi();
+ let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
+
+ if let Some(ref src) = fmpos.sf.src {
+ let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
+
+ if let Some(non_whitespace_offset) = non_whitespace_offset {
+ remove_span = remove_span
+ .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")));
+ }
+ }
+
+ self.span_suggestion(remove_span, msg, String::new(), applicability);
+ }
+}
+
+/// Suggestion results for handling closure
+/// args dereferencing and borrowing
+pub struct DerefClosure {
+ /// confidence on the built suggestion
+ pub applicability: Applicability,
+ /// gradually built suggestion
+ pub suggestion: String,
+}
+
+/// Build suggestion gradually by handling closure arg specific usages,
+/// such as explicit deref and borrowing cases.
+/// Returns `None` if no such use cases have been triggered in closure body
+///
+/// note: this only works on single line immutable closures with exactly one input parameter.
+pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option<DerefClosure> {
+ if let hir::ExprKind::Closure(_, fn_decl, body_id, ..) = closure.kind {
+ let closure_body = cx.tcx.hir().body(body_id);
+ // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`)
+ // a type annotation is present if param `kind` is different from `TyKind::Infer`
+ let closure_arg_is_type_annotated_double_ref = if let TyKind::Rptr(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
+ {
+ matches!(ty.kind, TyKind::Rptr(_, MutTy { .. }))
+ } else {
+ false
+ };
+
+ let mut visitor = DerefDelegate {
+ cx,
+ closure_span: closure.span,
+ closure_arg_is_type_annotated_double_ref,
+ next_pos: closure.span.lo(),
+ suggestion_start: String::new(),
+ applicability: Applicability::MachineApplicable,
+ };
+
+ let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
+ .consume_body(closure_body);
+ });
+
+ if !visitor.suggestion_start.is_empty() {
+ return Some(DerefClosure {
+ applicability: visitor.applicability,
+ suggestion: visitor.finish(),
+ });
+ }
+ }
+ None
+}
+
+/// Visitor struct used for tracking down
+/// dereferencing and borrowing of closure's args
+struct DerefDelegate<'a, 'tcx> {
+ /// The late context of the lint
+ cx: &'a LateContext<'tcx>,
+ /// The span of the input closure to adapt
+ closure_span: Span,
+ /// Indicates if the arg of the closure is a type annotated double reference
+ closure_arg_is_type_annotated_double_ref: bool,
+ /// last position of the span to gradually build the suggestion
+ next_pos: BytePos,
+ /// starting part of the gradually built suggestion
+ suggestion_start: String,
+ /// confidence on the built suggestion
+ applicability: Applicability,
+}
+
+impl<'tcx> DerefDelegate<'_, 'tcx> {
+ /// build final suggestion:
+ /// - create the ending part of suggestion
+ /// - concatenate starting and ending parts
+ /// - potentially remove needless borrowing
+ pub fn finish(&mut self) -> String {
+ let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None);
+ let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability);
+ let sugg = format!("{}{}", self.suggestion_start, end_snip);
+ if self.closure_arg_is_type_annotated_double_ref {
+ sugg.replacen('&', "", 1)
+ } else {
+ sugg
+ }
+ }
+
+ /// indicates whether the function from `parent_expr` takes its args by double reference
+ fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
+ let (call_args, inputs) = match parent_expr.kind {
+ ExprKind::MethodCall(_, call_args, _) => {
+ if let Some(method_did) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) {
+ (call_args, self.cx.tcx.fn_sig(method_did).skip_binder().inputs())
+ } else {
+ return false;
+ }
+ },
+ ExprKind::Call(func, call_args) => {
+ let typ = self.cx.typeck_results().expr_ty(func);
+ (call_args, typ.fn_sig(self.cx.tcx).skip_binder().inputs())
+ },
+ _ => return false,
+ };
+
+ iter::zip(call_args, inputs)
+ .any(|(arg, ty)| arg.hir_id == cmt_hir_id && matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
+ if let PlaceBase::Local(id) = cmt.place.base {
+ let map = self.cx.tcx.hir();
+ let span = map.span(cmt.hir_id);
+ let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
+ let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
+
+ // identifier referring to the variable currently triggered (i.e.: `fp`)
+ let ident_str = map.name(id).to_string();
+ // full identifier that includes projection (i.e.: `fp.field`)
+ let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
+
+ if cmt.place.projections.is_empty() {
+ // handle item without any projection, that needs an explicit borrowing
+ // i.e.: suggest `&x` instead of `x`
+ let _ = write!(self.suggestion_start, "{}&{}", start_snip, ident_str);
+ } else {
+ // cases where a parent `Call` or `MethodCall` is using the item
+ // i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()`
+ //
+ // Note about method calls:
+ // - compiler automatically dereference references if the target type is a reference (works also for
+ // function call)
+ // - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and
+ // no projection should be suggested
+ if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) {
+ match &parent_expr.kind {
+ // given expression is the self argument and will be handled completely by the compiler
+ // i.e.: `|x| x.is_something()`
+ ExprKind::MethodCall(_, [self_expr, ..], _) if self_expr.hir_id == cmt.hir_id => {
+ let _ = write!(self.suggestion_start, "{}{}", start_snip, ident_str_with_proj);
+ self.next_pos = span.hi();
+ return;
+ },
+ // item is used in a call
+ // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
+ ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, [_, call_args @ ..], _) => {
+ let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
+ let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
+
+ if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
+ // suggest ampersand if call function is taking args by double reference
+ let takes_arg_by_double_ref =
+ self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id);
+
+ // compiler will automatically dereference field or index projection, so no need
+ // to suggest ampersand, but full identifier that includes projection is required
+ let has_field_or_index_projection =
+ cmt.place.projections.iter().any(|proj| {
+ matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index)
+ });
+
+ // no need to bind again if the function doesn't take arg by double ref
+ // and if the item is already a double ref
+ let ident_sugg = if !call_args.is_empty()
+ && !takes_arg_by_double_ref
+ && (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection)
+ {
+ let ident = if has_field_or_index_projection {
+ ident_str_with_proj
+ } else {
+ ident_str
+ };
+ format!("{}{}", start_snip, ident)
+ } else {
+ format!("{}&{}", start_snip, ident_str)
+ };
+ self.suggestion_start.push_str(&ident_sugg);
+ self.next_pos = span.hi();
+ return;
+ }
+
+ self.applicability = Applicability::Unspecified;
+ },
+ _ => (),
+ }
+ }
+
+ let mut replacement_str = ident_str;
+ let mut projections_handled = false;
+ cmt.place.projections.iter().enumerate().for_each(|(i, proj)| {
+ match proj.kind {
+ // Field projection like `|v| v.foo`
+ // no adjustment needed here, as field projections are handled by the compiler
+ ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() {
+ ty::Adt(..) | ty::Tuple(_) => {
+ replacement_str = ident_str_with_proj.clone();
+ projections_handled = true;
+ },
+ _ => (),
+ },
+ // Index projection like `|x| foo[x]`
+ // the index is dropped so we can't get it to build the suggestion,
+ // so the span is set-up again to get more code, using `span.hi()` (i.e.: `foo[x]`)
+ // instead of `span.lo()` (i.e.: `foo`)
+ ProjectionKind::Index => {
+ let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None);
+ start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
+ replacement_str.clear();
+ projections_handled = true;
+ },
+ // note: unable to trigger `Subslice` kind in tests
+ ProjectionKind::Subslice => (),
+ ProjectionKind::Deref => {
+ // Explicit derefs are typically handled later on, but
+ // some items do not need explicit deref, such as array accesses,
+ // so we mark them as already processed
+ // i.e.: don't suggest `*sub[1..4].len()` for `|sub| sub[1..4].len() == 3`
+ if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() {
+ if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) {
+ projections_handled = true;
+ }
+ }
+ },
+ }
+ });
+
+ // handle `ProjectionKind::Deref` by removing one explicit deref
+ // if no special case was detected (i.e.: suggest `*x` instead of `**x`)
+ if !projections_handled {
+ let last_deref = cmt
+ .place
+ .projections
+ .iter()
+ .rposition(|proj| proj.kind == ProjectionKind::Deref);
+
+ if let Some(pos) = last_deref {
+ let mut projections = cmt.place.projections.clone();
+ projections.truncate(pos);
+
+ for item in projections {
+ if item.kind == ProjectionKind::Deref {
+ replacement_str = format!("*{}", replacement_str);
+ }
+ }
+ }
+ }
+
+ let _ = write!(self.suggestion_start, "{}{}", start_snip, replacement_str);
+ }
+ self.next_pos = span.hi();
+ }
+ }
+
+ fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn fake_read(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+#[cfg(test)]
+mod test {
+ use super::Sugg;
+
+ use rustc_ast::util::parser::AssocOp;
+ use std::borrow::Cow;
+
+ const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
+
+ #[test]
+ fn make_return_transform_sugg_into_a_return_call() {
+ assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
+ }
+
+ #[test]
+ fn blockify_transforms_sugg_into_a_block() {
+ assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
+ }
+
+ #[test]
+ fn binop_maybe_par() {
+ let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into());
+ assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
+
+ let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into());
+ assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
+ }
+ #[test]
+ fn not_op() {
+ use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual};
+
+ fn test_not(op: AssocOp, correct: &str) {
+ let sugg = Sugg::BinOp(op, "x".into(), "y".into());
+ assert_eq!((!sugg).to_string(), correct);
+ }
+
+ // Invert the comparison operator.
+ test_not(Equal, "x != y");
+ test_not(NotEqual, "x == y");
+ test_not(Less, "x >= y");
+ test_not(LessEqual, "x > y");
+ test_not(Greater, "x <= y");
+ test_not(GreaterEqual, "x < y");
+
+ // Other operators are inverted like !(..).
+ test_not(Add, "!(x + y)");
+ test_not(LAnd, "!(x && y)");
+ test_not(LOr, "!(x || y)");
+ }
+}
--- /dev/null
- #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
+//! Util methods for [`rustc_middle::ty`]
+
+#![allow(clippy::module_name_repetitions)]
+
+use rustc_ast::ast::Mutability;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, LangItem, TyKind, Unsafety};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::mir::interpret::{ConstValue, Scalar};
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
+use rustc_middle::ty::{
+ self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, VariantDiscr,
+};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span, Symbol, DUMMY_SP};
+use rustc_target::abi::{Size, VariantIdx};
+use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::query::normalize::AtExt;
+use std::iter;
+
+use crate::{match_def_path, path_res, paths};
+
+// Checks if the given type implements copy.
+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 whether a type can be partially moved.
+pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if has_drop(cx, ty) || is_copy(cx, ty) {
+ return false;
+ }
+ match ty.kind() {
+ ty::Param(_) => false,
+ ty::Adt(def, subs) => def.all_fields().any(|f| !is_copy(cx, f.ty(cx.tcx, subs))),
+ _ => true,
+ }
+}
+
+/// 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) => other_ty == inner_ty,
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
+/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
+/// constructor.
+pub fn contains_adt_constructor(ty: Ty<'_>, adt: AdtDef<'_>) -> bool {
+ ty.walk().any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
+/// Resolves `<T as Iterator>::Item` for `T`
+/// Do not invoke without first verifying that the type implements `Iterator`
+pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ cx.tcx
+ .get_diagnostic_item(sym::Iterator)
+ .and_then(|iter_did| get_associated_type(cx, ty, iter_did, "Item"))
+}
+
+/// Returns the associated type `name` for `ty` as an implementation of `trait_id`.
+/// Do not invoke without first verifying that the type implements the trait.
+pub fn get_associated_type<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ name: &str,
+) -> Option<Ty<'tcx>> {
+ cx.tcx
+ .associated_items(trait_id)
+ .find_by_name_and_kind(cx.tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id)
+ .map(|assoc| {
+ let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[]));
+ cx.tcx.normalize_erasing_regions(cx.param_env, proj)
+ })
+}
+
+/// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type
+/// implements a trait marked with a diagnostic item use [`implements_trait`].
+///
+/// For a further exploitation what diagnostic items are see [diagnostic items] in
+/// rustc-dev-guide.
+///
+/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
+pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> {
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.get_diagnostic_name(adt.did()),
+ _ => None,
+ }
+}
+
+/// Returns true if ty has `iter` or `iter_mut` methods
+pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
+ // 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: &[Symbol] = &[
+ sym::Vec,
+ sym::Option,
+ sym::Result,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::BinaryHeap,
+ sym::HashSet,
+ sym::HashMap,
+ sym::PathBuf,
+ sym::Path,
+ sym::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(sym::array),
+ ty::Slice(..) => return Some(sym::slice),
+ ty::Adt(adt, _) => adt.did(),
+ _ => return None,
+ };
+
+ for &name in into_iter_collections {
+ if cx.tcx.is_diagnostic_item(name, def_id) {
+ return Some(cx.tcx.item_name(def_id));
+ }
+ }
+ None
+}
+
+/// Checks whether a type implements a trait.
+/// The function returns false in case the type contains an inference variable.
+///
+/// See:
+/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
+/// * [Common tools for writing lints] for an example how to use this function and other options.
+///
+/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
+pub fn implements_trait<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ // Clippy shouldn't have infer types
+ assert!(!ty.needs_infer());
+
+ 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.infer_ctxt().enter(|infcx| {
+ infcx
+ .type_implements_trait(trait_id, ty, ty_params, cx.param_env)
+ .must_apply_modulo_regions()
+ })
+}
+
+/// 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 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(adt, _) => cx.tcx.has_attr(adt.did(), sym::must_use),
+ ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use),
+ ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::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(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)),
+ ty::Opaque(def_id, _) => {
+ for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
+ if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() {
+ if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ ty::Dynamic(binder, _) => {
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
+ if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize
+// this function can be removed once the `normalize` method does not panic when normalization does
+// not succeed
+/// 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 {
+ is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default())
+}
+
+fn is_normalizable_helper<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ ty: Ty<'tcx>,
+ cache: &mut FxHashMap<Ty<'tcx>, bool>,
+) -> bool {
+ if let Some(&cached_result) = cache.get(&ty) {
+ return cached_result;
+ }
+ // prevent recursive loops, false-negative is better than endless loop leading to stack overflow
+ cache.insert(ty, false);
+ let result = cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ if infcx.at(&cause, param_env).normalize(ty).is_ok() {
+ match ty.kind() {
+ ty::Adt(def, substs) => def.variants().iter().all(|variant| {
+ variant
+ .fields
+ .iter()
+ .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache))
+ }),
+ _ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
+ GenericArgKind::Type(inner_ty) if inner_ty != ty => {
+ is_normalizable_helper(cx, param_env, inner_ty, cache)
+ },
+ _ => true, // if inner_ty == ty, we've already checked it
+ }),
+ }
+ } else {
+ false
+ }
+ });
+ cache.insert(ty, result);
+ result
+}
+
+/// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any
+/// integer or floating-point number type). For checking aggregation of primitive types (e.g.
+/// tuples and slices of primitive type) see `is_recursively_primitive_type`
+pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool {
+ matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_))
+}
+
+/// Returns `true` if 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.iter().all(is_recursively_primitive_type),
+ _ => false,
+ }
+}
+
+/// Checks if the type is a reference equals to a diagnostic item
+pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
+ match ty.kind() {
+ ty::Ref(_, ref_ty, _) => match ref_ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
+ _ => false,
+ },
+ _ => false,
+ }
+}
+
+/// Checks if the type is equal to a diagnostic item. To check if a type implements a
+/// trait marked with a diagnostic item use [`implements_trait`].
+///
+/// For a further exploitation what diagnostic items are see [diagnostic items] in
+/// rustc-dev-guide.
+///
+/// ---
+///
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
+///
+/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
+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.
+///
+/// Returns `false` if the `LangItem` is not defined.
+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)
+ .map_or(false, |li| li == adt.did()),
+ _ => false,
+ }
+}
+
+/// Return `true` if the passed `typ` is `isize` or `usize`.
+pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
+ matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+}
+
+/// 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 drop order for a type matters. Some std types implement drop solely to
+/// deallocate memory. For these types, and composites containing them, changing the drop order
+/// won't result in any observable side effects.
+pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ fn needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
+ if !seen.insert(ty) {
+ return false;
+ }
+ if !ty.has_significant_drop(cx.tcx, cx.param_env) {
+ false
+ }
+ // Check for std types which implement drop, but only for memory allocation.
+ else if is_type_lang_item(cx, ty, LangItem::OwnedBox)
+ || matches!(
+ get_type_diagnostic_name(cx, ty),
+ Some(sym::HashSet | sym::Rc | sym::Arc | sym::cstring_type)
+ )
+ || match_type(cx, ty, &paths::WEAK_RC)
+ || match_type(cx, ty, &paths::WEAK_ARC)
+ {
+ // Check all of the generic arguments.
+ if let ty::Adt(_, subs) = ty.kind() {
+ subs.types().any(|ty| needs_ordered_drop_inner(cx, ty, seen))
+ } else {
+ true
+ }
+ } else if !cx
+ .tcx
+ .lang_items()
+ .drop_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+ {
+ // This type doesn't implement drop, so no side effects here.
+ // Check if any component type has any.
+ match ty.kind() {
+ ty::Tuple(fields) => fields.iter().any(|ty| needs_ordered_drop_inner(cx, ty, seen)),
+ ty::Array(ty, _) => needs_ordered_drop_inner(cx, *ty, seen),
+ ty::Adt(adt, subs) => adt
+ .all_fields()
+ .map(|f| f.ty(cx.tcx, subs))
+ .any(|ty| needs_ordered_drop_inner(cx, ty, seen)),
+ _ => true,
+ }
+ } else {
+ true
+ }
+ }
+
+ needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
+}
+
+/// 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)
+}
+
+/// 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,
+ }
+}
+
+/// 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)
+}
+
+/// Returns `true` if types `a` and `b` are same types having same `Const` generic args,
+/// otherwise returns `false`
+pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
+ match (&a.kind(), &b.kind()) {
+ (&ty::Adt(did_a, substs_a), &ty::Adt(did_b, substs_b)) => {
+ if did_a != did_b {
+ return false;
+ }
+
+ substs_a
+ .iter()
+ .zip(substs_b.iter())
+ .all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) {
+ (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b,
+ (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => {
+ same_type_and_consts(type_a, type_b)
+ },
+ _ => true,
+ })
+ },
+ _ => a == b,
+ }
+}
+
+/// Checks if a given type looks safe to be uninitialized.
+pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ match *ty.kind() {
+ ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
+ ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
+ ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()),
+ _ => false,
+ }
+}
+
+/// Gets an iterator over all predicates which apply to the given item.
+pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(Predicate<'_>, Span)> {
+ let mut next_id = Some(id);
+ iter::from_fn(move || {
+ next_id.take().map(|id| {
+ let preds = tcx.predicates_of(id);
+ next_id = preds.parent;
+ preds.predicates.iter()
+ })
+ })
+ .flatten()
+}
+
+/// A signature for a function like type.
+#[derive(Clone, Copy)]
+pub enum ExprFnSig<'tcx> {
+ Sig(Binder<'tcx, FnSig<'tcx>>),
+ Closure(Binder<'tcx, FnSig<'tcx>>),
+ Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>),
+}
+impl<'tcx> ExprFnSig<'tcx> {
+ /// Gets the argument type at the given offset.
+ pub fn input(self, i: usize) -> Binder<'tcx, Ty<'tcx>> {
+ match self {
+ Self::Sig(sig) => sig.input(i),
+ Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
+ Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_fields()[i]),
+ }
+ }
+
+ /// Gets the result type, if one could be found. Note that the result type of a trait may not be
+ /// specified.
+ pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
+ match self {
+ Self::Sig(sig) | Self::Closure(sig) => Some(sig.output()),
+ Self::Trait(_, output) => output,
+ }
+ }
+}
+
+/// If the expression is function like, get the signature for it.
+pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> {
+ if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) {
+ Some(ExprFnSig::Sig(cx.tcx.fn_sig(id)))
+ } else {
+ let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs();
+ match *ty.kind() {
+ ty::Closure(_, subs) => Some(ExprFnSig::Closure(subs.as_closure().sig())),
+ ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))),
+ ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)),
+ ty::Dynamic(bounds, _) => {
+ let lang_items = cx.tcx.lang_items();
+ match bounds.principal() {
+ Some(bound)
+ if Some(bound.def_id()) == lang_items.fn_trait()
+ || Some(bound.def_id()) == lang_items.fn_once_trait()
+ || Some(bound.def_id()) == lang_items.fn_mut_trait() =>
+ {
+ let output = bounds
+ .projection_bounds()
+ .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
+ .map(|p| p.map_bound(|p| p.term.ty().expect("return type was a const")));
+ Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output))
+ },
+ _ => None,
+ }
+ },
+ ty::Param(_) | ty::Projection(..) => {
+ let mut inputs = None;
+ let mut output = None;
+ let lang_items = cx.tcx.lang_items();
+
+ for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) {
+ let mut is_input = false;
+ if let Some(ty) = pred
+ .kind()
+ .map_bound(|pred| match pred {
+ PredicateKind::Trait(p)
+ if (lang_items.fn_trait() == Some(p.def_id())
+ || lang_items.fn_mut_trait() == Some(p.def_id())
+ || lang_items.fn_once_trait() == Some(p.def_id()))
+ && p.self_ty() == ty =>
+ {
+ is_input = true;
+ Some(p.trait_ref.substs.type_at(1))
+ },
+ PredicateKind::Projection(p)
+ if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
+ && p.projection_ty.self_ty() == ty =>
+ {
+ is_input = false;
+ p.term.ty()
+ },
+ _ => None,
+ })
+ .transpose()
+ {
+ if is_input && inputs.is_none() {
+ inputs = Some(ty);
+ } else if !is_input && output.is_none() {
+ output = Some(ty);
+ } else {
+ // Multiple different fn trait impls. Is this even allowed?
+ return None;
+ }
+ }
+ }
+
+ inputs.map(|ty| ExprFnSig::Trait(ty, output))
+ },
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum EnumValue {
+ Unsigned(u128),
+ Signed(i128),
+}
+impl core::ops::Add<u32> for EnumValue {
+ type Output = Self;
+ fn add(self, n: u32) -> Self::Output {
+ match self {
+ Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)),
+ Self::Signed(x) => Self::Signed(x + i128::from(n)),
+ }
+ }
+}
+
+/// Attempts to read the given constant as though it were an an enum value.
++#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
+pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
+ if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
+ match tcx.type_of(id).kind() {
+ ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() {
+ 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
+ 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
+ 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
+ 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
+ 16 => value.assert_bits(Size::from_bytes(16)) as i128,
+ _ => return None,
+ })),
+ ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() {
+ 1 => value.assert_bits(Size::from_bytes(1)),
+ 2 => value.assert_bits(Size::from_bytes(2)),
+ 4 => value.assert_bits(Size::from_bytes(4)),
+ 8 => value.assert_bits(Size::from_bytes(8)),
+ 16 => value.assert_bits(Size::from_bytes(16)),
+ _ => return None,
+ })),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
+
+/// Gets the value of the given variant.
+pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: AdtDef<'_>, i: VariantIdx) -> EnumValue {
+ let variant = &adt.variant(i);
+ match variant.discr {
+ VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(),
+ VariantDiscr::Relative(x) => match adt.variant((i.as_usize() - x as usize).into()).discr {
+ VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x,
+ VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()),
+ },
+ }
+}
+
+/// Check if the given type is either `core::ffi::c_void`, `std::os::raw::c_void`, or one of the
+/// platform specific `libc::<platform>::c_void` types in libc.
+pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ if let ty::Adt(adt, _) = ty.kind()
+ && let &[krate, .., name] = &*cx.get_def_path(adt.did())
+ && let sym::libc | sym::core | sym::std = krate
+ && name.as_str() == "c_void"
+ {
+ true
+ } else {
+ false
+ }
+}
--- /dev/null
- #[allow(clippy::similar_names)]
+use crate as utils;
+use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirIdSet;
+use rustc_hir::{Expr, ExprKind, HirId, Node};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty;
+use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+
+/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
+pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
+ let mut delegate = MutVarsDelegate {
+ used_mutably: HirIdSet::default(),
+ skip: false,
+ };
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ expr.hir_id.owner,
+ cx.param_env,
+ cx.typeck_results(),
+ )
+ .walk_expr(expr);
+ });
+
+ if delegate.skip {
+ return None;
+ }
+ Some(delegate.used_mutably)
+}
+
+pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
+ mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
+}
+
+struct MutVarsDelegate {
+ used_mutably: HirIdSet,
+ skip: bool,
+}
+
+impl<'tcx> MutVarsDelegate {
+ fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
+ match cat.place.base {
+ PlaceBase::Local(id) => {
+ self.used_mutably.insert(id);
+ },
+ PlaceBase::Upvar(_) => {
+ //FIXME: This causes false negatives. We can't get the `NodeId` from
+ //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
+ //`while`-body, not just the ones in the condition.
+ self.skip = true;
+ },
+ _ => {},
+ }
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
+ if bk == ty::BorrowKind::MutBorrow {
+ self.update(cmt);
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ self.update(cmt);
+ }
+
+ fn fake_read(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+pub struct ParamBindingIdCollector {
+ pub binding_hir_ids: Vec<hir::HirId>,
+}
+impl<'tcx> ParamBindingIdCollector {
+ fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
+ let mut hir_ids: Vec<hir::HirId> = Vec::new();
+ for param in body.params.iter() {
+ let mut finder = ParamBindingIdCollector {
+ binding_hir_ids: Vec::new(),
+ };
+ finder.visit_param(param);
+ for hir_id in &finder.binding_hir_ids {
+ hir_ids.push(*hir_id);
+ }
+ }
+ hir_ids
+ }
+}
+impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
+ self.binding_hir_ids.push(hir_id);
+ }
+ intravisit::walk_pat(self, pat);
+ }
+}
+
+pub struct BindingUsageFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ binding_ids: Vec<hir::HirId>,
+ usage_found: bool,
+}
+impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
+ pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
+ let mut finder = BindingUsageFinder {
+ cx,
+ binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
+ usage_found: false,
+ };
+ finder.visit_body(body);
+ finder.usage_found
+ }
+}
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if !self.usage_found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ if let hir::def::Res::Local(id) = path.res {
+ if self.binding_ids.contains(&id) {
+ self.usage_found = true;
+ }
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
+ let mut seen_return_break_continue = false;
+ expr_visitor_no_bodies(|ex| {
+ if seen_return_break_continue {
+ return false;
+ }
+ match &ex.kind {
+ ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
+ seen_return_break_continue = true;
+ },
+ // Something special could be done here to handle while or for loop
+ // desugaring, as this will detect a break if there's a while loop
+ // or a for loop inside the expression.
+ _ => {
+ if ex.span.from_expansion() {
+ seen_return_break_continue = true;
+ }
+ },
+ }
+ !seen_return_break_continue
+ })
+ .visit_expr(expression);
+ seen_return_break_continue
+}
+
+pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
+ let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false };
+
+ // for _ in 1..3 {
+ // local
+ // }
+ //
+ // let closure = || local;
+ // closure();
+ // closure();
+ let in_loop_or_closure = cx
+ .tcx
+ .hir()
+ .parent_iter(after.hir_id)
+ .take_while(|&(id, _)| id != block.hir_id)
+ .any(|(_, node)| {
+ matches!(
+ node,
+ Node::Expr(Expr {
+ kind: ExprKind::Loop(..) | ExprKind::Closure(..),
+ ..
+ })
+ )
+ });
+ if in_loop_or_closure {
+ return true;
+ }
+
+ let mut used_after_expr = false;
+ let mut past_expr = false;
+ expr_visitor(cx, |expr| {
+ if used_after_expr {
+ return false;
+ }
+
+ if expr.hir_id == after.hir_id {
+ past_expr = true;
+ return false;
+ }
+
+ if past_expr && utils::path_to_local_id(expr, local_id) {
+ used_after_expr = true;
+ }
+ !used_after_expr
+ })
+ .visit_block(block);
+ used_after_expr
+}
--- /dev/null
- use crate::path_to_local_id;
++use crate::{get_enclosing_block, path_to_local_id};
+use core::ops::ControlFlow;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
+use rustc_hir::{
+ Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource,
+ Unsafety,
+};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty;
+
+/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
+/// bodies (i.e. closures) are visited.
+/// If the callback returns `true`, the expr just provided to the callback is walked.
+#[must_use]
+pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
+ struct V<'tcx, F> {
+ hir: Map<'tcx>,
+ f: F,
+ }
+ impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.hir
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ if (self.f)(expr) {
+ walk_expr(self, expr);
+ }
+ }
+ }
+ V { hir: cx.tcx.hir(), f }
+}
+
+/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
+/// bodies (i.e. closures) are not visited.
+/// If the callback returns `true`, the expr just provided to the callback is walked.
+#[must_use]
+pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
+ struct V<F>(F);
+ impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if (self.0)(e) {
+ walk_expr(self, e);
+ }
+ }
+ }
+ V(f)
+}
+
+/// returns `true` if expr contains match expr desugared from try
+fn contains_try(expr: &hir::Expr<'_>) -> bool {
+ let mut found = false;
+ expr_visitor_no_bodies(|e| {
+ if !found {
+ found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
+ }
+ !found
+ })
+ .visit_expr(expr);
+ found
+}
+
+pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
+where
+ F: FnMut(&'hir hir::Expr<'hir>) -> bool,
+{
+ struct RetFinder<F> {
+ in_stmt: bool,
+ failed: bool,
+ cb: F,
+ }
+
+ struct WithStmtGuarg<'a, F> {
+ val: &'a mut RetFinder<F>,
+ prev_in_stmt: bool,
+ }
+
+ impl<F> RetFinder<F> {
+ fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
+ let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
+ WithStmtGuarg {
+ val: self,
+ prev_in_stmt,
+ }
+ }
+ }
+
+ impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
+ type Target = RetFinder<F>;
+
+ fn deref(&self) -> &Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> Drop for WithStmtGuarg<'_, F> {
+ fn drop(&mut self) {
+ self.val.in_stmt = self.prev_in_stmt;
+ }
+ }
+
+ impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
+ fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
+ intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
+ if self.failed {
+ return;
+ }
+ if self.in_stmt {
+ match expr.kind {
+ hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
+ _ => intravisit::walk_expr(self, expr),
+ }
+ } else {
+ match expr.kind {
+ hir::ExprKind::If(cond, then, else_opt) => {
+ self.inside_stmt(true).visit_expr(cond);
+ self.visit_expr(then);
+ if let Some(el) = else_opt {
+ self.visit_expr(el);
+ }
+ },
+ hir::ExprKind::Match(cond, arms, _) => {
+ self.inside_stmt(true).visit_expr(cond);
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
+ hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
+ _ => self.failed |= !(self.cb)(expr),
+ }
+ }
+ }
+ }
+
+ !contains_try(expr) && {
+ let mut ret_finder = RetFinder {
+ in_stmt: false,
+ failed: false,
+ cb: callback,
+ };
+ ret_finder.visit_expr(expr);
+ !ret_finder.failed
+ }
+}
+
+/// A type which can be visited.
+pub trait Visitable<'tcx> {
+ /// Calls the corresponding `visit_*` function on the visitor.
+ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
+}
+macro_rules! visitable_ref {
+ ($t:ident, $f:ident) => {
+ impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
+ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+ visitor.$f(self);
+ }
+ }
+ };
+}
+visitable_ref!(Arm, visit_arm);
+visitable_ref!(Block, visit_block);
+visitable_ref!(Body, visit_body);
+visitable_ref!(Expr, visit_expr);
+visitable_ref!(Stmt, visit_stmt);
+
+// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
+// where
+// I::Item: Visitable<'tcx>,
+// {
+// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+// for x in self {
+// x.visit(visitor);
+// }
+// }
+// }
+
+/// Checks if the given resolved path is used in the given body.
+pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
+ let mut found = false;
+ expr_visitor(cx, |e| {
+ if found {
+ return false;
+ }
+
+ if let ExprKind::Path(p) = &e.kind {
+ if cx.qpath_res(p, e.hir_id) == res {
+ found = true;
+ }
+ }
+ !found
+ })
+ .visit_expr(&cx.tcx.hir().body(body).value);
+ found
+}
+
+/// Checks if the given local is used.
+pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
+ let mut is_used = false;
+ let mut visitor = expr_visitor(cx, |expr| {
+ if !is_used {
+ is_used = path_to_local_id(expr, id);
+ }
+ !is_used
+ });
+ visitable.visit(&mut visitor);
+ drop(visitor);
+ is_used
+}
+
+/// Checks if the given expression is a constant.
+pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_const: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.is_const {
+ return;
+ }
+ match e.kind {
+ ExprKind::ConstBlock(_) => return,
+ ExprKind::Call(
+ &Expr {
+ kind: ExprKind::Path(ref p),
+ hir_id,
+ ..
+ },
+ _,
+ ) if self
+ .cx
+ .qpath_res(p, hir_id)
+ .opt_def_id()
+ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+ ExprKind::MethodCall(..)
+ if self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+ ExprKind::Binary(_, lhs, rhs)
+ if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
+ && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
+ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
+ ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
+ ExprKind::Index(base, _)
+ if matches!(
+ self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
+ ty::Slice(_) | ty::Array(..)
+ ) => {},
+ ExprKind::Path(ref p)
+ if matches!(
+ self.cx.qpath_res(p, e.hir_id),
+ Res::Def(
+ DefKind::Const
+ | DefKind::AssocConst
+ | DefKind::AnonConst
+ | DefKind::ConstParam
+ | DefKind::Ctor(..)
+ | DefKind::Fn
+ | DefKind::AssocFn,
+ _
+ ) | Res::SelfCtor(_)
+ ) => {},
+
+ ExprKind::AddrOf(..)
+ | ExprKind::Array(_)
+ | ExprKind::Block(..)
+ | ExprKind::Cast(..)
+ | ExprKind::DropTemps(_)
+ | ExprKind::Field(..)
+ | ExprKind::If(..)
+ | ExprKind::Let(..)
+ | ExprKind::Lit(_)
+ | ExprKind::Match(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Tup(_)
+ | ExprKind::Type(..) => (),
+
+ _ => {
+ self.is_const = false;
+ return;
+ },
+ }
+ walk_expr(self, e);
+ }
+ }
+
+ let mut v = V { cx, is_const: true };
+ v.visit_expr(e);
+ v.is_const
+}
+
+/// Checks if the given expression performs an unsafe operation outside of an unsafe block.
+pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_unsafe: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.is_unsafe {
+ return;
+ }
+ match e.kind {
+ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
+ self.is_unsafe = true;
+ },
+ ExprKind::MethodCall(..)
+ if self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
+ {
+ self.is_unsafe = true;
+ },
+ ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
+ ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
+ ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
+ _ => walk_expr(self, e),
+ },
+ ExprKind::Path(ref p)
+ if self
+ .cx
+ .qpath_res(p, e.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
+ {
+ self.is_unsafe = true;
+ },
+ _ => walk_expr(self, e),
+ }
+ }
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
+ walk_block(self, b);
+ }
+ }
+ fn visit_nested_item(&mut self, id: ItemId) {
+ if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
+ self.is_unsafe = i.unsafety == Unsafety::Unsafe;
+ }
+ }
+ }
+ let mut v = V { cx, is_unsafe: false };
+ v.visit_expr(e);
+ v.is_unsafe
+}
+
+/// Checks if the given expression contains an unsafe block
+pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ found_unsafe: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ if self.found_unsafe {
+ return;
+ }
+ if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
+ self.found_unsafe = true;
+ return;
+ }
+ walk_block(self, b);
+ }
+ }
+ let mut v = V {
+ cx,
+ found_unsafe: false,
+ };
+ v.visit_expr(e);
+ v.found_unsafe
+}
+
+/// Runs the given function for each sub-expression producing the final value consumed by the parent
+/// of the give expression.
+///
+/// e.g. for the following expression
+/// ```rust,ignore
+/// if foo {
+/// f(0)
+/// } else {
+/// 1 + 1
+/// }
+/// ```
+/// this will pass both `f(0)` and `1+1` to the given function.
+pub fn for_each_value_source<'tcx, B>(
+ e: &'tcx Expr<'tcx>,
+ f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ match e.kind {
+ ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f),
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ for_each_value_source(arm.body, f)?;
+ }
+ ControlFlow::Continue(())
+ },
+ ExprKind::If(_, if_expr, Some(else_expr)) => {
+ for_each_value_source(if_expr, f)?;
+ for_each_value_source(else_expr, f)
+ },
+ ExprKind::DropTemps(e) => for_each_value_source(e, f),
+ _ => f(e),
+ }
+}
++
++/// Runs the given function for each path expression referencing the given local which occur after
++/// the given expression.
++pub fn for_each_local_use_after_expr<'tcx, B>(
++ cx: &LateContext<'tcx>,
++ local_id: HirId,
++ expr_id: HirId,
++ f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
++) -> ControlFlow<B> {
++ struct V<'cx, 'tcx, F, B> {
++ cx: &'cx LateContext<'tcx>,
++ local_id: HirId,
++ expr_id: HirId,
++ found: bool,
++ res: ControlFlow<B>,
++ f: F,
++ }
++ impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
++ type NestedFilter = nested_filter::OnlyBodies;
++ fn nested_visit_map(&mut self) -> Self::Map {
++ self.cx.tcx.hir()
++ }
++
++ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
++ if !self.found {
++ if e.hir_id == self.expr_id {
++ self.found = true;
++ } else {
++ walk_expr(self, e);
++ }
++ return;
++ }
++ if self.res.is_break() {
++ return;
++ }
++ if path_to_local_id(e, self.local_id) {
++ self.res = (self.f)(e);
++ } else {
++ walk_expr(self, e);
++ }
++ }
++ }
++
++ if let Some(b) = get_enclosing_block(cx, local_id) {
++ let mut v = V {
++ cx,
++ local_id,
++ expr_id,
++ found: false,
++ res: ControlFlow::Continue(()),
++ f,
++ };
++ v.visit_block(b);
++ v.res
++ } else {
++ ControlFlow::Continue(())
++ }
++}
--- /dev/null
- - [Cheatsheet](#cheatsheet)
+# Adding a new lint
+
+You are probably here because you want to add a new lint to Clippy. If this is
+the first time you're contributing to Clippy, this document guides you through
+creating an example lint from scratch.
+
+To get started, we will create a lint that detects functions called `foo`,
+because that's clearly a non-descriptive name.
+
+- [Adding a new lint](#adding-a-new-lint)
+ - [Setup](#setup)
+ - [Getting Started](#getting-started)
+ - [Testing](#testing)
+ - [Cargo lints](#cargo-lints)
+ - [Rustfix tests](#rustfix-tests)
+ - [Edition 2018 tests](#edition-2018-tests)
+ - [Testing manually](#testing-manually)
+ - [Lint declaration](#lint-declaration)
+ - [Lint registration](#lint-registration)
+ - [Lint passes](#lint-passes)
+ - [Emitting a lint](#emitting-a-lint)
+ - [Adding the lint logic](#adding-the-lint-logic)
+ - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv)
+ - [Author lint](#author-lint)
+ - [Print HIR lint](#print-hir-lint)
+ - [Documentation](#documentation)
+ - [Running rustfmt](#running-rustfmt)
+ - [Debugging](#debugging)
+ - [PR Checklist](#pr-checklist)
+ - [Adding configuration to a lint](#adding-configuration-to-a-lint)
- if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) {
++ - [Cheat Sheet](#cheat-sheet)
+
+## Setup
+
+See the [Basics](basics.md#get-the-code) documentation.
+
+## Getting Started
+
+There is a bit of boilerplate code that needs to be set up when creating a new
+lint. Fortunately, you can use the clippy dev tools to handle this for you. We
+are naming our new lint `foo_functions` (lints are generally written in snake
+case), and we don't need type information so it will have an early pass type
+(more on this later on). If you're not sure if the name you chose fits the lint,
+take a look at our [lint naming guidelines][lint_naming]. To get started on this
+lint you can run `cargo dev new_lint --name=foo_functions --pass=early
+--category=pedantic` (category will default to nursery if not provided). This
+command will create two files: `tests/ui/foo_functions.rs` and
+`clippy_lints/src/foo_functions.rs`, as well as
+[registering the lint](#lint-registration). For cargo lints, two project
+hierarchies (fail/pass) will be created by default under `tests/ui-cargo`.
+
+Next, we'll open up these files and add our lint!
+
+## Testing
+
+Let's write some tests first that we can execute while we iterate on our lint.
+
+Clippy uses UI tests for testing. UI tests check that the output of Clippy is
+exactly as expected. Each test is just a plain Rust file that contains the code
+we want to check. The output of Clippy is compared against a `.stderr` file.
+Note that you don't have to create this file yourself, we'll get to
+generating the `.stderr` files further down.
+
+We start by opening the test file created at `tests/ui/foo_functions.rs`.
+
+Update the file with some examples to get started:
+
+```rust
+#![warn(clippy::foo_functions)]
+
+// Impl methods
+struct A;
+impl A {
+ pub fn fo(&self) {}
+ pub fn foo(&self) {}
+ pub fn food(&self) {}
+}
+
+// Default trait methods
+trait B {
+ fn fo(&self) {}
+ fn foo(&self) {}
+ fn food(&self) {}
+}
+
+// Plain functions
+fn fo() {}
+fn foo() {}
+fn food() {}
+
+fn main() {
+ // We also don't want to lint method calls
+ foo();
+ let a = A;
+ a.foo();
+}
+```
+
+Now we can run the test with `TESTNAME=foo_functions cargo uitest`,
+currently this test is meaningless though.
+
+While we are working on implementing our lint, we can keep running the UI
+test. That allows us to check if the output is turning into what we want.
+
+Once we are satisfied with the output, we need to run
+`cargo dev bless` to update the `.stderr` file for our lint.
+Please note that, we should run `TESTNAME=foo_functions cargo uitest`
+every time before running `cargo dev bless`.
+Running `TESTNAME=foo_functions cargo uitest` should pass then. When we commit
+our lint, we need to commit the generated `.stderr` files, too. In general, you
+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
+the `Cargo.toml` manifest file. We also need a minimal crate associated
+with that manifest.
+
+If our new lint is named e.g. `foo_categories`, after running `cargo dev new_lint`
+we will find by default two new crates, each with its manifest file:
+
+* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the new lint to raise an error.
+* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger the lint.
+
+If you need more cases, you can copy one of those crates (under `foo_categories`) and rename it.
+
+The process of generating the `.stderr` file is the same, and prepending the `TESTNAME`
+variable to `cargo uitest` works too.
+
+## Rustfix tests
+
+If the lint you are working on is making use of structured suggestions, the
+test file should include a `// run-rustfix` comment at the top. This will
+additionally run [rustfix] for that test. Rustfix will apply the suggestions
+from the lint to the code of the test file and compare that to the contents of
+a `.fixed` file.
+
+Use `cargo dev bless` to automatically generate the
+`.fixed` file after running the tests.
+
+[rustfix]: https://github.com/rust-lang/rustfix
+
+## Edition 2018 tests
+
+Some features require the 2018 edition to work (e.g. `async_await`), but
+compile-test tests run on the 2015 edition by default. To change this behavior
+add `// edition:2018` at the top of the test file (note that it's space-sensitive).
+
+## Testing manually
+
+Manually testing against an example file can be useful if you have added some
+`println!`s and the test suite output becomes unreadable. To try Clippy with
+your local modifications, run
+
+```
+cargo dev lint input.rs
+```
+
+from the working copy root. With tests in place, let's have a look at
+implementing our lint now.
+
+## Lint declaration
+
+Let's start by opening the new file created in the `clippy_lints` crate
+at `clippy_lints/src/foo_functions.rs`. That's the crate where all the
+lint code is. This file has already imported some initial things we will need:
+
+```rust
+use rustc_lint::{EarlyLintPass, EarlyContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_ast::ast::*;
+```
+
+The next step is to update the lint declaration. Lints are declared using the
+[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update
+the auto-generated lint declaration to have a real description, something like this:
+
+```rust
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code
+ /// ```
+ #[clippy::version = "1.29.0"]
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+* The section of lines prefixed with `///` constitutes the lint documentation
+ section. This is the default documentation style and will be displayed
+ [like this][example_lint_page]. To render and open this documentation locally
+ in a browser, run `cargo dev serve`.
+* The `#[clippy::version]` attribute will be rendered as part of the lint documentation.
+ The value should be set to the current Rust version that the lint is developed in,
+ it can be retrieved by running `rustc -vV` in the rust-clippy directory. The version
+ is listed under *release*. (Use the version without the `-nightly`) suffix.
+* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the
+ [lint naming guidelines][lint_naming] here when naming your lint.
+ In short, the name should state the thing that is being checked for and
+ read well when used with `allow`/`warn`/`deny`.
+* `pedantic` sets the lint level to `Allow`.
+ The exact mapping can be found [here][category_level_mapping]
+* The last part should be a text that explains what exactly is wrong with the
+ code
+
+The rest of this file contains an empty implementation for our lint pass,
+which in this case is `EarlyLintPass` and should look like this:
+
+```rust
+// clippy_lints/src/foo_functions.rs
+
+// .. imports and lint declaration ..
+
+declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]);
+
+impl EarlyLintPass for FooFunctions {}
+```
+
+[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60
+[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
+[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110
+
+## Lint registration
+
+When using `cargo dev new_lint`, the lint is automatically registered and
+nothing more has to be done.
+
+When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
+pass may have to be registered manually in the `register_plugins` function in
+`clippy_lints/src/lib.rs`:
+
+```rust
+store.register_early_pass(|| Box::new(foo_functions::FooFunctions));
+```
+
+As one may expect, there is a corresponding `register_late_pass` method
+available as well. Without a call to one of `register_early_pass` or
+`register_late_pass`, the lint pass in question will not be run.
+
+One reason that `cargo dev update_lints` does not automate this step is that
+multiple lints can use the same lint pass, so registering the lint pass may
+already be done when adding a new lint. Another reason that this step is not
+automated is that the order that the passes are registered determines the order
+the passes actually run, which in turn affects the order that any emitted lints
+are output in.
+
+## Lint passes
+
+Writing a lint that only checks for the name of a function means that we only
+have to deal with the AST and don't have to deal with the type system at all.
+This is good, because it makes writing this particular lint less complicated.
+
+We have to make this decision with every new Clippy lint. It boils down to using
+either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass].
+
+In short, the `LateLintPass` has access to type information while the
+`EarlyLintPass` doesn't. If you don't need access to type information, use the
+`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed
+hasn't really been a concern with Clippy so far.
+
+Since we don't need type information for checking the function name, we used
+`--pass=early` when running the new lint automation and all the imports were
+added accordingly.
+
+[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
+[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
+
+## Emitting a lint
+
+With UI tests and the lint declaration in place, we can start working on the
+implementation of the lint logic.
+
+Let's start by implementing the `EarlyLintPass` for our `FooFunctions`:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ // TODO: Emit lint here
+ }
+}
+```
+
+We implement the [`check_fn`][check_fn] method from the
+[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various
+information about the function that is currently being checked. More on that in
+the next section. Let's worry about the details later and emit our lint for
+*every* function definition first.
+
+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_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
+automatically. This is how it looks:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+}
+```
+
+Running our UI test should now produce output that contains the lint message.
+
+According to [the rustc-dev-guide], the text should be matter of fact and avoid
+capitalization and periods, unless multiple sentences are needed.
+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_utils/src/diagnostics.rs
+[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html
+
+## Adding the lint logic
+
+Writing the logic for your lint will most likely be different from our example,
+so this section is kept rather short.
+
+Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind]
+that has the [`FnKind::Fn`] variant. It provides access to the name of the
+function/method via an [`Ident`][ident].
+
+With that we can expand our `check_fn` method to:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ if is_foo_fn(fn_kind) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+ }
+}
+```
+
+We separate the lint conditional from the lint emissions because it makes the
+code a bit easier to read. In some cases this separation would also allow to
+write some unit tests (as opposed to only UI tests) for the separate function.
+
+In our example, `is_foo_fn` looks like:
+
+```rust
+// use statements, impl EarlyLintPass, check_fn, ..
+
+fn is_foo_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => {
+ // check if `fn` name is `foo`
+ ident.name.as_str() == "foo"
+ }
+ // ignore closures
+ FnKind::Closure(..) => false
+ }
+}
+```
+
+Now we should also run the full test suite with `cargo test`. At this point
+running `cargo test` should produce the expected output. Remember to run
+`cargo dev bless` to update the `.stderr` file.
+
+`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint
+implementation is not violating any Clippy lints itself.
+
+That should be it for the lint implementation. Running `cargo test` should now
+pass.
+
+[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html
+[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn
+[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html
+
+## Specifying the lint's minimum supported Rust version (MSRV)
+
+Sometimes a lint makes suggestions that require a certain version of Rust. For example, the `manual_strip` lint suggests
+using `str::strip_prefix` and `str::strip_suffix` which is only available after Rust 1.45. In such cases, you need to
+ensure that the MSRV configured for the project is >= the MSRV of the required Rust feature. If multiple features are
+required, just use the one with a lower MSRV.
+
+First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be
+accessed later as `msrvs::STR_STRIP_PREFIX`, for example.
+
+```rust
+msrv_aliases! {
+ ..
+ 1,45,0 { STR_STRIP_PREFIX }
+}
+```
+
+In order to access the project-configured MSRV, you need to have an `msrv` field in the LintPass struct, and a
+constructor to initialize the field. The `msrv` value is passed to the constructor in `clippy_lints/lib.rs`.
+
+```rust
+pub struct ManualStrip {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+```
+
+The project's MSRV can then be matched against the feature MSRV in the LintPass
+using the `meets_msrv` utility function.
+
+``` rust
- ## Cheatsheet
++if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) {
+ return;
+}
+```
+
+The project's MSRV can also be specified as an inner attribute, which overrides
+the value from `clippy.toml`. This can be accounted for using the
+`extract_msrv_attr!(LintContext)` macro and passing
+`LateContext`/`EarlyContext`.
+
+```rust
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ ...
+ }
+ extract_msrv_attr!(LateContext);
+}
+```
+
+Once the `msrv` is added to the lint, a relevant test case should be added to
+`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted
+if the project's MSRV is lower.
+
+As a last step, the lint should be added to the lint documentation. This is done
+in `clippy_lints/src/utils/conf.rs`:
+
+```rust
+define_Conf! {
+ /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
+ (msrv: Option<String> = None),
+ ...
+}
+```
+
+## Author lint
+
+If you have trouble implementing your lint, there is also the internal `author`
+lint to generate Clippy code that detects the offending pattern. It does not
+work for all of the Rust syntax, but can give a good starting point.
+
+The quickest way to use it, is the
+[Rust playground: play.rust-lang.org][author_example].
+Put the code you want to lint into the editor and add the `#[clippy::author]`
+attribute above the item. Then run Clippy via `Tools -> Clippy` and you should
+see the generated code in the output below.
+
+[Here][author_example] is an example on the playground.
+
+If the command was executed successfully, you can copy the code over to where
+you are implementing your lint.
+
+[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55
+
+## Print HIR lint
+
+To implement a lint, it's helpful to first understand the internal representation
+that rustc uses. Clippy has the `#[clippy::dump]` attribute that prints the
+[_High-Level Intermediate Representation (HIR)_] of the item, statement, or
+expression that the attribute is attached to. To attach the attribute to expressions
+you often need to enable `#![feature(stmt_expr_attributes)]`.
+
+[Here][print_hir_example] you can find an example, just select _Tools_ and run _Clippy_.
+
+[_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html
+[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb
+
+## Documentation
+
+The final thing before submitting our PR is to add some documentation to our
+lint declaration.
+
+Please document your lint with a doc comment akin to the following:
+
+```rust
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for ... (describe what the lint matches).
+ ///
+ /// ### Why is this bad?
+ /// Supply the reason for linting the code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// Insert a short example of code that triggers the lint
+ ///
+ /// // Good
+ /// Insert a short example of improved code that doesn't trigger the lint
+ /// ```
+ #[clippy::version = "1.29.0"]
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+Once your lint is merged, this documentation will show up in the [lint
+list][lint_list].
+
+[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html
+
+## Running rustfmt
+
+[Rustfmt] is a tool for formatting Rust code according to style guidelines.
+Your code has to be formatted by `rustfmt` before a PR can be merged.
+Clippy uses nightly `rustfmt` in the CI.
+
+It can be installed via `rustup`:
+
+```bash
+rustup component add rustfmt --toolchain=nightly
+```
+
+Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is
+installed for the nightly toolchain.
+
+[Rustfmt]: https://github.com/rust-lang/rustfmt
+
+## Debugging
+
+If you want to debug parts of your lint implementation, you can use the [`dbg!`]
+macro anywhere in your code. Running the tests should then include the debug
+output in the `stdout` part.
+
+[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html
+
+## PR Checklist
+
+Before submitting your PR make sure you followed all of the basic requirements:
+
+<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` -->
+
+- \[ ] Followed [lint naming conventions][lint_naming]
+- \[ ] Added passing UI tests (including committed `.stderr` file)
+- \[ ] `cargo test` passes locally
+- \[ ] Executed `cargo dev update_lints`
+- \[ ] Added lint documentation
+- \[ ] Run `cargo dev fmt`
+
+## Adding configuration to a lint
+
+Clippy supports the configuration of lints values using a `clippy.toml` file in the workspace
+directory. Adding a configuration to a lint can be useful for thresholds or to constrain some
+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)
+ like this:
+ ```rust
+ /// Lint: LINT_NAME.
+ ///
+ /// <The configuration field doc comment>
+ (configuration_ident: Type = DefaultValue),
+ ```
+ The doc comment is automatically added to the documentation of the listed lints. The default
+ value will be formatted using the `Debug` implementation of the type.
+2. Adding the configuration value to the lint impl struct:
+ 1. This first requires the definition of a lint impl struct. Lint impl structs are usually
+ generated with the `declare_lint_pass!` macro. This struct needs to be defined manually
+ to add some kind of metadata to it:
+ ```rust
+ // Generated struct definition
+ declare_lint_pass!(StructName => [
+ LINT_NAME
+ ]);
+
+ // New manual definition struct
+ #[derive(Copy, Clone)]
+ pub struct StructName {}
+
+ impl_lint_pass!(StructName => [
+ LINT_NAME
+ ]);
+ ```
+
+ 2. Next add the configuration value and a corresponding creation method like this:
+ ```rust
+ #[derive(Copy, Clone)]
+ pub struct StructName {
+ configuration_ident: Type,
+ }
+
+ // ...
+
+ impl StructName {
+ pub fn new(configuration_ident: Type) -> Self {
+ Self {
+ configuration_ident,
+ }
+ }
+ }
+ ```
+3. Passing the configuration value to the lint impl struct:
+
+ First find the struct construction in the [clippy_lints lib file](/clippy_lints/src/lib.rs).
+ The configuration value is now cloned or copied into a local value that is then passed to the
+ impl struct like this:
+ ```rust
+ // Default generated registration:
+ store.register_*_pass(|| box module::StructName);
+
+ // New registration with configuration value
+ let configuration_ident = conf.configuration_ident.clone();
+ store.register_*_pass(move || box module::StructName::new(configuration_ident));
+ ```
+
+ Congratulations the work is almost done. The configuration value can now be accessed
+ in the linting code via `self.configuration_ident`.
+
+4. Adding tests:
+ 1. The default configured value can be tested like any normal lint in [`tests/ui`](/tests/ui).
+ 2. The configuration itself will be tested separately in [`tests/ui-toml`](/tests/ui-toml).
+ Simply add a new subfolder with a fitting name. This folder contains a `clippy.toml` file
+ with the configuration value and a rust file that should be linted by Clippy. The test can
+ otherwise be written as usual.
+
- * [The `if_chain` macro][if_chain]
++## Cheat Sheet
+
+Here are some pointers to things you are likely going to need for every lint:
+
+* [Clippy utils][utils] - Various helper functions. Maybe the function you need
+ is already in here ([`is_type_diagnostic_item`], [`implements_trait`], [`snippet`], etc)
+* [Clippy diagnostics][diagnostics]
- [if_chain]: https://docs.rs/if_chain/*/if_chain/
++* [Let chains][let-chains]
+* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro]
+* [`Span`][span]
+* [`Applicability`][applicability]
+* [Common tools for writing lints](common_tools_writing_lints.md) helps with common operations
+* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts
+* [The nightly rustc docs][nightly_docs] which has been linked to throughout
+ this guide
+
+For `EarlyLintPass` lints:
+
+* [`EarlyLintPass`][early_lint_pass]
+* [`rustc_ast::ast`][ast]
+
+For `LateLintPass` lints:
+
+* [`LateLintPass`][late_lint_pass]
+* [`Ty::TyKind`][ty]
+
+While most of Clippy's lint utils are documented, most of rustc's internals lack
+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://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html
+[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html
+[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html
+[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html
++[let-chains]: https://github.com/rust-lang/rust/pull/94927
+[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
+[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html
+[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html
+[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/
+[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/
+[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html
+[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
--- /dev/null
- if_chain! {
- // Check our expr is calling a method
- if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind;
+# Common tools for writing lints
+
+You may need following tooltips to catch up with common operations.
+
+- [Common tools for writing lints](#common-tools-for-writing-lints)
+ - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
+ - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
+ - [Checking for a specific type](#checking-for-a-specific-type)
+ - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
+ - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method)
+ - [Dealing with macros](#dealing-with-macros-and-expansions)
+
+Useful Rustc dev guide links:
+- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
+- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
+- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
+- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
+
+## Retrieving the type of an expression
+
+Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:
+
+- which type does this expression correspond to (using its [`TyKind`][TyKind])?
+- is it a sized type?
+- is it a primitive type?
+- does it implement a trait?
+
+This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct,
+that gives you access to the underlying structure [`Ty`][Ty].
+
+Example of use:
+```rust
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // Get type of `expr`
+ let ty = cx.typeck_results().expr_ty(expr);
+ // Match its kind to enter its type
+ match ty.kind {
+ ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
+ _ => ()
+ }
+ }
+}
+```
+
+Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
+to retrieve a type from a pattern.
+
+Two noticeable items here:
+- `cx` is the lint context [`LateContext`][LateContext]. The two most useful
+ data structures in this context are `tcx` and the `TypeckResults` returned by
+ `LateContext::typeck_results`, allowing us to jump to type definitions and
+ other compilation stages such as HIR.
+- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is
+ created by type checking step, it includes useful information such as types
+ of expressions, ways to resolve methods and so on.
+
+## Checking if an expr is calling a specific method
+
+Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
+
+```rust
+impl<'tcx> LateLintPass<'tcx> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- if path.ident.name == sym!(some_method);
++ // Check our expr is calling a method
++ if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind
+ // Check the name of this method is `some_method`
- then {
++ && path.ident.name == sym!(some_method)
+ // Optionally, check the type of the self argument.
+ // - See "Checking for a specific type"
- }
++ {
+ // ...
- if_chain! {
- // Check if item is a method/function
- if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
+ }
+ }
+}
+```
+
+## Checking for a specific type
+
+There are three ways to check if an expression type is a specific type we want to check for.
+All of these methods only check for the base type, generic arguments have to be checked separately.
+
+```rust
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use clippy_utils::{paths, match_def_path};
+use rustc_span::symbol::sym;
+use rustc_hir::LangItem;
+
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // Getting the expression type
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ // 1. Using diagnostic items
+ // The last argument is the diagnostic item to check for
+ if is_type_diagnostic_item(cx, ty, sym::Option) {
+ // The type is an `Option`
+ }
+
+ // 2. Using lang items
+ if is_type_lang_item(cx, ty, LangItem::RangeFull) {
+ // The type is a full range like `.drain(..)`
+ }
+
+ // 3. Using the type path
+ // This method should be avoided if possible
+ if match_def_path(cx, def_id, &paths::RESULT) {
+ // The type is a `core::result::Result`
+ }
+ }
+}
+```
+
+Prefer using diagnostic items and lang items where possible.
+
+## Checking if a type implements a specific trait
+
+There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
+
+```rust
+use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
+use rustc_span::symbol::sym;
+
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // 1. Using diagnostic items with the expression
+ // we use `is_trait_method` function from Clippy's utils
+ if is_trait_method(cx, expr, sym::Iterator) {
+ // method call in `expr` belongs to `Iterator` trait
+ }
+
+ // 2. Using lang items with the expression type
+ let ty = cx.typeck_results().expr_ty(expr);
+ if cx.tcx.lang_items()
+ // we are looking for the `DefId` of `Drop` trait in lang items
+ .drop_trait()
+ // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
+ .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
+ // `expr` implements `Drop` trait
+ }
+
+ // 3. Using the type path with the expression
+ // we use `match_trait_method` function from Clippy's utils
+ // (This method should be avoided if possible)
+ if match_trait_method(cx, expr, &paths::INTO) {
+ // `expr` implements `Into` trait
+ }
+ }
+}
+```
+
+> Prefer using diagnostic and lang items, if the target trait has one.
+
+We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
+A list of defined paths for Clippy can be found in [paths.rs][paths]
+
+## Checking if a type defines a specific method
+
+To check if our type defines a method called `some_method`:
+
+```rust
+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<'_>) {
- if impl_item.ident.name == sym!(some_method);
++ // Check if item is a method/function
++ if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
+ // Check the method is named `some_method`
- if signature.decl.implicit_self.has_implicit_self();
++ && impl_item.ident.name == sym!(some_method)
+ // We can also check it has a parameter `self`
- if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type));
- then {
- // ...
- }
++ && signature.decl.implicit_self.has_implicit_self()
+ // We can go further and even check if its return type is `String`
++ && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
++ {
++ // ...
+ }
+ }
+}
+```
+
+## Dealing with macros and expansions
+
+Keep in mind that macros are already expanded and desugaring is already applied
+to the code representation that you are working with in Clippy. This unfortunately causes a lot of
+false positives because macro expansions are "invisible" unless you actively check for them.
+Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
+dynamic in ways that are difficult or impossible to see.
+Use the following functions to deal with macros:
+
+- `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
+ Checking this is a common first step in a lint.
+
+ ```rust
+ if expr.span.from_expansion() {
+ // just forget it
+ return;
+ }
+ ```
+
+- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
+ It is sometimes useful to check if the context of two spans are equal.
+
+ ```rust
+ // expands to `1 + 0`, but don't lint
+ 1 + mac!()
+ ```
+ ```rust
+ if left.span.ctxt() != right.span.ctxt() {
+ // the coder most likely cannot modify this expression
+ return;
+ }
+ ```
+ Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
+ be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
+
+
+- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
+ If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
+ not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
+
+ You may want to use it for example to not start linting in macros from other crates
+
+ ```rust
+ #[macro_use]
+ extern crate a_crate_with_macros;
+
+ // `foo` is defined in `a_crate_with_macros`
+ foo!("bar");
+
+ // if we lint the `match` of `foo` call and test its span
+ assert_eq!(in_external_macro(cx.sess(), match_span), true);
+ ```
+
+- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
+
+One thing `SpanContext` is useful for is to check if two spans are in the same context. For example,
+in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some
+expression with a different context from `a`.
+
+ ```rust
+ macro_rules! m {
+ ($a:expr, $b:expr) => {
+ if $a.is_some() {
+ $b;
+ }
+ }
+ }
+
+ let x: Option<u32> = Some(42);
+ m!(x, x.unwrap());
+
+ // These spans are not from the same context
+ // x.is_some() is from inside the macro
+ // x.unwrap() is from outside the macro
+ assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
+ ```
+
+[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
+[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
+[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
+[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
+[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_utils/src/paths.rs
--- /dev/null
- edition = "2018"
+[package]
+name = "lintcheck"
+version = "0.0.1"
+description = "tool to monitor impact of changes in Clippys lints on a part of the ecosystem"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/rust-clippy"
+categories = ["development-tools"]
- fs_extra = "1.2"
++edition = "2021"
+publish = false
+
+[dependencies]
++cargo_metadata = "0.14"
+clap = "2.33"
+flate2 = "1.0"
- serde_json = "1.0"
+rayon = "1.5.1"
+serde = { version = "1.0", features = ["derive"] }
+tar = "0.4"
+toml = "0.5"
+ureq = "2.2"
+walkdir = "2.3"
+
+[features]
+deny-warnings = []
--- /dev/null
- ryu = {name = "ryu", version = ['1.0.5']}
+[crates]
+# some of these are from cargotest
+cargo = {name = "cargo", versions = ['0.49.0']}
+iron = {name = "iron", versions = ['0.6.1']}
+ripgrep = {name = "ripgrep", versions = ['12.1.1']}
+xsv = {name = "xsv", versions = ['0.13.0']}
+# commented out because of 173K clippy::match_same_arms msgs in language_type.rs
+#tokei = { name = "tokei", versions = ['12.0.4']}
+rayon = {name = "rayon", versions = ['1.5.0']}
+serde = {name = "serde", versions = ['1.0.118']}
+# top 10 crates.io dls
+bitflags = {name = "bitflags", versions = ['1.2.1']}
+# crash = {name = "clippy_crash", path = "/tmp/clippy_crash"}
+libc = {name = "libc", versions = ['0.2.81']}
+log = {name = "log", versions = ['0.4.11']}
+proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']}
+quote = {name = "quote", versions = ['1.0.7']}
+rand = {name = "rand", versions = ['0.7.3']}
+rand_core = {name = "rand_core", versions = ['0.6.0']}
+regex = {name = "regex", versions = ['1.3.2']}
+syn = {name = "syn", versions = ['1.0.54']}
+unicode-xid = {name = "unicode-xid", versions = ['0.2.1']}
+# some more of dtolnays crates
+anyhow = {name = "anyhow", versions = ['1.0.38']}
+async-trait = {name = "async-trait", versions = ['0.1.42']}
+cxx = {name = "cxx", versions = ['1.0.32']}
++ryu = {name = "ryu", versions = ['1.0.5']}
+serde_yaml = {name = "serde_yaml", versions = ['0.8.17']}
+thiserror = {name = "thiserror", versions = ['1.0.24']}
+# some embark crates, there are other interesting crates but
+# unfortunately adding them increases lintcheck runtime drastically
+cfg-expr = {name = "cfg-expr", versions = ['0.7.1']}
+puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
+rpmalloc = {name = "rpmalloc", versions = ['0.2.0']}
+tame-oidc = {name = "tame-oidc", versions = ['0.1.0']}
--- /dev/null
--- /dev/null
++use clap::{App, Arg, ArgMatches};
++use std::env;
++use std::path::PathBuf;
++
++fn get_clap_config<'a>() -> ArgMatches<'a> {
++ App::new("lintcheck")
++ .about("run clippy on a set of crates and check output")
++ .arg(
++ Arg::with_name("only")
++ .takes_value(true)
++ .value_name("CRATE")
++ .long("only")
++ .help("Only process a single crate of the list"),
++ )
++ .arg(
++ Arg::with_name("crates-toml")
++ .takes_value(true)
++ .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"),
++ )
++ .arg(
++ Arg::with_name("fix")
++ .long("--fix")
++ .help("Runs cargo clippy --fix and checks if all suggestions apply"),
++ )
++ .arg(
++ Arg::with_name("filter")
++ .long("--filter")
++ .takes_value(true)
++ .multiple(true)
++ .value_name("clippy_lint_name")
++ .help("Apply a filter to only collect specified lints, this also overrides `allow` attributes"),
++ )
++ .arg(
++ Arg::with_name("markdown")
++ .long("--markdown")
++ .help("Change the reports table to use markdown links"),
++ )
++ .get_matches()
++}
++
++#[derive(Debug)]
++pub(crate) struct LintcheckConfig {
++ /// max number of jobs to spawn (default 1)
++ pub max_jobs: usize,
++ /// we read the sources to check from here
++ pub sources_toml_path: PathBuf,
++ /// we save the clippy lint results here
++ pub lintcheck_results_path: PathBuf,
++ /// Check only a specified package
++ pub only: Option<String>,
++ /// whether to just run --fix and not collect all the warnings
++ pub fix: bool,
++ /// A list of lints that this lintcheck run should focus on
++ pub lint_filter: Vec<String>,
++ /// Indicate if the output should support markdown syntax
++ pub markdown: bool,
++}
++
++impl LintcheckConfig {
++ pub fn new() -> Self {
++ let clap_config = get_clap_config();
++
++ // first, check if we got anything passed via the LINTCHECK_TOML env var,
++ // if not, ask clap if we got any value for --crates-toml <foo>
++ // if not, use the default "lintcheck/lintcheck_crates.toml"
++ let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| {
++ clap_config
++ .value_of("crates-toml")
++ .clone()
++ .unwrap_or("lintcheck/lintcheck_crates.toml")
++ .to_string()
++ });
++
++ let markdown = clap_config.is_present("markdown");
++ let sources_toml_path = PathBuf::from(sources_toml);
++
++ // for the path where we save the lint results, get the filename without extension (so for
++ // wasd.toml, use "wasd"...)
++ let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
++ let lintcheck_results_path = PathBuf::from(format!(
++ "lintcheck-logs/{}_logs.{}",
++ filename.display(),
++ if markdown { "md" } else { "txt" }
++ ));
++
++ // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
++ // use half of that for the physical core count
++ // by default use a single thread
++ let max_jobs = match clap_config.value_of("threads") {
++ Some(threads) => {
++ let threads: usize = threads
++ .parse()
++ .unwrap_or_else(|_| panic!("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 lint_filter: Vec<String> = clap_config
++ .values_of("filter")
++ .map(|iter| {
++ iter.map(|lint_name| {
++ let mut filter = lint_name.replace('_', "-");
++ if !filter.starts_with("clippy::") {
++ filter.insert_str(0, "clippy::");
++ }
++ filter
++ })
++ .collect()
++ })
++ .unwrap_or_default();
++
++ LintcheckConfig {
++ max_jobs,
++ sources_toml_path,
++ lintcheck_results_path,
++ only: clap_config.value_of("only").map(String::from),
++ fix: clap_config.is_present("fix"),
++ lint_filter,
++ markdown,
++ }
++ }
++}
--- /dev/null
- use std::ffi::OsStr;
+// Run clippy on a fixed set of crates and collect the warnings.
+// This helps observing the impact clippy changes have on a set of real-world code (and not just our
+// testsuite).
+//
+// When a new lint is introduced, we can search the results for new warnings and check for false
+// positives.
+
+#![allow(clippy::collapsible_else_if)]
+
- use std::{collections::HashMap, io::ErrorKind};
- use std::{
- env,
- fs::write,
- path::{Path, PathBuf},
- thread,
- time::Duration,
- };
-
- use clap::{App, Arg, ArgMatches};
++mod config;
++
++use config::LintcheckConfig;
++
++use std::collections::HashMap;
++use std::env;
+use std::fmt::Write as _;
++use std::fs::write;
++use std::io::ErrorKind;
++use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::sync::atomic::{AtomicUsize, Ordering};
- use serde_json::Value;
++use std::thread;
++use std::time::Duration;
++
++use cargo_metadata::diagnostic::DiagnosticLevel;
++use cargo_metadata::Message;
+use rayon::prelude::*;
+use serde::{Deserialize, Serialize};
- crate_version: String,
+use walkdir::{DirEntry, WalkDir};
+
+#[cfg(not(windows))]
+const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
+#[cfg(not(windows))]
+const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy";
+
+#[cfg(windows)]
+const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver.exe";
+#[cfg(windows)]
+const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy.exe";
+
+const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
+const LINTCHECK_SOURCES: &str = "target/lintcheck/sources";
+
+/// List of sources to check, loaded from a .toml file
+#[derive(Debug, Serialize, Deserialize)]
+struct SourceList {
+ crates: HashMap<String, TomlCrate>,
+}
+
+/// 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,
+ versions: Option<Vec<String>>,
+ git_url: Option<String>,
+ git_hash: Option<String>,
+ path: Option<String>,
+ options: Option<Vec<String>>,
+}
+
+/// Represents an archive we download from crates.io, or a git repo, or a local repo/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,
+ 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 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,
- line: String,
- column: String,
- linttype: String,
+ file: String,
- let file = format!("{}-{}/{}", &self.crate_name, &self.crate_version, &self.file);
- let file_with_pos = format!("{}:{}:{}", &file, &self.line, &self.column);
++ line: usize,
++ column: usize,
++ lint_type: String,
+ message: String,
+ is_ice: bool,
+}
+
+#[allow(unused)]
+impl ClippyWarning {
++ fn new(cargo_message: Message, krate: &Crate) -> Option<Self> {
++ let diag = match cargo_message {
++ Message::CompilerMessage(message) => message.message,
++ _ => return None,
++ };
++
++ let lint_type = diag.code?.code;
++ if !(lint_type.contains("clippy") || diag.message.contains("clippy"))
++ || diag.message.contains("could not read cargo metadata")
++ {
++ return None;
++ }
++
++ let span = diag.spans.into_iter().find(|span| span.is_primary)?;
++
++ let file = match Path::new(&span.file_name).strip_prefix(env!("CARGO_HOME")) {
++ Ok(stripped) => format!("$CARGO_HOME/{}", stripped.display()),
++ Err(_) => format!(
++ "target/lintcheck/sources/{}-{}/{}",
++ krate.name, krate.version, span.file_name
++ ),
++ };
++
++ Some(Self {
++ crate_name: krate.name.clone(),
++ file,
++ line: span.line_start,
++ column: span.column_start,
++ lint_type,
++ message: diag.message,
++ is_ice: diag.level == DiagnosticLevel::Ice,
++ })
++ }
++
+ fn to_output(&self, markdown: bool) -> String {
- let lint = format!("`{}`", self.linttype);
++ let file_with_pos = format!("{}:{}:{}", &self.file, &self.line, &self.column);
+ if markdown {
- let _ = write!(
- output,
- "[`{}`](../target/lintcheck/sources/{}#L{})",
- file_with_pos, file, self.line
- );
++ let lint = format!("`{}`", self.lint_type);
++
++ let mut file = self.file.clone();
++ if !file.starts_with('$') {
++ file.insert_str(0, "../");
++ }
+
+ let mut output = String::from("| ");
- format!(
- "target/lintcheck/sources/{} {} \"{}\"\n",
- file_with_pos, self.linttype, self.message
- )
++ let _ = write!(output, "[`{}`]({}#L{})", file_with_pos, file, self.line);
+ let _ = write!(output, r#" | {:<50} | "{}" |"#, lint, self.message);
+ output.push('\n');
+ output
+ } else {
- thread_limit: usize,
++ format!("{} {} \"{}\"\n", file_with_pos, self.lint_type, self.message)
+ }
+ }
+}
+
+fn get(path: &str) -> Result<ureq::Response, ureq::Error> {
+ const MAX_RETRIES: u8 = 4;
+ let mut retries = 0;
+ loop {
+ match ureq::get(path).call() {
+ Ok(res) => return Ok(res),
+ Err(e) if retries >= MAX_RETRIES => return Err(e),
+ Err(ureq::Error::Transport(e)) => eprintln!("Error: {}", e),
+ Err(e) => return Err(e),
+ }
+ eprintln!("retrying in {} seconds...", retries);
+ thread::sleep(Duration::from_secs(retries as u64));
+ retries += 1;
+ }
+}
+
+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, options } => {
+ let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
+ let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
+
+ // url to download the crate from crates.io
+ let url = format!("https://crates.io/api/v1/crates/{}/{}/download", name, version);
+ println!("Downloading and extracting {} {} from {}", name, version, url);
+ create_dirs(&krate_download_dir, &extract_dir);
+
+ let krate_file_path = krate_download_dir.join(format!("{}-{}.crate.tar.gz", name, version));
+ // don't download/extract if we already have done so
+ if !krate_file_path.is_file() {
+ // create a file path to download and write the crate data into
+ let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap();
+ let mut krate_req = get(&url).unwrap().into_reader();
+ // copy the crate into the file
+ std::io::copy(&mut krate_req, &mut krate_dest).unwrap();
+
+ // unzip the tarball
+ let ungz_tar = flate2::read::GzDecoder::new(std::fs::File::open(&krate_file_path).unwrap());
+ // extract the tar archive
+ let mut archive = tar::Archive::new(ungz_tar);
+ archive.unpack(&extract_dir).expect("Failed to extract!");
+ }
+ // crate is extracted, return a new Krate object which contains the path to the extracted
+ // sources that clippy can check
+ Crate {
+ version: version.clone(),
+ name: name.clone(),
+ path: extract_dir.join(format!("{}-{}/", name, version)),
+ options: options.clone(),
+ }
+ },
+ CrateSource::Git {
+ name,
+ url,
+ commit,
+ options,
+ } => {
+ let repo_path = {
+ let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
+ // add a -git suffix in case we have the same crate from crates.io and a git repo
+ repo_path.push(format!("{}-git", name));
+ repo_path
+ };
+ // clone the repo if we have not done so
+ if !repo_path.is_dir() {
+ println!("Cloning {} and checking out {}", url, commit);
+ if !Command::new("git")
+ .arg("clone")
+ .arg(url)
+ .arg(&repo_path)
+ .status()
+ .expect("Failed to clone git repo!")
+ .success()
+ {
+ eprintln!("Failed to clone {} into {}", url, repo_path.display())
+ }
+ }
+ // check out the commit/branch/whatever
+ if !Command::new("git")
+ .arg("checkout")
+ .arg(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, options } => {
+ // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
+ // The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
+ // as a result of this filter.
+ let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
+ if dest_crate_root.exists() {
+ println!("Deleting existing directory at {:?}", dest_crate_root);
+ std::fs::remove_dir_all(&dest_crate_root).unwrap();
+ }
+
+ println!("Copying {:?} to {:?}", path, dest_crate_root);
+
+ fn is_cache_dir(entry: &DirEntry) -> bool {
+ std::fs::read(entry.path().join("CACHEDIR.TAG"))
+ .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
+ .unwrap_or(false)
+ }
+
+ for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
+ let entry = entry.unwrap();
+ let entry_path = entry.path();
+ let relative_entry_path = entry_path.strip_prefix(path).unwrap();
+ let dest_path = dest_crate_root.join(relative_entry_path);
+ let metadata = entry_path.symlink_metadata().unwrap();
+
+ if metadata.is_dir() {
+ std::fs::create_dir(dest_path).unwrap();
+ } else if metadata.is_file() {
+ std::fs::copy(entry_path, dest_path).unwrap();
+ }
+ }
+
+ Crate {
+ version: String::from("local"),
+ name: name.clone(),
+ path: dest_crate_root,
+ options: options.clone(),
+ }
+ },
+ }
+ }
+}
+
+impl Crate {
+ /// 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: &Path,
+ target_dir_index: &AtomicUsize,
- fix: bool,
+ total_crates_to_lint: usize,
- let thread_index = index % thread_limit;
++ config: &LintcheckConfig,
+ lint_filter: &Vec<String>,
+ ) -> 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
- if thread_limit == 1 {
++ let thread_index = index % config.max_jobs;
+ let perc = (index * 100) / total_crates_to_lint;
+
- let mut args = if fix {
++ if config.max_jobs == 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, thread_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");
+
- if fix {
++ let mut args = if config.fix {
+ vec!["--fix", "--"]
+ } else {
+ vec!["--", "--message-format=json", "--"]
+ };
+
+ if let Some(options) = &self.options {
+ for opt in options {
+ args.push(opt);
+ }
+ } else {
+ args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
+ }
+
+ if lint_filter.is_empty() {
+ args.push("--cap-lints=warn");
+ } else {
+ args.push("--cap-lints=allow");
+ args.extend(lint_filter.iter().map(|filter| filter.as_str()))
+ }
+
+ let all_output = std::process::Command::new(&cargo_clippy_path)
+ // use the looping index to create individual target dirs
+ .env(
+ "CARGO_TARGET_DIR",
+ shared_target_dir.join(format!("_{:?}", thread_index)),
+ )
+ // lint warnings will look like this:
+ // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter`
+ .args(&args)
+ .current_dir(&self.path)
+ .output()
+ .unwrap_or_else(|error| {
+ panic!(
+ "Encountered error:\n{:?}\ncargo_clippy_path: {}\ncrate path:{}\n",
+ error,
+ &cargo_clippy_path.display(),
+ &self.path.display()
+ );
+ });
+ let stdout = String::from_utf8_lossy(&all_output.stdout);
+ let stderr = String::from_utf8_lossy(&all_output.stderr);
+ let status = &all_output.status;
+
+ if !status.success() {
+ eprintln!(
+ "\nWARNING: bad exit status after checking {} {} \n",
+ self.name, self.version
+ );
+ }
+
- let output_lines = stdout.lines();
- let warnings: Vec<ClippyWarning> = output_lines
- .into_iter()
- // get all clippy warnings and ICEs
- .filter(|line| filter_clippy_warnings(&line))
- .map(|json_msg| parse_json_message(json_msg, &self))
++ if config.fix {
+ if let Some(stderr) = stderr
+ .lines()
+ .find(|line| line.contains("failed to automatically apply fixes suggested by rustc to crate"))
+ {
+ let subcrate = &stderr[63..];
+ println!(
+ "ERROR: failed to apply some suggetion to {} / to (sub)crate {}",
+ self.name, subcrate
+ );
+ }
+ // fast path, we don't need the warnings anyway
+ return Vec::new();
+ }
+
- #[derive(Debug)]
- struct LintcheckConfig {
- /// max number of jobs to spawn (default 1)
- max_jobs: usize,
- /// we read the sources to check from here
- sources_toml_path: PathBuf,
- /// we save the clippy lint results here
- lintcheck_results_path: PathBuf,
- /// whether to just run --fix and not collect all the warnings
- fix: bool,
- /// A list of lints that this lintcheck run should focus on
- lint_filter: Vec<String>,
- /// Indicate if the output should support markdown syntax
- markdown: bool,
- }
-
- impl LintcheckConfig {
- fn from_clap(clap_config: &ArgMatches) -> Self {
- // first, check if we got anything passed via the LINTCHECK_TOML env var,
- // if not, ask clap if we got any value for --crates-toml <foo>
- // if not, use the default "lintcheck/lintcheck_crates.toml"
- let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| {
- clap_config
- .value_of("crates-toml")
- .clone()
- .unwrap_or("lintcheck/lintcheck_crates.toml")
- .to_string()
- });
-
- let markdown = clap_config.is_present("markdown");
- let sources_toml_path = PathBuf::from(sources_toml);
-
- // for the path where we save the lint results, get the filename without extension (so for
- // wasd.toml, use "wasd"...)
- let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
- let lintcheck_results_path = PathBuf::from(format!(
- "lintcheck-logs/{}_logs.{}",
- filename.display(),
- if markdown { "md" } else { "txt" }
- ));
-
- // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
- // use half of that for the physical core count
- // by default use a single thread
- let max_jobs = match clap_config.value_of("threads") {
- Some(threads) => {
- let threads: usize = threads
- .parse()
- .unwrap_or_else(|_| panic!("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 fix: bool = clap_config.is_present("fix");
- let lint_filter: Vec<String> = clap_config
- .values_of("filter")
- .map(|iter| {
- iter.map(|lint_name| {
- let mut filter = lint_name.replace('_', "-");
- if !filter.starts_with("clippy::") {
- filter.insert_str(0, "clippy::");
- }
- filter
- })
- .collect()
- })
- .unwrap_or_default();
-
- LintcheckConfig {
- max_jobs,
- sources_toml_path,
- lintcheck_results_path,
- fix,
- lint_filter,
- markdown,
- }
- }
- }
-
- /// 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 all clippy warnings and ICEs
++ let warnings: Vec<ClippyWarning> = Message::parse_stream(stdout.as_bytes())
++ .filter_map(|msg| ClippyWarning::new(msg.unwrap(), &self))
+ .collect();
+
+ warnings
+ }
+}
+
- }
-
- // if we have multiple versions, save each one
- if let Some(ref versions) = tk.versions {
+/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
+fn build_clippy() {
+ let status = Command::new("cargo")
+ .arg("build")
+ .status()
+ .expect("Failed to build clippy!");
+ if !status.success() {
+ eprintln!("Error: Failed to compile Clippy!");
+ std::process::exit(1);
+ }
+}
+
+/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
+fn read_crates(toml_path: &Path) -> Vec<CrateSource> {
+ let toml_content: String =
+ std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
+ 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
+ .crates
+ .into_iter()
+ .map(|(_cratename, tomlcrate)| tomlcrate)
+ .collect();
+
+ // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
+ // multiple Cratesources)
+ let mut crate_sources = Vec::new();
+ tomlcrates.into_iter().for_each(|tk| {
+ if let Some(ref path) = tk.path {
+ crate_sources.push(CrateSource::Path {
+ name: tk.name.clone(),
+ path: PathBuf::from(path),
+ options: tk.options.clone(),
+ });
- }
- // otherwise, we should have a git source
- if tk.git_url.is_some() && tk.git_hash.is_some() {
++ } else if let Some(ref versions) = tk.versions {
++ // if we have multiple versions, save each one
+ versions.iter().for_each(|ver| {
+ crate_sources.push(CrateSource::CratesIo {
+ name: tk.name.clone(),
+ version: ver.to_string(),
+ options: tk.options.clone(),
+ });
+ })
- /// 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));
-
- let file: String = jmsg["message"]["spans"][0]["file_name"]
- .to_string()
- .trim_matches('"')
- .into();
-
- let file = if file.contains(".cargo") {
- // if we deal with macros, a filename may show the origin of a macro which can be inside a dep from
- // the registry.
- // don't show the full path in that case.
-
- // /home/matthias/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.63/src/custom_keyword.rs
- let path = PathBuf::from(file);
- let mut piter = path.iter();
- // consume all elements until we find ".cargo", so that "/home/matthias" is skipped
- let _: Option<&OsStr> = piter.find(|x| x == &std::ffi::OsString::from(".cargo"));
- // collect the remaining segments
- let file = piter.collect::<PathBuf>();
- format!("{}", file.display())
- } else {
- file
- };
-
- ClippyWarning {
- crate_name: krate.name.to_string(),
- crate_version: krate.version.to_string(),
- file,
- line: jmsg["message"]["spans"][0]["line_start"]
- .to_string()
- .trim_matches('"')
- .into(),
- column: jmsg["message"]["spans"][0]["text"][0]["highlight_start"]
- .to_string()
- .trim_matches('"')
- .into(),
- linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(),
- message: jmsg["message"]["message"].to_string().trim_matches('"').into(),
- is_ice: json_message.contains("internal compiler error: "),
- }
- }
-
++ } else if tk.git_url.is_some() && tk.git_hash.is_some() {
++ // otherwise, we should have a git source
+ crate_sources.push(CrateSource::Git {
+ name: tk.name.clone(),
+ url: tk.git_url.clone().unwrap(),
+ commit: tk.git_hash.clone().unwrap(),
+ options: tk.options.clone(),
+ });
++ } else {
++ panic!("Invalid crate source: {tk:?}");
+ }
++
+ // if we have a version as well as a git data OR only one git data, something is funky
+ if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some())
+ || tk.git_hash.is_some() != tk.git_url.is_some()
+ {
+ eprintln!("tomlkrate: {:?}", tk);
+ if tk.git_hash.is_some() != tk.git_url.is_some() {
+ panic!("Error: Encountered TomlCrate with only one of git_hash and git_url!");
+ }
+ if tk.path.is_some() && (tk.git_hash.is_some() || tk.versions.is_some()) {
+ panic!("Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields");
+ }
+ unreachable!("Failed to translate TomlCrate into CrateSource!");
+ }
+ });
+ // sort the crates
+ crate_sources.sort();
+
+ crate_sources
+}
+
- .for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
+/// Generate a short list of occurring lints-types and their count
+fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
+ // count lint type occurrences
+ let mut counter: HashMap<&String, usize> = HashMap::new();
+ clippy_warnings
+ .iter()
- /// lintchecks `main()` function
- ///
- /// # Panics
- ///
- /// This function panics if the clippy binaries don't exist
- /// or if lintcheck is executed from the wrong directory (aka none-repo-root)
- pub fn main() {
++ .for_each(|wrn| *counter.entry(&wrn.lint_type).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 mut header = String::from("| lint | count |\n");
+ header.push_str("| -------------------------------------------------- | ----- |\n");
+ let stats_string = stats
+ .iter()
+ .map(|(lint, count)| format!("| {:<50} | {:>4} |\n", lint, count))
+ .fold(header, |mut table, line| {
+ table.push_str(&line);
+ table
+ });
+
+ (stats_string, counter)
+}
+
+/// 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(lintcheck_logs_path: &Path) -> bool {
+ if !lintcheck_logs_path.exists() {
+ return true;
+ }
+
+ let clippy_modified: std::time::SystemTime = {
+ let mut times = [CLIPPY_DRIVER_PATH, CARGO_CLIPPY_PATH].iter().map(|p| {
+ std::fs::metadata(p)
+ .expect("failed to get metadata of file")
+ .modified()
+ .expect("failed to get modification date")
+ });
+ // the oldest 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_logs_path)
+ .expect("failed to get metadata of file")
+ .modified()
+ .expect("failed to get modification date");
+
+ // time is represented in seconds since X
+ // logs_modified 2 and clippy_modified 5 means clippy binary is older and we need to recheck
+ logs_modified < clippy_modified
+}
+
- let clap_config = &get_clap_config();
-
- let config = LintcheckConfig::from_clap(clap_config);
++fn main() {
+ // assert that we launch lintcheck from the repo root (via cargo lintcheck)
+ if std::fs::metadata("lintcheck/Cargo.toml").is_err() {
+ eprintln!("lintcheck needs to be run from clippys repo root!\nUse `cargo lintcheck` alternatively.");
+ std::process::exit(3);
+ }
+
- 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
- if !crates.iter().any(|krate| {
- let name = match krate {
- CrateSource::CratesIo { name, .. } | CrateSource::Git { name, .. } | CrateSource::Path { name, .. } => {
- name
- },
- };
- name == only_one_crate
- }) {
- eprintln!(
- "ERROR: could not find crate '{}' in lintcheck/lintcheck_crates.toml",
- only_one_crate
- );
- std::process::exit(1);
- }
++ let config = LintcheckConfig::new();
+
+ println!("Compiling clippy...");
+ build_clippy();
+ println!("Done compiling");
+
+ // if the clippy bin is newer than our logs, throw away target dirs to force clippy to
+ // refresh the logs
+ if lintcheck_needs_rerun(&config.lintcheck_results_path) {
+ let shared_target_dir = "target/lintcheck/shared_target_dir";
+ // if we get an Err here, the shared target dir probably does simply not exist
+ if let Ok(metadata) = std::fs::metadata(&shared_target_dir) {
+ 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");
+ }
+ }
+ }
+
+ let cargo_clippy_path: PathBuf = PathBuf::from(CARGO_CLIPPY_PATH)
+ .canonicalize()
+ .expect("failed to canonicalize path to clippy binary");
+
+ // assert that clippy is found
+ assert!(
+ cargo_clippy_path.is_file(),
+ "target/debug/cargo-clippy binary not found! {}",
+ cargo_clippy_path.display()
+ );
+
+ let clippy_ver = std::process::Command::new(CARGO_CLIPPY_PATH)
+ .arg("--version")
+ .output()
+ .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
+ .expect("could not get clippy version!");
+
+ // download and extract the crates, then run clippy on them and collect clippys warnings
+ // flatten into one big list of warnings
+
+ let crates = read_crates(&config.sources_toml_path);
+ let old_stats = read_stats_from_file(&config.lintcheck_results_path);
+
+ let counter = AtomicUsize::new(1);
+ let lint_filter: Vec<String> = config
+ .lint_filter
+ .iter()
+ .map(|filter| {
+ let mut filter = filter.clone();
+ filter.insert_str(0, "--force-warn=");
+ filter
+ })
+ .collect();
+
- // only check a single crate that was passed via cmdline
- crates
- .into_iter()
- .map(|krate| krate.download_and_extract())
- .filter(|krate| krate.name == only_one_crate)
- .flat_map(|krate| {
- krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1, config.fix, &lint_filter)
- })
- .collect()
- } else {
- if config.max_jobs > 1 {
- // run parallel with rayon
-
- // 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 achieve some kind of parallelism
-
- // by default, use a single thread
- let num_cpus = config.max_jobs;
- let num_crates = crates.len();
-
- // check all crates (default)
- crates
- .into_par_iter()
- .map(|krate| krate.download_and_extract())
- .flat_map(|krate| {
- krate.run_clippy_lints(
- &cargo_clippy_path,
- &counter,
- num_cpus,
- num_crates,
- config.fix,
- &lint_filter,
- )
- })
- .collect()
- } else {
- // run sequential
- let num_crates = crates.len();
- crates
- .into_iter()
- .map(|krate| krate.download_and_extract())
- .flat_map(|krate| {
- krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates, config.fix, &lint_filter)
- })
- .collect()
- }
- };
++ let crates: Vec<Crate> = crates
++ .into_iter()
++ .filter(|krate| {
++ if let Some(only_one_crate) = &config.only {
++ let name = match krate {
++ CrateSource::CratesIo { name, .. }
++ | CrateSource::Git { name, .. }
++ | CrateSource::Path { name, .. } => name,
++ };
+
- write!(text, "{}", all_msgs.join(""));
++ name == only_one_crate
++ } else {
++ true
++ }
++ })
++ .map(|krate| krate.download_and_extract())
++ .collect();
++
++ if crates.is_empty() {
++ eprintln!(
++ "ERROR: could not find crate '{}' in lintcheck/lintcheck_crates.toml",
++ config.only.unwrap(),
++ );
++ std::process::exit(1);
++ }
++
++ // run parallel with rayon
++
++ // This helps when we check many small crates with dep-trees that don't have a lot of branches in
++ // order to achieve some kind of parallelism
++
++ rayon::ThreadPoolBuilder::new()
++ .num_threads(config.max_jobs)
++ .build_global()
++ .unwrap();
++
++ let clippy_warnings: Vec<ClippyWarning> = crates
++ .par_iter()
++ .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, crates.len(), &config, &lint_filter))
++ .collect();
+
+ // if we are in --fix mode, don't change the log files, terminate here
+ if config.fix {
+ return;
+ }
+
+ // generate some stats
+ let (stats_formatted, new_stats) = 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.is_ice)
+ .map(|w| (&w.crate_name, &w.message))
+ .collect();
+
+ let mut all_msgs: Vec<String> = clippy_warnings
+ .iter()
+ .map(|warn| warn.to_output(config.markdown))
+ .collect();
+ all_msgs.sort();
+ all_msgs.push("\n\n### Stats:\n\n".into());
+ all_msgs.push(stats_formatted);
+
+ // save the text into lintcheck-logs/logs.txt
+ let mut text = clippy_ver; // clippy version number on top
+ text.push_str("\n### Reports\n\n");
+ if config.markdown {
+ text.push_str("| file | lint | message |\n");
+ text.push_str("| --- | --- | --- |\n");
+ }
- fn get_clap_config<'a>() -> ArgMatches<'a> {
- App::new("lintcheck")
- .about("run clippy on a set of crates and check output")
- .arg(
- Arg::with_name("only")
- .takes_value(true)
- .value_name("CRATE")
- .long("only")
- .help("only process a single crate of the list"),
- )
- .arg(
- Arg::with_name("crates-toml")
- .takes_value(true)
- .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"),
- )
- .arg(
- Arg::with_name("fix")
- .long("--fix")
- .help("runs cargo clippy --fix and checks if all suggestions apply"),
- )
- .arg(
- Arg::with_name("filter")
- .long("--filter")
- .takes_value(true)
- .multiple(true)
- .value_name("clippy_lint_name")
- .help("apply a filter to only collect specified lints, this also overrides `allow` attributes"),
- )
- .arg(
- Arg::with_name("markdown")
- .long("--markdown")
- .help("change the reports table to use markdown links"),
- )
- .get_matches()
- }
-
++ write!(text, "{}", all_msgs.join("")).unwrap();
+ text.push_str("\n\n### ICEs:\n");
+ for (cratename, msg) in ices.iter() {
+ let _ = write!(text, "{}: '{}'", cratename, msg);
+ }
+
+ println!("Writing logs to {}", config.lintcheck_results_path.display());
+ std::fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap();
+ write(&config.lintcheck_results_path, text).unwrap();
+
+ print_stats(old_stats, new_stats, &config.lint_filter);
+}
+
+/// read the previous stats from the lintcheck-log file
+fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
+ let file_content: String = match std::fs::read_to_string(file_path).ok() {
+ Some(content) => content,
+ None => {
+ return HashMap::new();
+ },
+ };
+
+ let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
+
+ lines
+ .iter()
+ .skip_while(|line| line.as_str() != "### Stats:")
+ // Skipping the table header and the `Stats:` label
+ .skip(4)
+ .take_while(|line| line.starts_with("| "))
+ .filter_map(|line| {
+ let mut spl = line.split('|');
+ // Skip the first `|` symbol
+ spl.next();
+ if let (Some(lint), Some(count)) = (spl.next(), spl.next()) {
+ Some((lint.trim().to_string(), count.trim().parse::<usize>().unwrap()))
+ } else {
+ None
+ }
+ })
+ .collect::<HashMap<String, usize>>()
+}
+
+/// print how lint counts changed between runs
+fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &Vec<String>) {
+ let same_in_both_hashmaps = old_stats
+ .iter()
+ .filter(|(old_key, old_val)| new_stats.get::<&String>(&old_key) == Some(old_val))
+ .map(|(k, v)| (k.to_string(), *v))
+ .collect::<Vec<(String, usize)>>();
+
+ let mut old_stats_deduped = old_stats;
+ let mut new_stats_deduped = new_stats;
+
+ // remove duplicates from both hashmaps
+ same_in_both_hashmaps.iter().for_each(|(k, v)| {
+ assert!(old_stats_deduped.remove(k) == Some(*v));
+ assert!(new_stats_deduped.remove(k) == Some(*v));
+ });
+
+ println!("\nStats:");
+
+ // list all new counts (key is in new stats but not in old stats)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _)| old_stats_deduped.get::<str>(&new_key).is_none())
+ .for_each(|(new_key, new_value)| {
+ println!("{} 0 => {}", new_key, new_value);
+ });
+
+ // list all changed counts (key is in both maps but value differs)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(&new_key).is_some())
+ .for_each(|(new_key, new_val)| {
+ let old_val = old_stats_deduped.get::<str>(&new_key).unwrap();
+ println!("{} {} => {}", new_key, old_val, new_val);
+ });
+
+ // list all gone counts (key is in old status but not in new stats)
+ old_stats_deduped
+ .iter()
+ .filter(|(old_key, _)| new_stats_deduped.get::<&String>(&old_key).is_none())
+ .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
+ .for_each(|(old_key, old_value)| {
+ println!("{} {} => 0", old_key, old_value);
+ });
+}
+
+/// Create necessary directories to run the lintcheck tool.
+///
+/// # Panics
+///
+/// This function panics if creating one of the dirs fails.
+fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
+ std::fs::create_dir("target/lintcheck/").unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create lintcheck target dir");
+ }
+ });
+ std::fs::create_dir(&krate_download_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create crate download dir");
+ }
+ });
+ std::fs::create_dir(&extract_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create crate extraction dir");
+ }
+ });
+}
+
- ///
- /// # Panics
- ///
- /// Panics if the current directory could not be retrieved, there was an error reading any of the
- /// Cargo.toml files or ancestor directory is the clippy root directory
+/// Returns the path to the Clippy project directory
- pub fn clippy_project_root() -> PathBuf {
- let current_dir = std::env::current_dir().unwrap();
- for path in current_dir.ancestors() {
- let result = std::fs::read_to_string(path.join("Cargo.toml"));
- if let Err(err) = &result {
- if err.kind() == std::io::ErrorKind::NotFound {
- continue;
- }
- }
-
- let content = result.unwrap();
- if content.contains("[package]\nname = \"clippy\"") {
- return path.to_path_buf();
- }
- }
- panic!("error: Can't determine root of project. Please run inside a Clippy working dir.");
+#[must_use]
++fn clippy_project_root() -> &'static Path {
++ Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()
+}
+
+#[test]
+fn lintcheck_test() {
+ let args = [
+ "run",
+ "--target-dir",
+ "lintcheck/target",
+ "--manifest-path",
+ "./lintcheck/Cargo.toml",
+ "--",
+ "--crates-toml",
+ "lintcheck/test_sources.toml",
+ ];
+ let status = std::process::Command::new("cargo")
+ .args(&args)
+ .current_dir("..") // repo root
+ .status();
+ //.output();
+
+ assert!(status.unwrap().success());
+}
--- /dev/null
- channel = "nightly-2022-05-05"
+[toolchain]
++channel = "nightly-2022-05-19"
+components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
--- /dev/null
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use rustc_tools_util::VersionInfo;
+use std::env;
+use std::path::PathBuf;
+use std::process::{self, Command};
+
+const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<opts>...]
+
+Common options:
+ --no-deps Run Clippy only on the given crate, without linting the dependencies
+ --fix Automatically apply lint suggestions. This flag implies `--no-deps`
+ -h, --help Print this message
+ -V, --version Print version info and exit
+
+Other options are the same as `cargo check`.
+
+To allow or deny a lint from the command line you can use `cargo clippy --`
+with:
+
+ -W --warn OPT Set lint warnings
+ -A --allow OPT Set lint allowed
+ -D --deny OPT Set lint denied
+ -F --forbid OPT Set lint forbidden
+
+You can use tool lints to allow or deny lints from your code, eg.:
+
+ #[allow(clippy::needless_lifetimes)]
+"#;
+
+fn show_help() {
+ println!("{}", CARGO_CLIPPY_HELP);
+}
+
+fn show_version() {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{}", version_info);
+}
+
+pub fn main() {
+ // Check for version and help flags even when invoked as 'cargo-clippy'
+ if env::args().any(|a| a == "--help" || a == "-h") {
+ show_help();
+ return;
+ }
+
+ if env::args().any(|a| a == "--version" || a == "-V") {
+ show_version();
+ return;
+ }
+
+ if let Err(code) = process(env::args().skip(2)) {
+ process::exit(code);
+ }
+}
+
+struct ClippyCmd {
+ cargo_subcommand: &'static str,
+ args: Vec<String>,
+ clippy_args: Vec<String>,
+}
+
+impl ClippyCmd {
+ fn new<I>(mut old_args: I) -> Self
+ where
+ I: Iterator<Item = String>,
+ {
+ let mut cargo_subcommand = "check";
+ let mut args = vec![];
+ let mut clippy_args: Vec<String> = vec![];
+
+ for arg in old_args.by_ref() {
+ match arg.as_str() {
+ "--fix" => {
+ cargo_subcommand = "fix";
+ continue;
+ },
+ "--no-deps" => {
+ clippy_args.push("--no-deps".into());
+ continue;
+ },
+ "--" => break,
+ _ => {},
+ }
+
+ args.push(arg);
+ }
+
+ clippy_args.append(&mut (old_args.collect()));
+ if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") {
+ clippy_args.push("--no-deps".into());
+ }
+
+ Self {
+ cargo_subcommand,
+ args,
+ clippy_args,
+ }
+ }
+
+ fn path() -> PathBuf {
+ let mut path = env::current_exe()
+ .expect("current executable path invalid")
+ .with_file_name("clippy-driver");
+
+ if cfg!(windows) {
+ path.set_extension("exe");
+ }
+
+ path
+ }
+
+ fn into_std_cmd(self) -> Command {
+ let mut cmd = Command::new("cargo");
+ let clippy_args: String = self
+ .clippy_args
+ .iter()
+ .map(|arg| format!("{}__CLIPPY_HACKERY__", arg))
+ .collect();
+
++ // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
++ let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
++
+ cmd.env("RUSTC_WORKSPACE_WRAPPER", Self::path())
+ .env("CLIPPY_ARGS", clippy_args)
++ .env("CLIPPY_TERMINAL_WIDTH", terminal_width.to_string())
+ .arg(self.cargo_subcommand)
+ .args(&self.args);
+
+ cmd
+ }
+}
+
+fn process<I>(old_args: I) -> Result<(), i32>
+where
+ I: Iterator<Item = String>,
+{
+ let cmd = ClippyCmd::new(old_args);
+
+ let mut cmd = cmd.into_std_cmd();
+
+ let exit_status = cmd
+ .spawn()
+ .expect("could not run cargo")
+ .wait()
+ .expect("failed to wait for cargo?");
+
+ if exit_status.success() {
+ Ok(())
+ } else {
+ Err(exit_status.code().unwrap_or(-1))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ClippyCmd;
+
+ #[test]
+ fn fix() {
+ let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!("fix", cmd.cargo_subcommand);
+ assert!(!cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
+ }
+
+ #[test]
+ fn fix_implies_no_deps() {
+ let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert!(cmd.clippy_args.iter().any(|arg| arg == "--no-deps"));
+ }
+
+ #[test]
+ fn no_deps_not_duplicated_with_fix() {
+ let args = "cargo clippy --fix -- --no-deps"
+ .split_whitespace()
+ .map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!(cmd.clippy_args.iter().filter(|arg| *arg == "--no-deps").count(), 1);
+ }
+
+ #[test]
+ fn check() {
+ let args = "cargo clippy".split_whitespace().map(ToString::to_string);
+ let cmd = ClippyCmd::new(args);
+ assert_eq!("check", cmd.cargo_subcommand);
+ }
+}
--- /dev/null
- run_clippy_for_package(package);
+//! This test is a part of quality control and makes clippy eat what it produces. Awesome lints and
+//! long error messages
+//!
+//! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context
+
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use std::path::PathBuf;
+use std::process::Command;
+use test_utils::IS_RUSTC_TEST_SUITE;
+
+mod test_utils;
+
+#[test]
+fn dogfood_clippy() {
+ if IS_RUSTC_TEST_SUITE {
+ return;
+ }
+
+ // "" is the root package
+ for package in &["", "clippy_dev", "clippy_lints", "clippy_utils", "rustc_tools_util"] {
- run_clippy_for_package("clippy_lints");
++ run_clippy_for_package(package, &[]);
+ }
+}
+
+#[test]
+#[ignore]
+#[cfg(feature = "internal")]
+fn run_metadata_collection_lint() {
+ use std::fs::File;
+ use std::time::SystemTime;
+
+ // Setup for validation
+ let metadata_output_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("util/gh-pages/lints.json");
+ let start_time = SystemTime::now();
+
+ // Run collection as is
+ std::env::set_var("ENABLE_METADATA_COLLECTION", "1");
- run_clippy_for_package("clippy_lints");
++ run_clippy_for_package("clippy_lints", &["-A", "unfulfilled_lint_expectations"]);
+
+ // Check if cargo caching got in the way
+ if let Ok(file) = File::open(metadata_output_path) {
+ if let Ok(metadata) = file.metadata() {
+ if let Ok(last_modification) = metadata.modified() {
+ if last_modification > start_time {
+ // The output file has been modified. Most likely by a hungry
+ // metadata collection monster. So We'll return.
+ return;
+ }
+ }
+ }
+ }
+
+ // Force cargo to invalidate the caches
+ filetime::set_file_mtime(
+ PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("clippy_lints/src/lib.rs"),
+ filetime::FileTime::now(),
+ )
+ .unwrap();
+
+ // Running the collection again
- fn run_clippy_for_package(project: &str) {
++ run_clippy_for_package("clippy_lints", &["-A", "unfulfilled_lint_expectations"]);
+}
+
++fn run_clippy_for_package(project: &str, args: &[&str]) {
+ let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+
+ let mut command = Command::new(&*test_utils::CARGO_CLIPPY_PATH);
+
+ command
+ .current_dir(root_dir.join(project))
+ .env("CARGO_INCREMENTAL", "0")
+ .arg("clippy")
+ .arg("--all-targets")
+ .arg("--all-features")
+ .arg("--")
++ .args(args)
+ .args(&["-D", "clippy::all"])
+ .args(&["-D", "clippy::pedantic"])
+ .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
+
+ if cfg!(feature = "internal") {
+ // internal lints only exist if we build with the internal feature
+ command.args(&["-D", "clippy::internal"]);
+ } else {
+ // running a clippy built without internal lints on the clippy source
+ // that contains e.g. `allow(clippy::invalid_paths)`
+ command.args(&["-A", "unknown_lints"]);
+ }
+
+ let output = command.output().unwrap();
+
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(output.status.success());
+}
--- /dev/null
--- /dev/null
++[package]
++name = "duplicate_mod"
++edition = "2021"
++publish = false
++version = "0.1.0"
--- /dev/null
--- /dev/null
++
--- /dev/null
--- /dev/null
++
--- /dev/null
--- /dev/null
++
--- /dev/null
--- /dev/null
++
--- /dev/null
--- /dev/null
++mod a;
++
++mod b;
++#[path = "b.rs"]
++mod b2;
++
++mod c;
++#[path = "c.rs"]
++mod c2;
++#[path = "c.rs"]
++mod c3;
++
++mod from_other_module;
++mod other_module;
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: file is loaded as a module multiple times: `$DIR/b.rs`
++ --> $DIR/main.rs:3:1
++ |
++LL | mod b;
++ | ^^^^^^ first loaded here
++LL | / #[path = "b.rs"]
++LL | | mod b2;
++ | |_______^ loaded again here
++ |
++ = note: `-D clippy::duplicate-mod` implied by `-D warnings`
++ = help: replace all but one `mod` item with `use` items
++
++error: file is loaded as a module multiple times: `$DIR/c.rs`
++ --> $DIR/main.rs:7:1
++ |
++LL | mod c;
++ | ^^^^^^ first loaded here
++LL | / #[path = "c.rs"]
++LL | | mod c2;
++ | |_______^ loaded again here
++LL | / #[path = "c.rs"]
++LL | | mod c3;
++ | |_______^ loaded again here
++ |
++ = help: replace all but one `mod` item with `use` items
++
++error: file is loaded as a module multiple times: `$DIR/from_other_module.rs`
++ --> $DIR/main.rs:13:1
++ |
++LL | mod from_other_module;
++ | ^^^^^^^^^^^^^^^^^^^^^^ first loaded here
++ |
++ ::: $DIR/other_module/mod.rs:1:1
++ |
++LL | / #[path = "../from_other_module.rs"]
++LL | | mod m;
++ | |______^ loaded again here
++ |
++ = help: replace all but one `mod` item with `use` items
++
++error: aborting due to 3 previous errors
++
--- /dev/null
--- /dev/null
++#[path = "../from_other_module.rs"]
++mod m;
--- /dev/null
--- /dev/null
++allow-expect-in-tests = true
--- /dev/null
--- /dev/null
++// compile-flags: --test
++#![warn(clippy::expect_used)]
++
++fn expect_option() {
++ let opt = Some(0);
++ let _ = opt.expect("");
++}
++
++fn expect_result() {
++ let res: Result<u8, ()> = Ok(0);
++ let _ = res.expect("");
++}
++
++fn main() {
++ expect_option();
++ expect_result();
++}
++
++#[test]
++fn test_expect_option() {
++ let opt = Some(0);
++ let _ = opt.expect("");
++}
++
++#[test]
++fn test_expect_result() {
++ let res: Result<u8, ()> = Ok(0);
++ let _ = res.expect("");
++}
--- /dev/null
--- /dev/null
++error: used `expect()` on `an Option` value
++ --> $DIR/expect_used.rs:6:13
++ |
++LL | let _ = opt.expect("");
++ | ^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::expect-used` implied by `-D warnings`
++ = help: if this value is an `None`, it will panic
++
++error: used `expect()` on `a Result` value
++ --> $DIR/expect_used.rs:11:13
++ |
++LL | let _ = res.expect("");
++ | ^^^^^^^^^^^^^^
++ |
++ = help: if this value is an `Err`, it will panic
++
++error: aborting due to 2 previous errors
++
--- /dev/null
+#![warn(clippy::disallowed_methods)]
+
+extern crate regex;
+use regex::Regex;
+
+fn main() {
+ let re = Regex::new(r"ab.*c").unwrap();
+ re.is_match("abc");
+
+ let mut a = vec![1, 2, 3, 4];
+ a.iter().sum::<i32>();
+
+ a.sort_unstable();
+
+ let _ = 2.0f32.clamp(3.0f32, 4.0f32);
+ let _ = 2.0f64.clamp(3.0f64, 4.0f64);
++
++ let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
++ let re = indirect(".").unwrap();
++
++ let in_call = Box::new(f32::clamp);
++ let in_method_call = ["^", "$"].into_iter().map(Regex::new);
+}
--- /dev/null
- error: aborting due to 5 previous errors
+error: use of a disallowed method `regex::Regex::new`
+ --> $DIR/conf_disallowed_methods.rs:7:14
+ |
+LL | let re = Regex::new(r"ab.*c").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::disallowed-methods` implied by `-D warnings`
+
+error: use of a disallowed method `regex::Regex::is_match`
+ --> $DIR/conf_disallowed_methods.rs:8:5
+ |
+LL | re.is_match("abc");
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = note: no matching allowed (from clippy.toml)
+
+error: use of a disallowed method `std::iter::Iterator::sum`
+ --> $DIR/conf_disallowed_methods.rs:11:5
+ |
+LL | a.iter().sum::<i32>();
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `slice::sort_unstable`
+ --> $DIR/conf_disallowed_methods.rs:13:5
+ |
+LL | a.sort_unstable();
+ | ^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `f32::clamp`
+ --> $DIR/conf_disallowed_methods.rs:15:13
+ |
+LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
++error: use of a disallowed method `regex::Regex::new`
++ --> $DIR/conf_disallowed_methods.rs:18:61
++ |
++LL | let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
++ | ^^^^^^^^^^
++
++error: use of a disallowed method `f32::clamp`
++ --> $DIR/conf_disallowed_methods.rs:21:28
++ |
++LL | let in_call = Box::new(f32::clamp);
++ | ^^^^^^^^^^
++
++error: use of a disallowed method `regex::Regex::new`
++ --> $DIR/conf_disallowed_methods.rs:22:53
++ |
++LL | let in_method_call = ["^", "$"].into_iter().map(Regex::new);
++ | ^^^^^^^^^^
++
++error: aborting due to 8 previous errors
+
--- /dev/null
- error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `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`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `max-suggested-slice-pattern-length`, `await-holding-invalid-types`, `max-include-file-size`, `third-party` at line 5 column 1
++error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of
++ allow-expect-in-tests
++ allow-unwrap-in-tests
++ allowed-scripts
++ array-size-threshold
++ avoid-breaking-exported-api
++ await-holding-invalid-types
++ blacklisted-names
++ cargo-ignore-publish
++ cognitive-complexity-threshold
++ cyclomatic-complexity-threshold
++ disallowed-methods
++ disallowed-types
++ doc-valid-idents
++ enable-raw-pointer-heuristic-for-send
++ enforced-import-renames
++ enum-variant-name-threshold
++ enum-variant-size-threshold
++ literal-representation-threshold
++ max-fn-params-bools
++ max-include-file-size
++ max-struct-bools
++ max-suggested-slice-pattern-length
++ max-trait-bounds
++ msrv
++ pass-by-value-size-limit
++ single-char-binding-names-threshold
++ standard-macro-braces
++ third-party
++ too-large-for-stack
++ too-many-arguments-threshold
++ too-many-lines-threshold
++ trivial-copy-size-limit
++ type-complexity-threshold
++ unreadable-literal-lint-fractions
++ upper-case-acronyms-aggressive
++ vec-box-size-threshold
++ verbose-bit-mask-threshold
++ warn-on-all-wildcard-imports
++ at line 5 column 1
+
+error: aborting due to previous error
+
--- /dev/null
--- /dev/null
++allow-unwrap-in-tests = true
--- /dev/null
--- /dev/null
++// compile-flags: --test
++
++#![allow(unused_mut, clippy::from_iter_instead_of_collect)]
++#![warn(clippy::unwrap_used)]
++#![deny(clippy::get_unwrap)]
++
++use std::collections::BTreeMap;
++use std::collections::HashMap;
++use std::collections::VecDeque;
++
++struct GetFalsePositive {
++ arr: [u32; 3],
++}
++
++impl GetFalsePositive {
++ fn get(&self, pos: usize) -> Option<&u32> {
++ self.arr.get(pos)
++ }
++ fn get_mut(&mut self, pos: usize) -> Option<&mut u32> {
++ self.arr.get_mut(pos)
++ }
++}
++
++fn main() {
++ let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
++ let mut some_slice = &mut [0, 1, 2, 3];
++ let mut some_vec = vec![0, 1, 2, 3];
++ let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect();
++ let mut some_hashmap: HashMap<u8, char> = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]);
++ let mut some_btreemap: BTreeMap<u8, char> = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]);
++ let mut false_positive = GetFalsePositive { arr: [0, 1, 2] };
++
++ {
++ // Test `get().unwrap()`
++ let _ = boxed_slice.get(1).unwrap();
++ let _ = some_slice.get(0).unwrap();
++ let _ = some_vec.get(0).unwrap();
++ let _ = some_vecdeque.get(0).unwrap();
++ let _ = some_hashmap.get(&1).unwrap();
++ let _ = some_btreemap.get(&1).unwrap();
++ #[allow(clippy::unwrap_used)]
++ let _ = false_positive.get(0).unwrap();
++ // Test with deref
++ let _: u8 = *boxed_slice.get(1).unwrap();
++ }
++
++ {
++ // Test `get_mut().unwrap()`
++ *boxed_slice.get_mut(0).unwrap() = 1;
++ *some_slice.get_mut(0).unwrap() = 1;
++ *some_vec.get_mut(0).unwrap() = 1;
++ *some_vecdeque.get_mut(0).unwrap() = 1;
++ // Check false positives
++ #[allow(clippy::unwrap_used)]
++ {
++ *some_hashmap.get_mut(&1).unwrap() = 'b';
++ *some_btreemap.get_mut(&1).unwrap() = 'b';
++ *false_positive.get_mut(0).unwrap() = 1;
++ }
++ }
++
++ {
++ // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()`
++ let _ = some_vec.get(0..1).unwrap().to_vec();
++ let _ = some_vec.get_mut(0..1).unwrap().to_vec();
++ }
++}
++
++#[test]
++fn test() {
++ let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
++ let _ = boxed_slice.get(1).unwrap();
++}
--- /dev/null
--- /dev/null
++error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:35:17
++ |
++LL | let _ = boxed_slice.get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
++ |
++note: the lint level is defined here
++ --> $DIR/unwrap_used.rs:5:9
++ |
++LL | #![deny(clippy::get_unwrap)]
++ | ^^^^^^^^^^^^^^^^^^
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:35:17
++ |
++LL | let _ = boxed_slice.get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::unwrap-used` implied by `-D warnings`
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:36:17
++ |
++LL | let _ = some_slice.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:36:17
++ |
++LL | let _ = some_slice.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:37:17
++ |
++LL | let _ = some_vec.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:37:17
++ |
++LL | let _ = some_vec.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:38:17
++ |
++LL | let _ = some_vecdeque.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:38:17
++ |
++LL | let _ = some_vecdeque.get(0).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:39:17
++ |
++LL | let _ = some_hashmap.get(&1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:39:17
++ |
++LL | let _ = some_hashmap.get(&1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:40:17
++ |
++LL | let _ = some_btreemap.get(&1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:40:17
++ |
++LL | let _ = some_btreemap.get(&1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:44:21
++ |
++LL | let _: u8 = *boxed_slice.get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:44:22
++ |
++LL | let _: u8 = *boxed_slice.get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:49:9
++ |
++LL | *boxed_slice.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:49:10
++ |
++LL | *boxed_slice.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:50:9
++ |
++LL | *some_slice.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:50:10
++ |
++LL | *some_slice.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:51:9
++ |
++LL | *some_vec.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:51:10
++ |
++LL | *some_vec.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:52:9
++ |
++LL | *some_vecdeque.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:52:10
++ |
++LL | *some_vecdeque.get_mut(0).unwrap() = 1;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:64:17
++ |
++LL | let _ = some_vec.get(0..1).unwrap().to_vec();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:64:17
++ |
++LL | let _ = some_vec.get(0..1).unwrap().to_vec();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:65:17
++ |
++LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
++
++error: used `unwrap()` on `an Option` value
++ --> $DIR/unwrap_used.rs:65:17
++ |
++LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
++
++error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
++ --> $DIR/unwrap_used.rs:72:13
++ |
++LL | let _ = boxed_slice.get(1).unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
++
++error: aborting due to 27 previous errors
++
--- /dev/null
- #[derive(PartialEq, PartialOrd)]
+#![warn(clippy::absurd_extreme_comparisons)]
+#![allow(
+ unused,
+ clippy::eq_op,
+ clippy::no_effect,
+ clippy::unnecessary_operation,
+ clippy::needless_pass_by_value
+)]
+
+#[rustfmt::skip]
+fn main() {
+ const Z: u32 = 0;
+ let u: u32 = 42;
+ u <= 0;
+ u <= Z;
+ u < Z;
+ Z >= u;
+ Z > u;
+ u > u32::MAX;
+ u >= u32::MAX;
+ u32::MAX < u;
+ u32::MAX <= u;
+ 1-1 > u;
+ u >= !0;
+ u <= 12 - 2*6;
+ let i: i8 = 0;
+ i < -127 - 1;
+ i8::MAX >= i;
+ 3-7 < i32::MIN;
+ let b = false;
+ b >= true;
+ false > b;
+ u > 0; // ok
+ // this is handled by clippy::unit_cmp
+ () < {};
+}
+
+use std::cmp::{Ordering, PartialEq, PartialOrd};
+
++#[derive(PartialEq, Eq, PartialOrd)]
+pub struct U(u64);
+
+impl PartialEq<u32> for U {
+ fn eq(&self, other: &u32) -> bool {
+ self.eq(&U(u64::from(*other)))
+ }
+}
+impl PartialOrd<u32> for U {
+ fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
+ self.partial_cmp(&U(u64::from(*other)))
+ }
+}
+
+pub fn foo(val: U) -> bool {
+ val > u32::MAX
+}
+
+pub fn bar(len: u64) -> bool {
+ // This is OK as we are casting from target sized to fixed size
+ len >= usize::MAX as u64
+}
--- /dev/null
- #[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(unused_assignments)]
+#[warn(clippy::misrefactored_assign_op, clippy::assign_op_pattern)]
+fn main() {
+ let mut a = 5;
+ a += a + 1;
+ a += 1 + a;
+ a -= a - 1;
+ a *= a * 99;
+ a *= 42 * a;
+ a /= a / 2;
+ a %= a % 5;
+ a &= a & 1;
+ a *= a * a;
+ a = a * a * a;
+ a = a * 42 * a;
+ a = a * 2 + a;
+ a -= 1 - a;
+ a /= 5 / a;
+ a %= 42 % a;
+ a <<= 6 << a;
+}
+
+// check that we don't lint on op assign impls, because that's just the way to impl them
+
+use std::ops::{Mul, MulAssign};
+
++#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Wrap(i64);
+
+impl Mul<i64> for Wrap {
+ type Output = Self;
+
+ fn mul(self, rhs: i64) -> Self {
+ Wrap(self.0 * rhs)
+ }
+}
+
+impl MulAssign<i64> for Wrap {
+ fn mul_assign(&mut self, rhs: i64) {
+ *self = *self * rhs
+ }
+}
+
+fn cow_add_assign() {
+ use std::borrow::Cow;
+ let mut buf = Cow::Owned(String::from("bar"));
+ let cows = Cow::Borrowed("foo");
+
+ // this can be linted
+ buf = buf + cows.clone();
+
+ // this should not as cow<str> Add is not commutative
+ buf = cows + buf;
+ println!("{}", buf);
+}
--- /dev/null
- use std::iter::FromIterator;
+// compile-flags: --emit=link
+// no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+use proc_macro::{token_stream, Delimiter, Group, Ident, Span, TokenStream, TokenTree};
+
+fn read_ident(iter: &mut token_stream::IntoIter) -> Ident {
+ match iter.next() {
+ Some(TokenTree::Ident(i)) => i,
+ _ => panic!("expected ident"),
+ }
+}
+
+#[proc_macro_derive(DeriveBadSpan)]
+pub fn derive_bad_span(input: TokenStream) -> TokenStream {
+ let mut input = input.into_iter();
+ assert_eq!(read_ident(&mut input).to_string(), "struct");
+ let ident = read_ident(&mut input);
+ let mut tys = match input.next() {
+ Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => g.stream().into_iter(),
+ _ => panic!(),
+ };
+ let field1 = read_ident(&mut tys);
+ tys.next();
+ let field2 = read_ident(&mut tys);
+
+ <TokenStream as FromIterator<TokenTree>>::from_iter(
+ [
+ Ident::new("impl", Span::call_site()).into(),
+ ident.into(),
+ Group::new(
+ Delimiter::Brace,
+ <TokenStream as FromIterator<TokenTree>>::from_iter(
+ [
+ Ident::new("fn", Span::call_site()).into(),
+ Ident::new("_foo", Span::call_site()).into(),
+ Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
+ Group::new(
+ Delimiter::Brace,
+ <TokenStream as FromIterator<TokenTree>>::from_iter(
+ [
+ Ident::new("if", field1.span()).into(),
+ Ident::new("true", field1.span()).into(),
+ {
+ let mut group = Group::new(Delimiter::Brace, TokenStream::new());
+ group.set_span(field1.span());
+ group.into()
+ },
+ Ident::new("if", field2.span()).into(),
+ Ident::new("true", field2.span()).into(),
+ {
+ let mut group = Group::new(Delimiter::Brace, TokenStream::new());
+ group.set_span(field2.span());
+ group.into()
+ },
+ ]
+ .iter()
+ .cloned(),
+ ),
+ )
+ .into(),
+ ]
+ .iter()
+ .cloned(),
+ ),
+ )
+ .into(),
+ ]
+ .iter()
+ .cloned(),
+ )
+}
--- /dev/null
- // `blackisted_name` lint should not be triggered inside of the test code.
+#![allow(
+ dead_code,
+ clippy::similar_names,
+ clippy::single_match,
+ clippy::toplevel_ref_arg,
+ unused_mut,
+ unused_variables
+)]
+#![warn(clippy::blacklisted_name)]
+
+fn test(foo: ()) {}
+
+fn main() {
+ let foo = 42;
+ let baz = 42;
+ let quux = 42;
+ // Unlike these others, `bar` is actually considered an acceptable name.
+ // Among many other legitimate uses, bar commonly refers to a period of time in music.
+ // See https://github.com/rust-lang/rust-clippy/issues/5225.
+ let bar = 42;
+
+ let food = 42;
+ let foodstuffs = 42;
+ let bazaar = 42;
+
+ match (42, Some(1337), Some(0)) {
+ (foo, Some(baz), quux @ Some(_)) => (),
+ _ => (),
+ }
+}
+
+fn issue_1647(mut foo: u8) {
+ let mut baz = 0;
+ if let Some(mut quux) = Some(42) {}
+}
+
+fn issue_1647_ref() {
+ let ref baz = 0;
+ if let Some(ref quux) = Some(42) {}
+}
+
+fn issue_1647_ref_mut() {
+ let ref mut baz = 0;
+ if let Some(ref mut quux) = Some(42) {}
+}
+
+mod tests {
+ fn issue_7305() {
- // Check that even in nested functions warning is still not triggere.
++ // `blacklisted_name` lint should not be triggered inside of the test code.
+ let foo = 0;
+
++ // Check that even in nested functions warning is still not triggered.
+ fn nested() {
+ let foo = 0;
+ }
+ }
+}
--- /dev/null
- #![allow(dead_code, clippy::eval_order_dependence)]
++#![allow(dead_code, clippy::mixed_read_write_in_expression)]
+#![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
+
+// This tests the branches_sharing_code lint at the start of blocks
+
+fn simple_examples() {
+ let x = 0;
+
+ // Simple
+ if true {
+ println!("Hello World!");
+ println!("I'm branch nr: 1");
+ } else {
+ println!("Hello World!");
+ println!("I'm branch nr: 2");
+ }
+
+ // Else if
+ if x == 0 {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("I'm the true start index of arrays");
+ } else if x == 1 {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("I start counting from 1 so my array starts from `1`");
+ } else {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("Ha, Pascal allows you to start the array where you want")
+ }
+
+ // Return a value
+ let _ = if x == 7 {
+ let y = 16;
+ println!("What can I say except: \"you're welcome?\"");
+ let _ = y;
+ x
+ } else {
+ let y = 16;
+ println!("Thank you");
+ y
+ };
+}
+
+/// Simple examples where the move can cause some problems due to moved values
+fn simple_but_suggestion_is_invalid() {
+ let x = 10;
+
+ // Can't be automatically moved because used_value_name is getting used again
+ let used_value_name = 19;
+ if x == 10 {
+ let used_value_name = "Different type";
+ println!("Str: {}", used_value_name);
+ let _ = 1;
+ } else {
+ let used_value_name = "Different type";
+ println!("Str: {}", used_value_name);
+ let _ = 2;
+ }
+ let _ = used_value_name;
+
+ // This can be automatically moved as `can_be_overridden` is not used again
+ let can_be_overridden = 8;
+ let _ = can_be_overridden;
+ if x == 11 {
+ let can_be_overridden = "Move me";
+ println!("I'm also moveable");
+ let _ = 111;
+ } else {
+ let can_be_overridden = "Move me";
+ println!("I'm also moveable");
+ let _ = 222;
+ }
+}
+
+/// This function tests that the `IS_SAME_THAN_ELSE` only covers the lint if it's enabled.
+fn check_if_same_than_else_mask() {
+ let x = 2021;
+
+ #[allow(clippy::if_same_then_else)]
+ if x == 2020 {
+ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+ println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+ } else {
+ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+ println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+ }
+
+ if x == 2019 {
+ println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+ } else {
+ println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+ }
+}
+
+#[allow(clippy::vec_init_then_push)]
+fn pf_local_with_inferred_type_issue7053() {
+ if true {
+ let mut v = Vec::new();
+ v.push(0);
+ } else {
+ let mut v = Vec::new();
+ v.push("");
+ };
+}
+
+fn main() {}
--- /dev/null
- #![allow(dead_code, clippy::eval_order_dependence)]
++#![allow(dead_code, clippy::mixed_read_write_in_expression)]
+#![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
+
+// This tests valid if blocks that shouldn't trigger the lint
+
+// Tests with value references are includes in "shared_code_at_bottom.rs"
+
+fn valid_examples() {
+ let x = 2;
+
+ // The edge statements are different
+ if x == 9 {
+ let y = 1 << 5;
+
+ println!("This is the same: vvv");
+ let _z = y;
+ println!("The block expression is different");
+
+ println!("Different end 1");
+ } else {
+ let y = 1 << 7;
+
+ println!("This is the same: vvv");
+ let _z = y;
+ println!("The block expression is different");
+
+ println!("Different end 2");
+ }
+
+ // No else
+ if x == 2 {
+ println!("Hello world!");
+ println!("Hello back, how are you?");
+
+ // This is different vvvv
+ println!("Howdy stranger =^.^=");
+
+ println!("Bye Bye World");
+ } else if x == 9 {
+ println!("Hello world!");
+ println!("Hello back, how are you?");
+
+ // This is different vvvv
+ println!("Hello reviewer :D");
+
+ println!("Bye Bye World");
+ }
+
+ // Overlapping statements only in else if blocks -> Don't lint
+ if x == 0 {
+ println!("I'm important!")
+ } else if x == 17 {
+ println!("I share code in else if");
+
+ println!("x is 17");
+ } else {
+ println!("I share code in else if");
+
+ println!("x is nether x nor 17");
+ }
+
+ // Mutability is different
+ if x == 13 {
+ let mut y = 9;
+ println!("Value y is: {}", y);
+ y += 16;
+ let _z1 = y;
+ } else {
+ let y = 9;
+ println!("Value y is: {}", y);
+ let _z2 = y;
+ }
+
+ // Same blocks but at start and bottom so no `if_same_then_else` lint
+ if x == 418 {
+ let y = 9;
+ let z = 8;
+ let _ = (x, y, z);
+ // Don't tell the programmer, my code is also in the else block
+ } else if x == 419 {
+ println!("+-----------+");
+ println!("| |");
+ println!("| O O |");
+ println!("| ° |");
+ println!("| \\_____/ |");
+ println!("| |");
+ println!("+-----------+");
+ } else {
+ let y = 9;
+ let z = 8;
+ let _ = (x, y, z);
+ // I'm so much better than the x == 418 block. Trust me
+ }
+
+ let x = 1;
+ if true {
+ println!("{}", x);
+ } else {
+ let x = 2;
+ println!("{}", x);
+ }
+
+ // Let's test empty blocks
+ if false {
+ } else {
+ }
+}
+
+/// This makes sure that the `if_same_then_else` masks the `shared_code_in_if_blocks` lint
+fn trigger_other_lint() {
+ let x = 0;
+ let y = 1;
+
+ // Same block
+ if x == 0 {
+ let u = 19;
+ println!("How are u today?");
+ let _ = "This is a string";
+ } else {
+ let u = 19;
+ println!("How are u today?");
+ let _ = "This is a string";
+ }
+
+ // Only same expression
+ let _ = if x == 6 { 7 } else { 7 };
+
+ // Same in else if block
+ let _ = if x == 67 {
+ println!("Well I'm the most important block");
+ "I'm a pretty string"
+ } else if x == 68 {
+ println!("I'm a doppelgänger");
+ // Don't listen to my clone below
+
+ if y == 90 { "=^.^=" } else { ":D" }
+ } else {
+ // Don't listen to my clone above
+ println!("I'm a doppelgänger");
+
+ if y == 90 { "=^.^=" } else { ":D" }
+ };
+
+ if x == 0 {
+ println!("I'm single");
+ } else if x == 68 {
+ println!("I'm a doppelgänger");
+ // Don't listen to my clone below
+ } else {
+ // Don't listen to my clone above
+ println!("I'm a doppelgänger");
+ }
+}
+
+fn main() {}
--- /dev/null
- error: casting `isize` to `f64` may become silently lossy if you later change the type
- --> $DIR/cast_size_32bit.rs:15:5
- |
- LL | x0 as f64;
- | ^^^^^^^^^ help: try: `f64::from(x0)`
- |
- = note: `-D clippy::cast-lossless` implied by `-D warnings`
-
+error: casting `isize` to `i8` may truncate the value
+ --> $DIR/cast_size_32bit.rs:12:5
+ |
+LL | 1isize as i8;
+ | ^^^^^^^^^^^^
+ |
+ = note: `-D clippy::cast-possible-truncation` implied by `-D warnings`
+
+error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
+ --> $DIR/cast_size_32bit.rs:15:5
+ |
+LL | x0 as f64;
+ | ^^^^^^^^^
+ |
+ = note: `-D clippy::cast-precision-loss` implied by `-D warnings`
+
- error: casting `usize` to `f64` may become silently lossy if you later change the type
- --> $DIR/cast_size_32bit.rs:16:5
- |
- LL | x1 as f64;
- | ^^^^^^^^^ help: try: `f64::from(x1)`
-
+error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
+ --> $DIR/cast_size_32bit.rs:16:5
+ |
+LL | x1 as f64;
+ | ^^^^^^^^^
+
+error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
+ --> $DIR/cast_size_32bit.rs:17:5
+ |
+LL | x0 as f32;
+ | ^^^^^^^^^
+
+error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
+ --> $DIR/cast_size_32bit.rs:18:5
+ |
+LL | x1 as f32;
+ | ^^^^^^^^^
+
+error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:19:5
+ |
+LL | 1isize as i32;
+ | ^^^^^^^^^^^^^
+
+error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:20:5
+ |
+LL | 1isize as u32;
+ | ^^^^^^^^^^^^^
+
+error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:21:5
+ |
+LL | 1usize as u32;
+ | ^^^^^^^^^^^^^
+
+error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:22:5
+ |
+LL | 1usize as i32;
+ | ^^^^^^^^^^^^^
+
+error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:22:5
+ |
+LL | 1usize as i32;
+ | ^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::cast-possible-wrap` implied by `-D warnings`
+
+error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:24:5
+ |
+LL | 1i64 as isize;
+ | ^^^^^^^^^^^^^
+
+error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:25:5
+ |
+LL | 1i64 as usize;
+ | ^^^^^^^^^^^^^
+
+error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:26:5
+ |
+LL | 1u64 as isize;
+ | ^^^^^^^^^^^^^
+
+error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:26:5
+ |
+LL | 1u64 as isize;
+ | ^^^^^^^^^^^^^
+
+error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:27:5
+ |
+LL | 1u64 as usize;
+ | ^^^^^^^^^^^^^
+
+error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers
+ --> $DIR/cast_size_32bit.rs:28:5
+ |
+LL | 1u32 as isize;
+ | ^^^^^^^^^^^^^
+
+error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
+ --> $DIR/cast_size_32bit.rs:33:5
+ |
+LL | 999_999_999 as f32;
+ | ^^^^^^^^^^^^^^^^^^
+
+error: casting integer literal to `f64` is unnecessary
+ --> $DIR/cast_size_32bit.rs:34:5
+ |
+LL | 3_999_999_999usize as f64;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `3_999_999_999_f64`
+ |
+ = note: `-D clippy::unnecessary-cast` implied by `-D warnings`
+
+error: aborting due to 20 previous errors
+
--- /dev/null
- use std::convert::TryFrom;
-
+// run-rustfix
+
+#![allow(
+ clippy::cast_lossless,
+ // Int::max_value will be deprecated in the future
+ deprecated,
+)]
+#![warn(clippy::checked_conversions)]
+
+// Positive tests
+
+// Signed to unsigned
+
+pub fn i64_to_u32(value: i64) {
+ let _ = u32::try_from(value).is_ok();
+ let _ = u32::try_from(value).is_ok();
+}
+
+pub fn i64_to_u16(value: i64) {
+ let _ = u16::try_from(value).is_ok();
+ let _ = u16::try_from(value).is_ok();
+}
+
+pub fn isize_to_u8(value: isize) {
+ let _ = u8::try_from(value).is_ok();
+ let _ = u8::try_from(value).is_ok();
+}
+
+// Signed to signed
+
+pub fn i64_to_i32(value: i64) {
+ let _ = i32::try_from(value).is_ok();
+ let _ = i32::try_from(value).is_ok();
+}
+
+pub fn i64_to_i16(value: i64) {
+ let _ = i16::try_from(value).is_ok();
+ let _ = i16::try_from(value).is_ok();
+}
+
+// Unsigned to X
+
+pub fn u32_to_i32(value: u32) {
+ let _ = i32::try_from(value).is_ok();
+ let _ = i32::try_from(value).is_ok();
+}
+
+pub fn usize_to_isize(value: usize) {
+ let _ = isize::try_from(value).is_ok() && value as i32 == 5;
+ let _ = isize::try_from(value).is_ok() && value as i32 == 5;
+}
+
+pub fn u32_to_u16(value: u32) {
+ let _ = u16::try_from(value).is_ok() && value as i32 == 5;
+ let _ = u16::try_from(value).is_ok() && value as i32 == 5;
+}
+
+// Negative tests
+
+pub fn no_i64_to_i32(value: i64) {
+ let _ = value <= (i32::max_value() as i64) && value >= 0;
+ let _ = value <= (i32::MAX as i64) && value >= 0;
+}
+
+pub fn no_isize_to_u8(value: isize) {
+ let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize);
+ let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize);
+}
+
+pub fn i8_to_u8(value: i8) {
+ let _ = value >= 0;
+}
+
+fn main() {}
--- /dev/null
- use std::convert::TryFrom;
-
+// run-rustfix
+
+#![allow(
+ clippy::cast_lossless,
+ // Int::max_value will be deprecated in the future
+ deprecated,
+)]
+#![warn(clippy::checked_conversions)]
+
+// Positive tests
+
+// Signed to unsigned
+
+pub fn i64_to_u32(value: i64) {
+ let _ = value <= (u32::max_value() as i64) && value >= 0;
+ let _ = value <= (u32::MAX as i64) && value >= 0;
+}
+
+pub fn i64_to_u16(value: i64) {
+ let _ = value <= i64::from(u16::max_value()) && value >= 0;
+ let _ = value <= i64::from(u16::MAX) && value >= 0;
+}
+
+pub fn isize_to_u8(value: isize) {
+ let _ = value <= (u8::max_value() as isize) && value >= 0;
+ let _ = value <= (u8::MAX as isize) && value >= 0;
+}
+
+// Signed to signed
+
+pub fn i64_to_i32(value: i64) {
+ let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64);
+ let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64);
+}
+
+pub fn i64_to_i16(value: i64) {
+ let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value());
+ let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN);
+}
+
+// Unsigned to X
+
+pub fn u32_to_i32(value: u32) {
+ let _ = value <= i32::max_value() as u32;
+ let _ = value <= i32::MAX as u32;
+}
+
+pub fn usize_to_isize(value: usize) {
+ let _ = value <= isize::max_value() as usize && value as i32 == 5;
+ let _ = value <= isize::MAX as usize && value as i32 == 5;
+}
+
+pub fn u32_to_u16(value: u32) {
+ let _ = value <= u16::max_value() as u32 && value as i32 == 5;
+ let _ = value <= u16::MAX as u32 && value as i32 == 5;
+}
+
+// Negative tests
+
+pub fn no_i64_to_i32(value: i64) {
+ let _ = value <= (i32::max_value() as i64) && value >= 0;
+ let _ = value <= (i32::MAX as i64) && value >= 0;
+}
+
+pub fn no_isize_to_u8(value: isize) {
+ let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize);
+ let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize);
+}
+
+pub fn i8_to_u8(value: i8) {
+ let _ = value >= 0;
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/checked_conversions.rs:17:13
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:18:13
++ --> $DIR/checked_conversions.rs:15:13
+ |
+LL | let _ = value <= (u32::max_value() as i64) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
+ |
+ = note: `-D clippy::checked-conversions` implied by `-D warnings`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:22:13
++ --> $DIR/checked_conversions.rs:16:13
+ |
+LL | let _ = value <= (u32::MAX as i64) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:23:13
++ --> $DIR/checked_conversions.rs:20:13
+ |
+LL | let _ = value <= i64::from(u16::max_value()) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:27:13
++ --> $DIR/checked_conversions.rs:21:13
+ |
+LL | let _ = value <= i64::from(u16::MAX) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:28:13
++ --> $DIR/checked_conversions.rs:25:13
+ |
+LL | let _ = value <= (u8::max_value() as isize) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:34:13
++ --> $DIR/checked_conversions.rs:26:13
+ |
+LL | let _ = value <= (u8::MAX as isize) && value >= 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:35:13
++ --> $DIR/checked_conversions.rs:32:13
+ |
+LL | let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:39:13
++ --> $DIR/checked_conversions.rs:33:13
+ |
+LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:40:13
++ --> $DIR/checked_conversions.rs:37:13
+ |
+LL | let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:46:13
++ --> $DIR/checked_conversions.rs:38:13
+ |
+LL | let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:47:13
++ --> $DIR/checked_conversions.rs:44:13
+ |
+LL | let _ = value <= i32::max_value() as u32;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:51:13
++ --> $DIR/checked_conversions.rs:45:13
+ |
+LL | let _ = value <= i32::MAX as u32;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:52:13
++ --> $DIR/checked_conversions.rs:49:13
+ |
+LL | let _ = value <= isize::max_value() as usize && value as i32 == 5;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:56:13
++ --> $DIR/checked_conversions.rs:50:13
+ |
+LL | let _ = value <= isize::MAX as usize && value as i32 == 5;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()`
+
+error: checked cast can be simplified
- --> $DIR/checked_conversions.rs:57:13
++ --> $DIR/checked_conversions.rs:54:13
+ |
+LL | let _ = value <= u16::max_value() as u32 && value as i32 == 5;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
+
+error: checked cast can be simplified
++ --> $DIR/checked_conversions.rs:55:13
+ |
+LL | let _ = value <= u16::MAX as u32 && value as i32 == 5;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()`
+
+error: aborting due to 16 previous errors
+
--- /dev/null
- #![allow(unused, clippy::redundant_clone)] // See #5700
+// run-rustfix
++#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
+
+// Define the types in each module to avoid trait impls leaking between modules.
+macro_rules! impl_types {
+ () => {
+ #[derive(PartialEq)]
+ pub struct Owned;
+
+ pub struct Borrowed;
+
+ impl ToOwned for Borrowed {
+ type Owned = Owned;
+ fn to_owned(&self) -> Owned {
+ Owned {}
+ }
+ }
+
+ impl std::borrow::Borrow<Borrowed> for Owned {
+ fn borrow(&self) -> &Borrowed {
+ static VALUE: Borrowed = Borrowed {};
+ &VALUE
+ }
+ }
+ };
+}
+
+// Only Borrowed == Owned is implemented
+mod borrowed_eq_owned {
+ impl_types!();
+
+ impl PartialEq<Owned> for Borrowed {
+ fn eq(&self, _: &Owned) -> bool {
+ true
+ }
+ }
+
+ pub fn compare() {
+ let owned = Owned {};
+ let borrowed = Borrowed {};
+
+ if borrowed == owned {}
+ if borrowed == owned {}
+ }
+}
+
+// Only Owned == Borrowed is implemented
+mod owned_eq_borrowed {
+ impl_types!();
+
+ impl PartialEq<Borrowed> for Owned {
+ fn eq(&self, _: &Borrowed) -> bool {
+ true
+ }
+ }
+
+ fn compare() {
+ let owned = Owned {};
+ let borrowed = Borrowed {};
+
+ if owned == borrowed {}
+ if owned == borrowed {}
+ }
+}
+
+mod issue_4874 {
+ impl_types!();
+
+ // NOTE: PartialEq<Borrowed> for T can't be implemented due to the orphan rules
+ impl<T> PartialEq<T> for Borrowed
+ where
+ T: AsRef<str> + ?Sized,
+ {
+ fn eq(&self, _: &T) -> bool {
+ true
+ }
+ }
+
+ impl std::fmt::Display for Borrowed {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "borrowed")
+ }
+ }
+
+ fn compare() {
+ let borrowed = Borrowed {};
+
+ if borrowed == "Hi" {}
+ if borrowed == "Hi" {}
+ }
+}
+
+fn main() {}
--- /dev/null
- #![allow(unused, clippy::redundant_clone)] // See #5700
+// run-rustfix
++#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
+
+// Define the types in each module to avoid trait impls leaking between modules.
+macro_rules! impl_types {
+ () => {
+ #[derive(PartialEq)]
+ pub struct Owned;
+
+ pub struct Borrowed;
+
+ impl ToOwned for Borrowed {
+ type Owned = Owned;
+ fn to_owned(&self) -> Owned {
+ Owned {}
+ }
+ }
+
+ impl std::borrow::Borrow<Borrowed> for Owned {
+ fn borrow(&self) -> &Borrowed {
+ static VALUE: Borrowed = Borrowed {};
+ &VALUE
+ }
+ }
+ };
+}
+
+// Only Borrowed == Owned is implemented
+mod borrowed_eq_owned {
+ impl_types!();
+
+ impl PartialEq<Owned> for Borrowed {
+ fn eq(&self, _: &Owned) -> bool {
+ true
+ }
+ }
+
+ pub fn compare() {
+ let owned = Owned {};
+ let borrowed = Borrowed {};
+
+ if borrowed.to_owned() == owned {}
+ if owned == borrowed.to_owned() {}
+ }
+}
+
+// Only Owned == Borrowed is implemented
+mod owned_eq_borrowed {
+ impl_types!();
+
+ impl PartialEq<Borrowed> for Owned {
+ fn eq(&self, _: &Borrowed) -> bool {
+ true
+ }
+ }
+
+ fn compare() {
+ let owned = Owned {};
+ let borrowed = Borrowed {};
+
+ if owned == borrowed.to_owned() {}
+ if borrowed.to_owned() == owned {}
+ }
+}
+
+mod issue_4874 {
+ impl_types!();
+
+ // NOTE: PartialEq<Borrowed> for T can't be implemented due to the orphan rules
+ impl<T> PartialEq<T> for Borrowed
+ where
+ T: AsRef<str> + ?Sized,
+ {
+ fn eq(&self, _: &T) -> bool {
+ true
+ }
+ }
+
+ impl std::fmt::Display for Borrowed {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "borrowed")
+ }
+ }
+
+ fn compare() {
+ let borrowed = Borrowed {};
+
+ if "Hi" == borrowed.to_string() {}
+ if borrowed.to_string() == "Hi" {}
+ }
+}
+
+fn main() {}
--- /dev/null
- #[derive(PartialEq)]
+// run-rustfix
+
+#[warn(clippy::cmp_owned)]
+#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)]
+fn main() {
+ fn with_to_string(x: &str) {
+ x != "foo";
+
+ "foo" != x;
+ }
+
+ let x = "oh";
+
+ with_to_string(x);
+
+ x != "foo";
+
+ x != "foo";
+
+ 42.to_string() == "42";
+
+ Foo == Foo;
+
+ "abc".chars().filter(|c| *c != 'X');
+
+ "abc".chars().filter(|c| *c != 'X');
+}
+
+struct Foo;
+
+impl PartialEq for Foo {
+ // Allow this here, because it emits the lint
+ // without a suggestion. This is tested in
+ // `tests/ui/cmp_owned/without_suggestion.rs`
+ #[allow(clippy::cmp_owned)]
+ fn eq(&self, other: &Self) -> bool {
+ self.to_owned() == *other
+ }
+}
+
+impl ToOwned for Foo {
+ type Owned = Bar;
+ fn to_owned(&self) -> Bar {
+ Bar
+ }
+}
+
- #[derive(PartialEq)]
++#[derive(PartialEq, Eq)]
+struct Bar;
+
+impl PartialEq<Foo> for Bar {
+ fn eq(&self, _: &Foo) -> bool {
+ true
+ }
+}
+
+impl std::borrow::Borrow<Foo> for Bar {
+ fn borrow(&self) -> &Foo {
+ static FOO: Foo = Foo;
+ &FOO
+ }
+}
+
++#[derive(PartialEq, Eq)]
+struct Baz;
+
+impl ToOwned for Baz {
+ type Owned = Baz;
+ fn to_owned(&self) -> Baz {
+ Baz
+ }
+}
--- /dev/null
- #[derive(PartialEq)]
+// run-rustfix
+
+#[warn(clippy::cmp_owned)]
+#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)]
+fn main() {
+ fn with_to_string(x: &str) {
+ x != "foo".to_string();
+
+ "foo".to_string() != x;
+ }
+
+ let x = "oh";
+
+ with_to_string(x);
+
+ x != "foo".to_owned();
+
+ x != String::from("foo");
+
+ 42.to_string() == "42";
+
+ Foo.to_owned() == Foo;
+
+ "abc".chars().filter(|c| c.to_owned() != 'X');
+
+ "abc".chars().filter(|c| *c != 'X');
+}
+
+struct Foo;
+
+impl PartialEq for Foo {
+ // Allow this here, because it emits the lint
+ // without a suggestion. This is tested in
+ // `tests/ui/cmp_owned/without_suggestion.rs`
+ #[allow(clippy::cmp_owned)]
+ fn eq(&self, other: &Self) -> bool {
+ self.to_owned() == *other
+ }
+}
+
+impl ToOwned for Foo {
+ type Owned = Bar;
+ fn to_owned(&self) -> Bar {
+ Bar
+ }
+}
+
- #[derive(PartialEq)]
++#[derive(PartialEq, Eq)]
+struct Bar;
+
+impl PartialEq<Foo> for Bar {
+ fn eq(&self, _: &Foo) -> bool {
+ true
+ }
+}
+
+impl std::borrow::Borrow<Foo> for Bar {
+ fn borrow(&self) -> &Foo {
+ static FOO: Foo = Foo;
+ &FOO
+ }
+}
+
++#[derive(PartialEq, Eq)]
+struct Baz;
+
+impl ToOwned for Baz {
+ type Owned = Baz;
+ fn to_owned(&self) -> Baz {
+ Baz
+ }
+}
--- /dev/null
- #[derive(PartialEq)]
+#[allow(clippy::unnecessary_operation)]
+#[allow(clippy::implicit_clone)]
+
+fn main() {
+ let x = &Baz;
+ let y = &Baz;
+ y.to_owned() == *x;
+
+ let x = &&Baz;
+ let y = &Baz;
+ y.to_owned() == **x;
++
++ let x = 0u32;
++ let y = U32Wrapper(x);
++ let _ = U32Wrapper::from(x) == y;
+}
+
+struct Foo;
+
+impl PartialEq for Foo {
+ fn eq(&self, other: &Self) -> bool {
+ self.to_owned() == *other
+ }
+}
+
+impl ToOwned for Foo {
+ type Owned = Bar;
+ fn to_owned(&self) -> Bar {
+ Bar
+ }
+}
+
- #[derive(PartialEq)]
++#[derive(PartialEq, Eq)]
+struct Baz;
+
+impl ToOwned for Baz {
+ type Owned = Baz;
+ fn to_owned(&self) -> Baz {
+ Baz
+ }
+}
+
++#[derive(PartialEq, Eq)]
+struct Bar;
+
+impl PartialEq<Foo> for Bar {
+ fn eq(&self, _: &Foo) -> bool {
+ true
+ }
+}
+
+impl std::borrow::Borrow<Foo> for Bar {
+ fn borrow(&self) -> &Foo {
+ static FOO: Foo = Foo;
+ &FOO
+ }
+}
++
++#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
++struct U32Wrapper(u32);
++impl From<u32> for U32Wrapper {
++ fn from(x: u32) -> Self {
++ Self(x)
++ }
++}
++impl PartialEq<u32> for U32Wrapper {
++ fn eq(&self, other: &u32) -> bool {
++ self.0 == *other
++ }
++}
++impl PartialEq<U32Wrapper> for u32 {
++ fn eq(&self, other: &U32Wrapper) -> bool {
++ *self == other.0
++ }
++}
--- /dev/null
- --> $DIR/without_suggestion.rs:18:9
+error: this creates an owned instance just for comparison
+ --> $DIR/without_suggestion.rs:7:5
+ |
+LL | y.to_owned() == *x;
+ | ^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating
+ |
+ = note: `-D clippy::cmp-owned` implied by `-D warnings`
+
+error: this creates an owned instance just for comparison
+ --> $DIR/without_suggestion.rs:11:5
+ |
+LL | y.to_owned() == **x;
+ | ^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating
+
+error: this creates an owned instance just for comparison
++ --> $DIR/without_suggestion.rs:22:9
+ |
+LL | self.to_owned() == *other
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- use std::iter::FromIterator;
+// compile-flags: --emit=link
+// no-prefer-dynamic
+// ^ compiletest by default builds all aux files as dylibs, but we don't want that for proc-macro
+// crates. If we don't set this, compiletest will override the `crate_type` attribute below and
+// compile this as dylib. Removing this then causes the test to fail because a `dylib` crate can't
+// contain a proc-macro.
+
+#![feature(repr128)]
+#![allow(incomplete_features)]
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
+
+#[proc_macro]
+pub fn macro_test(input_stream: TokenStream) -> TokenStream {
+ let first_token = input_stream.into_iter().next().unwrap();
+ let span = first_token.span();
+
+ TokenStream::from_iter(vec![
+ TokenTree::Ident(Ident::new("fn", Span::call_site())),
+ TokenTree::Ident(Ident::new("code", Span::call_site())),
+ TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+ TokenTree::Group(Group::new(Delimiter::Brace, {
+ let mut clause = Group::new(Delimiter::Brace, TokenStream::new());
+ clause.set_span(span);
+
+ TokenStream::from_iter(vec![
+ TokenTree::Ident(Ident::new("if", Span::call_site())),
+ TokenTree::Ident(Ident::new("true", Span::call_site())),
+ TokenTree::Group(clause.clone()),
+ TokenTree::Ident(Ident::new("else", Span::call_site())),
+ TokenTree::Group(clause),
+ ])
+ })),
+ ])
+}
--- /dev/null
+// originally from ./src/test/ui/pattern/usefulness/consts-opaque.rs
+// panicked at 'assertion failed: rows.iter().all(|r| r.len() == v.len())',
+// compiler/rustc_mir_build/src/thir/pattern/_match.rs:2030:5
+
++#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(PartialEq)]
+struct Foo(i32);
+const FOO_REF_REF: &&Foo = &&Foo(42);
+
+fn main() {
+ // This used to cause an ICE (https://github.com/rust-lang/rust/issues/78071)
+ match FOO_REF_REF {
+ FOO_REF_REF => {},
+ Foo(_) => {},
+ }
+}
--- /dev/null
- --> $DIR/ice-6254.rs:12:9
+error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]`
++ --> $DIR/ice-6254.rs:13:9
+ |
+LL | FOO_REF_REF => {},
+ | ^^^^^^^^^^^
+ |
+ = note: `-D indirect-structural-match` implied by `-D warnings`
+ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+ = note: for more information, see issue #62411 <https://github.com/rust-lang/rust/issues/62411>
+
+error: aborting due to previous error
+
--- /dev/null
--- /dev/null
++#![warn(clippy::let_unit_value)]
++
++fn f() {}
++static FN: fn() = f;
++
++fn main() {
++ let _: () = FN();
++}
--- /dev/null
--- /dev/null
++error: this let-binding has unit value
++ --> $DIR/ice-8821.rs:7:5
++ |
++LL | let _: () = FN();
++ | ^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `FN();`
++ |
++ = note: `-D clippy::let-unit-value` implied by `-D warnings`
++
++error: aborting due to previous error
++
--- /dev/null
+// compile-flags: --test
+#![warn(clippy::dbg_macro)]
+
+fn foo(n: u32) -> u32 {
+ if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
+}
+
+fn factorial(n: u32) -> u32 {
+ if dbg!(n <= 1) {
+ dbg!(1)
+ } else {
+ dbg!(n * factorial(n - 1))
+ }
+}
+
+fn main() {
+ dbg!(42);
+ dbg!(dbg!(dbg!(42)));
+ foo(3) + dbg!(factorial(4));
+ dbg!(1, 2, dbg!(3, 4));
+ dbg!(1, 2, 3, 4, 5);
+}
+
+mod issue7274 {
+ trait Thing<'b> {
+ fn foo(&self);
+ }
+
+ macro_rules! define_thing {
+ ($thing:ident, $body:expr) => {
+ impl<'a> Thing<'a> for $thing {
+ fn foo<'b>(&self) {
+ $body
+ }
+ }
+ };
+ }
+
+ struct MyThing;
+ define_thing!(MyThing, {
+ dbg!(2);
+ });
+}
+
+#[test]
+pub fn issue8481() {
+ dbg!(1);
+}
++
++#[cfg(test)]
++fn foo2() {
++ dbg!(1);
++}
++
++#[cfg(test)]
++mod mod1 {
++ fn func() {
++ dbg!(1);
++ }
++}
--- /dev/null
++#![allow(clippy::derive_partial_eq_without_eq)]
++
+#[derive(PartialEq, Hash)]
+struct Foo;
+
+impl PartialEq<u64> for Foo {
+ fn eq(&self, _: &u64) -> bool {
+ true
+ }
+}
+
+#[derive(Hash)]
+struct Bar;
+
+impl PartialEq for Bar {
+ fn eq(&self, _: &Bar) -> bool {
+ true
+ }
+}
+
+#[derive(Hash)]
+struct Baz;
+
+impl PartialEq<Baz> for Baz {
+ fn eq(&self, _: &Baz) -> bool {
+ true
+ }
+}
+
+#[derive(PartialEq)]
+struct Bah;
+
+impl std::hash::Hash for Bah {
+ fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
+}
+
+#[derive(PartialEq)]
+struct Foo2;
+
+trait Hash {}
+
+// We don't want to lint on user-defined traits called `Hash`
+impl Hash for Foo2 {}
+
+mod use_hash {
+ use std::hash::{Hash, Hasher};
+
+ #[derive(PartialEq)]
+ struct Foo3;
+
+ impl Hash for Foo3 {
+ fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/derive_hash_xor_eq.rs:10:10
+error: you are deriving `Hash` but have implemented `PartialEq` explicitly
- --> $DIR/derive_hash_xor_eq.rs:13:1
++ --> $DIR/derive_hash_xor_eq.rs:12:10
+ |
+LL | #[derive(Hash)]
+ | ^^^^
+ |
+ = note: `#[deny(clippy::derive_hash_xor_eq)]` on by default
+note: `PartialEq` implemented here
- --> $DIR/derive_hash_xor_eq.rs:19:10
++ --> $DIR/derive_hash_xor_eq.rs:15:1
+ |
+LL | / impl PartialEq for Bar {
+LL | | fn eq(&self, _: &Bar) -> bool {
+LL | | true
+LL | | }
+LL | | }
+ | |_^
+ = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are deriving `Hash` but have implemented `PartialEq` explicitly
- --> $DIR/derive_hash_xor_eq.rs:22:1
++ --> $DIR/derive_hash_xor_eq.rs:21:10
+ |
+LL | #[derive(Hash)]
+ | ^^^^
+ |
+note: `PartialEq` implemented here
- --> $DIR/derive_hash_xor_eq.rs:31:1
++ --> $DIR/derive_hash_xor_eq.rs:24:1
+ |
+LL | / impl PartialEq<Baz> for Baz {
+LL | | fn eq(&self, _: &Baz) -> bool {
+LL | | true
+LL | | }
+LL | | }
+ | |_^
+ = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are implementing `Hash` explicitly but have derived `PartialEq`
- --> $DIR/derive_hash_xor_eq.rs:28:10
++ --> $DIR/derive_hash_xor_eq.rs:33:1
+ |
+LL | / impl std::hash::Hash for Bah {
+LL | | fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
+LL | | }
+ | |_^
+ |
+note: `PartialEq` implemented here
- --> $DIR/derive_hash_xor_eq.rs:49:5
++ --> $DIR/derive_hash_xor_eq.rs:30:10
+ |
+LL | #[derive(PartialEq)]
+ | ^^^^^^^^^
+ = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are implementing `Hash` explicitly but have derived `PartialEq`
- --> $DIR/derive_hash_xor_eq.rs:46:14
++ --> $DIR/derive_hash_xor_eq.rs:51:5
+ |
+LL | / impl Hash for Foo3 {
+LL | | fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
+LL | | }
+ | |_____^
+ |
+note: `PartialEq` implemented here
++ --> $DIR/derive_hash_xor_eq.rs:48:14
+ |
+LL | #[derive(PartialEq)]
+ | ^^^^^^^^^
+ = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 4 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused)]
++#![warn(clippy::derive_partial_eq_without_eq)]
++
++// Don't warn on structs that aren't PartialEq
++struct NotPartialEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq can be derived but is missing
++#[derive(Debug, PartialEq, Eq)]
++struct MissingEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq is derived
++#[derive(PartialEq, Eq)]
++struct NotMissingEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq is manually implemented
++#[derive(PartialEq)]
++struct ManualEqImpl {
++ foo: u32,
++ bar: String,
++}
++
++impl Eq for ManualEqImpl {}
++
++// Cannot be Eq because f32 isn't Eq
++#[derive(PartialEq)]
++struct CannotBeEq {
++ foo: u32,
++ bar: f32,
++}
++
++// Don't warn if PartialEq is manually implemented
++struct ManualPartialEqImpl {
++ foo: u32,
++ bar: String,
++}
++
++impl PartialEq for ManualPartialEqImpl {
++ fn eq(&self, other: &Self) -> bool {
++ self.foo == other.foo && self.bar == other.bar
++ }
++}
++
++// Generic fields should be properly checked for Eq-ness
++#[derive(PartialEq)]
++struct GenericNotEq<T: Eq, U: PartialEq> {
++ foo: T,
++ bar: U,
++}
++
++#[derive(PartialEq, Eq)]
++struct GenericEq<T: Eq, U: Eq> {
++ foo: T,
++ bar: U,
++}
++
++#[derive(PartialEq, Eq)]
++struct TupleStruct(u32);
++
++#[derive(PartialEq, Eq)]
++struct GenericTupleStruct<T: Eq>(T);
++
++#[derive(PartialEq)]
++struct TupleStructNotEq(f32);
++
++#[derive(PartialEq, Eq)]
++enum Enum {
++ Foo(u32),
++ Bar { a: String, b: () },
++}
++
++#[derive(PartialEq, Eq)]
++enum GenericEnum<T: Eq, U: Eq, V: Eq> {
++ Foo(T),
++ Bar { a: U, b: V },
++}
++
++#[derive(PartialEq)]
++enum EnumNotEq {
++ Foo(u32),
++ Bar { a: String, b: f32 },
++}
++
++// Ensure that rustfix works properly when `PartialEq` has other derives on either side
++#[derive(Debug, PartialEq, Eq, Clone)]
++struct RustFixWithOtherDerives;
++
++fn main() {}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused)]
++#![warn(clippy::derive_partial_eq_without_eq)]
++
++// Don't warn on structs that aren't PartialEq
++struct NotPartialEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq can be derived but is missing
++#[derive(Debug, PartialEq)]
++struct MissingEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq is derived
++#[derive(PartialEq, Eq)]
++struct NotMissingEq {
++ foo: u32,
++ bar: String,
++}
++
++// Eq is manually implemented
++#[derive(PartialEq)]
++struct ManualEqImpl {
++ foo: u32,
++ bar: String,
++}
++
++impl Eq for ManualEqImpl {}
++
++// Cannot be Eq because f32 isn't Eq
++#[derive(PartialEq)]
++struct CannotBeEq {
++ foo: u32,
++ bar: f32,
++}
++
++// Don't warn if PartialEq is manually implemented
++struct ManualPartialEqImpl {
++ foo: u32,
++ bar: String,
++}
++
++impl PartialEq for ManualPartialEqImpl {
++ fn eq(&self, other: &Self) -> bool {
++ self.foo == other.foo && self.bar == other.bar
++ }
++}
++
++// Generic fields should be properly checked for Eq-ness
++#[derive(PartialEq)]
++struct GenericNotEq<T: Eq, U: PartialEq> {
++ foo: T,
++ bar: U,
++}
++
++#[derive(PartialEq)]
++struct GenericEq<T: Eq, U: Eq> {
++ foo: T,
++ bar: U,
++}
++
++#[derive(PartialEq)]
++struct TupleStruct(u32);
++
++#[derive(PartialEq)]
++struct GenericTupleStruct<T: Eq>(T);
++
++#[derive(PartialEq)]
++struct TupleStructNotEq(f32);
++
++#[derive(PartialEq)]
++enum Enum {
++ Foo(u32),
++ Bar { a: String, b: () },
++}
++
++#[derive(PartialEq)]
++enum GenericEnum<T: Eq, U: Eq, V: Eq> {
++ Foo(T),
++ Bar { a: U, b: V },
++}
++
++#[derive(PartialEq)]
++enum EnumNotEq {
++ Foo(u32),
++ Bar { a: String, b: f32 },
++}
++
++// Ensure that rustfix works properly when `PartialEq` has other derives on either side
++#[derive(Debug, PartialEq, Clone)]
++struct RustFixWithOtherDerives;
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:13:17
++ |
++LL | #[derive(Debug, PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++ |
++ = note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:61:10
++ |
++LL | #[derive(PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:67:10
++ |
++LL | #[derive(PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:70:10
++ |
++LL | #[derive(PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:76:10
++ |
++LL | #[derive(PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:82:10
++ |
++LL | #[derive(PartialEq)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: you are deriving `PartialEq` and can implement `Eq`
++ --> $DIR/derive_partial_eq_without_eq.rs:95:17
++ |
++LL | #[derive(Debug, PartialEq, Clone)]
++ | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
++
++error: aborting due to 7 previous errors
++
--- /dev/null
- #![allow(unused_variables, dead_code)]
+// run-rustfix
+
++#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
+#![warn(clippy::equatable_if_let)]
+
+use std::cmp::Ordering;
+
+#[derive(PartialEq)]
+enum Enum {
+ TupleVariant(i32, u64),
+ RecordVariant { a: i64, b: u32 },
+ UnitVariant,
+ Recursive(Struct),
+}
+
+#[derive(PartialEq)]
+struct Struct {
+ a: i32,
+ b: bool,
+}
+
+enum NotPartialEq {
+ A,
+ B,
+}
+
+enum NotStructuralEq {
+ A,
+ B,
+}
+
+impl PartialEq for NotStructuralEq {
+ fn eq(&self, _: &NotStructuralEq) -> bool {
+ false
+ }
+}
+
+fn main() {
+ let a = 2;
+ let b = 3;
+ let c = Some(2);
+ let d = Struct { a: 2, b: false };
+ let e = Enum::UnitVariant;
+ let f = NotPartialEq::A;
+ let g = NotStructuralEq::A;
+
+ // true
+
+ if a == 2 {}
+ if a.cmp(&b) == Ordering::Greater {}
+ if c == Some(2) {}
+ if d == (Struct { a: 2, b: false }) {}
+ if e == Enum::TupleVariant(32, 64) {}
+ if e == (Enum::RecordVariant { a: 64, b: 32 }) {}
+ if e == Enum::UnitVariant {}
+ if (e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false }) {}
+
+ // false
+
+ if let 2 | 3 = a {}
+ if let x @ 2 = a {}
+ if let Some(3 | 4) = c {}
+ if let Struct { a, b: false } = d {}
+ if let Struct { a: 2, b: x } = d {}
+ if let NotPartialEq::A = f {}
+ if g == NotStructuralEq::A {}
+ if let Some(NotPartialEq::A) = Some(f) {}
+ if Some(g) == Some(NotStructuralEq::A) {}
+
+ macro_rules! m1 {
+ (x) => {
+ "abc"
+ };
+ }
+ if "abc" == m1!(x) {
+ println!("OK");
+ }
+}
--- /dev/null
- #![allow(unused_variables, dead_code)]
+// run-rustfix
+
++#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
+#![warn(clippy::equatable_if_let)]
+
+use std::cmp::Ordering;
+
+#[derive(PartialEq)]
+enum Enum {
+ TupleVariant(i32, u64),
+ RecordVariant { a: i64, b: u32 },
+ UnitVariant,
+ Recursive(Struct),
+}
+
+#[derive(PartialEq)]
+struct Struct {
+ a: i32,
+ b: bool,
+}
+
+enum NotPartialEq {
+ A,
+ B,
+}
+
+enum NotStructuralEq {
+ A,
+ B,
+}
+
+impl PartialEq for NotStructuralEq {
+ fn eq(&self, _: &NotStructuralEq) -> bool {
+ false
+ }
+}
+
+fn main() {
+ let a = 2;
+ let b = 3;
+ let c = Some(2);
+ let d = Struct { a: 2, b: false };
+ let e = Enum::UnitVariant;
+ let f = NotPartialEq::A;
+ let g = NotStructuralEq::A;
+
+ // true
+
+ if let 2 = a {}
+ if let Ordering::Greater = a.cmp(&b) {}
+ if let Some(2) = c {}
+ if let Struct { a: 2, b: false } = d {}
+ if let Enum::TupleVariant(32, 64) = e {}
+ if let Enum::RecordVariant { a: 64, b: 32 } = e {}
+ if let Enum::UnitVariant = e {}
+ if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {}
+
+ // false
+
+ if let 2 | 3 = a {}
+ if let x @ 2 = a {}
+ if let Some(3 | 4) = c {}
+ if let Struct { a, b: false } = d {}
+ if let Struct { a: 2, b: x } = d {}
+ if let NotPartialEq::A = f {}
+ if let NotStructuralEq::A = g {}
+ if let Some(NotPartialEq::A) = Some(f) {}
+ if let Some(NotStructuralEq::A) = Some(g) {}
+
+ macro_rules! m1 {
+ (x) => {
+ "abc"
+ };
+ }
+ if let m1!(x) = "abc" {
+ println!("OK");
+ }
+}
--- /dev/null
- use std::iter::FromIterator;
+// run-rustfix
+
+#![warn(clippy::from_iter_instead_of_collect)]
+#![allow(unused_imports)]
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
+
+struct Foo(Vec<bool>);
+
+impl FromIterator<bool> for Foo {
+ fn from_iter<T: IntoIterator<Item = bool>>(_: T) -> Self {
+ todo!()
+ }
+}
+
+impl<'a> FromIterator<&'a bool> for Foo {
+ fn from_iter<T: IntoIterator<Item = &'a bool>>(iter: T) -> Self {
+ iter.into_iter().copied().collect::<Self>()
+ }
+}
+
+fn main() {
+ let iter_expr = std::iter::repeat(5).take(5);
+ let _ = iter_expr.collect::<Vec<_>>();
+
+ let _ = vec![5, 5, 5, 5].iter().enumerate().collect::<HashMap<usize, &i8>>();
+
+ Vec::from_iter(vec![42u32]);
+
+ let a = vec![0, 1, 2];
+ assert_eq!(a, (0..3).collect::<Vec<_>>());
+ assert_eq!(a, (0..3).collect::<Vec<i32>>());
+
+ let mut b = (0..3).collect::<VecDeque<_>>();
+ b.push_back(4);
+
+ let mut b = (0..3).collect::<VecDeque<i32>>();
+ b.push_back(4);
+
+ {
+ use std::collections;
+ let mut b = (0..3).collect::<collections::VecDeque<i32>>();
+ b.push_back(4);
+ }
+
+ let values = [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')];
+ let bm = values.iter().cloned().collect::<BTreeMap<_, _>>();
+ let mut bar = bm.range(0..2).collect::<BTreeMap<_, _>>();
+ bar.insert(&4, &'e');
+
+ let mut bts = (0..3).collect::<BTreeSet<_>>();
+ bts.insert(2);
+ {
+ use std::collections;
+ let _ = (0..3).collect::<collections::BTreeSet<_>>();
+ let _ = (0..3).collect::<collections::BTreeSet<u32>>();
+ }
+
+ for _i in [1, 2, 3].iter().collect::<Vec<_>>() {}
+ for _i in [1, 2, 3].iter().collect::<Vec<&i32>>() {}
+}
--- /dev/null
- use std::iter::FromIterator;
+// run-rustfix
+
+#![warn(clippy::from_iter_instead_of_collect)]
+#![allow(unused_imports)]
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
+
+struct Foo(Vec<bool>);
+
+impl FromIterator<bool> for Foo {
+ fn from_iter<T: IntoIterator<Item = bool>>(_: T) -> Self {
+ todo!()
+ }
+}
+
+impl<'a> FromIterator<&'a bool> for Foo {
+ fn from_iter<T: IntoIterator<Item = &'a bool>>(iter: T) -> Self {
+ <Self as FromIterator<bool>>::from_iter(iter.into_iter().copied())
+ }
+}
+
+fn main() {
+ let iter_expr = std::iter::repeat(5).take(5);
+ let _ = Vec::from_iter(iter_expr);
+
+ let _ = HashMap::<usize, &i8>::from_iter(vec![5, 5, 5, 5].iter().enumerate());
+
+ Vec::from_iter(vec![42u32]);
+
+ let a = vec![0, 1, 2];
+ assert_eq!(a, Vec::from_iter(0..3));
+ assert_eq!(a, Vec::<i32>::from_iter(0..3));
+
+ let mut b = VecDeque::from_iter(0..3);
+ b.push_back(4);
+
+ let mut b = VecDeque::<i32>::from_iter(0..3);
+ b.push_back(4);
+
+ {
+ use std::collections;
+ let mut b = collections::VecDeque::<i32>::from_iter(0..3);
+ b.push_back(4);
+ }
+
+ let values = [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')];
+ let bm = BTreeMap::from_iter(values.iter().cloned());
+ let mut bar = BTreeMap::from_iter(bm.range(0..2));
+ bar.insert(&4, &'e');
+
+ let mut bts = BTreeSet::from_iter(0..3);
+ bts.insert(2);
+ {
+ use std::collections;
+ let _ = collections::BTreeSet::from_iter(0..3);
+ let _ = collections::BTreeSet::<u32>::from_iter(0..3);
+ }
+
+ for _i in Vec::from_iter([1, 2, 3].iter()) {}
+ for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {}
+}
--- /dev/null
- --> $DIR/from_iter_instead_of_collect.rs:19:9
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:25:13
++ --> $DIR/from_iter_instead_of_collect.rs:18:9
+ |
+LL | <Self as FromIterator<bool>>::from_iter(iter.into_iter().copied())
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.into_iter().copied().collect::<Self>()`
+ |
+ = note: `-D clippy::from-iter-instead-of-collect` implied by `-D warnings`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:27:13
++ --> $DIR/from_iter_instead_of_collect.rs:24:13
+ |
+LL | let _ = Vec::from_iter(iter_expr);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter_expr.collect::<Vec<_>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:32:19
++ --> $DIR/from_iter_instead_of_collect.rs:26:13
+ |
+LL | let _ = HashMap::<usize, &i8>::from_iter(vec![5, 5, 5, 5].iter().enumerate());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `vec![5, 5, 5, 5].iter().enumerate().collect::<HashMap<usize, &i8>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:33:19
++ --> $DIR/from_iter_instead_of_collect.rs:31:19
+ |
+LL | assert_eq!(a, Vec::from_iter(0..3));
+ | ^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<Vec<_>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:35:17
++ --> $DIR/from_iter_instead_of_collect.rs:32:19
+ |
+LL | assert_eq!(a, Vec::<i32>::from_iter(0..3));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<Vec<i32>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:38:17
++ --> $DIR/from_iter_instead_of_collect.rs:34:17
+ |
+LL | let mut b = VecDeque::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<VecDeque<_>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:43:21
++ --> $DIR/from_iter_instead_of_collect.rs:37:17
+ |
+LL | let mut b = VecDeque::<i32>::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<VecDeque<i32>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:48:14
++ --> $DIR/from_iter_instead_of_collect.rs:42:21
+ |
+LL | let mut b = collections::VecDeque::<i32>::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<collections::VecDeque<i32>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:49:19
++ --> $DIR/from_iter_instead_of_collect.rs:47:14
+ |
+LL | let bm = BTreeMap::from_iter(values.iter().cloned());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `values.iter().cloned().collect::<BTreeMap<_, _>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:52:19
++ --> $DIR/from_iter_instead_of_collect.rs:48:19
+ |
+LL | let mut bar = BTreeMap::from_iter(bm.range(0..2));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `bm.range(0..2).collect::<BTreeMap<_, _>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:56:17
++ --> $DIR/from_iter_instead_of_collect.rs:51:19
+ |
+LL | let mut bts = BTreeSet::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<BTreeSet<_>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:57:17
++ --> $DIR/from_iter_instead_of_collect.rs:55:17
+ |
+LL | let _ = collections::BTreeSet::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<collections::BTreeSet<_>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:60:15
++ --> $DIR/from_iter_instead_of_collect.rs:56:17
+ |
+LL | let _ = collections::BTreeSet::<u32>::from_iter(0..3);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `(0..3).collect::<collections::BTreeSet<u32>>()`
+
+error: usage of `FromIterator::from_iter`
- --> $DIR/from_iter_instead_of_collect.rs:61:15
++ --> $DIR/from_iter_instead_of_collect.rs:59:15
+ |
+LL | for _i in Vec::from_iter([1, 2, 3].iter()) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `[1, 2, 3].iter().collect::<Vec<_>>()`
+
+error: usage of `FromIterator::from_iter`
++ --> $DIR/from_iter_instead_of_collect.rs:60:15
+ |
+LL | for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `[1, 2, 3].iter().collect::<Vec<&i32>>()`
+
+error: aborting due to 15 previous errors
+
--- /dev/null
- use std::iter::FromIterator;
+// run-rustfix
+
+#![allow(unused_mut, clippy::from_iter_instead_of_collect)]
+#![warn(clippy::unwrap_used)]
+#![deny(clippy::get_unwrap)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::collections::VecDeque;
+
+struct GetFalsePositive {
+ arr: [u32; 3],
+}
+
+impl GetFalsePositive {
+ fn get(&self, pos: usize) -> Option<&u32> {
+ self.arr.get(pos)
+ }
+ fn get_mut(&mut self, pos: usize) -> Option<&mut u32> {
+ self.arr.get_mut(pos)
+ }
+}
+
+fn main() {
+ let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
+ let mut some_slice = &mut [0, 1, 2, 3];
+ let mut some_vec = vec![0, 1, 2, 3];
+ let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect();
+ let mut some_hashmap: HashMap<u8, char> = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut some_btreemap: BTreeMap<u8, char> = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut false_positive = GetFalsePositive { arr: [0, 1, 2] };
+
+ {
+ // Test `get().unwrap()`
+ let _ = &boxed_slice[1];
+ let _ = &some_slice[0];
+ let _ = &some_vec[0];
+ let _ = &some_vecdeque[0];
+ let _ = &some_hashmap[&1];
+ let _ = &some_btreemap[&1];
+ #[allow(clippy::unwrap_used)]
+ let _ = false_positive.get(0).unwrap();
+ // Test with deref
+ let _: u8 = boxed_slice[1];
+ }
+
+ {
+ // Test `get_mut().unwrap()`
+ boxed_slice[0] = 1;
+ some_slice[0] = 1;
+ some_vec[0] = 1;
+ some_vecdeque[0] = 1;
+ // Check false positives
+ #[allow(clippy::unwrap_used)]
+ {
+ *some_hashmap.get_mut(&1).unwrap() = 'b';
+ *some_btreemap.get_mut(&1).unwrap() = 'b';
+ *false_positive.get_mut(0).unwrap() = 1;
+ }
+ }
+
+ {
+ // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()`
+ let _ = some_vec[0..1].to_vec();
+ let _ = some_vec[0..1].to_vec();
+ }
+}
--- /dev/null
- use std::iter::FromIterator;
+// run-rustfix
+
+#![allow(unused_mut, clippy::from_iter_instead_of_collect)]
+#![warn(clippy::unwrap_used)]
+#![deny(clippy::get_unwrap)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::collections::VecDeque;
+
+struct GetFalsePositive {
+ arr: [u32; 3],
+}
+
+impl GetFalsePositive {
+ fn get(&self, pos: usize) -> Option<&u32> {
+ self.arr.get(pos)
+ }
+ fn get_mut(&mut self, pos: usize) -> Option<&mut u32> {
+ self.arr.get_mut(pos)
+ }
+}
+
+fn main() {
+ let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]);
+ let mut some_slice = &mut [0, 1, 2, 3];
+ let mut some_vec = vec![0, 1, 2, 3];
+ let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect();
+ let mut some_hashmap: HashMap<u8, char> = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut some_btreemap: BTreeMap<u8, char> = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]);
+ let mut false_positive = GetFalsePositive { arr: [0, 1, 2] };
+
+ {
+ // Test `get().unwrap()`
+ let _ = boxed_slice.get(1).unwrap();
+ let _ = some_slice.get(0).unwrap();
+ let _ = some_vec.get(0).unwrap();
+ let _ = some_vecdeque.get(0).unwrap();
+ let _ = some_hashmap.get(&1).unwrap();
+ let _ = some_btreemap.get(&1).unwrap();
+ #[allow(clippy::unwrap_used)]
+ let _ = false_positive.get(0).unwrap();
+ // Test with deref
+ let _: u8 = *boxed_slice.get(1).unwrap();
+ }
+
+ {
+ // Test `get_mut().unwrap()`
+ *boxed_slice.get_mut(0).unwrap() = 1;
+ *some_slice.get_mut(0).unwrap() = 1;
+ *some_vec.get_mut(0).unwrap() = 1;
+ *some_vecdeque.get_mut(0).unwrap() = 1;
+ // Check false positives
+ #[allow(clippy::unwrap_used)]
+ {
+ *some_hashmap.get_mut(&1).unwrap() = 'b';
+ *some_btreemap.get_mut(&1).unwrap() = 'b';
+ *false_positive.get_mut(0).unwrap() = 1;
+ }
+ }
+
+ {
+ // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()`
+ let _ = some_vec.get(0..1).unwrap().to_vec();
+ let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ }
+}
--- /dev/null
- --> $DIR/get_unwrap.rs:36:17
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:36:17
++ --> $DIR/get_unwrap.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]`
+ |
+note: the lint level is defined here
+ --> $DIR/get_unwrap.rs:5:9
+ |
+LL | #![deny(clippy::get_unwrap)]
+ | ^^^^^^^^^^^^^^^^^^
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:37:17
++ --> $DIR/get_unwrap.rs:35:17
+ |
+LL | let _ = boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::unwrap-used` implied by `-D warnings`
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:37:17
++ --> $DIR/get_unwrap.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:38:17
++ --> $DIR/get_unwrap.rs:36:17
+ |
+LL | let _ = some_slice.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:38:17
++ --> $DIR/get_unwrap.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:39:17
++ --> $DIR/get_unwrap.rs:37:17
+ |
+LL | let _ = some_vec.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:39:17
++ --> $DIR/get_unwrap.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:40:17
++ --> $DIR/get_unwrap.rs:38:17
+ |
+LL | let _ = some_vecdeque.get(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:40:17
++ --> $DIR/get_unwrap.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:41:17
++ --> $DIR/get_unwrap.rs:39:17
+ |
+LL | let _ = some_hashmap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:41:17
++ --> $DIR/get_unwrap.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:45:21
++ --> $DIR/get_unwrap.rs:40:17
+ |
+LL | let _ = some_btreemap.get(&1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:45:22
++ --> $DIR/get_unwrap.rs:44:21
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:50:9
++ --> $DIR/get_unwrap.rs:44:22
+ |
+LL | let _: u8 = *boxed_slice.get(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:50:10
++ --> $DIR/get_unwrap.rs:49:9
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:51:9
++ --> $DIR/get_unwrap.rs:49:10
+ |
+LL | *boxed_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:51:10
++ --> $DIR/get_unwrap.rs:50:9
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:52:9
++ --> $DIR/get_unwrap.rs:50:10
+ |
+LL | *some_slice.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:52:10
++ --> $DIR/get_unwrap.rs:51:9
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:53:9
++ --> $DIR/get_unwrap.rs:51:10
+ |
+LL | *some_vec.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:53:10
++ --> $DIR/get_unwrap.rs:52:9
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:65:17
++ --> $DIR/get_unwrap.rs:52:10
+ |
+LL | *some_vecdeque.get_mut(0).unwrap() = 1;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:65:17
++ --> $DIR/get_unwrap.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
+error: used `unwrap()` on `an Option` value
- --> $DIR/get_unwrap.rs:66:17
++ --> $DIR/get_unwrap.rs:64:17
+ |
+LL | let _ = some_vec.get(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
- --> $DIR/get_unwrap.rs:66:17
++ --> $DIR/get_unwrap.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]`
+
+error: used `unwrap()` on `an Option` value
++ --> $DIR/get_unwrap.rs:65:17
+ |
+LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
+
+error: aborting due to 26 previous errors
+
--- /dev/null
- use std::iter::FromIterator;
+use std::iter::repeat;
+fn square_is_lower_64(x: &u32) -> bool {
+ x * x < 64
+}
+
+#[allow(clippy::maybe_infinite_iter)]
+#[deny(clippy::infinite_iter)]
+fn infinite_iters() {
+ repeat(0_u8).collect::<Vec<_>>(); // infinite iter
+ (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter
+ (0..8_u64).chain(0..).max(); // infinite iter
+ (0_usize..)
+ .chain([0usize, 1, 2].iter().cloned())
+ .skip_while(|x| *x != 42)
+ .min(); // infinite iter
+ (0..8_u32)
+ .rev()
+ .cycle()
+ .map(|x| x + 1_u32)
+ .for_each(|x| println!("{}", x)); // infinite iter
+ (0..3_u32).flat_map(|x| x..).sum::<u32>(); // infinite iter
+ (0_usize..).flat_map(|x| 0..x).product::<usize>(); // infinite iter
+ (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter
+ (0..42_u64).by_ref().last(); // not an infinite, because ranges are double-ended
+ (0..).next(); // iterator is not exhausted
+}
+
+#[deny(clippy::maybe_infinite_iter)]
+fn potential_infinite_iters() {
+ (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter
+ repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter
+ (1..)
+ .scan(0, |state, x| {
+ *state += x;
+ Some(*state)
+ })
+ .min(); // maybe infinite iter
+ (0..).find(|x| *x == 24); // maybe infinite iter
+ (0..).position(|x| x == 24); // maybe infinite iter
+ (0..).any(|x| x == 24); // maybe infinite iter
+ (0..).all(|x| x == 24); // maybe infinite iter
+
+ (0..).zip(0..42).take_while(|&(x, _)| x != 42).count(); // not infinite
+ repeat(42).take_while(|x| *x == 42).next(); // iterator is not exhausted
+}
+
+fn main() {
+ infinite_iters();
+ potential_infinite_iters();
+}
+
+mod finite_collect {
+ use std::collections::HashSet;
+
+ struct C;
+ impl FromIterator<i32> for C {
+ fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
+ C
+ }
+ }
+
+ fn check_collect() {
+ let _: HashSet<i32> = (0..).collect(); // Infinite iter
+
+ // Some data structures don't collect infinitely, such as `ArrayVec`
+ let _: C = (0..).collect();
+ }
+}
--- /dev/null
- --> $DIR/infinite_iter.rs:64:31
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:9:5
+ |
+LL | repeat(0_u8).collect::<Vec<_>>(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/infinite_iter.rs:7:8
+ |
+LL | #[deny(clippy::infinite_iter)]
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:10:5
+ |
+LL | (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:11:5
+ |
+LL | (0..8_u64).chain(0..).max(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:16:5
+ |
+LL | / (0..8_u32)
+LL | | .rev()
+LL | | .cycle()
+LL | | .map(|x| x + 1_u32)
+LL | | .for_each(|x| println!("{}", x)); // infinite iter
+ | |________________________________________^
+
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:22:5
+ |
+LL | (0_usize..).flat_map(|x| 0..x).product::<usize>(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
+ --> $DIR/infinite_iter.rs:23:5
+ |
+LL | (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:30:5
+ |
+LL | (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/infinite_iter.rs:28:8
+ |
+LL | #[deny(clippy::maybe_infinite_iter)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:31:5
+ |
+LL | repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:32:5
+ |
+LL | / (1..)
+LL | | .scan(0, |state, x| {
+LL | | *state += x;
+LL | | Some(*state)
+LL | | })
+LL | | .min(); // maybe infinite iter
+ | |______________^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:38:5
+ |
+LL | (0..).find(|x| *x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:39:5
+ |
+LL | (0..).position(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:40:5
+ |
+LL | (0..).any(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
+ --> $DIR/infinite_iter.rs:41:5
+ |
+LL | (0..).all(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
++ --> $DIR/infinite_iter.rs:63:31
+ |
+LL | let _: HashSet<i32> = (0..).collect(); // Infinite iter
+ | ^^^^^^^^^^^^^^^
+ |
+ = note: `#[deny(clippy::infinite_iter)]` on by default
+
+error: aborting due to 14 previous errors
+
--- /dev/null
- use std::iter::{repeat, FromIterator};
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_str_repeat)]
+
+use std::borrow::Cow;
++use std::iter::repeat;
+
+fn main() {
+ let _: String = "test".repeat(10);
+ let _: String = "x".repeat(10);
+ let _: String = "'".repeat(10);
+ let _: String = "\"".repeat(10);
+
+ let x = "test";
+ let count = 10;
+ let _ = x.repeat(count + 2);
+
+ macro_rules! m {
+ ($e:expr) => {{ $e }};
+ }
+ // FIXME: macro args are fine
+ let _: String = repeat(m!("test")).take(m!(count)).collect();
+
+ let x = &x;
+ let _: String = (*x).repeat(count);
+
+ macro_rules! repeat_m {
+ ($e:expr) => {{ repeat($e) }};
+ }
+ // Don't lint, repeat is from a macro.
+ let _: String = repeat_m!("test").take(count).collect();
+
+ let x: Box<str> = Box::from("test");
+ let _: String = x.repeat(count);
+
+ #[derive(Clone)]
+ struct S;
+ impl FromIterator<Box<S>> for String {
+ fn from_iter<T: IntoIterator<Item = Box<S>>>(_: T) -> Self {
+ Self::new()
+ }
+ }
+ // Don't lint, wrong box type
+ let _: String = repeat(Box::new(S)).take(count).collect();
+
+ let _: String = Cow::Borrowed("test").repeat(count);
+
+ let x = "x".to_owned();
+ let _: String = x.repeat(count);
+
+ let x = 'x';
+ // Don't lint, not char literal
+ let _: String = repeat(x).take(count).collect();
+}
+
+fn _msrv_1_15() {
+ #![clippy::msrv = "1.15"]
+ // `str::repeat` was stabilized in 1.16. Do not lint this
+ let _: String = std::iter::repeat("test").take(10).collect();
+}
+
+fn _msrv_1_16() {
+ #![clippy::msrv = "1.16"]
+ let _: String = "test".repeat(10);
+}
--- /dev/null
- use std::iter::{repeat, FromIterator};
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_str_repeat)]
+
+use std::borrow::Cow;
++use std::iter::repeat;
+
+fn main() {
+ let _: String = std::iter::repeat("test").take(10).collect();
+ let _: String = std::iter::repeat('x').take(10).collect();
+ let _: String = std::iter::repeat('\'').take(10).collect();
+ let _: String = std::iter::repeat('"').take(10).collect();
+
+ let x = "test";
+ let count = 10;
+ let _ = repeat(x).take(count + 2).collect::<String>();
+
+ macro_rules! m {
+ ($e:expr) => {{ $e }};
+ }
+ // FIXME: macro args are fine
+ let _: String = repeat(m!("test")).take(m!(count)).collect();
+
+ let x = &x;
+ let _: String = repeat(*x).take(count).collect();
+
+ macro_rules! repeat_m {
+ ($e:expr) => {{ repeat($e) }};
+ }
+ // Don't lint, repeat is from a macro.
+ let _: String = repeat_m!("test").take(count).collect();
+
+ let x: Box<str> = Box::from("test");
+ let _: String = repeat(x).take(count).collect();
+
+ #[derive(Clone)]
+ struct S;
+ impl FromIterator<Box<S>> for String {
+ fn from_iter<T: IntoIterator<Item = Box<S>>>(_: T) -> Self {
+ Self::new()
+ }
+ }
+ // Don't lint, wrong box type
+ let _: String = repeat(Box::new(S)).take(count).collect();
+
+ let _: String = repeat(Cow::Borrowed("test")).take(count).collect();
+
+ let x = "x".to_owned();
+ let _: String = repeat(x).take(count).collect();
+
+ let x = 'x';
+ // Don't lint, not char literal
+ let _: String = repeat(x).take(count).collect();
+}
+
+fn _msrv_1_15() {
+ #![clippy::msrv = "1.15"]
+ // `str::repeat` was stabilized in 1.16. Do not lint this
+ let _: String = std::iter::repeat("test").take(10).collect();
+}
+
+fn _msrv_1_16() {
+ #![clippy::msrv = "1.16"]
+ let _: String = std::iter::repeat("test").take(10).collect();
+}
--- /dev/null
- use std::convert::TryFrom;
+#![warn(clippy::map_err_ignore)]
+#![allow(clippy::unnecessary_wraps)]
+use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+enum Errors {
+ Ignored,
+}
+
+impl Error for Errors {}
+
+impl fmt::Display for Errors {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Error")
+ }
+}
+
+fn main() -> Result<(), Errors> {
+ let x = u32::try_from(-123_i32);
+
+ println!("{:?}", x.map_err(|_| Errors::Ignored));
+
+ // Should not warn you because you explicitly ignore the parameter
+ // using a named wildcard value
+ println!("{:?}", x.map_err(|_foo| Errors::Ignored));
+
+ Ok(())
+}
--- /dev/null
- --> $DIR/map_err.rs:23:32
+error: `map_err(|_|...` wildcard pattern discards the original error
++ --> $DIR/map_err.rs:22:32
+ |
+LL | println!("{:?}", x.map_err(|_| Errors::Ignored));
+ | ^^^
+ |
+ = note: `-D clippy::map-err-ignore` implied by `-D warnings`
+ = help: consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)
+
+error: aborting due to previous error
+
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::match_single_binding)]
+#![allow(unused_variables, clippy::toplevel_ref_arg)]
+
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+fn coords() -> Point {
+ Point { x: 1, y: 2 }
+}
+
+macro_rules! foo {
+ ($param:expr) => {
+ match $param {
+ _ => println!("whatever"),
+ }
+ };
+}
+
+fn main() {
+ let a = 1;
+ let b = 2;
+ let c = 3;
+ // Lint
+ let (x, y, z) = (a, b, c);
+ {
+ println!("{} {} {}", x, y, z);
+ }
+ // Lint
+ let (x, y, z) = (a, b, c);
+ println!("{} {} {}", x, y, z);
+ // Ok
+ foo!(a);
+ // Ok
+ match a {
+ 2 => println!("2"),
+ _ => println!("Not 2"),
+ }
+ // Ok
+ let d = Some(5);
+ match d {
+ Some(d) => println!("{}", d),
+ _ => println!("None"),
+ }
+ // Lint
+ println!("whatever");
+ // Lint
+ {
+ let x = 29;
+ println!("x has a value of {}", x);
+ }
+ // Lint
+ {
+ let e = 5 * a;
+ if e >= 5 {
+ println!("e is superior to 5");
+ }
+ }
+ // Lint
+ let p = Point { x: 0, y: 7 };
+ let Point { x, y } = p;
+ println!("Coords: ({}, {})", x, y);
+ // Lint
+ let Point { x: x1, y: y1 } = p;
+ println!("Coords: ({}, {})", x1, y1);
+ // Lint
+ let x = 5;
+ let ref r = x;
+ println!("Got a reference to {}", r);
+ // Lint
+ let mut x = 5;
+ let ref mut mr = x;
+ println!("Got a mutable reference to {}", mr);
+ // Lint
+ let Point { x, y } = coords();
+ let product = x * y;
+ // Lint
+ let v = vec![Some(1), Some(2), Some(3), Some(4)];
+ #[allow(clippy::let_and_return)]
+ let _ = v
+ .iter()
+ .map(|i| {
+ let unwrapped = i.unwrap();
+ unwrapped
+ })
+ .collect::<Vec<u8>>();
+ // Ok
+ let x = 1;
+ match x {
+ #[cfg(disabled_feature)]
+ 0 => println!("Disabled branch"),
+ _ => println!("Enabled branch"),
+ }
+
+ // Ok
+ let x = 1;
+ let y = 1;
+ match match y {
+ 0 => 1,
+ _ => 2,
+ } {
+ #[cfg(disabled_feature)]
+ 0 => println!("Array index start"),
+ _ => println!("Not an array index start"),
+ }
+
+ // Lint
+ let x = 1;
+ println!("Not an array index start");
+}
++
++#[allow(dead_code)]
++fn issue_8723() {
++ let (mut val, idx) = ("a b", 1);
++
++ let (pre, suf) = val.split_at(idx);
++ val = {
++ println!("{}", pre);
++ suf
++ };
++
++ let _ = val;
++}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::match_single_binding)]
+#![allow(unused_variables, clippy::toplevel_ref_arg)]
+
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+fn coords() -> Point {
+ Point { x: 1, y: 2 }
+}
+
+macro_rules! foo {
+ ($param:expr) => {
+ match $param {
+ _ => println!("whatever"),
+ }
+ };
+}
+
+fn main() {
+ let a = 1;
+ let b = 2;
+ let c = 3;
+ // Lint
+ match (a, b, c) {
+ (x, y, z) => {
+ println!("{} {} {}", x, y, z);
+ },
+ }
+ // Lint
+ match (a, b, c) {
+ (x, y, z) => println!("{} {} {}", x, y, z),
+ }
+ // Ok
+ foo!(a);
+ // Ok
+ match a {
+ 2 => println!("2"),
+ _ => println!("Not 2"),
+ }
+ // Ok
+ let d = Some(5);
+ match d {
+ Some(d) => println!("{}", d),
+ _ => println!("None"),
+ }
+ // Lint
+ match a {
+ _ => println!("whatever"),
+ }
+ // Lint
+ match a {
+ _ => {
+ let x = 29;
+ println!("x has a value of {}", x);
+ },
+ }
+ // Lint
+ match a {
+ _ => {
+ let e = 5 * a;
+ if e >= 5 {
+ println!("e is superior to 5");
+ }
+ },
+ }
+ // Lint
+ let p = Point { x: 0, y: 7 };
+ match p {
+ Point { x, y } => println!("Coords: ({}, {})", x, y),
+ }
+ // Lint
+ match p {
+ Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1),
+ }
+ // Lint
+ let x = 5;
+ match x {
+ ref r => println!("Got a reference to {}", r),
+ }
+ // Lint
+ let mut x = 5;
+ match x {
+ ref mut mr => println!("Got a mutable reference to {}", mr),
+ }
+ // Lint
+ let product = match coords() {
+ Point { x, y } => x * y,
+ };
+ // Lint
+ let v = vec![Some(1), Some(2), Some(3), Some(4)];
+ #[allow(clippy::let_and_return)]
+ let _ = v
+ .iter()
+ .map(|i| match i.unwrap() {
+ unwrapped => unwrapped,
+ })
+ .collect::<Vec<u8>>();
+ // Ok
+ let x = 1;
+ match x {
+ #[cfg(disabled_feature)]
+ 0 => println!("Disabled branch"),
+ _ => println!("Enabled branch"),
+ }
+
+ // Ok
+ let x = 1;
+ let y = 1;
+ match match y {
+ 0 => 1,
+ _ => 2,
+ } {
+ #[cfg(disabled_feature)]
+ 0 => println!("Array index start"),
+ _ => println!("Not an array index start"),
+ }
+
+ // Lint
+ let x = 1;
+ match x {
+ // =>
+ _ => println!("Not an array index start"),
+ }
+}
++
++#[allow(dead_code)]
++fn issue_8723() {
++ let (mut val, idx) = ("a b", 1);
++
++ val = match val.split_at(idx) {
++ (pre, suf) => {
++ println!("{}", pre);
++ suf
++ },
++ };
++
++ let _ = val;
++}
--- /dev/null
- help: consider using `let` statement
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:28:5
+ |
+LL | / match (a, b, c) {
+LL | | (x, y, z) => {
+LL | | println!("{} {} {}", x, y, z);
+LL | | },
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::match-single-binding` implied by `-D warnings`
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let (x, y, z) = (a, b, c);
+LL + {
+LL + println!("{} {} {}", x, y, z);
+LL + }
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:34:5
+ |
+LL | / match (a, b, c) {
+LL | | (x, y, z) => println!("{} {} {}", x, y, z),
+LL | | }
+ | |_____^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let (x, y, z) = (a, b, c);
+LL + println!("{} {} {}", x, y, z);
+ |
+
+error: this match could be replaced by its body itself
+ --> $DIR/match_single_binding.rs:51:5
+ |
+LL | / match a {
+LL | | _ => println!("whatever"),
+LL | | }
+ | |_____^ help: consider using the match body instead: `println!("whatever");`
+
+error: this match could be replaced by its body itself
+ --> $DIR/match_single_binding.rs:55:5
+ |
+LL | / match a {
+LL | | _ => {
+LL | | let x = 29;
+LL | | println!("x has a value of {}", x);
+LL | | },
+LL | | }
+ | |_____^
+ |
+help: consider using the match body instead
+ |
+LL ~ {
+LL + let x = 29;
+LL + println!("x has a value of {}", x);
+LL + }
+ |
+
+error: this match could be replaced by its body itself
+ --> $DIR/match_single_binding.rs:62:5
+ |
+LL | / match a {
+LL | | _ => {
+LL | | let e = 5 * a;
+LL | | if e >= 5 {
+... |
+LL | | },
+LL | | }
+ | |_____^
+ |
+help: consider using the match body instead
+ |
+LL ~ {
+LL + let e = 5 * a;
+LL + if e >= 5 {
+LL + println!("e is superior to 5");
+LL + }
+LL + }
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:72:5
+ |
+LL | / match p {
+LL | | Point { x, y } => println!("Coords: ({}, {})", x, y),
+LL | | }
+ | |_____^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let Point { x, y } = p;
+LL + println!("Coords: ({}, {})", x, y);
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:76:5
+ |
+LL | / match p {
+LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1),
+LL | | }
+ | |_____^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let Point { x: x1, y: y1 } = p;
+LL + println!("Coords: ({}, {})", x1, y1);
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:81:5
+ |
+LL | / match x {
+LL | | ref r => println!("Got a reference to {}", r),
+LL | | }
+ | |_____^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let ref r = x;
+LL + println!("Got a reference to {}", r);
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:86:5
+ |
+LL | / match x {
+LL | | ref mut mr => println!("Got a mutable reference to {}", mr),
+LL | | }
+ | |_____^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let ref mut mr = x;
+LL + println!("Got a mutable reference to {}", mr);
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:90:5
+ |
+LL | / let product = match coords() {
+LL | | Point { x, y } => x * y,
+LL | | };
+ | |______^
+ |
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ let Point { x, y } = coords();
+LL + let product = x * y;
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding.rs:98:18
+ |
+LL | .map(|i| match i.unwrap() {
+ | __________________^
+LL | | unwrapped => unwrapped,
+LL | | })
+ | |_________^
+ |
- error: aborting due to 12 previous errors
++help: consider using a `let` statement
+ |
+LL ~ .map(|i| {
+LL + let unwrapped = i.unwrap();
+LL + unwrapped
+LL ~ })
+ |
+
+error: this match could be replaced by its body itself
+ --> $DIR/match_single_binding.rs:124:5
+ |
+LL | / match x {
+LL | | // =>
+LL | | _ => println!("Not an array index start"),
+LL | | }
+ | |_____^ help: consider using the match body instead: `println!("Not an array index start");`
+
++error: this assignment could be simplified
++ --> $DIR/match_single_binding.rs:134:5
++ |
++LL | / val = match val.split_at(idx) {
++LL | | (pre, suf) => {
++LL | | println!("{}", pre);
++LL | | suf
++LL | | },
++LL | | };
++ | |_____^
++ |
++help: consider removing the `match` expression
++ |
++LL ~ let (pre, suf) = val.split_at(idx);
++LL + val = {
++LL + println!("{}", pre);
++LL + suf
++LL ~ };
++ |
++
++error: aborting due to 13 previous errors
+
--- /dev/null
- help: consider using `let` statement
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding2.rs:18:36
+ |
+LL | Some((iter, _item)) => match iter.size_hint() {
+ | ____________________________________^
+LL | | (min, max) => (min.saturating_add(1), max.and_then(|max| max.checked_add(1))),
+LL | | },
+ | |_____________^
+ |
+ = note: `-D clippy::match-single-binding` implied by `-D warnings`
- help: consider using `let` statement
++help: consider using a `let` statement
+ |
+LL ~ Some((iter, _item)) => {
+LL + let (min, max) = iter.size_hint();
+LL + (min.saturating_add(1), max.and_then(|max| max.checked_add(1)))
+LL ~ },
+ |
+
+error: this match could be written as a `let` statement
+ --> $DIR/match_single_binding2.rs:31:13
+ |
+LL | / match get_tup() {
+LL | | (a, b) => println!("a {:?} and b {:?}", a, b),
+LL | | }
+ | |_____________^
+ |
++help: consider using a `let` statement
+ |
+LL ~ let (a, b) = get_tup();
+LL + println!("a {:?} and b {:?}", a, b);
+ |
+
+error: this match could be replaced by its scrutinee and body
+ --> $DIR/match_single_binding2.rs:42:5
+ |
+LL | / match side_effects() {
+LL | | _ => println!("Side effects"),
+LL | | }
+ | |_____^
+ |
+help: consider using the scrutinee and body instead
+ |
+LL ~ side_effects();
+LL + println!("Side effects");
+ |
+
+error: this match could be replaced by its scrutinee and body
+ --> $DIR/match_single_binding2.rs:49:5
+ |
+LL | / match match x {
+LL | | 0 => 1,
+LL | | _ => 2,
+LL | | } {
+LL | | _ => println!("Single branch"),
+LL | | }
+ | |_____^
+ |
+help: consider using the scrutinee and body instead
+ |
+LL ~ match x {
+LL + 0 => 1,
+LL + _ => 2,
+LL + };
+LL + println!("Single branch");
+ |
+
+error: aborting due to 4 previous errors
+
--- /dev/null
- use std::iter::FromIterator;
+// aux-build:option_helpers.rs
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(
+ clippy::blacklisted_name,
+ clippy::default_trait_access,
+ clippy::missing_docs_in_private_items,
+ clippy::missing_safety_doc,
+ clippy::non_ascii_literal,
+ clippy::new_without_default,
+ clippy::needless_pass_by_value,
+ clippy::needless_lifetimes,
+ clippy::print_stdout,
+ clippy::must_use_candidate,
+ clippy::use_self,
+ clippy::useless_format,
+ clippy::wrong_self_convention,
+ clippy::unused_self,
+ unused
+)]
+
+#[macro_use]
+extern crate option_helpers;
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::collections::VecDeque;
+use std::ops::Mul;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+use option_helpers::{IteratorFalsePositives, IteratorMethodFalsePositives};
+
+struct Lt<'a> {
+ foo: &'a u32,
+}
+
+impl<'a> Lt<'a> {
+ // The lifetime is different, but that’s irrelevant; see issue #734.
+ #[allow(clippy::needless_lifetimes)]
+ pub fn new<'b>(s: &'b str) -> Lt<'b> {
+ unimplemented!()
+ }
+}
+
+struct Lt2<'a> {
+ foo: &'a u32,
+}
+
+impl<'a> Lt2<'a> {
+ // The lifetime is different, but that’s irrelevant; see issue #734.
+ pub fn new(s: &str) -> Lt2 {
+ unimplemented!()
+ }
+}
+
+struct Lt3<'a> {
+ foo: &'a u32,
+}
+
+impl<'a> Lt3<'a> {
+ // The lifetime is different, but that’s irrelevant; see issue #734.
+ pub fn new() -> Lt3<'static> {
+ unimplemented!()
+ }
+}
+
+#[derive(Clone, Copy)]
+struct U;
+
+impl U {
+ fn new() -> Self {
+ U
+ }
+ // Ok because `U` is `Copy`.
+ fn to_something(self) -> u32 {
+ 0
+ }
+}
+
+struct V<T> {
+ _dummy: T,
+}
+
+impl<T> V<T> {
+ fn new() -> Option<V<T>> {
+ None
+ }
+}
+
+struct AsyncNew;
+
+impl AsyncNew {
+ async fn new() -> Option<Self> {
+ None
+ }
+}
+
+struct BadNew;
+
+impl BadNew {
+ fn new() -> i32 {
+ 0
+ }
+}
+
+struct T;
+
+impl Mul<T> for T {
+ type Output = T;
+ // No error, obviously.
+ fn mul(self, other: T) -> T {
+ self
+ }
+}
+
+/// Checks implementation of `FILTER_NEXT` lint.
+#[rustfmt::skip]
+fn filter_next() {
+ let v = vec![3, 2, 1, 0, -1, -2, -3];
+
+ // Multi-line case.
+ let _ = v.iter().filter(|&x| {
+ *x < 0
+ }
+ ).next();
+
+ // Check that we don't lint if the caller is not an `Iterator`.
+ let foo = IteratorFalsePositives { foo: 0 };
+ let _ = foo.filter().next();
+
+ let foo = IteratorMethodFalsePositives {};
+ let _ = foo.filter(42).next();
+}
+
+fn main() {
+ filter_next();
+}
--- /dev/null
- --> $DIR/methods.rs:104:5
+error: methods called `new` usually return `Self`
- --> $DIR/methods.rs:125:13
++ --> $DIR/methods.rs:103:5
+ |
+LL | / fn new() -> i32 {
+LL | | 0
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::new-ret-no-self` implied by `-D warnings`
+
+error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead
++ --> $DIR/methods.rs:124:13
+ |
+LL | let _ = v.iter().filter(|&x| {
+ | _____________^
+LL | | *x < 0
+LL | | }
+LL | | ).next();
+ | |___________________________^
+ |
+ = note: `-D clippy::filter-next` implied by `-D warnings`
+
+error: aborting due to 2 previous errors
+
--- /dev/null
--- /dev/null
++#[warn(clippy::mixed_read_write_in_expression)]
++#[allow(
++ unused_assignments,
++ unused_variables,
++ clippy::no_effect,
++ dead_code,
++ clippy::blacklisted_name
++)]
++fn main() {
++ let mut x = 0;
++ let a = {
++ x = 1;
++ 1
++ } + x;
++
++ // Example from iss#277
++ x += {
++ x = 20;
++ 2
++ };
++
++ // Does it work in weird places?
++ // ...in the base for a struct expression?
++ struct Foo {
++ a: i32,
++ b: i32,
++ };
++ let base = Foo { a: 4, b: 5 };
++ let foo = Foo {
++ a: x,
++ ..{
++ x = 6;
++ base
++ }
++ };
++ // ...inside a closure?
++ let closure = || {
++ let mut x = 0;
++ x += {
++ x = 20;
++ 2
++ };
++ };
++ // ...not across a closure?
++ let mut y = 0;
++ let b = (y, || y = 1);
++
++ // && and || evaluate left-to-right.
++ let a = {
++ x = 1;
++ true
++ } && (x == 3);
++ let a = {
++ x = 1;
++ true
++ } || (x == 3);
++
++ // Make sure we don't get confused by alpha conversion.
++ let a = {
++ let mut x = 1;
++ x = 2;
++ 1
++ } + x;
++
++ // No warning if we don't read the variable...
++ x = {
++ x = 20;
++ 2
++ };
++ // ...if the assignment is in a closure...
++ let b = {
++ || {
++ x = 1;
++ };
++ 1
++ } + x;
++ // ... or the access is under an address.
++ let b = (
++ {
++ let p = &x;
++ 1
++ },
++ {
++ x = 1;
++ x
++ },
++ );
++
++ // Limitation: l-values other than simple variables don't trigger
++ // the warning.
++ let mut tup = (0, 0);
++ let c = {
++ tup.0 = 1;
++ 1
++ } + tup.0;
++ // Limitation: you can get away with a read under address-of.
++ let mut z = 0;
++ let b = (
++ &{
++ z = x;
++ x
++ },
++ {
++ x = 3;
++ x
++ },
++ );
++}
++
++async fn issue_6925() {
++ let _ = vec![async { true }.await, async { false }.await];
++}
--- /dev/null
--- /dev/null
++error: unsequenced read of `x`
++ --> $DIR/mixed_read_write_in_expression.rs:14:9
++ |
++LL | } + x;
++ | ^
++ |
++ = note: `-D clippy::mixed-read-write-in-expression` implied by `-D warnings`
++note: whether read occurs before this write depends on evaluation order
++ --> $DIR/mixed_read_write_in_expression.rs:12:9
++ |
++LL | x = 1;
++ | ^^^^^
++
++error: unsequenced read of `x`
++ --> $DIR/mixed_read_write_in_expression.rs:17:5
++ |
++LL | x += {
++ | ^
++ |
++note: whether read occurs before this write depends on evaluation order
++ --> $DIR/mixed_read_write_in_expression.rs:18:9
++ |
++LL | x = 20;
++ | ^^^^^^
++
++error: unsequenced read of `x`
++ --> $DIR/mixed_read_write_in_expression.rs:30:12
++ |
++LL | a: x,
++ | ^
++ |
++note: whether read occurs before this write depends on evaluation order
++ --> $DIR/mixed_read_write_in_expression.rs:32:13
++ |
++LL | x = 6;
++ | ^^^^^
++
++error: unsequenced read of `x`
++ --> $DIR/mixed_read_write_in_expression.rs:39:9
++ |
++LL | x += {
++ | ^
++ |
++note: whether read occurs before this write depends on evaluation order
++ --> $DIR/mixed_read_write_in_expression.rs:40:13
++ |
++LL | x = 20;
++ | ^^^^^^
++
++error: aborting due to 4 previous errors
++
--- /dev/null
- let x = 9_u32;
+// run-rustfix
+
+#[warn(clippy::manual_range_contains)]
+#[allow(unused)]
+#[allow(clippy::no_effect)]
+#[allow(clippy::short_circuit_statement)]
+#[allow(clippy::unnecessary_operation)]
+fn main() {
++ let x = 9_i32;
+
+ // order shouldn't matter
+ (8..12).contains(&x);
+ (21..42).contains(&x);
+ (1..100).contains(&x);
+
+ // also with inclusive ranges
+ (9..=99).contains(&x);
+ (1..=33).contains(&x);
+ (1..=999).contains(&x);
+
+ // and the outside
+ !(8..12).contains(&x);
+ !(21..42).contains(&x);
+ !(1..100).contains(&x);
+
+ // also with the outside of inclusive ranges
+ !(9..=99).contains(&x);
+ !(1..=33).contains(&x);
+ !(1..=999).contains(&x);
+
+ // not a range.contains
+ x > 8 && x < 12; // lower bound not inclusive
+ x < 8 && x <= 12; // same direction
+ x >= 12 && 12 >= x; // same bounds
+ x < 8 && x > 12; // wrong direction
+
+ x <= 8 || x >= 12;
+ x >= 8 || x >= 12;
+ x < 12 || 12 < x;
+ x >= 8 || x <= 12;
+
+ // Fix #6315
+ let y = 3.;
+ (0. ..1.).contains(&y);
+ !(0. ..=1.).contains(&y);
++
++ // handle negatives #8721
++ (-10..=10).contains(&x);
++ x >= 10 && x <= -10;
++ (-3. ..=3.).contains(&y);
++ y >= 3. && y <= -3.;
+}
+
+// Fix #6373
+pub const fn in_range(a: i32) -> bool {
+ 3 <= a && a <= 20
+}
--- /dev/null
- let x = 9_u32;
+// run-rustfix
+
+#[warn(clippy::manual_range_contains)]
+#[allow(unused)]
+#[allow(clippy::no_effect)]
+#[allow(clippy::short_circuit_statement)]
+#[allow(clippy::unnecessary_operation)]
+fn main() {
++ let x = 9_i32;
+
+ // order shouldn't matter
+ x >= 8 && x < 12;
+ x < 42 && x >= 21;
+ 100 > x && 1 <= x;
+
+ // also with inclusive ranges
+ x >= 9 && x <= 99;
+ x <= 33 && x >= 1;
+ 999 >= x && 1 <= x;
+
+ // and the outside
+ x < 8 || x >= 12;
+ x >= 42 || x < 21;
+ 100 <= x || 1 > x;
+
+ // also with the outside of inclusive ranges
+ x < 9 || x > 99;
+ x > 33 || x < 1;
+ 999 < x || 1 > x;
+
+ // not a range.contains
+ x > 8 && x < 12; // lower bound not inclusive
+ x < 8 && x <= 12; // same direction
+ x >= 12 && 12 >= x; // same bounds
+ x < 8 && x > 12; // wrong direction
+
+ x <= 8 || x >= 12;
+ x >= 8 || x >= 12;
+ x < 12 || 12 < x;
+ x >= 8 || x <= 12;
+
+ // Fix #6315
+ let y = 3.;
+ y >= 0. && y < 1.;
+ y < 0. || y > 1.;
++
++ // handle negatives #8721
++ x >= -10 && x <= 10;
++ x >= 10 && x <= -10;
++ y >= -3. && y <= 3.;
++ y >= 3. && y <= -3.;
+}
+
+// Fix #6373
+pub const fn in_range(a: i32) -> bool {
+ 3 <= a && a <= 20
+}
--- /dev/null
- error: aborting due to 14 previous errors
+error: manual `Range::contains` implementation
+ --> $DIR/range_contains.rs:12:5
+ |
+LL | x >= 8 && x < 12;
+ | ^^^^^^^^^^^^^^^^ help: use: `(8..12).contains(&x)`
+ |
+ = note: `-D clippy::manual-range-contains` implied by `-D warnings`
+
+error: manual `Range::contains` implementation
+ --> $DIR/range_contains.rs:13:5
+ |
+LL | x < 42 && x >= 21;
+ | ^^^^^^^^^^^^^^^^^ help: use: `(21..42).contains(&x)`
+
+error: manual `Range::contains` implementation
+ --> $DIR/range_contains.rs:14:5
+ |
+LL | 100 > x && 1 <= x;
+ | ^^^^^^^^^^^^^^^^^ help: use: `(1..100).contains(&x)`
+
+error: manual `RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:17:5
+ |
+LL | x >= 9 && x <= 99;
+ | ^^^^^^^^^^^^^^^^^ help: use: `(9..=99).contains(&x)`
+
+error: manual `RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:18:5
+ |
+LL | x <= 33 && x >= 1;
+ | ^^^^^^^^^^^^^^^^^ help: use: `(1..=33).contains(&x)`
+
+error: manual `RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:19:5
+ |
+LL | 999 >= x && 1 <= x;
+ | ^^^^^^^^^^^^^^^^^^ help: use: `(1..=999).contains(&x)`
+
+error: manual `!Range::contains` implementation
+ --> $DIR/range_contains.rs:22:5
+ |
+LL | x < 8 || x >= 12;
+ | ^^^^^^^^^^^^^^^^ help: use: `!(8..12).contains(&x)`
+
+error: manual `!Range::contains` implementation
+ --> $DIR/range_contains.rs:23:5
+ |
+LL | x >= 42 || x < 21;
+ | ^^^^^^^^^^^^^^^^^ help: use: `!(21..42).contains(&x)`
+
+error: manual `!Range::contains` implementation
+ --> $DIR/range_contains.rs:24:5
+ |
+LL | 100 <= x || 1 > x;
+ | ^^^^^^^^^^^^^^^^^ help: use: `!(1..100).contains(&x)`
+
+error: manual `!RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:27:5
+ |
+LL | x < 9 || x > 99;
+ | ^^^^^^^^^^^^^^^ help: use: `!(9..=99).contains(&x)`
+
+error: manual `!RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:28:5
+ |
+LL | x > 33 || x < 1;
+ | ^^^^^^^^^^^^^^^ help: use: `!(1..=33).contains(&x)`
+
+error: manual `!RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:29:5
+ |
+LL | 999 < x || 1 > x;
+ | ^^^^^^^^^^^^^^^^ help: use: `!(1..=999).contains(&x)`
+
+error: manual `Range::contains` implementation
+ --> $DIR/range_contains.rs:44:5
+ |
+LL | y >= 0. && y < 1.;
+ | ^^^^^^^^^^^^^^^^^ help: use: `(0. ..1.).contains(&y)`
+
+error: manual `!RangeInclusive::contains` implementation
+ --> $DIR/range_contains.rs:45:5
+ |
+LL | y < 0. || y > 1.;
+ | ^^^^^^^^^^^^^^^^ help: use: `!(0. ..=1.).contains(&y)`
+
++error: manual `RangeInclusive::contains` implementation
++ --> $DIR/range_contains.rs:48:5
++ |
++LL | x >= -10 && x <= 10;
++ | ^^^^^^^^^^^^^^^^^^^ help: use: `(-10..=10).contains(&x)`
++
++error: manual `RangeInclusive::contains` implementation
++ --> $DIR/range_contains.rs:50:5
++ |
++LL | y >= -3. && y <= 3.;
++ | ^^^^^^^^^^^^^^^^^^^ help: use: `(-3. ..=3.).contains(&y)`
++
++error: aborting due to 16 previous errors
+
--- /dev/null
--- /dev/null
++#![warn(clippy::rc_clone_in_vec_init)]
++use std::sync::{Arc, Mutex};
++
++fn main() {}
++
++fn should_warn_simple_case() {
++ let v = vec![Arc::new("x".to_string()); 2];
++}
++
++fn should_warn_simple_case_with_big_indentation() {
++ if true {
++ let k = 1;
++ dbg!(k);
++ if true {
++ let v = vec![Arc::new("x".to_string()); 2];
++ }
++ }
++}
++
++fn should_warn_complex_case() {
++ let v = vec![
++ std::sync::Arc::new(Mutex::new({
++ let x = 1;
++ dbg!(x);
++ x
++ }));
++ 2
++ ];
++
++ let v1 = vec![
++ Arc::new(Mutex::new({
++ let x = 1;
++ dbg!(x);
++ x
++ }));
++ 2
++ ];
++}
++
++fn should_not_warn_custom_arc() {
++ #[derive(Clone)]
++ struct Arc;
++
++ impl Arc {
++ fn new() -> Self {
++ Arc
++ }
++ }
++
++ let v = vec![Arc::new(); 2];
++}
++
++fn should_not_warn_vec_from_elem_but_not_arc() {
++ let v = vec![String::new(); 2];
++ let v1 = vec![1; 2];
++ let v2 = vec![
++ Box::new(std::sync::Arc::new({
++ let y = 3;
++ dbg!(y);
++ y
++ }));
++ 2
++ ];
++}
++
++fn should_not_warn_vec_macro_but_not_from_elem() {
++ let v = vec![Arc::new("x".to_string())];
++}
--- /dev/null
--- /dev/null
++error: calling `Arc::new` in `vec![elem; len]`
++ --> $DIR/arc.rs:7:13
++ |
++LL | let v = vec![Arc::new("x".to_string()); 2];
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::rc-clone-in-vec-init` implied by `-D warnings`
++ = note: each element will point to the same `Arc` instance
++help: consider initializing each `Arc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Arc::new("x".to_string())));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Arc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = Arc::new("x".to_string());
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Arc::new` in `vec![elem; len]`
++ --> $DIR/arc.rs:15:21
++ |
++LL | let v = vec![Arc::new("x".to_string()); 2];
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: each element will point to the same `Arc` instance
++help: consider initializing each `Arc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Arc::new("x".to_string())));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Arc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = Arc::new("x".to_string());
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Arc::new` in `vec![elem; len]`
++ --> $DIR/arc.rs:21:13
++ |
++LL | let v = vec![
++ | _____________^
++LL | | std::sync::Arc::new(Mutex::new({
++LL | | let x = 1;
++LL | | dbg!(x);
++... |
++LL | | 2
++LL | | ];
++ | |_____^
++ |
++ = note: each element will point to the same `Arc` instance
++help: consider initializing each `Arc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(std::sync::Arc::new(..)));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Arc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = std::sync::Arc::new(..);
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Arc::new` in `vec![elem; len]`
++ --> $DIR/arc.rs:30:14
++ |
++LL | let v1 = vec![
++ | ______________^
++LL | | Arc::new(Mutex::new({
++LL | | let x = 1;
++LL | | dbg!(x);
++... |
++LL | | 2
++LL | | ];
++ | |_____^
++ |
++ = note: each element will point to the same `Arc` instance
++help: consider initializing each `Arc` element individually
++ |
++LL ~ let v1 = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Arc::new(..)));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Arc` initialization to a variable
++ |
++LL ~ let v1 = {
++LL + let data = Arc::new(..);
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: aborting due to 4 previous errors
++
--- /dev/null
--- /dev/null
++#![warn(clippy::rc_clone_in_vec_init)]
++use std::rc::Rc;
++use std::sync::Mutex;
++
++fn main() {}
++
++fn should_warn_simple_case() {
++ let v = vec![Rc::new("x".to_string()); 2];
++}
++
++fn should_warn_simple_case_with_big_indentation() {
++ if true {
++ let k = 1;
++ dbg!(k);
++ if true {
++ let v = vec![Rc::new("x".to_string()); 2];
++ }
++ }
++}
++
++fn should_warn_complex_case() {
++ let v = vec![
++ std::rc::Rc::new(Mutex::new({
++ let x = 1;
++ dbg!(x);
++ x
++ }));
++ 2
++ ];
++
++ let v1 = vec![
++ Rc::new(Mutex::new({
++ let x = 1;
++ dbg!(x);
++ x
++ }));
++ 2
++ ];
++}
++
++fn should_not_warn_custom_arc() {
++ #[derive(Clone)]
++ struct Rc;
++
++ impl Rc {
++ fn new() -> Self {
++ Rc
++ }
++ }
++
++ let v = vec![Rc::new(); 2];
++}
++
++fn should_not_warn_vec_from_elem_but_not_rc() {
++ let v = vec![String::new(); 2];
++ let v1 = vec![1; 2];
++ let v2 = vec![
++ Box::new(std::rc::Rc::new({
++ let y = 3;
++ dbg!(y);
++ y
++ }));
++ 2
++ ];
++}
++
++fn should_not_warn_vec_macro_but_not_from_elem() {
++ let v = vec![Rc::new("x".to_string())];
++}
--- /dev/null
--- /dev/null
++error: calling `Rc::new` in `vec![elem; len]`
++ --> $DIR/rc.rs:8:13
++ |
++LL | let v = vec![Rc::new("x".to_string()); 2];
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::rc-clone-in-vec-init` implied by `-D warnings`
++ = note: each element will point to the same `Rc` instance
++help: consider initializing each `Rc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Rc::new("x".to_string())));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Rc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = Rc::new("x".to_string());
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Rc::new` in `vec![elem; len]`
++ --> $DIR/rc.rs:16:21
++ |
++LL | let v = vec![Rc::new("x".to_string()); 2];
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: each element will point to the same `Rc` instance
++help: consider initializing each `Rc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Rc::new("x".to_string())));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Rc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = Rc::new("x".to_string());
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Rc::new` in `vec![elem; len]`
++ --> $DIR/rc.rs:22:13
++ |
++LL | let v = vec![
++ | _____________^
++LL | | std::rc::Rc::new(Mutex::new({
++LL | | let x = 1;
++LL | | dbg!(x);
++... |
++LL | | 2
++LL | | ];
++ | |_____^
++ |
++ = note: each element will point to the same `Rc` instance
++help: consider initializing each `Rc` element individually
++ |
++LL ~ let v = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(std::rc::Rc::new(..)));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Rc` initialization to a variable
++ |
++LL ~ let v = {
++LL + let data = std::rc::Rc::new(..);
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: calling `Rc::new` in `vec![elem; len]`
++ --> $DIR/rc.rs:31:14
++ |
++LL | let v1 = vec![
++ | ______________^
++LL | | Rc::new(Mutex::new({
++LL | | let x = 1;
++LL | | dbg!(x);
++... |
++LL | | 2
++LL | | ];
++ | |_____^
++ |
++ = note: each element will point to the same `Rc` instance
++help: consider initializing each `Rc` element individually
++ |
++LL ~ let v1 = {
++LL + let mut v = Vec::with_capacity(2);
++LL + (0..2).for_each(|_| v.push(Rc::new(..)));
++LL + v
++LL ~ };
++ |
++help: or if this is intentional, consider extracting the `Rc` initialization to a variable
++ |
++LL ~ let v1 = {
++LL + let data = Rc::new(..);
++LL + vec![data; 2]
++LL ~ };
++ |
++
++error: aborting due to 4 previous errors
++
--- /dev/null
- error: unnecessary use of `to_string`
- --> $DIR/recursive_format_impl.rs:61:50
- |
- LL | Self::E(string) => write!(f, "E {}", string.to_string()),
- | ^^^^^^^^^^^^^^^^^^
- |
- = note: `-D clippy::unnecessary-to-owned` implied by `-D warnings`
- = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info)
-
+error: using `self.to_string` in `fmt::Display` implementation will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:29:25
+ |
+LL | write!(f, "{}", self.to_string())
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::recursive-format-impl` implied by `-D warnings`
+
- error: aborting due to 11 previous errors
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:73:9
+ |
+LL | write!(f, "{}", self)
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:82:9
+ |
+LL | write!(f, "{}", &self)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Debug` in `impl Debug` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:88:9
+ |
+LL | write!(f, "{:?}", &self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:97:9
+ |
+LL | write!(f, "{}", &&&self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:171:9
+ |
+LL | write!(f, "{}", &*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Debug` in `impl Debug` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:177:9
+ |
+LL | write!(f, "{:?}", &*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:193:9
+ |
+LL | write!(f, "{}", *self)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:209:9
+ |
+LL | write!(f, "{}", **&&*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
+ --> $DIR/recursive_format_impl.rs:225:9
+ |
+LL | write!(f, "{}", &&**&&*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
++error: aborting due to 10 previous errors
+
--- /dev/null
+#![warn(clippy::all)]
+#![allow(clippy::boxed_local, clippy::needless_pass_by_value)]
+#![allow(clippy::blacklisted_name, unused_variables, dead_code)]
+#![allow(unused_imports)]
+
+pub struct MyStruct;
+
+pub struct SubT<T> {
+ foo: T,
+}
+
+pub enum MyEnum {
+ One,
+ Two,
+}
+
+mod outer_box {
+ use crate::MyEnum;
+ use crate::MyStruct;
+ use crate::SubT;
+ use std::boxed::Box;
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ pub fn box_test6<T>(foo: Box<Rc<T>>) {}
+
+ pub fn box_test7<T>(foo: Box<Arc<T>>) {}
+
+ pub fn box_test8() -> Box<Rc<SubT<usize>>> {
+ unimplemented!();
+ }
+
+ pub fn box_test9<T>(foo: Box<Arc<T>>) -> Box<Arc<SubT<T>>> {
+ unimplemented!();
+ }
+}
+
+mod outer_rc {
+ use crate::MyEnum;
+ use crate::MyStruct;
+ use crate::SubT;
+ use std::boxed::Box;
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ pub fn rc_test5(a: Rc<Box<bool>>) {}
+
+ pub fn rc_test7(a: Rc<Arc<bool>>) {}
+
+ pub fn rc_test8() -> Rc<Box<SubT<usize>>> {
+ unimplemented!();
+ }
+
+ pub fn rc_test9<T>(foo: Rc<Arc<T>>) -> Rc<Arc<SubT<T>>> {
+ unimplemented!();
+ }
+}
+
+mod outer_arc {
+ use crate::MyEnum;
+ use crate::MyStruct;
+ use crate::SubT;
+ use std::boxed::Box;
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ pub fn arc_test5(a: Arc<Box<bool>>) {}
+
+ pub fn arc_test6(a: Arc<Rc<bool>>) {}
+
+ pub fn arc_test8() -> Arc<Box<SubT<usize>>> {
+ unimplemented!();
+ }
+
+ pub fn arc_test9<T>(foo: Arc<Rc<T>>) -> Arc<Rc<SubT<T>>> {
+ unimplemented!();
+ }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/7487
+mod box_dyn {
+ use std::boxed::Box;
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ pub trait T {}
+
+ struct S {
+ a: Box<Box<dyn T>>,
+ b: Rc<Box<dyn T>>,
+ c: Arc<Box<dyn T>>,
+ }
+
+ pub fn test_box(_: Box<Box<dyn T>>) {}
+ pub fn test_rc(_: Rc<Box<dyn T>>) {}
+ pub fn test_arc(_: Arc<Box<dyn T>>) {}
+ pub fn test_rc_box(_: Rc<Box<Box<dyn T>>>) {}
+}
+
++// https://github.com/rust-lang/rust-clippy/issues/8604
++mod box_fat_ptr {
++ use std::boxed::Box;
++ use std::path::Path;
++ use std::rc::Rc;
++ use std::sync::Arc;
++
++ pub struct DynSized {
++ foo: [usize],
++ }
++
++ struct S {
++ a: Box<Box<str>>,
++ b: Rc<Box<str>>,
++ c: Arc<Box<str>>,
++
++ e: Box<Box<[usize]>>,
++ f: Box<Box<Path>>,
++ g: Box<Box<DynSized>>,
++ }
++
++ pub fn test_box_str(_: Box<Box<str>>) {}
++ pub fn test_rc_str(_: Rc<Box<str>>) {}
++ pub fn test_arc_str(_: Arc<Box<str>>) {}
++
++ pub fn test_box_slice(_: Box<Box<[usize]>>) {}
++ pub fn test_box_path(_: Box<Box<Path>>) {}
++ pub fn test_box_custom(_: Box<Box<DynSized>>) {}
++
++ pub fn test_rc_box_str(_: Rc<Box<Box<str>>>) {}
++ pub fn test_rc_box_slice(_: Rc<Box<Box<[usize]>>>) {}
++ pub fn test_rc_box_path(_: Rc<Box<Box<Path>>>) {}
++ pub fn test_rc_box_custom(_: Rc<Box<Box<DynSized>>>) {}
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 16 previous errors
+error: usage of `Box<Rc<T>>`
+ --> $DIR/redundant_allocation.rs:25:30
+ |
+LL | pub fn box_test6<T>(foo: Box<Rc<T>>) {}
+ | ^^^^^^^^^^
+ |
+ = note: `-D clippy::redundant-allocation` implied by `-D warnings`
+ = note: `Rc<T>` is already on the heap, `Box<Rc<T>>` makes an extra allocation
+ = help: consider using just `Box<T>` or `Rc<T>`
+
+error: usage of `Box<Arc<T>>`
+ --> $DIR/redundant_allocation.rs:27:30
+ |
+LL | pub fn box_test7<T>(foo: Box<Arc<T>>) {}
+ | ^^^^^^^^^^^
+ |
+ = note: `Arc<T>` is already on the heap, `Box<Arc<T>>` makes an extra allocation
+ = help: consider using just `Box<T>` or `Arc<T>`
+
+error: usage of `Box<Rc<SubT<usize>>>`
+ --> $DIR/redundant_allocation.rs:29:27
+ |
+LL | pub fn box_test8() -> Box<Rc<SubT<usize>>> {
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `Rc<SubT<usize>>` is already on the heap, `Box<Rc<SubT<usize>>>` makes an extra allocation
+ = help: consider using just `Box<SubT<usize>>` or `Rc<SubT<usize>>`
+
+error: usage of `Box<Arc<T>>`
+ --> $DIR/redundant_allocation.rs:33:30
+ |
+LL | pub fn box_test9<T>(foo: Box<Arc<T>>) -> Box<Arc<SubT<T>>> {
+ | ^^^^^^^^^^^
+ |
+ = note: `Arc<T>` is already on the heap, `Box<Arc<T>>` makes an extra allocation
+ = help: consider using just `Box<T>` or `Arc<T>`
+
+error: usage of `Box<Arc<SubT<T>>>`
+ --> $DIR/redundant_allocation.rs:33:46
+ |
+LL | pub fn box_test9<T>(foo: Box<Arc<T>>) -> Box<Arc<SubT<T>>> {
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = note: `Arc<SubT<T>>` is already on the heap, `Box<Arc<SubT<T>>>` makes an extra allocation
+ = help: consider using just `Box<SubT<T>>` or `Arc<SubT<T>>`
+
+error: usage of `Rc<Box<bool>>`
+ --> $DIR/redundant_allocation.rs:46:24
+ |
+LL | pub fn rc_test5(a: Rc<Box<bool>>) {}
+ | ^^^^^^^^^^^^^
+ |
+ = note: `Box<bool>` is already on the heap, `Rc<Box<bool>>` makes an extra allocation
+ = help: consider using just `Rc<bool>` or `Box<bool>`
+
+error: usage of `Rc<Arc<bool>>`
+ --> $DIR/redundant_allocation.rs:48:24
+ |
+LL | pub fn rc_test7(a: Rc<Arc<bool>>) {}
+ | ^^^^^^^^^^^^^
+ |
+ = note: `Arc<bool>` is already on the heap, `Rc<Arc<bool>>` makes an extra allocation
+ = help: consider using just `Rc<bool>` or `Arc<bool>`
+
+error: usage of `Rc<Box<SubT<usize>>>`
+ --> $DIR/redundant_allocation.rs:50:26
+ |
+LL | pub fn rc_test8() -> Rc<Box<SubT<usize>>> {
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `Box<SubT<usize>>` is already on the heap, `Rc<Box<SubT<usize>>>` makes an extra allocation
+ = help: consider using just `Rc<SubT<usize>>` or `Box<SubT<usize>>`
+
+error: usage of `Rc<Arc<T>>`
+ --> $DIR/redundant_allocation.rs:54:29
+ |
+LL | pub fn rc_test9<T>(foo: Rc<Arc<T>>) -> Rc<Arc<SubT<T>>> {
+ | ^^^^^^^^^^
+ |
+ = note: `Arc<T>` is already on the heap, `Rc<Arc<T>>` makes an extra allocation
+ = help: consider using just `Rc<T>` or `Arc<T>`
+
+error: usage of `Rc<Arc<SubT<T>>>`
+ --> $DIR/redundant_allocation.rs:54:44
+ |
+LL | pub fn rc_test9<T>(foo: Rc<Arc<T>>) -> Rc<Arc<SubT<T>>> {
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `Arc<SubT<T>>` is already on the heap, `Rc<Arc<SubT<T>>>` makes an extra allocation
+ = help: consider using just `Rc<SubT<T>>` or `Arc<SubT<T>>`
+
+error: usage of `Arc<Box<bool>>`
+ --> $DIR/redundant_allocation.rs:67:25
+ |
+LL | pub fn arc_test5(a: Arc<Box<bool>>) {}
+ | ^^^^^^^^^^^^^^
+ |
+ = note: `Box<bool>` is already on the heap, `Arc<Box<bool>>` makes an extra allocation
+ = help: consider using just `Arc<bool>` or `Box<bool>`
+
+error: usage of `Arc<Rc<bool>>`
+ --> $DIR/redundant_allocation.rs:69:25
+ |
+LL | pub fn arc_test6(a: Arc<Rc<bool>>) {}
+ | ^^^^^^^^^^^^^
+ |
+ = note: `Rc<bool>` is already on the heap, `Arc<Rc<bool>>` makes an extra allocation
+ = help: consider using just `Arc<bool>` or `Rc<bool>`
+
+error: usage of `Arc<Box<SubT<usize>>>`
+ --> $DIR/redundant_allocation.rs:71:27
+ |
+LL | pub fn arc_test8() -> Arc<Box<SubT<usize>>> {
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `Box<SubT<usize>>` is already on the heap, `Arc<Box<SubT<usize>>>` makes an extra allocation
+ = help: consider using just `Arc<SubT<usize>>` or `Box<SubT<usize>>`
+
+error: usage of `Arc<Rc<T>>`
+ --> $DIR/redundant_allocation.rs:75:30
+ |
+LL | pub fn arc_test9<T>(foo: Arc<Rc<T>>) -> Arc<Rc<SubT<T>>> {
+ | ^^^^^^^^^^
+ |
+ = note: `Rc<T>` is already on the heap, `Arc<Rc<T>>` makes an extra allocation
+ = help: consider using just `Arc<T>` or `Rc<T>`
+
+error: usage of `Arc<Rc<SubT<T>>>`
+ --> $DIR/redundant_allocation.rs:75:45
+ |
+LL | pub fn arc_test9<T>(foo: Arc<Rc<T>>) -> Arc<Rc<SubT<T>>> {
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `Rc<SubT<T>>` is already on the heap, `Arc<Rc<SubT<T>>>` makes an extra allocation
+ = help: consider using just `Arc<SubT<T>>` or `Rc<SubT<T>>`
+
+error: usage of `Rc<Box<Box<dyn T>>>`
+ --> $DIR/redundant_allocation.rs:97:27
+ |
+LL | pub fn test_rc_box(_: Rc<Box<Box<dyn T>>>) {}
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `Box<Box<dyn T>>` is already on the heap, `Rc<Box<Box<dyn T>>>` makes an extra allocation
+ = help: consider using just `Rc<Box<dyn T>>` or `Box<Box<dyn T>>`
+
++error: usage of `Rc<Box<Box<str>>>`
++ --> $DIR/redundant_allocation.rs:129:31
++ |
++LL | pub fn test_rc_box_str(_: Rc<Box<Box<str>>>) {}
++ | ^^^^^^^^^^^^^^^^^
++ |
++ = note: `Box<Box<str>>` is already on the heap, `Rc<Box<Box<str>>>` makes an extra allocation
++ = help: consider using just `Rc<Box<str>>` or `Box<Box<str>>`
++
++error: usage of `Rc<Box<Box<[usize]>>>`
++ --> $DIR/redundant_allocation.rs:130:33
++ |
++LL | pub fn test_rc_box_slice(_: Rc<Box<Box<[usize]>>>) {}
++ | ^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `Box<Box<[usize]>>` is already on the heap, `Rc<Box<Box<[usize]>>>` makes an extra allocation
++ = help: consider using just `Rc<Box<[usize]>>` or `Box<Box<[usize]>>`
++
++error: usage of `Rc<Box<Box<Path>>>`
++ --> $DIR/redundant_allocation.rs:131:32
++ |
++LL | pub fn test_rc_box_path(_: Rc<Box<Box<Path>>>) {}
++ | ^^^^^^^^^^^^^^^^^^
++ |
++ = note: `Box<Box<Path>>` is already on the heap, `Rc<Box<Box<Path>>>` makes an extra allocation
++ = help: consider using just `Rc<Box<Path>>` or `Box<Box<Path>>`
++
++error: usage of `Rc<Box<Box<DynSized>>>`
++ --> $DIR/redundant_allocation.rs:132:34
++ |
++LL | pub fn test_rc_box_custom(_: Rc<Box<Box<DynSized>>>) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `Box<Box<DynSized>>` is already on the heap, `Rc<Box<Box<DynSized>>>` makes an extra allocation
++ = help: consider using just `Rc<Box<DynSized>>` or `Box<Box<DynSized>>`
++
++error: aborting due to 20 previous errors
+
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+// run-rustfix
+
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::disallowed_methods)]
+#![allow(clippy::disallowed_types)]
++#![allow(clippy::mixed_read_write_in_expression)]
+#![allow(clippy::for_loops_over_fallibles)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::match_result_ok)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::needless_borrow)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::recursive_format_impl)]
+#![allow(clippy::invisible_characters)]
+#![allow(drop_bounds)]
+#![allow(array_into_iter)]
+#![allow(invalid_atomic_ordering)]
+#![allow(invalid_value)]
+#![allow(enum_intrinsics_non_enums)]
+#![allow(non_fmt_panics)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(unknown_lints)]
+#![allow(unused_labels)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::box_collection)]
+#![warn(clippy::redundant_static_lifetimes)]
+#![warn(clippy::cognitive_complexity)]
+#![warn(clippy::disallowed_methods)]
+#![warn(clippy::disallowed_types)]
++#![warn(clippy::mixed_read_write_in_expression)]
+#![warn(clippy::for_loops_over_fallibles)]
+#![warn(clippy::for_loops_over_fallibles)]
+#![warn(clippy::useless_conversion)]
+#![warn(clippy::match_result_ok)]
+#![warn(clippy::new_without_default)]
+#![warn(clippy::bind_instead_of_map)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::needless_borrow)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::single_char_add_str)]
+#![warn(clippy::module_name_repetitions)]
+#![warn(clippy::recursive_format_impl)]
+#![warn(clippy::invisible_characters)]
+#![warn(drop_bounds)]
+#![warn(array_into_iter)]
+#![warn(invalid_atomic_ordering)]
+#![warn(invalid_value)]
+#![warn(enum_intrinsics_non_enums)]
+#![warn(non_fmt_panics)]
+#![warn(temporary_cstring_as_ptr)]
+#![warn(unknown_lints)]
+#![warn(unused_labels)]
+
+fn main() {}
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+// run-rustfix
+
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::disallowed_methods)]
+#![allow(clippy::disallowed_types)]
++#![allow(clippy::mixed_read_write_in_expression)]
+#![allow(clippy::for_loops_over_fallibles)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::match_result_ok)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::needless_borrow)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::recursive_format_impl)]
+#![allow(clippy::invisible_characters)]
+#![allow(drop_bounds)]
+#![allow(array_into_iter)]
+#![allow(invalid_atomic_ordering)]
+#![allow(invalid_value)]
+#![allow(enum_intrinsics_non_enums)]
+#![allow(non_fmt_panics)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(unknown_lints)]
+#![allow(unused_labels)]
+#![warn(clippy::block_in_if_condition_expr)]
+#![warn(clippy::block_in_if_condition_stmt)]
+#![warn(clippy::box_vec)]
+#![warn(clippy::const_static_lifetime)]
+#![warn(clippy::cyclomatic_complexity)]
+#![warn(clippy::disallowed_method)]
+#![warn(clippy::disallowed_type)]
++#![warn(clippy::eval_order_dependence)]
+#![warn(clippy::for_loop_over_option)]
+#![warn(clippy::for_loop_over_result)]
+#![warn(clippy::identity_conversion)]
+#![warn(clippy::if_let_some_result)]
+#![warn(clippy::new_without_default_derive)]
+#![warn(clippy::option_and_then_some)]
+#![warn(clippy::option_expect_used)]
+#![warn(clippy::option_map_unwrap_or)]
+#![warn(clippy::option_map_unwrap_or_else)]
+#![warn(clippy::option_unwrap_used)]
+#![warn(clippy::ref_in_deref)]
+#![warn(clippy::result_expect_used)]
+#![warn(clippy::result_map_unwrap_or_else)]
+#![warn(clippy::result_unwrap_used)]
+#![warn(clippy::single_char_push_str)]
+#![warn(clippy::stutter)]
+#![warn(clippy::to_string_in_display)]
+#![warn(clippy::zero_width_space)]
+#![warn(clippy::drop_bounds)]
+#![warn(clippy::into_iter_on_array)]
+#![warn(clippy::invalid_atomic_ordering)]
+#![warn(clippy::invalid_ref)]
+#![warn(clippy::mem_discriminant_non_enum)]
+#![warn(clippy::panic_params)]
+#![warn(clippy::temporary_cstring_as_ptr)]
+#![warn(clippy::unknown_clippy_lints)]
+#![warn(clippy::unused_label)]
+
+fn main() {}
--- /dev/null
- --> $DIR/rename.rs:35:9
+error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:36:9
++ --> $DIR/rename.rs:36:9
+ |
+LL | #![warn(clippy::block_in_if_condition_expr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+ |
+ = note: `-D renamed-and-removed-lints` implied by `-D warnings`
+
+error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:37:9
++ --> $DIR/rename.rs:37:9
+ |
+LL | #![warn(clippy::block_in_if_condition_stmt)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+
+error: lint `clippy::box_vec` has been renamed to `clippy::box_collection`
- --> $DIR/rename.rs:38:9
++ --> $DIR/rename.rs:38:9
+ |
+LL | #![warn(clippy::box_vec)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection`
+
+error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes`
- --> $DIR/rename.rs:39:9
++ --> $DIR/rename.rs:39:9
+ |
+LL | #![warn(clippy::const_static_lifetime)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes`
+
+error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity`
- --> $DIR/rename.rs:40:9
++ --> $DIR/rename.rs:40:9
+ |
+LL | #![warn(clippy::cyclomatic_complexity)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity`
+
+error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods`
- --> $DIR/rename.rs:41:9
++ --> $DIR/rename.rs:41:9
+ |
+LL | #![warn(clippy::disallowed_method)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods`
+
+error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types`
- --> $DIR/rename.rs:42:9
++ --> $DIR/rename.rs:42:9
+ |
+LL | #![warn(clippy::disallowed_type)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types`
+
++error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression`
++ --> $DIR/rename.rs:43:9
++ |
++LL | #![warn(clippy::eval_order_dependence)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression`
++
+error: lint `clippy::for_loop_over_option` has been renamed to `clippy::for_loops_over_fallibles`
- --> $DIR/rename.rs:43:9
++ --> $DIR/rename.rs:44:9
+ |
+LL | #![warn(clippy::for_loop_over_option)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles`
+
+error: lint `clippy::for_loop_over_result` has been renamed to `clippy::for_loops_over_fallibles`
- --> $DIR/rename.rs:44:9
++ --> $DIR/rename.rs:45:9
+ |
+LL | #![warn(clippy::for_loop_over_result)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles`
+
+error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion`
- --> $DIR/rename.rs:45:9
++ --> $DIR/rename.rs:46:9
+ |
+LL | #![warn(clippy::identity_conversion)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion`
+
+error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok`
- --> $DIR/rename.rs:46:9
++ --> $DIR/rename.rs:47:9
+ |
+LL | #![warn(clippy::if_let_some_result)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok`
+
+error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default`
- --> $DIR/rename.rs:47:9
++ --> $DIR/rename.rs:48:9
+ |
+LL | #![warn(clippy::new_without_default_derive)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default`
+
+error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map`
- --> $DIR/rename.rs:48:9
++ --> $DIR/rename.rs:49:9
+ |
+LL | #![warn(clippy::option_and_then_some)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map`
+
+error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:49:9
++ --> $DIR/rename.rs:50:9
+ |
+LL | #![warn(clippy::option_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:50:9
++ --> $DIR/rename.rs:51:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:51:9
++ --> $DIR/rename.rs:52:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:52:9
++ --> $DIR/rename.rs:53:9
+ |
+LL | #![warn(clippy::option_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow`
- --> $DIR/rename.rs:53:9
++ --> $DIR/rename.rs:54:9
+ |
+LL | #![warn(clippy::ref_in_deref)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow`
+
+error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:54:9
++ --> $DIR/rename.rs:55:9
+ |
+LL | #![warn(clippy::result_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:55:9
++ --> $DIR/rename.rs:56:9
+ |
+LL | #![warn(clippy::result_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:56:9
++ --> $DIR/rename.rs:57:9
+ |
+LL | #![warn(clippy::result_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str`
- --> $DIR/rename.rs:57:9
++ --> $DIR/rename.rs:58:9
+ |
+LL | #![warn(clippy::single_char_push_str)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str`
+
+error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions`
- --> $DIR/rename.rs:58:9
++ --> $DIR/rename.rs:59:9
+ |
+LL | #![warn(clippy::stutter)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions`
+
+error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl`
- --> $DIR/rename.rs:59:9
++ --> $DIR/rename.rs:60:9
+ |
+LL | #![warn(clippy::to_string_in_display)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl`
+
+error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters`
- --> $DIR/rename.rs:60:9
++ --> $DIR/rename.rs:61:9
+ |
+LL | #![warn(clippy::zero_width_space)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters`
+
+error: lint `clippy::drop_bounds` has been renamed to `drop_bounds`
- --> $DIR/rename.rs:61:9
++ --> $DIR/rename.rs:62:9
+ |
+LL | #![warn(clippy::drop_bounds)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds`
+
+error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter`
- --> $DIR/rename.rs:62:9
++ --> $DIR/rename.rs:63:9
+ |
+LL | #![warn(clippy::into_iter_on_array)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter`
+
+error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering`
- --> $DIR/rename.rs:63:9
++ --> $DIR/rename.rs:64:9
+ |
+LL | #![warn(clippy::invalid_atomic_ordering)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering`
+
+error: lint `clippy::invalid_ref` has been renamed to `invalid_value`
- --> $DIR/rename.rs:64:9
++ --> $DIR/rename.rs:65:9
+ |
+LL | #![warn(clippy::invalid_ref)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value`
+
+error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums`
- --> $DIR/rename.rs:65:9
++ --> $DIR/rename.rs:66:9
+ |
+LL | #![warn(clippy::mem_discriminant_non_enum)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums`
+
+error: lint `clippy::panic_params` has been renamed to `non_fmt_panics`
- --> $DIR/rename.rs:66:9
++ --> $DIR/rename.rs:67:9
+ |
+LL | #![warn(clippy::panic_params)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics`
+
+error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr`
- --> $DIR/rename.rs:67:9
++ --> $DIR/rename.rs:68:9
+ |
+LL | #![warn(clippy::temporary_cstring_as_ptr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr`
+
+error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints`
- --> $DIR/rename.rs:68:9
++ --> $DIR/rename.rs:69:9
+ |
+LL | #![warn(clippy::unknown_clippy_lints)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints`
+
+error: lint `clippy::unused_label` has been renamed to `unused_labels`
- error: aborting due to 34 previous errors
++ --> $DIR/rename.rs:70:9
+ |
+LL | #![warn(clippy::unused_label)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels`
+
++error: aborting due to 35 previous errors
+
--- /dev/null
- #[allow(clippy::eval_order_dependence)]
+#![warn(clippy::self_assignment)]
+
+pub struct S<'a> {
+ a: i32,
+ b: [i32; 10],
+ c: Vec<Vec<i32>>,
+ e: &'a mut i32,
+ f: &'a mut i32,
+}
+
+pub fn positives(mut a: usize, b: &mut u32, mut s: S) {
+ a = a;
+ *b = *b;
+ s = s;
+ s.a = s.a;
+ s.b[10] = s.b[5 + 5];
+ s.c[0][1] = s.c[0][1];
+ s.b[a] = s.b[a];
+ *s.e = *s.e;
+ s.b[a + 10] = s.b[10 + a];
+
+ let mut t = (0, 1);
+ t.1 = t.1;
+ t.0 = (t.0);
+}
+
+pub fn negatives_not_equal(mut a: usize, b: &mut usize, mut s: S) {
+ dbg!(&a);
+ a = *b;
+ dbg!(&a);
+ s.b[1] += s.b[1];
+ s.b[1] = s.b[2];
+ s.c[1][0] = s.c[0][1];
+ s.b[a] = s.b[*b];
+ s.b[a + 10] = s.b[a + 11];
+ *s.e = *s.f;
+
+ let mut t = (0, 1);
+ t.0 = t.1;
+}
+
++#[allow(clippy::mixed_read_write_in_expression)]
+pub fn negatives_side_effects() {
+ let mut v = vec![1, 2, 3, 4, 5];
+ let mut i = 0;
+ v[{
+ i += 1;
+ i
+ }] = v[{
+ i += 1;
+ i
+ }];
+
+ fn next(n: &mut usize) -> usize {
+ let v = *n;
+ *n += 1;
+ v
+ }
+
+ let mut w = vec![1, 2, 3, 4, 5];
+ let mut i = 0;
+ let i = &mut i;
+ w[next(i)] = w[next(i)];
+ w[next(i)] = w[next(i)];
+}
+
+fn main() {}
--- /dev/null
- }
- false => {}
+// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934
+// // run-rustfix
+
+#![warn(clippy::significant_drop_in_scrutinee)]
+#![allow(clippy::single_match)]
+#![allow(clippy::match_single_binding)]
+#![allow(unused_assignments)]
+#![allow(dead_code)]
+
+use std::ops::Deref;
+use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::{Mutex, MutexGuard};
+
+struct State {}
+
+impl State {
+ fn foo(&self) -> bool {
+ true
+ }
+
+ fn bar(&self) {}
+}
+
+fn should_not_trigger_lint_with_mutex_guard_outside_match() {
+ let mutex = Mutex::new(State {});
+
+ // Should not trigger lint because the temporary should drop at the `;` on line before the match
+ let is_foo = mutex.lock().unwrap().foo();
+ match is_foo {
+ true => {
+ mutex.lock().unwrap().bar();
- }
- _ => {}
++ },
++ false => {},
+ };
+}
+
+fn should_not_trigger_lint_with_mutex_guard_when_taking_ownership_in_match() {
+ let mutex = Mutex::new(State {});
+
+ // Should not trigger lint because the scrutinee is explicitly returning the MutexGuard,
+ // so its lifetime should not be surprising.
+ match mutex.lock() {
+ Ok(guard) => {
+ guard.foo();
+ mutex.lock().unwrap().bar();
- }
- false => {}
++ },
++ _ => {},
+ };
+}
+
+fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() {
+ let mutex = Mutex::new(State {});
+
+ // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it
+ // is preserved until the end of the match, but there is no clear indication that this is the
+ // case.
+ match mutex.lock().unwrap().foo() {
+ true => {
+ mutex.lock().unwrap().bar();
- }
++ },
++ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_insignificant_drop() {
+ // Should not trigger lint because there are no temporaries whose drops have a significant
+ // side effect.
+ match 1u64.to_string().is_empty() {
+ true => {
+ println!("It was empty")
- }
++ },
+ false => {
+ println!("It was not empty")
- }
++ },
+ }
+}
+
+struct StateWithMutex {
+ m: Mutex<u64>,
+}
+
+struct MutexGuardWrapper<'a> {
+ mg: MutexGuard<'a, u64>,
+}
+
+impl<'a> MutexGuardWrapper<'a> {
+ fn get_the_value(&self) -> u64 {
+ *self.mg.deref()
+ }
+}
+
+struct MutexGuardWrapperWrapper<'a> {
+ mg: MutexGuardWrapper<'a>,
+}
+
+impl<'a> MutexGuardWrapperWrapper<'a> {
+ fn get_the_value(&self) -> u64 {
+ *self.mg.mg.deref()
+ }
+}
+
+impl StateWithMutex {
+ fn lock_m(&self) -> MutexGuardWrapper<'_> {
+ MutexGuardWrapper {
+ mg: self.m.lock().unwrap(),
+ }
+ }
+
+ fn lock_m_m(&self) -> MutexGuardWrapperWrapper<'_> {
+ MutexGuardWrapperWrapper {
+ mg: MutexGuardWrapper {
+ mg: self.m.lock().unwrap(),
+ },
+ }
+ }
+
+ fn foo(&self) -> bool {
+ true
+ }
+
+ fn bar(&self) {}
+}
+
+fn should_trigger_lint_with_wrapped_mutex() {
+ let s = StateWithMutex { m: Mutex::new(1) };
+
+ // Should trigger lint because a temporary contains a type with a significant drop and its
+ // lifetime is not obvious. Additionally, it is not obvious from looking at the scrutinee that
+ // the temporary contains such a type, making it potentially even more surprising.
+ match s.lock_m().get_the_value() {
+ 1 => {
+ println!("Got 1. Is it still 1?");
+ println!("{}", s.lock_m().get_the_value());
- }
- _ => {}
++ },
+ 2 => {
+ println!("Got 2. Is it still 2?");
+ println!("{}", s.lock_m().get_the_value());
- }
++ },
++ _ => {},
+ }
+ println!("All done!");
+}
+
+fn should_trigger_lint_with_double_wrapped_mutex() {
+ let s = StateWithMutex { m: Mutex::new(1) };
+
+ // Should trigger lint because a temporary contains a type which further contains a type with a
+ // significant drop and its lifetime is not obvious. Additionally, it is not obvious from
+ // looking at the scrutinee that the temporary contains such a type, making it potentially even
+ // more surprising.
+ match s.lock_m_m().get_the_value() {
+ 1 => {
+ println!("Got 1. Is it still 1?");
+ println!("{}", s.lock_m().get_the_value());
- }
- _ => {}
++ },
+ 2 => {
+ println!("Got 2. Is it still 2?");
+ println!("{}", s.lock_m().get_the_value());
- }
- 1 => {}
- 3 => {}
- _ => {}
++ },
++ _ => {},
+ }
+ println!("All done!");
+}
+
+struct Counter {
+ i: AtomicU64,
+}
+
+#[clippy::has_significant_drop]
+struct CounterWrapper<'a> {
+ counter: &'a Counter,
+}
+
+impl<'a> CounterWrapper<'a> {
+ fn new(counter: &Counter) -> CounterWrapper {
+ counter.i.fetch_add(1, Ordering::Relaxed);
+ CounterWrapper { counter }
+ }
+}
+
+impl<'a> Drop for CounterWrapper<'a> {
+ fn drop(&mut self) {
+ self.counter.i.fetch_sub(1, Ordering::Relaxed);
+ }
+}
+
+impl Counter {
+ fn temp_increment(&self) -> Vec<CounterWrapper> {
+ vec![CounterWrapper::new(self), CounterWrapper::new(self)]
+ }
+}
+
+fn should_trigger_lint_for_vec() {
+ let counter = Counter { i: AtomicU64::new(0) };
+
+ // Should trigger lint because the temporary in the scrutinee returns a collection of types
+ // which have significant drops. The types with significant drops are also non-obvious when
+ // reading the expression in the scrutinee.
+ match counter.temp_increment().len() {
+ 2 => {
+ let current_count = counter.i.load(Ordering::Relaxed);
+ println!("Current count {}", current_count);
+ assert_eq!(current_count, 0);
- }
- (_, _) => {}
++ },
++ 1 => {},
++ 3 => {},
++ _ => {},
+ };
+}
+
+struct StateWithField {
+ s: String,
+}
+
+// Should trigger lint only on the type in the tuple which is created using a temporary
+// with a significant drop. Additionally, this test ensures that the format of the tuple
+// is preserved correctly in the suggestion.
+fn should_trigger_lint_for_tuple_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ {
+ match (mutex1.lock().unwrap().s.len(), true) {
+ (3, _) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ println!("done");
- }
- (_, _, _) => {}
++ },
++ (_, _) => {},
+ };
+
+ match (true, mutex1.lock().unwrap().s.len(), true) {
+ (_, 3, _) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ println!("done");
- }
- (_, _, _) => {}
++ },
++ (_, _, _) => {},
+ };
+
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+ match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ (3, _, 3) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
- }
- _ => {}
++ },
++ (_, _, _) => {},
+ };
+
+ let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() });
+ match mutex3.lock().unwrap().s.as_str() {
+ "three" => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
-
++ },
++ _ => {},
+ };
+
- }
- (_, _) => {}
+ match (true, mutex3.lock().unwrap().s.as_str()) {
+ (_, "three") => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
- }
- false => {}
++ },
++ (_, _) => {},
+ };
+ }
+}
+
+// Should trigger lint when either side of a binary operation creates a temporary with a
+// significant drop.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_for_accessing_field_in_mutex_in_one_side_of_binary_op() {
+ let mutex = Mutex::new(StateWithField { s: "state".to_owned() });
+
+ match mutex.lock().unwrap().s.len() > 1 {
+ true => {
+ mutex.lock().unwrap().s.len();
- }
- false => {}
++ },
++ false => {},
+ };
+
+ match 1 < mutex.lock().unwrap().s.len() {
+ true => {
+ mutex.lock().unwrap().s.len();
- let mutex2 = Mutex::new(StateWithField { s: "statewithfield".to_owned() });
++ },
++ false => {},
+ };
+}
+
+// Should trigger lint when both sides of a binary operation creates a temporary with a
+// significant drop.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_for_accessing_fields_in_mutex_in_both_sides_of_binary_op() {
+ let mutex1 = Mutex::new(StateWithField { s: "state".to_owned() });
- println!("{} < {}", mutex1.lock().unwrap().s.len(), mutex2.lock().unwrap().s.len());
- }
- false => {}
++ let mutex2 = Mutex::new(StateWithField {
++ s: "statewithfield".to_owned(),
++ });
+
+ match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() {
+ true => {
- println!("{} >= {}", mutex1.lock().unwrap().s.len(), mutex2.lock().unwrap().s.len());
- }
- false => {}
++ println!(
++ "{} < {}",
++ mutex1.lock().unwrap().s.len(),
++ mutex2.lock().unwrap().s.len()
++ );
++ },
++ false => {},
+ };
+
+ match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() {
+ true => {
- }
- false => {}
++ println!(
++ "{} >= {}",
++ mutex1.lock().unwrap().s.len(),
++ mutex2.lock().unwrap().s.len()
++ );
++ },
++ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_closure_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ let get_mutex_guard = || mutex1.lock().unwrap().s.len();
+
+ // Should not trigger lint because the temporary with a significant drop will be dropped
+ // at the end of the closure, so the MutexGuard will be unlocked and not have a potentially
+ // surprising lifetime.
+ match get_mutex_guard() > 1 {
+ true => {
+ mutex1.lock().unwrap().s.len();
- }
- false => {}
++ },
++ false => {},
+ };
+}
+
+fn should_trigger_lint_for_return_from_closure_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ let get_mutex_guard = || mutex1.lock().unwrap();
+
+ // Should trigger lint because the temporary with a significant drop is returned from the
+ // closure but not used directly in any match arms, so it has a potentially surprising lifetime.
+ match get_mutex_guard().s.len() > 1 {
+ true => {
+ mutex1.lock().unwrap().s.len();
- match match i { 100 => mutex1.lock().unwrap(), _ => mutex2.lock().unwrap() }.s.len() > 1 {
++ },
++ false => {},
+ };
+}
+
+fn should_trigger_lint_for_return_from_match_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+
+ let i = 100;
+
+ // Should trigger lint because the nested match within the scrutinee returns a temporary with a
+ // significant drop is but not used directly in any match arms, so it has a potentially
+ // surprising lifetime.
- }
++ match match i {
++ 100 => mutex1.lock().unwrap(),
++ _ => mutex2.lock().unwrap(),
++ }
++ .s
++ .len()
++ > 1
++ {
+ true => {
+ mutex1.lock().unwrap().s.len();
- }
++ },
+ false => {
+ println!("nothing to do here");
- match if i > 1 { mutex1.lock().unwrap() } else { mutex2.lock().unwrap() }.s.len() > 1 {
++ },
+ };
+}
+
+fn should_trigger_lint_for_return_from_if_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+
+ let i = 100;
+
+ // Should trigger lint because the nested if-expression within the scrutinee returns a temporary
+ // with a significant drop is but not used directly in any match arms, so it has a potentially
+ // surprising lifetime.
- }
- false => {}
++ match if i > 1 {
++ mutex1.lock().unwrap()
++ } else {
++ mutex2.lock().unwrap()
++ }
++ .s
++ .len()
++ > 1
++ {
+ true => {
+ mutex1.lock().unwrap().s.len();
- match if i > 1 { mutex.lock().unwrap().s.len() > 1 } else { false } {
++ },
++ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_if_in_scrutinee() {
+ let mutex = Mutex::new(StateWithField { s: "state".to_owned() });
+
+ let i = 100;
+
+ // Should not trigger the lint because the temporary with a significant drop *is* dropped within
+ // the body of the if-expression nested within the match scrutinee, and therefore does not have
+ // a potentially surprising lifetime.
- }
- false => {}
++ match if i > 1 {
++ mutex.lock().unwrap().s.len() > 1
++ } else {
++ false
++ } {
+ true => {
+ mutex.lock().unwrap().s.len();
- StateStringWithBoxedMutexGuard { s: Mutex::new("A String".to_owned()) }
++ },
++ false => {},
+ };
+}
+
+struct StateWithBoxedMutexGuard {
+ u: Mutex<u64>,
+}
+
+impl StateWithBoxedMutexGuard {
+ fn new() -> StateWithBoxedMutexGuard {
+ StateWithBoxedMutexGuard { u: Mutex::new(42) }
+ }
+ fn lock(&self) -> Box<MutexGuard<u64>> {
+ Box::new(self.u.lock().unwrap())
+ }
+}
+
+fn should_trigger_lint_for_boxed_mutex_guard() {
+ let s = StateWithBoxedMutexGuard::new();
+
+ // Should trigger lint because a temporary Box holding a type with a significant drop in a match
+ // scrutinee may have a potentially surprising lifetime.
+ match s.lock().deref().deref() {
+ 0 | 1 => println!("Value was less than 2"),
+ _ => println!("Value is {}", s.lock().deref()),
+ };
+}
+
+struct StateStringWithBoxedMutexGuard {
+ s: Mutex<String>,
+}
+
+impl StateStringWithBoxedMutexGuard {
+ fn new() -> StateStringWithBoxedMutexGuard {
-
++ StateStringWithBoxedMutexGuard {
++ s: Mutex::new("A String".to_owned()),
++ }
+ }
+ fn lock(&self) -> Box<MutexGuard<String>> {
+ Box::new(self.s.lock().unwrap())
+ }
+}
+
+fn should_trigger_lint_for_boxed_mutex_guard_holding_string() {
+ let s = StateStringWithBoxedMutexGuard::new();
+
+ let matcher = String::from("A String");
+
+ // Should trigger lint because a temporary Box holding a type with a significant drop in a match
+ // scrutinee may have a potentially surprising lifetime.
+ match s.lock().deref().deref() {
+ matcher => println!("Value is {}", s.lock().deref()),
+ _ => println!("Value was not a match"),
+ };
+}
+
- }
+struct StateWithIntField {
+ i: u64,
+}
+
+// Should trigger lint when either side of an assign expression contains a temporary with a
+// significant drop, because the temporary's lifetime will be extended to the end of the match.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_in_assign_expr() {
+ let mutex = Mutex::new(StateWithIntField { i: 10 });
+
+ let mut i = 100;
+
+ match mutex.lock().unwrap().i = i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
- }
++ },
+ };
+
+ match i = mutex.lock().unwrap().i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
- }
++ },
+ };
+
+ match mutex.lock().unwrap().i += 1 {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
- }
++ },
+ };
+
+ match i += mutex.lock().unwrap().i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
- Foo(Option<Box<RecursiveEnum>>)
++ },
+ };
+}
+
+#[derive(Debug)]
+enum RecursiveEnum {
- Foo(T, Option<Box<GenericRecursiveEnum<T>>>)
++ Foo(Option<Box<RecursiveEnum>>),
+}
+
+#[derive(Debug)]
+enum GenericRecursiveEnum<T> {
- }
++ Foo(T, Option<Box<GenericRecursiveEnum<T>>>),
+}
+
+fn should_not_cause_stack_overflow() {
+ // Test that when a type recursively contains itself, a stack overflow does not occur when
+ // checking sub-types for significant drops.
+ let f = RecursiveEnum::Foo(Some(Box::new(RecursiveEnum::Foo(None))));
+ match f {
+ RecursiveEnum::Foo(Some(f)) => {
+ println!("{:?}", f)
- }
++ },
+ RecursiveEnum::Foo(f) => {
+ println!("{:?}", f)
- }
++ },
+ }
+
+ let f = GenericRecursiveEnum::Foo(1u64, Some(Box::new(GenericRecursiveEnum::Foo(2u64, None))));
+ match f {
+ GenericRecursiveEnum::Foo(i, Some(f)) => {
+ println!("{} {:?}", i, f)
- }
++ },
+ GenericRecursiveEnum::Foo(i, f) => {
+ println!("{} {:?}", i, f)
++ },
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/significant_drop_in_scrutinee.rs:263:22
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:57:11
+ |
+LL | match mutex.lock().unwrap().foo() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::significant-drop-in-scrutinee` implied by `-D warnings`
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex.lock().unwrap().foo();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:130:11
+ |
+LL | match s.lock_m().get_the_value() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = s.lock_m().get_the_value();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:151:11
+ |
+LL | match s.lock_m_m().get_the_value() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = s.lock_m_m().get_the_value();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:199:11
+ |
+LL | match counter.temp_increment().len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = counter.temp_increment().len();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:222:16
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (value, true) {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:231:22
+ |
+LL | match (true, mutex1.lock().unwrap().s.len(), true) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (true, value, true) {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:241:16
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (value, true, mutex2.lock().unwrap().s.len()) {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:241:54
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex2.lock().unwrap().s.len();
+LL ~ match (mutex1.lock().unwrap().s.len(), true, value) {
+ |
+
+error: temporary with significant drop in match scrutinee
+ --> $DIR/significant_drop_in_scrutinee.rs:252:15
+ |
+LL | match mutex3.lock().unwrap().s.as_str() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:282:11
++ --> $DIR/significant_drop_in_scrutinee.rs:262:22
+ |
+LL | match (true, mutex3.lock().unwrap().s.as_str()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:289:11
++ --> $DIR/significant_drop_in_scrutinee.rs:281:11
+ |
+LL | match mutex.lock().unwrap().s.len() > 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex.lock().unwrap().s.len() > 1;
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:305:11
++ --> $DIR/significant_drop_in_scrutinee.rs:288:11
+ |
+LL | match 1 < mutex.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = 1 < mutex.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:312:11
++ --> $DIR/significant_drop_in_scrutinee.rs:306:11
+ |
+LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:343:11
++ --> $DIR/significant_drop_in_scrutinee.rs:317:11
+ |
+LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:360:11
- |
- LL | match match i { 100 => mutex1.lock().unwrap(), _ => mutex2.lock().unwrap() }.s.len() > 1 {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/significant_drop_in_scrutinee.rs:352:11
+ |
+LL | match get_mutex_guard().s.len() > 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ let value = get_mutex_guard().s.len() > 1;
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- LL ~ let value = match i { 100 => mutex1.lock().unwrap(), _ => mutex2.lock().unwrap() }.s.len() > 1;
- LL ~ match value {
- |
++ --> $DIR/significant_drop_in_scrutinee.rs:369:11
++ |
++LL | match match i {
++ | ___________^
++LL | | 100 => mutex1.lock().unwrap(),
++LL | | _ => mutex2.lock().unwrap(),
++LL | | }
++LL | | .s
++LL | | .len()
++LL | | > 1
++ | |___________^
+ |
+help: try moving the temporary above the match
+ |
- --> $DIR/significant_drop_in_scrutinee.rs:379:11
- |
- LL | match if i > 1 { mutex1.lock().unwrap() } else { mutex2.lock().unwrap() }.s.len() > 1 {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++LL ~ let value = match i {
++LL + 100 => mutex1.lock().unwrap(),
++LL + _ => mutex2.lock().unwrap(),
++LL + }
++LL + .s
++LL + .len()
++ ...
+
+error: temporary with significant drop in match scrutinee
- LL ~ let value = if i > 1 { mutex1.lock().unwrap() } else { mutex2.lock().unwrap() }.s.len() > 1;
- LL ~ match value {
- |
++ --> $DIR/significant_drop_in_scrutinee.rs:395:11
++ |
++LL | match if i > 1 {
++ | ___________^
++LL | | mutex1.lock().unwrap()
++LL | | } else {
++LL | | mutex2.lock().unwrap()
++... |
++LL | | .len()
++LL | | > 1
++ | |___________^
+ |
+help: try moving the temporary above the match
+ |
- --> $DIR/significant_drop_in_scrutinee.rs:421:11
++LL ~ let value = if i > 1 {
++LL + mutex1.lock().unwrap()
++LL + } else {
++LL + mutex2.lock().unwrap()
++LL + }
++LL + .s
++ ...
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:447:11
++ --> $DIR/significant_drop_in_scrutinee.rs:449:11
+ |
+LL | match s.lock().deref().deref() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match and create a copy
+ |
+LL ~ let value = *s.lock().deref().deref();
+LL ~ match value {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:467:11
++ --> $DIR/significant_drop_in_scrutinee.rs:477:11
+ |
+LL | match s.lock().deref().deref() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:473:11
++ --> $DIR/significant_drop_in_scrutinee.rs:496:11
+ |
+LL | match mutex.lock().unwrap().i = i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ mutex.lock().unwrap().i = i;
+LL ~ match () {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:479:11
++ --> $DIR/significant_drop_in_scrutinee.rs:502:11
+ |
+LL | match i = mutex.lock().unwrap().i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ i = mutex.lock().unwrap().i;
+LL ~ match () {
+ |
+
+error: temporary with significant drop in match scrutinee
- --> $DIR/significant_drop_in_scrutinee.rs:485:11
++ --> $DIR/significant_drop_in_scrutinee.rs:508:11
+ |
+LL | match mutex.lock().unwrap().i += 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ mutex.lock().unwrap().i += 1;
+LL ~ match () {
+ |
+
+error: temporary with significant drop in match scrutinee
++ --> $DIR/significant_drop_in_scrutinee.rs:514:11
+ |
+LL | match i += mutex.lock().unwrap().i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try moving the temporary above the match
+ |
+LL ~ i += mutex.lock().unwrap().i;
+LL ~ match () {
+ |
+
+error: aborting due to 23 previous errors
+
--- /dev/null
- #![allow(clippy::let_unit_value)]
+// aux-build:proc_macro_unsafe.rs
+
+#![warn(clippy::undocumented_unsafe_blocks)]
++#![allow(clippy::let_unit_value, clippy::missing_safety_doc)]
+
+extern crate proc_macro_unsafe;
+
+// Valid comments
+
+fn nested_local() {
+ let _ = {
+ let _ = {
+ // SAFETY:
+ let _ = unsafe {};
+ };
+ };
+}
+
+fn deep_nest() {
+ let _ = {
+ let _ = {
+ // SAFETY:
+ let _ = unsafe {};
+
+ // Safety:
+ unsafe {};
+
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ // Safety:
+ let _ = unsafe {};
+
+ // SAFETY:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+
+ // SAFETY:
+ unsafe {};
+}
+
+fn local_tuple_expression() {
+ // Safety:
+ let _ = (42, unsafe {});
+}
+
+fn line_comment() {
+ // Safety:
+ unsafe {}
+}
+
+fn line_comment_newlines() {
+ // SAFETY:
+
+ unsafe {}
+}
+
+fn line_comment_empty() {
+ // Safety:
+ //
+ //
+ //
+ unsafe {}
+}
+
+fn line_comment_with_extras() {
+ // This is a description
+ // Safety:
+ unsafe {}
+}
+
+fn block_comment() {
+ /* Safety: */
+ unsafe {}
+}
+
+fn block_comment_newlines() {
+ /* SAFETY: */
+
+ unsafe {}
+}
+
+fn block_comment_with_extras() {
+ /* This is a description
+ * SAFETY:
+ */
+ unsafe {}
+}
+
+fn block_comment_terminator_same_line() {
+ /* This is a description
+ * Safety: */
+ unsafe {}
+}
+
+fn buried_safety() {
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ // incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ // reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+ // occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ // laborum. Safety:
+ // Tellus elementum sagittis vitae et leo duis ut diam quam. Sit amet nulla facilisi
+ // morbi tempus iaculis urna. Amet luctus venenatis lectus magna. At quis risus sed vulputate odio
+ // ut. Luctus venenatis lectus magna fringilla urna. Tortor id aliquet lectus proin nibh nisl
+ // condimentum id venenatis. Vulputate dignissim suspendisse in est ante in nibh mauris cursus.
+ unsafe {}
+}
+
+fn safety_with_prepended_text() {
+ // This is a test. safety:
+ unsafe {}
+}
+
+fn local_line_comment() {
+ // Safety:
+ let _ = unsafe {};
+}
+
+fn local_block_comment() {
+ /* SAFETY: */
+ let _ = unsafe {};
+}
+
+fn comment_array() {
+ // Safety:
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn comment_tuple() {
+ // sAFETY:
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn comment_unary() {
+ // SAFETY:
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn comment_match() {
+ // SAFETY:
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn comment_addr_of() {
+ // Safety:
+ let _ = &unsafe {};
+}
+
+fn comment_repeat() {
+ // Safety:
+ let _ = [unsafe {}; 5];
+}
+
+fn comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(
+ // SAFETY:
+ unsafe {}
+ );
+}
+
+fn comment_macro_def() {
+ macro_rules! t {
+ () => {
+ // Safety:
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn non_ascii_comment() {
+ // ॐ᧻໒ SaFeTy: ௵∰
+ unsafe {};
+}
+
+fn local_commented_block() {
+ let _ =
+ // safety:
+ unsafe {};
+}
+
+fn local_nest() {
+ // safety:
+ let _ = [(42, unsafe {}, unsafe {}), (52, unsafe {}, unsafe {})];
+}
+
+fn in_fn_call(x: *const u32) {
+ fn f(x: u32) {}
+
+ // Safety: reason
+ f(unsafe { *x });
+}
+
+fn multi_in_fn_call(x: *const u32) {
+ fn f(x: u32, y: u32) {}
+
+ // Safety: reason
+ f(unsafe { *x }, unsafe { *x });
+}
+
+fn in_multiline_fn_call(x: *const u32) {
+ fn f(x: u32, y: u32) {}
+
+ f(
+ // Safety: reason
+ unsafe { *x },
+ 0,
+ );
+}
+
+fn in_macro_call(x: *const u32) {
+ // Safety: reason
+ println!("{}", unsafe { *x });
+}
+
+fn in_multiline_macro_call(x: *const u32) {
+ println!(
+ "{}",
+ // Safety: reason
+ unsafe { *x },
+ );
+}
+
+fn from_proc_macro() {
+ proc_macro_unsafe::unsafe_block!(token);
+}
+
+// Invalid comments
+
+#[rustfmt::skip]
+fn inline_block_comment() {
+ /* Safety: */ unsafe {}
+}
+
+fn no_comment() {
+ unsafe {}
+}
+
+fn no_comment_array() {
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn no_comment_tuple() {
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn no_comment_unary() {
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn no_comment_match() {
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn no_comment_addr_of() {
+ let _ = &unsafe {};
+}
+
+fn no_comment_repeat() {
+ let _ = [unsafe {}; 5];
+}
+
+fn local_no_comment() {
+ let _ = unsafe {};
+}
+
+fn no_comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(unsafe {});
+}
+
+fn no_comment_macro_def() {
+ macro_rules! t {
+ () => {
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn trailing_comment() {
+ unsafe {} // SAFETY:
+}
+
+fn internal_comment() {
+ unsafe {
+ // SAFETY:
+ }
+}
+
+fn interference() {
+ // SAFETY
+
+ let _ = 42;
+
+ unsafe {};
+}
+
+pub fn print_binary_tree() {
+ println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
+}
+
++mod unsafe_impl_smoke_test {
++ unsafe trait A {}
++
++ // error: no safety comment
++ unsafe impl A for () {}
++
++ // Safety: ok
++ unsafe impl A for (i32) {}
++
++ mod sub_mod {
++ // error:
++ unsafe impl B for (u32) {}
++ unsafe trait B {}
++ }
++
++ #[rustfmt::skip]
++ mod sub_mod2 {
++ //
++ // SAFETY: ok
++ //
++
++ unsafe impl B for (u32) {}
++ unsafe trait B {}
++ }
++}
++
++mod unsafe_impl_from_macro {
++ unsafe trait T {}
++
++ // error
++ macro_rules! no_safety_comment {
++ ($t:ty) => {
++ unsafe impl T for $t {}
++ };
++ }
++
++ // ok
++ no_safety_comment!(());
++
++ // ok
++ macro_rules! with_safety_comment {
++ ($t:ty) => {
++ // SAFETY:
++ unsafe impl T for $t {}
++ };
++ }
++
++ // ok
++ with_safety_comment!((i32));
++}
++
++mod unsafe_impl_macro_and_not_macro {
++ unsafe trait T {}
++
++ // error
++ macro_rules! no_safety_comment {
++ ($t:ty) => {
++ unsafe impl T for $t {}
++ };
++ }
++
++ // ok
++ no_safety_comment!(());
++
++ // error
++ unsafe impl T for (i32) {}
++
++ // ok
++ no_safety_comment!(u32);
++
++ // error
++ unsafe impl T for (bool) {}
++}
++
++#[rustfmt::skip]
++mod unsafe_impl_valid_comment {
++ unsafe trait SaFety {}
++ // SaFety:
++ unsafe impl SaFety for () {}
++
++ unsafe trait MultiLineComment {}
++ // The following impl is safe
++ // ...
++ // Safety: reason
++ unsafe impl MultiLineComment for () {}
++
++ unsafe trait NoAscii {}
++ // 安全 SAFETY: 以下のコードは安全です
++ unsafe impl NoAscii for () {}
++
++ unsafe trait InlineAndPrecedingComment {}
++ // SAFETY:
++ /* comment */ unsafe impl InlineAndPrecedingComment for () {}
++
++ unsafe trait BuriedSafety {}
++ // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
++ // incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
++ // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
++ // reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
++ // occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
++ // laborum. Safety:
++ // Tellus elementum sagittis vitae et leo duis ut diam quam. Sit amet nulla facilisi
++ // morbi tempus iaculis urna. Amet luctus venenatis lectus magna. At quis risus sed vulputate odio
++ // ut. Luctus venenatis lectus magna fringilla urna. Tortor id aliquet lectus proin nibh nisl
++ // condimentum id venenatis. Vulputate dignissim suspendisse in est ante in nibh mauris cursus.
++ unsafe impl BuriedSafety for () {}
++
++ unsafe trait MultiLineBlockComment {}
++ /* This is a description
++ * Safety: */
++ unsafe impl MultiLineBlockComment for () {}
++}
++
++#[rustfmt::skip]
++mod unsafe_impl_invalid_comment {
++ unsafe trait NoComment {}
++
++ unsafe impl NoComment for () {}
++
++ unsafe trait InlineComment {}
++
++ /* SAFETY: */ unsafe impl InlineComment for () {}
++
++ unsafe trait TrailingComment {}
++
++ unsafe impl TrailingComment for () {} // SAFETY:
++
++ unsafe trait Interference {}
++ // SAFETY:
++ const BIG_NUMBER: i32 = 1000000;
++ unsafe impl Interference for () {}
++}
++
++unsafe trait ImplInFn {}
++
++fn impl_in_fn() {
++ // error
++ unsafe impl ImplInFn for () {}
++
++ // SAFETY: ok
++ unsafe impl ImplInFn for (i32) {}
++}
++
++unsafe trait CrateRoot {}
++
++// error
++unsafe impl CrateRoot for () {}
++
++// SAFETY: ok
++unsafe impl CrateRoot for (i32) {}
++
+fn main() {}
--- /dev/null
- error: aborting due to 18 previous errors
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:257:19
+ |
+LL | /* Safety: */ unsafe {}
+ | ^^^^^^^^^
+ |
+ = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:261:5
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:265:14
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:265:29
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:265:48
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:269:18
+ |
+LL | let _ = (42, unsafe {}, "test", unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:269:37
+ |
+LL | let _ = (42, unsafe {}, "test", unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:273:14
+ |
+LL | let _ = *unsafe { &42 };
+ | ^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:278:19
+ |
+LL | let _ = match unsafe {} {
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:284:14
+ |
+LL | let _ = &unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:288:14
+ |
+LL | let _ = [unsafe {}; 5];
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:292:13
+ |
+LL | let _ = unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:302:8
+ |
+LL | t!(unsafe {});
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:308:13
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+...
+LL | t!();
+ | ---- in this macro invocation
+ |
+ = help: consider adding a safety comment on the preceding line
+ = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:316:5
+ |
+LL | unsafe {} // SAFETY:
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:320:5
+ |
+LL | unsafe {
+ | ^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:330:5
+ |
+LL | unsafe {};
+ | ^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:334:20
+ |
+LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider adding a safety comment on the preceding line
+
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:341:5
++ |
++LL | unsafe impl A for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:348:9
++ |
++LL | unsafe impl B for (u32) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:369:13
++ |
++LL | unsafe impl T for $t {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++...
++LL | no_safety_comment!(());
++ | ---------------------- in this macro invocation
++ |
++ = help: consider adding a safety comment on the preceding line
++ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:394:13
++ |
++LL | unsafe impl T for $t {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++...
++LL | no_safety_comment!(());
++ | ---------------------- in this macro invocation
++ |
++ = help: consider adding a safety comment on the preceding line
++ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:402:5
++ |
++LL | unsafe impl T for (i32) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:394:13
++ |
++LL | unsafe impl T for $t {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++...
++LL | no_safety_comment!(u32);
++ | ----------------------- in this macro invocation
++ |
++ = help: consider adding a safety comment on the preceding line
++ = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:408:5
++ |
++LL | unsafe impl T for (bool) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:454:5
++ |
++LL | unsafe impl NoComment for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:458:19
++ |
++LL | /* SAFETY: */ unsafe impl InlineComment for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:462:5
++ |
++LL | unsafe impl TrailingComment for () {} // SAFETY:
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:467:5
++ |
++LL | unsafe impl Interference for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:474:5
++ |
++LL | unsafe impl ImplInFn for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: unsafe impl missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:483:1
++ |
++LL | unsafe impl CrateRoot for () {}
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider adding a safety comment on the preceding line
++
++error: aborting due to 31 previous errors
+
--- /dev/null
- #![allow(clippy::no_effect, clippy::unnecessary_operation)]
+#![warn(clippy::unit_cmp)]
++#![allow(
++ clippy::no_effect,
++ clippy::unnecessary_operation,
++ clippy::derive_partial_eq_without_eq
++)]
+
+#[derive(PartialEq)]
+pub struct ContainsUnit(()); // should be fine
+
+fn main() {
+ // this is fine
+ if true == false {}
+
+ // this warns
+ if {
+ true;
+ } == {
+ false;
+ } {}
+
+ if {
+ true;
+ } > {
+ false;
+ } {}
+
+ assert_eq!(
+ {
+ true;
+ },
+ {
+ false;
+ }
+ );
+ debug_assert_eq!(
+ {
+ true;
+ },
+ {
+ false;
+ }
+ );
+
+ assert_ne!(
+ {
+ true;
+ },
+ {
+ false;
+ }
+ );
+ debug_assert_ne!(
+ {
+ true;
+ },
+ {
+ false;
+ }
+ );
+}
--- /dev/null
- --> $DIR/unit_cmp.rs:12:8
+error: ==-comparison of unit values detected. This will always be true
- --> $DIR/unit_cmp.rs:18:8
++ --> $DIR/unit_cmp.rs:16:8
+ |
+LL | if {
+ | ________^
+LL | | true;
+LL | | } == {
+LL | | false;
+LL | | } {}
+ | |_____^
+ |
+ = note: `-D clippy::unit-cmp` implied by `-D warnings`
+
+error: >-comparison of unit values detected. This will always be false
- --> $DIR/unit_cmp.rs:24:5
++ --> $DIR/unit_cmp.rs:22:8
+ |
+LL | if {
+ | ________^
+LL | | true;
+LL | | } > {
+LL | | false;
+LL | | } {}
+ | |_____^
+
+error: `assert_eq` of unit values detected. This will always succeed
- --> $DIR/unit_cmp.rs:32:5
++ --> $DIR/unit_cmp.rs:28:5
+ |
+LL | / assert_eq!(
+LL | | {
+LL | | true;
+LL | | },
+... |
+LL | | }
+LL | | );
+ | |_____^
+
+error: `debug_assert_eq` of unit values detected. This will always succeed
- --> $DIR/unit_cmp.rs:41:5
++ --> $DIR/unit_cmp.rs:36:5
+ |
+LL | / debug_assert_eq!(
+LL | | {
+LL | | true;
+LL | | },
+... |
+LL | | }
+LL | | );
+ | |_____^
+
+error: `assert_ne` of unit values detected. This will always fail
- --> $DIR/unit_cmp.rs:49:5
++ --> $DIR/unit_cmp.rs:45:5
+ |
+LL | / assert_ne!(
+LL | | {
+LL | | true;
+LL | | },
+... |
+LL | | }
+LL | | );
+ | |_____^
+
+error: `debug_assert_ne` of unit values detected. This will always fail
++ --> $DIR/unit_cmp.rs:53:5
+ |
+LL | / debug_assert_ne!(
+LL | | {
+LL | | true;
+LL | | },
+... |
+LL | | }
+LL | | );
+ | |_____^
+
+error: aborting due to 6 previous errors
+
--- /dev/null
- require_slice(x_ref);
+// run-rustfix
+
+#![allow(clippy::ptr_arg)]
+#![warn(clippy::unnecessary_to_owned)]
+#![feature(custom_inner_attributes)]
+
+use std::borrow::Cow;
+use std::ffi::{CStr, CString, OsStr, OsString};
+use std::ops::Deref;
+
+#[derive(Clone)]
+struct X(String);
+
+impl Deref for X {
+ type Target = [u8];
+ fn deref(&self) -> &[u8] {
+ self.0.as_bytes()
+ }
+}
+
+impl AsRef<str> for X {
+ fn as_ref(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl ToString for X {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl X {
+ fn join(&self, other: impl AsRef<str>) -> Self {
+ let mut s = self.0.clone();
+ s.push_str(other.as_ref());
+ Self(s)
+ }
+}
+
+#[allow(dead_code)]
+#[derive(Clone)]
+enum FileType {
+ Account,
+ PrivateKey,
+ Certificate,
+}
+
+fn main() {
+ let c_str = CStr::from_bytes_with_nul(&[0]).unwrap();
+ let os_str = OsStr::new("x");
+ let path = std::path::Path::new("x");
+ let s = "x";
+ let array = ["x"];
+ let array_ref = &["x"];
+ let slice = &["x"][..];
+ let x = X(String::from("x"));
+ let x_ref = &x;
+
+ require_c_str(&Cow::from(c_str));
+ require_c_str(c_str);
+
+ require_os_str(os_str);
+ require_os_str(&Cow::from(os_str));
+ require_os_str(os_str);
+
+ require_path(path);
+ require_path(&Cow::from(path));
+ require_path(path);
+
+ require_str(s);
+ require_str(&Cow::from(s));
+ require_str(s);
+ require_str(x_ref.as_ref());
+
+ require_slice(slice);
+ require_slice(&Cow::from(slice));
+ require_slice(array.as_ref());
+ require_slice(array_ref.as_ref());
+ require_slice(slice);
- require_x(x_ref);
++ require_slice(&x_ref.to_owned()); // No longer flagged because of #8759.
+
+ require_x(&Cow::<X>::Owned(x.clone()));
++ require_x(&x_ref.to_owned()); // No longer flagged because of #8759.
+
+ require_deref_c_str(c_str);
+ require_deref_os_str(os_str);
+ require_deref_path(path);
+ require_deref_str(s);
+ require_deref_slice(slice);
+
+ require_impl_deref_c_str(c_str);
+ require_impl_deref_os_str(os_str);
+ require_impl_deref_path(path);
+ require_impl_deref_str(s);
+ require_impl_deref_slice(slice);
+
+ require_deref_str_slice(s, slice);
+ require_deref_slice_str(slice, s);
+
+ require_as_ref_c_str(c_str);
+ require_as_ref_os_str(os_str);
+ require_as_ref_path(path);
+ require_as_ref_str(s);
+ require_as_ref_str(&x);
+ require_as_ref_slice(array);
+ require_as_ref_slice(array_ref);
+ require_as_ref_slice(slice);
+
+ require_impl_as_ref_c_str(c_str);
+ require_impl_as_ref_os_str(os_str);
+ require_impl_as_ref_path(path);
+ require_impl_as_ref_str(s);
+ require_impl_as_ref_str(&x);
+ require_impl_as_ref_slice(array);
+ require_impl_as_ref_slice(array_ref);
+ require_impl_as_ref_slice(slice);
+
+ require_as_ref_str_slice(s, array);
+ require_as_ref_str_slice(s, array_ref);
+ require_as_ref_str_slice(s, slice);
+ require_as_ref_slice_str(array, s);
+ require_as_ref_slice_str(array_ref, s);
+ require_as_ref_slice_str(slice, s);
+
+ let _ = x.join(x_ref);
+
+ let _ = slice.iter().copied();
+ let _ = slice.iter().copied();
+ let _ = [std::path::PathBuf::new()][..].iter().cloned();
+ let _ = [std::path::PathBuf::new()][..].iter().cloned();
+
+ let _ = slice.iter().copied();
+ let _ = slice.iter().copied();
+ let _ = [std::path::PathBuf::new()][..].iter().cloned();
+ let _ = [std::path::PathBuf::new()][..].iter().cloned();
+
+ let _ = check_files(&[FileType::Account]);
+
+ // negative tests
+ require_string(&s.to_string());
+ require_string(&Cow::from(s).into_owned());
+ require_string(&s.to_owned());
+ require_string(&x_ref.to_string());
+
+ // `X` isn't copy.
+ require_slice(&x.to_owned());
+ require_deref_slice(x.to_owned());
+
+ // The following should be flagged by `redundant_clone`, but not by this lint.
+ require_c_str(&CString::from_vec_with_nul(vec![0]).unwrap());
+ require_os_str(&OsString::from("x"));
+ require_path(&std::path::PathBuf::from("x"));
+ require_str(&String::from("x"));
++ require_slice(&[String::from("x")]);
+}
+
+fn require_c_str(_: &CStr) {}
+fn require_os_str(_: &OsStr) {}
+fn require_path(_: &std::path::Path) {}
+fn require_str(_: &str) {}
+fn require_slice<T>(_: &[T]) {}
+fn require_x(_: &X) {}
+
+fn require_deref_c_str<T: Deref<Target = CStr>>(_: T) {}
+fn require_deref_os_str<T: Deref<Target = OsStr>>(_: T) {}
+fn require_deref_path<T: Deref<Target = std::path::Path>>(_: T) {}
+fn require_deref_str<T: Deref<Target = str>>(_: T) {}
+fn require_deref_slice<T, U: Deref<Target = [T]>>(_: U) {}
+
+fn require_impl_deref_c_str(_: impl Deref<Target = CStr>) {}
+fn require_impl_deref_os_str(_: impl Deref<Target = OsStr>) {}
+fn require_impl_deref_path(_: impl Deref<Target = std::path::Path>) {}
+fn require_impl_deref_str(_: impl Deref<Target = str>) {}
+fn require_impl_deref_slice<T>(_: impl Deref<Target = [T]>) {}
+
+fn require_deref_str_slice<T: Deref<Target = str>, U, V: Deref<Target = [U]>>(_: T, _: V) {}
+fn require_deref_slice_str<T, U: Deref<Target = [T]>, V: Deref<Target = str>>(_: U, _: V) {}
+
+fn require_as_ref_c_str<T: AsRef<CStr>>(_: T) {}
+fn require_as_ref_os_str<T: AsRef<OsStr>>(_: T) {}
+fn require_as_ref_path<T: AsRef<std::path::Path>>(_: T) {}
+fn require_as_ref_str<T: AsRef<str>>(_: T) {}
+fn require_as_ref_slice<T, U: AsRef<[T]>>(_: U) {}
+
+fn require_impl_as_ref_c_str(_: impl AsRef<CStr>) {}
+fn require_impl_as_ref_os_str(_: impl AsRef<OsStr>) {}
+fn require_impl_as_ref_path(_: impl AsRef<std::path::Path>) {}
+fn require_impl_as_ref_str(_: impl AsRef<str>) {}
+fn require_impl_as_ref_slice<T>(_: impl AsRef<[T]>) {}
+
+fn require_as_ref_str_slice<T: AsRef<str>, U, V: AsRef<[U]>>(_: T, _: V) {}
+fn require_as_ref_slice_str<T, U: AsRef<[T]>, V: AsRef<str>>(_: U, _: V) {}
+
+// `check_files` is based on:
+// https://github.com/breard-r/acmed/blob/1f0dcc32aadbc5e52de6d23b9703554c0f925113/acmed/src/storage.rs#L262
+fn check_files(file_types: &[FileType]) -> bool {
+ for t in file_types {
+ let path = match get_file_path(t) {
+ Ok(p) => p,
+ Err(_) => {
+ return false;
+ },
+ };
+ if !path.is_file() {
+ return false;
+ }
+ }
+ true
+}
+
+fn get_file_path(_file_type: &FileType) -> Result<std::path::PathBuf, std::io::Error> {
+ Ok(std::path::PathBuf::new())
+}
+
+fn require_string(_: &String) {}
+
+fn _msrv_1_35() {
+ #![clippy::msrv = "1.35"]
+ // `copied` was stabilized in 1.36, so clippy should use `cloned`.
+ let _ = &["x"][..].iter().cloned();
+}
+
+fn _msrv_1_36() {
+ #![clippy::msrv = "1.36"]
+ let _ = &["x"][..].iter().copied();
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+ #![allow(dead_code)]
+
+ struct Opaque<P>(P);
+
+ pub trait Abstracted {}
+
+ impl<P> Abstracted for Opaque<P> {}
+
+ fn build<P>(p: P) -> Opaque<P>
+ where
+ P: AsRef<str>,
+ {
+ Opaque(p)
+ }
+
+ // Should not lint.
+ fn test_str(s: &str) -> Box<dyn Abstracted> {
+ Box::new(build(s.to_string()))
+ }
+
+ // Should not lint.
+ fn test_x(x: super::X) -> Box<dyn Abstracted> {
+ Box::new(build(x))
+ }
+
+ #[derive(Clone, Copy)]
+ struct Y(&'static str);
+
+ impl AsRef<str> for Y {
+ fn as_ref(&self) -> &str {
+ self.0
+ }
+ }
+
+ impl ToString for Y {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+ }
+
+ // Should lint because Y is copy.
+ fn test_y(y: Y) -> Box<dyn Abstracted> {
+ Box::new(build(y))
+ }
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/8759
++mod issue_8759 {
++ #![allow(dead_code)]
++
++ #[derive(Default)]
++ struct View {}
++
++ impl std::borrow::ToOwned for View {
++ type Owned = View;
++ fn to_owned(&self) -> Self::Owned {
++ View {}
++ }
++ }
++
++ #[derive(Default)]
++ struct RenderWindow {
++ default_view: View,
++ }
++
++ impl RenderWindow {
++ fn default_view(&self) -> &View {
++ &self.default_view
++ }
++ fn set_view(&mut self, _view: &View) {}
++ }
++
++ fn main() {
++ let mut rw = RenderWindow::default();
++ rw.set_view(&rw.default_view().to_owned());
++ }
++}
++
++mod issue_8759_variant {
++ #![allow(dead_code)]
++
++ #[derive(Clone, Default)]
++ struct View {}
++
++ #[derive(Default)]
++ struct RenderWindow {
++ default_view: View,
++ }
++
++ impl RenderWindow {
++ fn default_view(&self) -> &View {
++ &self.default_view
++ }
++ fn set_view(&mut self, _view: &View) {}
++ }
++
++ fn main() {
++ let mut rw = RenderWindow::default();
++ rw.set_view(&rw.default_view().to_owned());
++ }
++}
--- /dev/null
- require_slice(&x_ref.to_owned());
+// run-rustfix
+
+#![allow(clippy::ptr_arg)]
+#![warn(clippy::unnecessary_to_owned)]
+#![feature(custom_inner_attributes)]
+
+use std::borrow::Cow;
+use std::ffi::{CStr, CString, OsStr, OsString};
+use std::ops::Deref;
+
+#[derive(Clone)]
+struct X(String);
+
+impl Deref for X {
+ type Target = [u8];
+ fn deref(&self) -> &[u8] {
+ self.0.as_bytes()
+ }
+}
+
+impl AsRef<str> for X {
+ fn as_ref(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl ToString for X {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl X {
+ fn join(&self, other: impl AsRef<str>) -> Self {
+ let mut s = self.0.clone();
+ s.push_str(other.as_ref());
+ Self(s)
+ }
+}
+
+#[allow(dead_code)]
+#[derive(Clone)]
+enum FileType {
+ Account,
+ PrivateKey,
+ Certificate,
+}
+
+fn main() {
+ let c_str = CStr::from_bytes_with_nul(&[0]).unwrap();
+ let os_str = OsStr::new("x");
+ let path = std::path::Path::new("x");
+ let s = "x";
+ let array = ["x"];
+ let array_ref = &["x"];
+ let slice = &["x"][..];
+ let x = X(String::from("x"));
+ let x_ref = &x;
+
+ require_c_str(&Cow::from(c_str).into_owned());
+ require_c_str(&c_str.to_owned());
+
+ require_os_str(&os_str.to_os_string());
+ require_os_str(&Cow::from(os_str).into_owned());
+ require_os_str(&os_str.to_owned());
+
+ require_path(&path.to_path_buf());
+ require_path(&Cow::from(path).into_owned());
+ require_path(&path.to_owned());
+
+ require_str(&s.to_string());
+ require_str(&Cow::from(s).into_owned());
+ require_str(&s.to_owned());
+ require_str(&x_ref.to_string());
+
+ require_slice(&slice.to_vec());
+ require_slice(&Cow::from(slice).into_owned());
+ require_slice(&array.to_owned());
+ require_slice(&array_ref.to_owned());
+ require_slice(&slice.to_owned());
- require_x(&x_ref.to_owned());
++ require_slice(&x_ref.to_owned()); // No longer flagged because of #8759.
+
+ require_x(&Cow::<X>::Owned(x.clone()).into_owned());
++ require_x(&x_ref.to_owned()); // No longer flagged because of #8759.
+
+ require_deref_c_str(c_str.to_owned());
+ require_deref_os_str(os_str.to_owned());
+ require_deref_path(path.to_owned());
+ require_deref_str(s.to_owned());
+ require_deref_slice(slice.to_owned());
+
+ require_impl_deref_c_str(c_str.to_owned());
+ require_impl_deref_os_str(os_str.to_owned());
+ require_impl_deref_path(path.to_owned());
+ require_impl_deref_str(s.to_owned());
+ require_impl_deref_slice(slice.to_owned());
+
+ require_deref_str_slice(s.to_owned(), slice.to_owned());
+ require_deref_slice_str(slice.to_owned(), s.to_owned());
+
+ require_as_ref_c_str(c_str.to_owned());
+ require_as_ref_os_str(os_str.to_owned());
+ require_as_ref_path(path.to_owned());
+ require_as_ref_str(s.to_owned());
+ require_as_ref_str(x.to_owned());
+ require_as_ref_slice(array.to_owned());
+ require_as_ref_slice(array_ref.to_owned());
+ require_as_ref_slice(slice.to_owned());
+
+ require_impl_as_ref_c_str(c_str.to_owned());
+ require_impl_as_ref_os_str(os_str.to_owned());
+ require_impl_as_ref_path(path.to_owned());
+ require_impl_as_ref_str(s.to_owned());
+ require_impl_as_ref_str(x.to_owned());
+ require_impl_as_ref_slice(array.to_owned());
+ require_impl_as_ref_slice(array_ref.to_owned());
+ require_impl_as_ref_slice(slice.to_owned());
+
+ require_as_ref_str_slice(s.to_owned(), array.to_owned());
+ require_as_ref_str_slice(s.to_owned(), array_ref.to_owned());
+ require_as_ref_str_slice(s.to_owned(), slice.to_owned());
+ require_as_ref_slice_str(array.to_owned(), s.to_owned());
+ require_as_ref_slice_str(array_ref.to_owned(), s.to_owned());
+ require_as_ref_slice_str(slice.to_owned(), s.to_owned());
+
+ let _ = x.join(&x_ref.to_string());
+
+ let _ = slice.to_vec().into_iter();
+ let _ = slice.to_owned().into_iter();
+ let _ = [std::path::PathBuf::new()][..].to_vec().into_iter();
+ let _ = [std::path::PathBuf::new()][..].to_owned().into_iter();
+
+ let _ = IntoIterator::into_iter(slice.to_vec());
+ let _ = IntoIterator::into_iter(slice.to_owned());
+ let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_vec());
+ let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_owned());
+
+ let _ = check_files(&[FileType::Account]);
+
+ // negative tests
+ require_string(&s.to_string());
+ require_string(&Cow::from(s).into_owned());
+ require_string(&s.to_owned());
+ require_string(&x_ref.to_string());
+
+ // `X` isn't copy.
+ require_slice(&x.to_owned());
+ require_deref_slice(x.to_owned());
+
+ // The following should be flagged by `redundant_clone`, but not by this lint.
+ require_c_str(&CString::from_vec_with_nul(vec![0]).unwrap().to_owned());
+ require_os_str(&OsString::from("x").to_os_string());
+ require_path(&std::path::PathBuf::from("x").to_path_buf());
+ require_str(&String::from("x").to_string());
++ require_slice(&[String::from("x")].to_owned());
+}
+
+fn require_c_str(_: &CStr) {}
+fn require_os_str(_: &OsStr) {}
+fn require_path(_: &std::path::Path) {}
+fn require_str(_: &str) {}
+fn require_slice<T>(_: &[T]) {}
+fn require_x(_: &X) {}
+
+fn require_deref_c_str<T: Deref<Target = CStr>>(_: T) {}
+fn require_deref_os_str<T: Deref<Target = OsStr>>(_: T) {}
+fn require_deref_path<T: Deref<Target = std::path::Path>>(_: T) {}
+fn require_deref_str<T: Deref<Target = str>>(_: T) {}
+fn require_deref_slice<T, U: Deref<Target = [T]>>(_: U) {}
+
+fn require_impl_deref_c_str(_: impl Deref<Target = CStr>) {}
+fn require_impl_deref_os_str(_: impl Deref<Target = OsStr>) {}
+fn require_impl_deref_path(_: impl Deref<Target = std::path::Path>) {}
+fn require_impl_deref_str(_: impl Deref<Target = str>) {}
+fn require_impl_deref_slice<T>(_: impl Deref<Target = [T]>) {}
+
+fn require_deref_str_slice<T: Deref<Target = str>, U, V: Deref<Target = [U]>>(_: T, _: V) {}
+fn require_deref_slice_str<T, U: Deref<Target = [T]>, V: Deref<Target = str>>(_: U, _: V) {}
+
+fn require_as_ref_c_str<T: AsRef<CStr>>(_: T) {}
+fn require_as_ref_os_str<T: AsRef<OsStr>>(_: T) {}
+fn require_as_ref_path<T: AsRef<std::path::Path>>(_: T) {}
+fn require_as_ref_str<T: AsRef<str>>(_: T) {}
+fn require_as_ref_slice<T, U: AsRef<[T]>>(_: U) {}
+
+fn require_impl_as_ref_c_str(_: impl AsRef<CStr>) {}
+fn require_impl_as_ref_os_str(_: impl AsRef<OsStr>) {}
+fn require_impl_as_ref_path(_: impl AsRef<std::path::Path>) {}
+fn require_impl_as_ref_str(_: impl AsRef<str>) {}
+fn require_impl_as_ref_slice<T>(_: impl AsRef<[T]>) {}
+
+fn require_as_ref_str_slice<T: AsRef<str>, U, V: AsRef<[U]>>(_: T, _: V) {}
+fn require_as_ref_slice_str<T, U: AsRef<[T]>, V: AsRef<str>>(_: U, _: V) {}
+
+// `check_files` is based on:
+// https://github.com/breard-r/acmed/blob/1f0dcc32aadbc5e52de6d23b9703554c0f925113/acmed/src/storage.rs#L262
+fn check_files(file_types: &[FileType]) -> bool {
+ for t in file_types.to_vec() {
+ let path = match get_file_path(&t) {
+ Ok(p) => p,
+ Err(_) => {
+ return false;
+ },
+ };
+ if !path.is_file() {
+ return false;
+ }
+ }
+ true
+}
+
+fn get_file_path(_file_type: &FileType) -> Result<std::path::PathBuf, std::io::Error> {
+ Ok(std::path::PathBuf::new())
+}
+
+fn require_string(_: &String) {}
+
+fn _msrv_1_35() {
+ #![clippy::msrv = "1.35"]
+ // `copied` was stabilized in 1.36, so clippy should use `cloned`.
+ let _ = &["x"][..].to_vec().into_iter();
+}
+
+fn _msrv_1_36() {
+ #![clippy::msrv = "1.36"]
+ let _ = &["x"][..].to_vec().into_iter();
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+ #![allow(dead_code)]
+
+ struct Opaque<P>(P);
+
+ pub trait Abstracted {}
+
+ impl<P> Abstracted for Opaque<P> {}
+
+ fn build<P>(p: P) -> Opaque<P>
+ where
+ P: AsRef<str>,
+ {
+ Opaque(p)
+ }
+
+ // Should not lint.
+ fn test_str(s: &str) -> Box<dyn Abstracted> {
+ Box::new(build(s.to_string()))
+ }
+
+ // Should not lint.
+ fn test_x(x: super::X) -> Box<dyn Abstracted> {
+ Box::new(build(x))
+ }
+
+ #[derive(Clone, Copy)]
+ struct Y(&'static str);
+
+ impl AsRef<str> for Y {
+ fn as_ref(&self) -> &str {
+ self.0
+ }
+ }
+
+ impl ToString for Y {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+ }
+
+ // Should lint because Y is copy.
+ fn test_y(y: Y) -> Box<dyn Abstracted> {
+ Box::new(build(y.to_string()))
+ }
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/8759
++mod issue_8759 {
++ #![allow(dead_code)]
++
++ #[derive(Default)]
++ struct View {}
++
++ impl std::borrow::ToOwned for View {
++ type Owned = View;
++ fn to_owned(&self) -> Self::Owned {
++ View {}
++ }
++ }
++
++ #[derive(Default)]
++ struct RenderWindow {
++ default_view: View,
++ }
++
++ impl RenderWindow {
++ fn default_view(&self) -> &View {
++ &self.default_view
++ }
++ fn set_view(&mut self, _view: &View) {}
++ }
++
++ fn main() {
++ let mut rw = RenderWindow::default();
++ rw.set_view(&rw.default_view().to_owned());
++ }
++}
++
++mod issue_8759_variant {
++ #![allow(dead_code)]
++
++ #[derive(Clone, Default)]
++ struct View {}
++
++ #[derive(Default)]
++ struct RenderWindow {
++ default_view: View,
++ }
++
++ impl RenderWindow {
++ fn default_view(&self) -> &View {
++ &self.default_view
++ }
++ fn set_view(&mut self, _view: &View) {}
++ }
++
++ fn main() {
++ let mut rw = RenderWindow::default();
++ rw.set_view(&rw.default_view().to_owned());
++ }
++}
--- /dev/null
- error: unnecessary use of `to_owned`
- --> $DIR/unnecessary_to_owned.rs:81:19
- |
- LL | require_slice(&x_ref.to_owned());
- | ^^^^^^^^^^^^^^^^^ help: use: `x_ref`
-
+error: redundant clone
+ --> $DIR/unnecessary_to_owned.rs:151:64
+ |
+LL | require_c_str(&CString::from_vec_with_nul(vec![0]).unwrap().to_owned());
+ | ^^^^^^^^^^^ help: remove this
+ |
+ = note: `-D clippy::redundant-clone` implied by `-D warnings`
+note: this value is dropped without further use
+ --> $DIR/unnecessary_to_owned.rs:151:20
+ |
+LL | require_c_str(&CString::from_vec_with_nul(vec![0]).unwrap().to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: redundant clone
+ --> $DIR/unnecessary_to_owned.rs:152:40
+ |
+LL | require_os_str(&OsString::from("x").to_os_string());
+ | ^^^^^^^^^^^^^^^ help: remove this
+ |
+note: this value is dropped without further use
+ --> $DIR/unnecessary_to_owned.rs:152:21
+ |
+LL | require_os_str(&OsString::from("x").to_os_string());
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: redundant clone
+ --> $DIR/unnecessary_to_owned.rs:153:48
+ |
+LL | require_path(&std::path::PathBuf::from("x").to_path_buf());
+ | ^^^^^^^^^^^^^^ help: remove this
+ |
+note: this value is dropped without further use
+ --> $DIR/unnecessary_to_owned.rs:153:19
+ |
+LL | require_path(&std::path::PathBuf::from("x").to_path_buf());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: redundant clone
+ --> $DIR/unnecessary_to_owned.rs:154:35
+ |
+LL | require_str(&String::from("x").to_string());
+ | ^^^^^^^^^^^^ help: remove this
+ |
+note: this value is dropped without further use
+ --> $DIR/unnecessary_to_owned.rs:154:18
+ |
+LL | require_str(&String::from("x").to_string());
+ | ^^^^^^^^^^^^^^^^^
+
++error: redundant clone
++ --> $DIR/unnecessary_to_owned.rs:155:39
++ |
++LL | require_slice(&[String::from("x")].to_owned());
++ | ^^^^^^^^^^^ help: remove this
++ |
++note: this value is dropped without further use
++ --> $DIR/unnecessary_to_owned.rs:155:20
++ |
++LL | require_slice(&[String::from("x")].to_owned());
++ | ^^^^^^^^^^^^^^^^^^^
++
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:60:36
+ |
+LL | require_c_str(&Cow::from(c_str).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+ |
+ = note: `-D clippy::unnecessary-to-owned` implied by `-D warnings`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:61:19
+ |
+LL | require_c_str(&c_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `c_str`
+
+error: unnecessary use of `to_os_string`
+ --> $DIR/unnecessary_to_owned.rs:63:20
+ |
+LL | require_os_str(&os_str.to_os_string());
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:64:38
+ |
+LL | require_os_str(&Cow::from(os_str).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:65:20
+ |
+LL | require_os_str(&os_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `to_path_buf`
+ --> $DIR/unnecessary_to_owned.rs:67:18
+ |
+LL | require_path(&path.to_path_buf());
+ | ^^^^^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:68:34
+ |
+LL | require_path(&Cow::from(path).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:69:18
+ |
+LL | require_path(&path.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `to_string`
+ --> $DIR/unnecessary_to_owned.rs:71:17
+ |
+LL | require_str(&s.to_string());
+ | ^^^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:72:30
+ |
+LL | require_str(&Cow::from(s).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:73:17
+ |
+LL | require_str(&s.to_owned());
+ | ^^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_string`
+ --> $DIR/unnecessary_to_owned.rs:74:17
+ |
+LL | require_str(&x_ref.to_string());
+ | ^^^^^^^^^^^^^^^^^^ help: use: `x_ref.as_ref()`
+
+error: unnecessary use of `to_vec`
+ --> $DIR/unnecessary_to_owned.rs:76:19
+ |
+LL | require_slice(&slice.to_vec());
+ | ^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:77:36
+ |
+LL | require_slice(&Cow::from(slice).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:78:19
+ |
+LL | require_slice(&array.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `array.as_ref()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:79:19
+ |
+LL | require_slice(&array_ref.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: use: `array_ref.as_ref()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:80:19
+ |
+LL | require_slice(&slice.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `slice`
+
- error: unnecessary use of `to_owned`
- --> $DIR/unnecessary_to_owned.rs:84:15
- |
- LL | require_x(&x_ref.to_owned());
- | ^^^^^^^^^^^^^^^^^ help: use: `x_ref`
-
+error: unnecessary use of `into_owned`
+ --> $DIR/unnecessary_to_owned.rs:83:42
+ |
+LL | require_x(&Cow::<X>::Owned(x.clone()).into_owned());
+ | ^^^^^^^^^^^^^ help: remove this
+
- --> $DIR/unnecessary_to_owned.rs:197:14
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:86:25
+ |
+LL | require_deref_c_str(c_str.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `c_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:87:26
+ |
+LL | require_deref_os_str(os_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:88:24
+ |
+LL | require_deref_path(path.to_owned());
+ | ^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:89:23
+ |
+LL | require_deref_str(s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:90:25
+ |
+LL | require_deref_slice(slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:92:30
+ |
+LL | require_impl_deref_c_str(c_str.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `c_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:93:31
+ |
+LL | require_impl_deref_os_str(os_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:94:29
+ |
+LL | require_impl_deref_path(path.to_owned());
+ | ^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:95:28
+ |
+LL | require_impl_deref_str(s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:96:30
+ |
+LL | require_impl_deref_slice(slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:98:29
+ |
+LL | require_deref_str_slice(s.to_owned(), slice.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:98:43
+ |
+LL | require_deref_str_slice(s.to_owned(), slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:99:29
+ |
+LL | require_deref_slice_str(slice.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:99:47
+ |
+LL | require_deref_slice_str(slice.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:101:26
+ |
+LL | require_as_ref_c_str(c_str.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `c_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:102:27
+ |
+LL | require_as_ref_os_str(os_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:103:25
+ |
+LL | require_as_ref_path(path.to_owned());
+ | ^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:104:24
+ |
+LL | require_as_ref_str(s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:105:24
+ |
+LL | require_as_ref_str(x.to_owned());
+ | ^^^^^^^^^^^^ help: use: `&x`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:106:26
+ |
+LL | require_as_ref_slice(array.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `array`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:107:26
+ |
+LL | require_as_ref_slice(array_ref.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^ help: use: `array_ref`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:108:26
+ |
+LL | require_as_ref_slice(slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:110:31
+ |
+LL | require_impl_as_ref_c_str(c_str.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `c_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:111:32
+ |
+LL | require_impl_as_ref_os_str(os_str.to_owned());
+ | ^^^^^^^^^^^^^^^^^ help: use: `os_str`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:112:30
+ |
+LL | require_impl_as_ref_path(path.to_owned());
+ | ^^^^^^^^^^^^^^^ help: use: `path`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:113:29
+ |
+LL | require_impl_as_ref_str(s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:114:29
+ |
+LL | require_impl_as_ref_str(x.to_owned());
+ | ^^^^^^^^^^^^ help: use: `&x`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:115:31
+ |
+LL | require_impl_as_ref_slice(array.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `array`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:116:31
+ |
+LL | require_impl_as_ref_slice(array_ref.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^ help: use: `array_ref`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:117:31
+ |
+LL | require_impl_as_ref_slice(slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:119:30
+ |
+LL | require_as_ref_str_slice(s.to_owned(), array.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:119:44
+ |
+LL | require_as_ref_str_slice(s.to_owned(), array.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `array`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:120:30
+ |
+LL | require_as_ref_str_slice(s.to_owned(), array_ref.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:120:44
+ |
+LL | require_as_ref_str_slice(s.to_owned(), array_ref.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^ help: use: `array_ref`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:121:30
+ |
+LL | require_as_ref_str_slice(s.to_owned(), slice.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:121:44
+ |
+LL | require_as_ref_str_slice(s.to_owned(), slice.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:122:30
+ |
+LL | require_as_ref_slice_str(array.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `array`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:122:48
+ |
+LL | require_as_ref_slice_str(array.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:123:30
+ |
+LL | require_as_ref_slice_str(array_ref.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^ help: use: `array_ref`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:123:52
+ |
+LL | require_as_ref_slice_str(array_ref.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:124:30
+ |
+LL | require_as_ref_slice_str(slice.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^^^^^ help: use: `slice`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:124:48
+ |
+LL | require_as_ref_slice_str(slice.to_owned(), s.to_owned());
+ | ^^^^^^^^^^^^ help: use: `s`
+
+error: unnecessary use of `to_string`
+ --> $DIR/unnecessary_to_owned.rs:126:20
+ |
+LL | let _ = x.join(&x_ref.to_string());
+ | ^^^^^^^^^^^^^^^^^^ help: use: `x_ref`
+
+error: unnecessary use of `to_vec`
+ --> $DIR/unnecessary_to_owned.rs:128:13
+ |
+LL | let _ = slice.to_vec().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `slice.iter().copied()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:129:13
+ |
+LL | let _ = slice.to_owned().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `slice.iter().copied()`
+
+error: unnecessary use of `to_vec`
+ --> $DIR/unnecessary_to_owned.rs:130:13
+ |
+LL | let _ = [std::path::PathBuf::new()][..].to_vec().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:131:13
+ |
+LL | let _ = [std::path::PathBuf::new()][..].to_owned().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()`
+
+error: unnecessary use of `to_vec`
+ --> $DIR/unnecessary_to_owned.rs:133:13
+ |
+LL | let _ = IntoIterator::into_iter(slice.to_vec());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `slice.iter().copied()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:134:13
+ |
+LL | let _ = IntoIterator::into_iter(slice.to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `slice.iter().copied()`
+
+error: unnecessary use of `to_vec`
+ --> $DIR/unnecessary_to_owned.rs:135:13
+ |
+LL | let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_vec());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()`
+
+error: unnecessary use of `to_owned`
+ --> $DIR/unnecessary_to_owned.rs:136:13
+ |
+LL | let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()`
+
+error: unnecessary use of `to_vec`
- --> $DIR/unnecessary_to_owned.rs:220:14
++ --> $DIR/unnecessary_to_owned.rs:198:14
+ |
+LL | for t in file_types.to_vec() {
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: use
+ |
+LL | for t in file_types {
+ | ~~~~~~~~~~
+help: remove this `&`
+ |
+LL - let path = match get_file_path(&t) {
+LL + let path = match get_file_path(t) {
+ |
+
+error: unnecessary use of `to_vec`
- --> $DIR/unnecessary_to_owned.rs:225:14
++ --> $DIR/unnecessary_to_owned.rs:221:14
+ |
+LL | let _ = &["x"][..].to_vec().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().cloned()`
+
+error: unnecessary use of `to_vec`
- --> $DIR/unnecessary_to_owned.rs:272:24
++ --> $DIR/unnecessary_to_owned.rs:226:14
+ |
+LL | let _ = &["x"][..].to_vec().into_iter();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().copied()`
+
+error: unnecessary use of `to_string`
- error: aborting due to 79 previous errors
++ --> $DIR/unnecessary_to_owned.rs:273:24
+ |
+LL | Box::new(build(y.to_string()))
+ | ^^^^^^^^^^^^^ help: use: `y`
+
++error: aborting due to 78 previous errors
+
--- /dev/null
- use std::convert::{TryFrom, TryInto};
-
+#![deny(clippy::useless_conversion)]
+
+fn test_generic<T: Copy>(val: T) -> T {
+ let _ = T::try_from(val).unwrap();
+ val.try_into().unwrap()
+}
+
+fn test_generic2<T: Copy + Into<i32> + Into<U>, U: From<T>>(val: T) {
+ // ok
+ let _: i32 = val.try_into().unwrap();
+ let _: U = val.try_into().unwrap();
+ let _ = U::try_from(val).unwrap();
+}
+
+fn main() {
+ test_generic(10i32);
+ test_generic2::<i32, i32>(10i32);
+
+ let _: String = "foo".try_into().unwrap();
+ let _: String = TryFrom::try_from("foo").unwrap();
+ let _ = String::try_from("foo").unwrap();
+ #[allow(clippy::useless_conversion)]
+ {
+ let _ = String::try_from("foo").unwrap();
+ let _: String = "foo".try_into().unwrap();
+ }
+ let _: String = "foo".to_string().try_into().unwrap();
+ let _: String = TryFrom::try_from("foo".to_string()).unwrap();
+ let _ = String::try_from("foo".to_string()).unwrap();
+ let _ = String::try_from(format!("A: {:04}", 123)).unwrap();
+ let _: String = format!("Hello {}", "world").try_into().unwrap();
+ let _: String = "".to_owned().try_into().unwrap();
+ let _: String = match String::from("_").try_into() {
+ Ok(a) => a,
+ Err(_) => "".into(),
+ };
+ // FIXME this is a false negative
+ #[allow(clippy::cmp_owned)]
+ if String::from("a") == TryInto::<String>::try_into(String::from("a")).unwrap() {}
+}
--- /dev/null
- --> $DIR/useless_conversion_try.rs:6:13
+error: useless conversion to the same type: `T`
- --> $DIR/useless_conversion_try.rs:7:5
++ --> $DIR/useless_conversion_try.rs:4:13
+ |
+LL | let _ = T::try_from(val).unwrap();
+ | ^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/useless_conversion_try.rs:1:9
+ |
+LL | #![deny(clippy::useless_conversion)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ = help: consider removing `T::try_from()`
+
+error: useless conversion to the same type: `T`
- --> $DIR/useless_conversion_try.rs:29:21
++ --> $DIR/useless_conversion_try.rs:5:5
+ |
+LL | val.try_into().unwrap()
+ | ^^^^^^^^^^^^^^
+ |
+ = help: consider removing `.try_into()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:30:21
++ --> $DIR/useless_conversion_try.rs:27:21
+ |
+LL | let _: String = "foo".to_string().try_into().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `.try_into()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:31:13
++ --> $DIR/useless_conversion_try.rs:28:21
+ |
+LL | let _: String = TryFrom::try_from("foo".to_string()).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `TryFrom::try_from()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:32:13
++ --> $DIR/useless_conversion_try.rs:29:13
+ |
+LL | let _ = String::try_from("foo".to_string()).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `String::try_from()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:33:21
++ --> $DIR/useless_conversion_try.rs:30:13
+ |
+LL | let _ = String::try_from(format!("A: {:04}", 123)).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `String::try_from()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:34:21
++ --> $DIR/useless_conversion_try.rs:31:21
+ |
+LL | let _: String = format!("Hello {}", "world").try_into().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `.try_into()`
+
+error: useless conversion to the same type: `std::string::String`
- --> $DIR/useless_conversion_try.rs:35:27
++ --> $DIR/useless_conversion_try.rs:32:21
+ |
+LL | let _: String = "".to_owned().try_into().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `.try_into()`
+
+error: useless conversion to the same type: `std::string::String`
++ --> $DIR/useless_conversion_try.rs:33:27
+ |
+LL | let _: String = match String::from("_").try_into() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider removing `.try_into()`
+
+error: aborting due to 9 previous errors
+
--- /dev/null
+#![allow(unused_variables)]
+#![warn(clippy::vec_init_then_push)]
+
+fn main() {
+ let mut def_err: Vec<u32> = Default::default();
+ def_err.push(0);
+
+ let mut new_err = Vec::<u32>::new();
+ new_err.push(1);
+
+ let mut cap_err = Vec::with_capacity(2);
+ 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);
+ }
++
++ let mut vec = Vec::with_capacity(5);
++ vec.push(1);
++ vec.push(2);
++ vec.push(3);
++ vec.push(4);
+}
+
+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;
+ },
+ }
+ }
+}
++
++fn _from_iter(items: impl Iterator<Item = u32>) -> Vec<u32> {
++ let mut v = Vec::new();
++ v.push(0);
++ v.push(1);
++ v.extend(items);
++ v
++}
++
++fn _cond_push(x: bool) -> Vec<u32> {
++ let mut v = Vec::new();
++ v.push(0);
++ if x {
++ v.push(1);
++ }
++ v.push(2);
++ v
++}
++
++fn _push_then_edit(x: u32) -> Vec<u32> {
++ let mut v = Vec::new();
++ v.push(x);
++ v.push(1);
++ v[0] = v[1] + 5;
++ v
++}
++
++fn _cond_push_with_large_start(x: bool) -> Vec<u32> {
++ let mut v = Vec::new();
++ v.push(0);
++ v.push(1);
++ v.push(0);
++ v.push(1);
++ v.push(0);
++ v.push(0);
++ v.push(1);
++ v.push(0);
++ if x {
++ v.push(1);
++ }
++
++ let mut v2 = Vec::new();
++ v2.push(0);
++ v2.push(1);
++ v2.push(0);
++ v2.push(1);
++ v2.push(0);
++ v2.push(0);
++ v2.push(1);
++ v2.push(0);
++ v2.extend(&v);
++
++ v2
++}
--- /dev/null
- | |____________________^ help: consider using the `vec![]` macro: `let mut def_err: Vec<u32> = vec![..];`
+error: calls to `push` immediately after creation
+ --> $DIR/vec_init_then_push.rs:5:5
+ |
+LL | / let mut def_err: Vec<u32> = Default::default();
+LL | | def_err.push(0);
- error: aborting due to 4 previous errors
++ | |____________________^ help: consider using the `vec![]` macro: `let def_err: Vec<u32> = vec![..];`
+ |
+ = note: `-D clippy::vec-init-then-push` implied by `-D warnings`
+
+error: calls to `push` immediately after creation
+ --> $DIR/vec_init_then_push.rs:8:5
+ |
+LL | / let mut new_err = Vec::<u32>::new();
+LL | | new_err.push(1);
+ | |____________________^ help: consider using the `vec![]` macro: `let mut new_err = vec![..];`
+
+error: calls to `push` immediately after creation
+ --> $DIR/vec_init_then_push.rs:11:5
+ |
+LL | / let mut cap_err = Vec::with_capacity(2);
+LL | | cap_err.push(0);
+LL | | cap_err.push(1);
+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:23:5
+ |
+LL | / new_err = Vec::new();
+LL | | new_err.push(0);
+ | |____________________^ help: consider using the `vec![]` macro: `new_err = vec![..];`
+
++error: calls to `push` immediately after creation
++ --> $DIR/vec_init_then_push.rs:73:5
++ |
++LL | / let mut v = Vec::new();
++LL | | v.push(x);
++LL | | v.push(1);
++ | |______________^ help: consider using the `vec![]` macro: `let mut v = vec![..];`
++
++error: calls to `push` immediately after creation
++ --> $DIR/vec_init_then_push.rs:81:5
++ |
++LL | / let mut v = Vec::new();
++LL | | v.push(0);
++LL | | v.push(1);
++LL | | v.push(0);
++... |
++LL | | v.push(1);
++LL | | v.push(0);
++ | |______________^ help: consider using the `vec![]` macro: `let mut v = vec![..];`
++
++error: calls to `push` immediately after creation
++ --> $DIR/vec_init_then_push.rs:94:5
++ |
++LL | / let mut v2 = Vec::new();
++LL | | v2.push(0);
++LL | | v2.push(1);
++LL | | v2.push(0);
++... |
++LL | | v2.push(1);
++LL | | v2.push(0);
++ | |_______________^ help: consider using the `vec![]` macro: `let mut v2 = vec![..];`
++
++error: aborting due to 7 previous errors
+
--- /dev/null
- .theme-choice>li:hover {
+<!DOCTYPE html>
+<!--
+Welcome to a Clippy's lint list, at least the source code of it. If you are
+interested in contributing to this website checkout `util/gh-pages/index.html`
+inside the rust-clippy repository.
+
+Otherwise, have a great day =^.^=
+-->
+<html lang="en">
+<head>
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+
+ <title>Clippy Lints</title>
+
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css"/>
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/styles/github.min.css"/>
+
+ <!-- The files are not copied over into the Clippy project since they use the MPL-2.0 License -->
+ <link rel="stylesheet" href="https://rust-lang.github.io/mdBook/css/variables.css"/>
+ <link id="styleHighlight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/highlight.css">
+ <link id="styleNight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/tomorrow-night.css" disabled="true">
+ <link id="styleAyu" rel="stylesheet" href="https://rust-lang.github.io/mdBook/ayu-highlight.css" disabled="true">
+ <style>
+ blockquote { font-size: 1em; }
+ [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }
+
+ .dropdown-menu {
+ color: var(--fg);
+ background: var(--theme-popup-bg);
+ border: 1px solid var(--theme-popup-border);
+ }
+
+ .dropdown-menu .divider {
+ background-color: var(--theme-popup-border);
+ }
+
+ .dropdown-menu .checkbox {
+ display: block;
+ white-space: nowrap;
+ margin: 0;
+ }
+ .dropdown-menu .checkbox label {
+ padding: 3px 20px;
+ width: 100%;
+ }
+
+ .dropdown-menu .checkbox input {
+ position: relative;
+ margin: 0 0.5rem 0;
+ padding: 0;
+ }
+
+ .dropdown-menu .checkbox:hover {
+ background-color: var(--theme-hover);
+ }
+
+ div.panel div.panel-body button.dropdown-toggle {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ }
+
+ div.panel div.panel-body button.dropdown-toggle:hover {
+ box-shadow: 0 0 3px var(--searchbar-shadow-color);
+ }
+
+ div.panel div.panel-body .open button.dropdown-toggle {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ filter: brightness(90%);
+ }
+
+ .dropdown-toggle .badge {
+ background-color: #777;
+ }
+
+ .panel-heading { cursor: pointer; }
+
+ .panel-title { display: flex; flex-wrap: wrap;}
+ .panel-title .label { display: inline-block; }
+
+ .panel-title-name { flex: 1; min-width: 400px;}
+ .panel-title-name span { vertical-align: bottom; }
+
+ .panel .panel-title-name .anchor { display: none; }
+ .panel:hover .panel-title-name .anchor { display: inline;}
+
+ .search-control {
+ margin-top: 15px;
+ }
+
+ @media (min-width: 992px) {
+ .search-control {
+ margin-top: 0;
++ float: right;
++ }
++ }
++
++ @media (min-width: 405px) {
++ #upper-filters {
++ display: flex;
++ }
++ }
++
++ @media (max-width: 430px) {
++ /* Turn the version filter list to the left */
++ #version-filter-selector {
++ right: 0;
++ left: auto;
++ }
++ }
++
++ @media (max-width: 412px) {
++ #upper-filters,
++ .panel-body .search-control {
++ padding-right: 8px;
++ padding-left: 8px;
+ }
+ }
+
+ .label {
+ padding-top: 0.3em;
+ padding-bottom: 0.3em;
+ }
+
+ .label-lint-group {
+ min-width: 8em;
+ }
+ .label-lint-level {
+ min-width: 4em;
+ }
+
+ .label-lint-level-allow {
+ background-color: #5cb85c;
+ }
+ .label-lint-level-warn {
+ background-color: #f0ad4e;
+ }
+ .label-lint-level-deny {
+ background-color: #d9534f;
+ }
+ .label-lint-level-none {
+ background-color: #777777;
+ opacity: 0.5;
+ }
+
+ .label-group-deprecated {
+ opacity: 0.5;
+ }
+
+ .label-doc-folding {
+ color: #000;
+ background-color: #fff;
+ border: 1px solid var(--theme-popup-border);
+ }
+ .label-doc-folding:hover {
+ background-color: #e6e6e6;
+ }
+
+ .lint-doc-md > h3 {
+ border-top: 1px solid var(--theme-popup-border);
+ padding: 10px 15px;
+ margin: 0 -15px;
+ font-size: 18px;
+ }
+ .lint-doc-md > h3:first-child {
+ border-top: none;
+ padding-top: 0px;
+ }
+
+ @media (max-width:749px) {
+ .lint-additional-info-container {
+ display: flex;
+ flex-flow: column;
+ }
+ .lint-additional-info-item + .lint-additional-info-item {
+ border-top: 1px solid var(--theme-popup-border);
+ }
+ }
+ @media (min-width:750px) {
+ .lint-additional-info-container {
+ display: flex;
+ flex-flow: row;
+ }
+ .lint-additional-info-item + .lint-additional-info-item {
+ border-left: 1px solid var(--theme-popup-border);
+ }
+ }
+
+ .lint-additional-info-item {
+ display: inline-flex;
+ min-width: 200px;
+ flex-grow: 1;
+ padding: 9px 5px 5px 15px;
+ }
+
+ .label-applicability {
+ background-color: #777777;
+ margin: auto 5px;
+ }
+
+ .label-version {
+ background-color: #777777;
+ margin: auto 5px;
+ font-family: monospace;
+ }
+ </style>
+ <style>
+ /* Expanding the mdBoom theme*/
+ .light {
+ --inline-code-bg: #f6f7f6;
+ }
+ .rust {
+ --inline-code-bg: #f6f7f6;
+ }
+ .coal {
+ --inline-code-bg: #1d1f21;
+ }
+ .navy {
+ --inline-code-bg: #1d1f21;
+ }
+ .ayu {
+ --inline-code-bg: #191f26;
+ }
+
+ .theme-dropdown {
+ position: absolute;
+ margin: 0.7em;
+ z-index: 10;
+ }
+
+ /* Applying the mdBook theme */
+ .theme-icon {
+ text-align: center;
+ width: 2em;
+ height: 2em;
+ line-height: 2em;
+ border: solid 1px var(--icons);
+ border-radius: 5px;
+ user-select: none;
+ cursor: pointer;
+ }
+ .theme-icon:hover {
+ background: var(--theme-hover);
+ }
+ .theme-choice {
+ display: none;
+ list-style: none;
+ border: 1px solid var(--theme-popup-border);
+ border-radius: 5px;
+ color: var(--fg);
+ background: var(--theme-popup-bg);
+ padding: 0 0;
+ overflow: hidden;
+ }
+
+ .theme-dropdown.open .theme-choice {
+ display: block;
+ }
+
+ .theme-choice > li {
+ padding: 5px 10px;
+ font-size: 0.8em;
+ user-select: none;
+ cursor: pointer;
+ }
+
- #filter-label, #filter-clear {
++ .theme-choice > li:hover {
+ background: var(--theme-hover);
+ }
+
+ .alert {
+ color: var(--fg);
+ background: var(--theme-hover);
+ border: 1px solid var(--theme-popup-border);
+ }
+ .page-header {
+ border-color: var(--theme-popup-border);
+ }
+ .panel-default > .panel-heading {
+ background: var(--theme-hover);
+ color: var(--fg);
+ border: 1px solid var(--theme-popup-border);
+ }
+ .panel-default > .panel-heading:hover {
+ filter: brightness(90%);
+ }
+ .list-group-item {
+ background: 0%;
+ border: 1px solid var(--theme-popup-border);
+ }
+ .panel, pre, hr {
+ background: var(--bg);
+ border: 1px solid var(--theme-popup-border);
+ }
+
- #filter-label:hover, #filter-clear:hover {
++ #version-filter-selector .checkbox {
++ display: flex;
++ }
++
++ #version-filter {
++ min-width: available;
++ }
++
++ #version-filter li label {
++ padding-right: 0;
++ width: 35%;
++ }
++
++ .version-filter-input {
++ height: 60%;
++ width: 30%;
++ text-align: center;
++ border: none;
++ border-bottom: 1px solid #000000;
++ }
++
++ #filter-label, .filter-clear {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ filter: brightness(95%);
+ }
- #filter-input {
++ #filter-label:hover, .filter-clear:hover {
+ filter: brightness(90%);
+ }
- #filter-input::-webkit-input-placeholder,
- #filter-input::-moz-placeholder {
++ .filter-input {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ }
+
- <div class="col-12 col-md-4">
++ .filter-input::-webkit-input-placeholder,
++ .filter-input::-moz-placeholder {
+ color: var(--searchbar-fg);
+ opacity: 30%;
+ }
+
+ :not(pre) > code {
+ color: var(--inline-code-color);
+ background-color: var(--inline-code-bg);
+ }
+ html {
+ scrollbar-color: var(--scrollbar) var(--bg);
+ }
+ body {
+ background: var(--bg);
+ color: var(--fg);
+ }
+
+ </style>
+</head>
+<body ng-app="clippy" ng-controller="lintList">
+ <div theme-dropdown class="theme-dropdown">
+ <div id="theme-icon" class="theme-icon">🖌</div>
+ <ul id="theme-menu" class="theme-choice">
+ <li id="{{id}}" ng-repeat="(id, name) in themes" ng-click="selectTheme(id)">{{name}}</li>
+ </ul>
+ </div>
+
+ <div class="container">
+ <div class="page-header">
+ <h1>Clippy Lints</h1>
+ </div>
+
+ <noscript>
+ <div class="alert alert-danger" role="alert">
+ Sorry, this site only works with JavaScript! :(
+ </div>
+ </noscript>
+
+ <div ng-cloak>
+
+ <div class="alert alert-info" role="alert" ng-if="loading">
+ Loading…
+ </div>
+ <div class="alert alert-danger" role="alert" ng-if="error">
+ Error loading lints!
+ </div>
+
+ <div class="panel panel-default" ng-show="data">
+ <div class="panel-body row">
- <div class="col-12 col-md-8 search-control">
++ <div id="upper-filters" class="col-12 col-md-4">
+ <div class="btn-group" filter-dropdown>
+ <button type="button" class="btn btn-default dropdown-toggle">
+ Lint levels <span class="badge">{{selectedValuesCount(levels)}}</span> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <li class="checkbox">
+ <label ng-click="toggleLevels(true)">
+ <input type="checkbox" class="invisible" />
+ All
+ </label>
+ </li>
+ <li class="checkbox">
+ <label ng-click="toggleLevels(false)">
+ <input type="checkbox" class="invisible" />
+ None
+ </label>
+ </li>
+ <li role="separator" class="divider"></li>
+ <li class="checkbox" ng-repeat="(level, enabled) in levels">
+ <label class="text-capitalize">
+ <input type="checkbox" ng-model="levels[level]" />
+ {{level}}
+ </label>
+ </li>
+ </ul>
+ </div>
+ <div class="btn-group" filter-dropdown>
+ <button type="button" class="btn btn-default dropdown-toggle">
+ Lint groups <span class="badge">{{selectedValuesCount(groups)}}</span> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <li class="checkbox">
+ <label ng-click="toggleGroups(true)">
+ <input type="checkbox" class="invisible" />
+ All
+ </label>
+ </li>
+ <li class="checkbox">
+ <label ng-click="toggleGroups(false)">
+ <input type="checkbox" class="invisible" />
+ None
+ </label>
+ </li>
+ <li role="separator" class="divider"></li>
+ <li class="checkbox" ng-repeat="(group, enabled) in groups">
+ <label class="text-capitalize">
+ <input type="checkbox" ng-model="groups[group]" />
+ {{group}}
+ </label>
+ </li>
+ </ul>
+ </div>
++ <div id="version-filter">
++ <div class="btn-group" filter-dropdown>
++ <button type="button" class="btn btn-default dropdown-toggle">
++ Version
++ <span id="version-filter-count" class="badge">
++ {{versionFilterCount(versionFilters)}}
++ </span>
++ <span class="caret"></span>
++ </button>
++ <ul id="version-filter-selector" class="dropdown-menu">
++ <li class="checkbox">
++ <label ng-click="clearVersionFilters()">
++ <input type="checkbox" class="invisible" />
++ Clear filters
++ </label>
++ </li>
++ <li role="separator" class="divider"></li>
++ <li class="checkbox" ng-repeat="(filter, vars) in versionFilters">
++ <label ng-attr-for="filter-{filter}">{{filter}}</label>
++ <span>1.</span>
++ <input type="number"
++ min="29"
++ ng-attr-id="filter-{filter}"
++ class="version-filter-input form-control filter-input"
++ maxlength="2"
++ ng-model="versionFilters[filter].minorVersion"
++ ng-model-options="{debounce: 50}"
++ ng-change="updateVersionFilters()" />
++ <span>.0</span>
++ </li>
++ </ul>
++ </div>
++ </div>
++
+ </div>
- <label class="input-group-addon" id="filter-label" for="filter-input">Filter:</label>
- <input type="text" class="form-control" placeholder="Keywords or search string" id="filter-input" ng-model="search" ng-model-options="{debounce: 50}"/>
++ <div class="col-12 col-md-7 search-control">
+ <div class="input-group">
- <button id="filter-clear" class="btn" type="button" ng-click="search = ''">
++ <label class="input-group-addon" id="filter-label" for="search-input">Filter:</label>
++ <input type="text" class="form-control filter-input" placeholder="Keywords or search string" id="search-input" ng-model="search" ng-model-options="{debounce: 50}"/>
+ <span class="input-group-btn">
- <article class="panel panel-default" id="{{lint.id}}" ng-repeat="lint in data | filter:bySearch | filter:byGroups | filter:byLevels">
++ <button class="filter-clear btn" type="button" ng-click="search = ''">
+ Clear
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- The order of the filters should be from most likely to remove a lint to least likely to improve performance. -->
- <script>
- (function () {
- var md = window.markdownit({
- html: true,
- linkify: true,
- typographer: true,
- highlight: function (str, lang) {
- if (lang && hljs.getLanguage(lang)) {
- try {
- return '<pre class="hljs"><code>' +
- hljs.highlight(lang, str, true).value +
- '</code></pre>';
- } catch (__) {}
- }
-
- return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
- }
- });
-
- function scrollToLint(lintId) {
- var target = document.getElementById(lintId);
- if (!target) {
- return;
- }
- target.scrollIntoView();
- }
-
- function scrollToLintByURL($scope) {
- var removeListener = $scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
- scrollToLint(window.location.hash.slice(1));
- removeListener();
- });
- }
-
- function selectGroup($scope, selectedGroup) {
- var groups = $scope.groups;
- for (var group in groups) {
- if (groups.hasOwnProperty(group)) {
- if (group === selectedGroup) {
- groups[group] = true;
- } else {
- groups[group] = false;
- }
- }
- }
- }
-
- angular.module("clippy", [])
- .filter('markdown', function ($sce) {
- return function (text) {
- return $sce.trustAsHtml(
- md.render(text || '')
- // Oh deer, what a hack :O
- .replace('<table', '<table class="table"')
- );
- };
- })
- .directive('themeDropdown', function ($document) {
- return {
- restrict: 'A',
- link: function ($scope, $element, $attr) {
- $element.bind('click', function () {
- $element.toggleClass('open');
- $element.addClass('open-recent');
- });
-
- $document.bind('click', function () {
- if (!$element.hasClass('open-recent')) {
- $element.removeClass('open');
- }
- $element.removeClass('open-recent');
- })
- }
- }
- })
- .directive('filterDropdown', function ($document) {
- return {
- restrict: 'A',
- link: function ($scope, $element, $attr) {
- $element.bind('click', function (event) {
- if (event.target.closest('button')) {
- $element.toggleClass('open');
- } else {
- $element.addClass('open');
- }
- $element.addClass('open-recent');
- });
-
- $document.bind('click', function () {
- if (!$element.hasClass('open-recent')) {
- $element.removeClass('open');
- }
- $element.removeClass('open-recent');
- })
- }
- }
- })
- .directive('onFinishRender', function ($timeout) {
- return {
- restrict: 'A',
- link: function (scope, element, attr) {
- if (scope.$last === true) {
- $timeout(function () {
- scope.$emit(attr.onFinishRender);
- });
- }
- }
- };
- })
- .controller("lintList", function ($scope, $http, $timeout) {
- // Level filter
- var LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true};
- $scope.levels = LEVEL_FILTERS_DEFAULT;
- $scope.byLevels = function (lint) {
- return $scope.levels[lint.level];
- };
-
- var GROUPS_FILTER_DEFAULT = {
- cargo: true,
- complexity: true,
- correctness: true,
- deprecated: false,
- nursery: true,
- pedantic: true,
- perf: true,
- restriction: true,
- style: true,
- suspicious: true,
- };
- $scope.groups = GROUPS_FILTER_DEFAULT;
- const THEMES_DEFAULT = {
- light: "Light",
- rust: "Rust",
- coal: "Coal",
- navy: "Navy",
- ayu: "Ayu"
- };
- $scope.themes = THEMES_DEFAULT;
-
- $scope.selectTheme = function (theme) {
- setTheme(theme, true);
- }
-
- $scope.toggleLevels = function (value) {
- const levels = $scope.levels;
- for (const key in levels) {
- if (levels.hasOwnProperty(key)) {
- levels[key] = value;
- }
- }
- };
- $scope.toggleGroups = function (value) {
- const groups = $scope.groups;
- for (const key in groups) {
- if (groups.hasOwnProperty(key)) {
- groups[key] = value;
- }
- }
- };
- $scope.selectedValuesCount = function (obj) {
- return Object.values(obj).filter(x => x).length;
- }
- $scope.byGroups = function (lint) {
- return $scope.groups[lint.group];
- };
-
- $scope.bySearch = function (lint, index, array) {
- let searchStr = $scope.search;
- // It can be `null` I haven't missed this value
- if (searchStr == null || searchStr.length < 3) {
- return true;
- }
- searchStr = searchStr.toLowerCase();
-
- // Search by id
- if (lint.id.indexOf(searchStr.replace("-", "_")) !== -1) {
- return true;
- }
-
- // Search the description
- // The use of `for`-loops instead of `foreach` enables us to return early
- let terms = searchStr.split(" ");
- let docsLowerCase = lint.docs.toLowerCase();
- for (index = 0; index < terms.length; index++) {
- // This is more likely and will therefor be checked first
- if (docsLowerCase.indexOf(terms[index]) !== -1) {
- continue;
- }
-
- if (lint.id.indexOf(terms[index]) !== -1) {
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-
- // Get data
- $scope.open = {};
- $scope.loading = true;
- // This will be used to jump into the source code of the version that this documentation is for.
- $scope.docVersion = window.location.pathname.split('/')[2] || "master";
-
- if (window.location.hash.length > 1) {
- $scope.search = window.location.hash.slice(1);
- $scope.open[window.location.hash.slice(1)] = true;
- scrollToLintByURL($scope);
- }
-
- $http.get('./lints.json')
- .success(function (data) {
- $scope.data = data;
- $scope.loading = false;
-
- var selectedGroup = getQueryVariable("sel");
- if (selectedGroup) {
- selectGroup($scope, selectedGroup.toLowerCase());
- }
-
- scrollToLintByURL($scope);
-
- setTimeout(function () {
- var el = document.getElementById('filter-input');
- if (el) { el.focus() }
- }, 0);
- })
- .error(function (data) {
- $scope.error = data;
- $scope.loading = false;
- });
-
- window.addEventListener('hashchange', function () {
- // trigger re-render
- $timeout(function () {
- $scope.levels = LEVEL_FILTERS_DEFAULT;
- $scope.search = window.location.hash.slice(1);
- $scope.open[window.location.hash.slice(1)] = true;
-
- scrollToLintByURL($scope);
- });
- return true;
- }, false);
- });
- })();
-
- function getQueryVariable(variable) {
- var query = window.location.search.substring(1);
- var vars = query.split('&');
- for (var i = 0; i < vars.length; i++) {
- var pair = vars[i].split('=');
- if (decodeURIComponent(pair[0]) == variable) {
- return decodeURIComponent(pair[1]);
- }
- }
- }
-
- function setTheme(theme, store) {
- let enableHighlight = false;
- let enableNight = false;
- let enableAyu = false;
-
- if (theme == "ayu") {
- enableAyu = true;
- } else if (theme == "coal" || theme == "navy") {
- enableNight = true;
- } else if (theme == "rust") {
- enableHighlight = true;
- } else {
- enableHighlight = true;
- // this makes sure that an unknown theme request gets set to a known one
- theme = "light";
- }
- document.getElementsByTagName("body")[0].className = theme;
-
- document.getElementById("styleHighlight").disabled = !enableHighlight;
- document.getElementById("styleNight").disabled = !enableNight;
- document.getElementById("styleAyu").disabled = !enableAyu;
-
- if (store) {
- try {
- localStorage.setItem('clippy-lint-list-theme', theme);
- } catch (e) { }
- }
- }
-
- // loading the theme after the initial load
- setTheme(localStorage.getItem('clippy-lint-list-theme'), false);
- </script>
++ <article class="panel panel-default" id="{{lint.id}}" ng-repeat="lint in data | filter:bySearch | filter:byGroups | filter:byLevels | filter:byVersion">
+ <header class="panel-heading" ng-click="open[lint.id] = !open[lint.id]">
+ <h2 class="panel-title">
+ <div class="panel-title-name">
+ <span>{{lint.id}}</span>
+ <a href="#{{lint.id}}" class="anchor label label-default" ng-click="open[lint.id] = true; $event.stopPropagation()">¶</a>
++ <a href="" id="clipboard-{{lint.id}}" class="anchor label label-default" ng-click="copyToClipboard(lint); $event.stopPropagation()">
++ 📋
++ </a>
+ </div>
+
+ <div class="panel-title-addons">
+ <span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span>
+
+ <span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span>
+
+
+ <span class="label label-doc-folding" ng-show="open[lint.id]">−</span>
+ <span class="label label-doc-folding" ng-hide="open[lint.id]">+</span>
+ </div>
+ </h2>
+ </header>
+
+ <div class="list-group lint-docs" ng-if="open[lint.id]" ng-class="{collapse: true, in: open[lint.id]}">
+ <div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
+ <div class="lint-additional-info-container">
+ <!-- Applicability -->
+ <div class="lint-additional-info-item">
+ <span> Applicability: </span>
+ <span class="label label-default label-applicability">{{lint.applicability.applicability}}</span>
+ <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.Applicability.html#variants">(?)</a>
+ </div>
+ <!-- Clippy version -->
+ <div class="lint-additional-info-item">
+ <span>{{lint.group == "deprecated" ? "Deprecated" : "Added"}} in: </span>
+ <span class="label label-default label-version">{{lint.version}}</span>
+ </div>
+ <!-- Open related issues -->
+ <div class="lint-additional-info-item">
+ <a href="https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+{{lint.id}}">Related Issues</a>
+ </div>
+ <!-- Jump to source -->
+ <div class="lint-additional-info-item">
+ <a href="https://github.com/rust-lang/rust-clippy/blob/{{docVersion}}/clippy_lints/{{lint.id_span.path}}#L{{lint.id_span.line}}">View Source</a>
+ </div>
+ </div>
+ </div>
+ </article>
+ </div>
+ </div>
+
+ <a href="https://github.com/rust-lang/rust-clippy">
+ <img style="position: absolute; top: 0; right: 0; border: 0; clip-path: polygon(0% 0%, 100% 0%, 100% 100%);" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
+ </a>
+
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/highlight.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/languages/rust.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.12/angular.min.js"></script>
++ <script src="script.js"></script>
+</body>
+</html>
--- /dev/null
--- /dev/null
++(function () {
++ var md = window.markdownit({
++ html: true,
++ linkify: true,
++ typographer: true,
++ highlight: function (str, lang) {
++ if (lang && hljs.getLanguage(lang)) {
++ try {
++ return '<pre class="hljs"><code>' +
++ hljs.highlight(lang, str, true).value +
++ '</code></pre>';
++ } catch (__) {}
++ }
++
++ return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
++ }
++ });
++
++ function scrollToLint(lintId) {
++ var target = document.getElementById(lintId);
++ if (!target) {
++ return;
++ }
++ target.scrollIntoView();
++ }
++
++ function scrollToLintByURL($scope) {
++ var removeListener = $scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
++ scrollToLint(window.location.hash.slice(1));
++ removeListener();
++ });
++ }
++
++ function selectGroup($scope, selectedGroup) {
++ var groups = $scope.groups;
++ for (var group in groups) {
++ if (groups.hasOwnProperty(group)) {
++ if (group === selectedGroup) {
++ groups[group] = true;
++ } else {
++ groups[group] = false;
++ }
++ }
++ }
++ }
++
++ angular.module("clippy", [])
++ .filter('markdown', function ($sce) {
++ return function (text) {
++ return $sce.trustAsHtml(
++ md.render(text || '')
++ // Oh deer, what a hack :O
++ .replace('<table', '<table class="table"')
++ );
++ };
++ })
++ .directive('themeDropdown', function ($document) {
++ return {
++ restrict: 'A',
++ link: function ($scope, $element, $attr) {
++ $element.bind('click', function () {
++ $element.toggleClass('open');
++ $element.addClass('open-recent');
++ });
++
++ $document.bind('click', function () {
++ if (!$element.hasClass('open-recent')) {
++ $element.removeClass('open');
++ }
++ $element.removeClass('open-recent');
++ })
++ }
++ }
++ })
++ .directive('filterDropdown', function ($document) {
++ return {
++ restrict: 'A',
++ link: function ($scope, $element, $attr) {
++ $element.bind('click', function (event) {
++ if (event.target.closest('button')) {
++ $element.toggleClass('open');
++ } else {
++ $element.addClass('open');
++ }
++ $element.addClass('open-recent');
++ });
++
++ $document.bind('click', function () {
++ if (!$element.hasClass('open-recent')) {
++ $element.removeClass('open');
++ }
++ $element.removeClass('open-recent');
++ })
++ }
++ }
++ })
++ .directive('onFinishRender', function ($timeout) {
++ return {
++ restrict: 'A',
++ link: function (scope, element, attr) {
++ if (scope.$last === true) {
++ $timeout(function () {
++ scope.$emit(attr.onFinishRender);
++ });
++ }
++ }
++ };
++ })
++ .controller("lintList", function ($scope, $http, $timeout) {
++ // Level filter
++ var LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true};
++ $scope.levels = LEVEL_FILTERS_DEFAULT;
++ $scope.byLevels = function (lint) {
++ return $scope.levels[lint.level];
++ };
++
++ var GROUPS_FILTER_DEFAULT = {
++ cargo: true,
++ complexity: true,
++ correctness: true,
++ deprecated: false,
++ nursery: true,
++ pedantic: true,
++ perf: true,
++ restriction: true,
++ style: true,
++ suspicious: true,
++ };
++ $scope.groups = GROUPS_FILTER_DEFAULT;
++ const THEMES_DEFAULT = {
++ light: "Light",
++ rust: "Rust",
++ coal: "Coal",
++ navy: "Navy",
++ ayu: "Ayu"
++ };
++ $scope.themes = THEMES_DEFAULT;
++
++ $scope.versionFilters = {
++ "≥": {enabled: false, minorVersion: null },
++ "≤": {enabled: false, minorVersion: null },
++ "=": {enabled: false, minorVersion: null },
++ };
++
++ $scope.selectTheme = function (theme) {
++ setTheme(theme, true);
++ }
++
++ $scope.toggleLevels = function (value) {
++ const levels = $scope.levels;
++ for (const key in levels) {
++ if (levels.hasOwnProperty(key)) {
++ levels[key] = value;
++ }
++ }
++ };
++
++ $scope.toggleGroups = function (value) {
++ const groups = $scope.groups;
++ for (const key in groups) {
++ if (groups.hasOwnProperty(key)) {
++ groups[key] = value;
++ }
++ }
++ };
++
++ $scope.selectedValuesCount = function (obj) {
++ return Object.values(obj).filter(x => x).length;
++ }
++
++ $scope.clearVersionFilters = function () {
++ for (let filter in $scope.versionFilters) {
++ $scope.versionFilters[filter] = { enabled: false, minorVersion: null };
++ }
++ }
++
++ $scope.versionFilterCount = function(obj) {
++ return Object.values(obj).filter(x => x.enabled).length;
++ }
++
++ $scope.updateVersionFilters = function() {
++ for (const filter in $scope.versionFilters) {
++ let minorVersion = $scope.versionFilters[filter].minorVersion;
++
++ // 1.29.0 and greater
++ if (minorVersion && minorVersion > 28) {
++ $scope.versionFilters[filter].enabled = true;
++ continue;
++ }
++
++ $scope.versionFilters[filter].enabled = false;
++ }
++ }
++
++ $scope.byVersion = function(lint) {
++ let filters = $scope.versionFilters;
++ for (const filter in filters) {
++ if (filters[filter].enabled) {
++ let minorVersion = filters[filter].minorVersion;
++
++ // Strip the "pre " prefix for pre 1.29.0 lints
++ let lintVersion = lint.version.startsWith("pre ") ? lint.version.substring(4, lint.version.length) : lint.version;
++ let lintMinorVersion = lintVersion.substring(2, 4);
++
++ switch (filter) {
++ // "=" gets the highest priority, since all filters are inclusive
++ case "=":
++ return (lintMinorVersion == minorVersion);
++ case "≥":
++ if (lintMinorVersion < minorVersion) { return false; }
++ break;
++ case "≤":
++ if (lintMinorVersion > minorVersion) { return false; }
++ break;
++ default:
++ return true
++ }
++ }
++ }
++
++ return true;
++ }
++
++ $scope.byGroups = function (lint) {
++ return $scope.groups[lint.group];
++ };
++
++ $scope.bySearch = function (lint, index, array) {
++ let searchStr = $scope.search;
++ // It can be `null` I haven't missed this value
++ if (searchStr == null || searchStr.length < 3) {
++ return true;
++ }
++ searchStr = searchStr.toLowerCase();
++
++ // Search by id
++ if (lint.id.indexOf(searchStr.replace("-", "_")) !== -1) {
++ return true;
++ }
++
++ // Search the description
++ // The use of `for`-loops instead of `foreach` enables us to return early
++ let terms = searchStr.split(" ");
++ let docsLowerCase = lint.docs.toLowerCase();
++ for (index = 0; index < terms.length; index++) {
++ // This is more likely and will therefor be checked first
++ if (docsLowerCase.indexOf(terms[index]) !== -1) {
++ continue;
++ }
++
++ if (lint.id.indexOf(terms[index]) !== -1) {
++ continue;
++ }
++
++ return false;
++ }
++
++ return true;
++ }
++
++ $scope.copyToClipboard = function (lint) {
++ const clipboard = document.getElementById("clipboard-" + lint.id);
++ if (clipboard) {
++ let resetClipboardTimeout = null;
++ let resetClipboardIcon = clipboard.innerHTML;
++
++ function resetClipboard() {
++ resetClipboardTimeout = null;
++ clipboard.innerHTML = resetClipboardIcon;
++ }
++
++ navigator.clipboard.writeText("clippy::" + lint.id);
++
++ clipboard.innerHTML = "✓";
++ if (resetClipboardTimeout !== null) {
++ clearTimeout(resetClipboardTimeout);
++ }
++ resetClipboardTimeout = setTimeout(resetClipboard, 1000);
++ }
++ }
++
++ // Get data
++ $scope.open = {};
++ $scope.loading = true;
++ // This will be used to jump into the source code of the version that this documentation is for.
++ $scope.docVersion = window.location.pathname.split('/')[2] || "master";
++
++ if (window.location.hash.length > 1) {
++ $scope.search = window.location.hash.slice(1);
++ $scope.open[window.location.hash.slice(1)] = true;
++ scrollToLintByURL($scope);
++ }
++
++ $http.get('./lints.json')
++ .success(function (data) {
++ $scope.data = data;
++ $scope.loading = false;
++
++ var selectedGroup = getQueryVariable("sel");
++ if (selectedGroup) {
++ selectGroup($scope, selectedGroup.toLowerCase());
++ }
++
++ scrollToLintByURL($scope);
++
++ setTimeout(function () {
++ var el = document.getElementById('filter-input');
++ if (el) { el.focus() }
++ }, 0);
++ })
++ .error(function (data) {
++ $scope.error = data;
++ $scope.loading = false;
++ });
++
++ window.addEventListener('hashchange', function () {
++ // trigger re-render
++ $timeout(function () {
++ $scope.levels = LEVEL_FILTERS_DEFAULT;
++ $scope.search = window.location.hash.slice(1);
++ $scope.open[window.location.hash.slice(1)] = true;
++
++ scrollToLintByURL($scope);
++ });
++ return true;
++ }, false);
++ });
++})();
++
++function getQueryVariable(variable) {
++ var query = window.location.search.substring(1);
++ var vars = query.split('&');
++ for (var i = 0; i < vars.length; i++) {
++ var pair = vars[i].split('=');
++ if (decodeURIComponent(pair[0]) == variable) {
++ return decodeURIComponent(pair[1]);
++ }
++ }
++}
++
++function setTheme(theme, store) {
++ let enableHighlight = false;
++ let enableNight = false;
++ let enableAyu = false;
++
++ if (theme == "ayu") {
++ enableAyu = true;
++ } else if (theme == "coal" || theme == "navy") {
++ enableNight = true;
++ } else if (theme == "rust") {
++ enableHighlight = true;
++ } else {
++ enableHighlight = true;
++ // this makes sure that an unknown theme request gets set to a known one
++ theme = "light";
++ }
++ document.getElementsByTagName("body")[0].className = theme;
++
++ document.getElementById("styleHighlight").disabled = !enableHighlight;
++ document.getElementById("styleNight").disabled = !enableNight;
++ document.getElementById("styleAyu").disabled = !enableAyu;
++
++ if (store) {
++ try {
++ localStorage.setItem('clippy-lint-list-theme', theme);
++ } catch (e) { }
++ }
++}
++
++// loading the theme after the initial load
++setTheme(localStorage.getItem('clippy-lint-list-theme'), false);