From ed7c2948b3b8861dc479d84b3b29a284a176d4ed Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 12 Oct 2021 08:59:39 +0200 Subject: [PATCH 1/4] Move lint source generator --- Cargo.lock | 2 + crates/ide_db/Cargo.toml | 2 + crates/ide_db/src/lib.rs | 6 + .../src/tests/sourcegen_lint_completions.rs | 167 ++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 crates/ide_db/src/tests/sourcegen_lint_completions.rs diff --git a/Cargo.lock b/Cargo.lock index 68c8536b2f..a80f05aa8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -659,11 +659,13 @@ dependencies = [ "profile", "rayon", "rustc-hash", + "sourcegen", "stdx", "syntax", "test_utils", "text_edit", "tracing", + "xshell", ] [[package]] diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml index e3c60edda1..54179b6fba 100644 --- a/crates/ide_db/Cargo.toml +++ b/crates/ide_db/Cargo.toml @@ -31,4 +31,6 @@ limit = { path = "../limit", version = "0.0.0" } [dev-dependencies] test_utils = { path = "../test_utils" } +sourcegen = { path = "../sourcegen" } +xshell = "0.1" expect-test = "1.2.0-pre.1" diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 384b66d81c..de7b889d71 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -3,6 +3,7 @@ //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. mod apply_change; + pub mod assists; pub mod label; pub mod line_index; @@ -161,3 +162,8 @@ pub enum SymbolKind { ValueParam, Variant, } + +#[cfg(test)] +mod tests { + mod sourcegen_lint_completions; +} diff --git a/crates/ide_db/src/tests/sourcegen_lint_completions.rs b/crates/ide_db/src/tests/sourcegen_lint_completions.rs new file mode 100644 index 0000000000..ed3ac7203b --- /dev/null +++ b/crates/ide_db/src/tests/sourcegen_lint_completions.rs @@ -0,0 +1,167 @@ +//! Generates descriptors structure for unstable feature from Unstable Book +use std::{borrow::Cow, fs, path::Path}; + +use stdx::format_to; +use test_utils::project_root; +use xshell::cmd; + +/// This clones rustc repo, and so is not worth to keep up-to-date. We update +/// manually by un-ignoring the test from time to time. +#[test] +#[ignore] +fn sourcegen_lint_completions() { + let rust_repo = project_root().join("./target/rust"); + if !rust_repo.exists() { + cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap(); + } + + let mut contents = r" +pub struct Lint { + pub label: &'static str, + pub description: &'static str, +} +" + .to_string(); + generate_lint_descriptor(&mut contents); + contents.push('\n'); + + generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src")); + contents.push('\n'); + + let lints_json = project_root().join("./target/clippy_lints.json"); + cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}") + .run() + .unwrap(); + generate_descriptor_clippy(&mut contents, &lints_json); + + let contents = + sourcegen::add_preamble("sourcegen_lint_completions", sourcegen::reformat(contents)); + + let destination = project_root().join("crates/ide_db/src/helpers/generated_lints.rs"); + sourcegen::ensure_file_contents(destination.as_path(), &contents); +} + +fn generate_lint_descriptor(buf: &mut String) { + let stdout = cmd!("rustc -W help").read().unwrap(); + let start_lints = stdout.find("---- ------- -------").unwrap(); + let start_lint_groups = stdout.find("---- ---------").unwrap(); + let end_lints = stdout.find("Lint groups provided by rustc:").unwrap(); + let end_lint_groups = stdout + .find("Lint tools like Clippy can provide additional lints and lint groups.") + .unwrap(); + buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#); + buf.push('\n'); + let mut lints = stdout[start_lints..end_lints] + .lines() + .skip(1) + .filter(|l| !l.is_empty()) + .map(|line| { + let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap(); + let (_default_level, description) = + rest.trim().split_once(char::is_whitespace).unwrap(); + (name.trim(), Cow::Borrowed(description.trim())) + }) + .collect::>(); + lints.extend( + stdout[start_lint_groups..end_lint_groups].lines().skip(1).filter(|l| !l.is_empty()).map( + |line| { + let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap(); + (name.trim(), format!("lint group for: {}", lints.trim()).into()) + }, + ), + ); + + lints.sort_by(|(ident, _), (ident2, _)| ident.cmp(ident2)); + lints.into_iter().for_each(|(name, description)| { + push_lint_completion(buf, &name.replace("-", "_"), &description) + }); + buf.push_str("];\n"); +} + +fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) { + let mut features = ["language-features", "library-features"] + .iter() + .flat_map(|it| sourcegen::list_files(&src_dir.join(it))) + .filter(|path| { + // Get all `.md ` files + path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md" + }) + .map(|path| { + let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_"); + let doc = fs::read_to_string(path).unwrap(); + (feature_ident, doc) + }) + .collect::>(); + features.sort_by(|(feature_ident, _), (feature_ident2, _)| feature_ident.cmp(feature_ident2)); + + buf.push_str(r#"pub const FEATURES: &[Lint] = &["#); + for (feature_ident, doc) in features.into_iter() { + push_lint_completion(buf, &feature_ident, &doc) + } + buf.push('\n'); + buf.push_str("];\n"); +} + +#[derive(Default)] +struct ClippyLint { + help: String, + id: String, +} + +fn unescape(s: &str) -> String { + s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "") +} + +fn generate_descriptor_clippy(buf: &mut String, path: &Path) { + let file_content = std::fs::read_to_string(path).unwrap(); + let mut clippy_lints: Vec = Vec::new(); + + for line in file_content.lines().map(|line| line.trim()) { + if line.starts_with(r#""id":"#) { + let clippy_lint = ClippyLint { + id: line + .strip_prefix(r#""id": ""#) + .expect("should be prefixed by id") + .strip_suffix(r#"","#) + .expect("should be suffixed by comma") + .into(), + help: String::new(), + }; + clippy_lints.push(clippy_lint) + } else if line.starts_with(r#""What it does":"#) { + // Typical line to strip: "What is doest": "Here is my useful content", + let prefix_to_strip = r#""What it does": ""#; + let suffix_to_strip = r#"","#; + + let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist"); + clippy_lint.help = line + .strip_prefix(prefix_to_strip) + .expect("should be prefixed by what it does") + .strip_suffix(suffix_to_strip) + .map(unescape) + .expect("should be suffixed by comma"); + } + } + clippy_lints.sort_by(|lint, lint2| lint.id.cmp(&lint2.id)); + + buf.push_str(r#"pub const CLIPPY_LINTS: &[Lint] = &["#); + buf.push('\n'); + for clippy_lint in clippy_lints.into_iter() { + let lint_ident = format!("clippy::{}", clippy_lint.id); + let doc = clippy_lint.help; + push_lint_completion(buf, &lint_ident, &doc); + } + buf.push_str("];\n"); +} + +fn push_lint_completion(buf: &mut String, label: &str, description: &str) { + format_to!( + buf, + r###" Lint {{ + label: "{}", + description: r##"{}"## + }},"###, + label, + description + ); +} From d71a4f40d9f82bf4da8012568ddad561613476c7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 12 Oct 2021 09:27:17 +0200 Subject: [PATCH 2/4] Regenerate lints --- crates/ide_db/src/helpers/generated_lints.rs | 592 +++++++++++++----- crates/ide_db/src/lib.rs | 2 +- .../src/tests/sourcegen_lint_completions.rs | 167 ----- .../src/tests/sourcegen_lints.rs} | 39 +- 4 files changed, 449 insertions(+), 351 deletions(-) delete mode 100644 crates/ide_db/src/tests/sourcegen_lint_completions.rs rename crates/{ide_completion/src/tests/sourcegen.rs => ide_db/src/tests/sourcegen_lints.rs} (84%) diff --git a/crates/ide_db/src/helpers/generated_lints.rs b/crates/ide_db/src/helpers/generated_lints.rs index 4ec63a234b..5abd6d360e 100644 --- a/crates/ide_db/src/helpers/generated_lints.rs +++ b/crates/ide_db/src/helpers/generated_lints.rs @@ -1,4 +1,4 @@ -//! Generated by `sourcegen_lint_completions`, do not edit by hand. +//! Generated by `sourcegen_lints`, do not edit by hand. pub struct Lint { pub label: &'static str, @@ -12,7 +12,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[ Lint { label: "ambiguous_associated_items", description: r##"ambiguous associated items"## }, Lint { label: "anonymous_parameters", description: r##"detects anonymous parameters"## }, Lint { label: "arithmetic_overflow", description: r##"arithmetic operation overflows"## }, - Lint { label: "array_into_iter", description: r##"detects calling `into_iter` on arrays"## }, + Lint { + label: "array_into_iter", + description: r##"detects calling `into_iter` on arrays in Rust 2015 and 2018"##, + }, Lint { label: "asm_sub_register", description: r##"using only a subset of a register for inline asm inputs"##, @@ -69,11 +72,11 @@ pub const DEFAULT_LINTS: &[Lint] = &[ label: "deref_nullptr", description: r##"detects when an null pointer is dereferenced"##, }, - Lint { - label: "disjoint_capture_drop_reorder", - description: r##"Drop reorder because of `capture_disjoint_fields`"##, - }, Lint { label: "drop_bounds", description: r##"bounds of the form `T: Drop` are useless"## }, + Lint { + label: "dyn_drop", + description: r##"trait objects of the form `dyn Drop` are useless"##, + }, Lint { label: "elided_lifetimes_in_paths", description: r##"hidden lifetime parameters in types are deprecated"##, @@ -97,7 +100,7 @@ pub const DEFAULT_LINTS: &[Lint] = &[ }, Lint { label: "future_incompatible", - description: r##"lint group for: keyword-idents, anonymous-parameters, forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, tyvar-behind-raw-pointer, absolute-paths-not-starting-with-crate, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, array-into-iter"##, + description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions"##, }, Lint { label: "ill_formed_attribute_input", @@ -132,13 +135,17 @@ pub const DEFAULT_LINTS: &[Lint] = &[ label: "inline_no_sanitize", description: r##"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"##, }, + Lint { + label: "invalid_doc_attributes", + description: r##"detects invalid `#[doc(...)]` attributes"##, + }, Lint { label: "invalid_type_param_default", description: r##"type parameter default erroneously allowed in invalid location"##, }, Lint { label: "invalid_value", - description: r##"an invalid value is being created (such as a NULL reference)"##, + description: r##"an invalid value is being created (such as a null reference)"##, }, Lint { label: "irrefutable_let_patterns", @@ -209,7 +216,7 @@ pub const DEFAULT_LINTS: &[Lint] = &[ description: r##"types, variants, traits and type parameters should have camel case names"##, }, Lint { - label: "non_fmt_panic", + label: "non_fmt_panics", description: r##"detect single-argument panic!() invocations in which the argument is not a format string"##, }, Lint { @@ -236,10 +243,6 @@ pub const DEFAULT_LINTS: &[Lint] = &[ label: "noop_method_call", description: r##"detects the use of well-known noop methods"##, }, - Lint { - label: "or_patterns_back_compat", - description: r##"detects usage of old versions of or-patterns"##, - }, Lint { label: "order_dependent_trait_objects", description: r##"trait-object types were treated as different depending on marker-trait order"##, @@ -290,6 +293,26 @@ pub const DEFAULT_LINTS: &[Lint] = &[ label: "rust_2018_idioms", description: r##"lint group for: bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements"##, }, + Lint { + label: "rust_2021_compatibility", + description: r##"lint group for: ellipsis-inclusive-range-patterns, bare-trait-objects, rust-2021-incompatible-closure-captures, rust-2021-incompatible-or-patterns, rust-2021-prelude-collisions, rust-2021-prefixes-incompatible-syntax, array-into-iter, non-fmt-panics"##, + }, + Lint { + label: "rust_2021_incompatible_closure_captures", + description: r##"detects closures affected by Rust 2021 changes"##, + }, + Lint { + label: "rust_2021_incompatible_or_patterns", + description: r##"detects usage of old versions of or-patterns"##, + }, + Lint { + label: "rust_2021_prefixes_incompatible_syntax", + description: r##"identifiers that will be parsed as a prefix in Rust 2021"##, + }, + Lint { + label: "rust_2021_prelude_collisions", + description: r##"detects the usage of trait methods which are ambiguous with traits added to the prelude in future editions"##, + }, Lint { label: "semicolon_in_expressions_from_macros", description: r##"trailing semicolon in macro body used as expression"##, @@ -375,6 +398,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[ label: "unstable_name_collisions", description: r##"detects name collision with an existing but unstable method"##, }, + Lint { + label: "unsupported_calling_conventions", + description: r##"use of unsupported calling convention"##, + }, Lint { label: "unsupported_naked_functions", description: r##"unsupported naked function definitions"##, @@ -722,47 +749,6 @@ detail of the `global_allocator` feature not intended for use outside the compiler. ------------------------ -"##, - }, - Lint { - label: "arbitrary_enum_discriminant", - description: r##"# `arbitrary_enum_discriminant` - -The tracking issue for this feature is: [#60553] - -[#60553]: https://github.com/rust-lang/rust/issues/60553 - ------------------------- - -The `arbitrary_enum_discriminant` feature permits tuple-like and -struct-like enum variants with `#[repr()]` to have explicit discriminants. - -## Examples - -```rust -#![feature(arbitrary_enum_discriminant)] - -#[allow(dead_code)] -#[repr(u8)] -enum Enum { - Unit = 3, - Tuple(u16) = 2, - Struct { - a: u8, - b: u16, - } = 1, -} - -impl Enum { - fn tag(&self) -> u8 { - unsafe { *(self as *const Self as *const u8) } - } -} - -assert_eq!(3, Enum::Unit.tag()); -assert_eq!(2, Enum::Tuple(5).tag()); -assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag()); -``` "##, }, Lint { @@ -800,6 +786,7 @@ Inline assembly is currently supported on the following architectures: - MIPS32r2 and MIPS64r2 - wasm32 - BPF +- SPIR-V ## Basic usage @@ -957,8 +944,7 @@ As you can see, this assembly fragment will still work correctly if `a` and `b` Some instructions require that the operands be in a specific register. Therefore, Rust inline assembly provides some more specific constraint specifiers. -While `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` -among others can be addressed by their name. +While `reg` is generally available on any architecture, explicit registers are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` among others can be addressed by their name. ```rust,allow_fail,no_run #![feature(asm)] @@ -968,11 +954,9 @@ unsafe { } ``` -In this example we call the `out` instruction to output the content of the `cmd` variable -to port `0x64`. Since the `out` instruction only accepts `eax` (and its sub registers) as operand -we had to use the `eax` constraint specifier. +In this example we call the `out` instruction to output the content of the `cmd` variable to port `0x64`. Since the `out` instruction only accepts `eax` (and its sub registers) as operand we had to use the `eax` constraint specifier. -Note that unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types. +> **Note**: unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types. Consider this example which uses the x86 `mul` instruction: @@ -1006,11 +990,9 @@ The higher 64 bits are stored in `rdx` from which we fill the variable `hi`. ## Clobbered registers In many cases inline assembly will modify state that is not needed as an output. -Usually this is either because we have to use a scratch register in the assembly, -or instructions modify state that we don't need to further examine. +Usually this is either because we have to use a scratch register in the assembly or because instructions modify state that we don't need to further examine. This state is generally referred to as being "clobbered". -We need to tell the compiler about this since it may need to save and restore this state -around the inline assembly block. +We need to tell the compiler about this since it may need to save and restore this state around the inline assembly block. ```rust,allow_fail #![feature(asm)] @@ -1059,44 +1041,40 @@ unsafe { assert_eq!(x, 4 * 6); ``` -## Symbol operands +## Symbol operands and ABI clobbers A special operand type, `sym`, allows you to use the symbol name of a `fn` or `static` in inline assembly code. This allows you to call a function or access a global variable without needing to keep its address in a register. ```rust,allow_fail #![feature(asm)] -extern "C" fn foo(arg: i32) { +extern "C" fn foo(arg: i32) -> i32 { println!("arg = {}", arg); + arg * 2 } -fn call_foo(arg: i32) { +fn call_foo(arg: i32) -> i32 { unsafe { + let result; asm!( "call {}", sym foo, - // 1st argument in rdi, which is caller-saved - inout("rdi") arg => _, - // All caller-saved registers must be marked as clobbered - out("rax") _, out("rcx") _, out("rdx") _, out("rsi") _, - out("r8") _, out("r9") _, out("r10") _, out("r11") _, - out("xmm0") _, out("xmm1") _, out("xmm2") _, out("xmm3") _, - out("xmm4") _, out("xmm5") _, out("xmm6") _, out("xmm7") _, - out("xmm8") _, out("xmm9") _, out("xmm10") _, out("xmm11") _, - out("xmm12") _, out("xmm13") _, out("xmm14") _, out("xmm15") _, - // Also mark AVX-512 registers as clobbered. This is accepted by the - // compiler even if AVX-512 is not enabled on the current target. - out("xmm16") _, out("xmm17") _, out("xmm18") _, out("xmm19") _, - out("xmm20") _, out("xmm21") _, out("xmm22") _, out("xmm23") _, - out("xmm24") _, out("xmm25") _, out("xmm26") _, out("xmm27") _, - out("xmm28") _, out("xmm29") _, out("xmm30") _, out("xmm31") _, - ) + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + ); + result } } ``` -Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: -the compiler will automatically insert the appropriate mangled symbol name into the assembly code. +Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: the compiler will automatically insert the appropriate mangled symbol name into the assembly code. + +By default, `asm!` assumes that any register not specified as an output will have its contents preserved by the assembly code. The [`clobber_abi`](#abi-clobbers) argument to `asm!` tells the compiler to automatically insert the necessary clobber operands according to the given calling convention ABI: any register which is not fully preserved in that ABI will be treated as clobbered. ## Register template modifiers @@ -1127,9 +1105,8 @@ If you use a smaller data type (e.g. `u16`) with an operand and forget the use t ## Memory address operands Sometimes assembly instructions require operands passed via memory addresses/memory locations. -You have to manually use the memory address syntax specified by the respectively architectures. -For example, in x86/x86_64 and intel assembly syntax, you should wrap inputs/outputs in `[]` -to indicate they are memory operands: +You have to manually use the memory address syntax specified by the target architecture. +For example, on x86/x86_64 using intel assembly syntax, you should wrap inputs/outputs in `[]` to indicate they are memory operands: ```rust,allow_fail #![feature(asm, llvm_asm)] @@ -1145,9 +1122,15 @@ unsafe { ## Labels -The compiler is allowed to instantiate multiple copies an `asm!` block, for example when the function containing it is inlined in multiple places. As a consequence, you should only use GNU assembler [local labels] inside inline assembly code. Defining symbols in assembly code may lead to assembler and/or linker errors due to duplicate symbol definitions. +Any reuse of a named label, local or otherwise, can result in a assembler or linker error or may cause other strange behavior. Reuse of a named label can happen in a variety of ways including: -Moreover, due to [an llvm bug], you shouldn't use labels exclusively made of `0` and `1` digits, e.g. `0`, `11` or `101010`, as they may end up being interpreted as binary values. +- explicitly: using a label more than once in one `asm!` block, or multiple times across blocks +- implicitly via inlining: the compiler is allowed to instantiate multiple copies of an `asm!` block, for example when the function containing it is inlined in multiple places. +- implicitly via LTO: LTO can cause code from *other crates* to be placed in the same codegen unit, and so could bring in arbitrary labels + +As a consequence, you should only use GNU assembler **numeric** [local labels] inside inline assembly code. Defining symbols in assembly code may lead to assembler and/or linker errors due to duplicate symbol definitions. + +Moreover, on x86 when using the default intel syntax, due to [an llvm bug], you shouldn't use labels exclusively made of `0` and `1` digits, e.g. `0`, `11` or `101010`, as they may end up being interpreted as binary values. Using `options(att_syntax)` will avoid any ambiguity, but that affects the syntax of the _entire_ `asm!` block. ```rust,allow_fail #![feature(asm)] @@ -1171,7 +1154,7 @@ assert_eq!(a, 5); This will decrement the `{0}` register value from 10 to 3, then add 2 and store it in `a`. -This example show a few thing: +This example shows a few things: First that the same number can be used as a label multiple times in the same inline block. @@ -1182,7 +1165,7 @@ Second, that when a numeric label is used as a reference (as an instruction oper ## Options -By default, an inline assembly block is treated the same way as an external FFI function call with a custom calling convention: it may read/write memory, have observable side effects, etc. However in many cases, it is desirable to give the compiler more information about what the assembly code is actually doing so that it can optimize better. +By default, an inline assembly block is treated the same way as an external FFI function call with a custom calling convention: it may read/write memory, have observable side effects, etc. However, in many cases it is desirable to give the compiler more information about what the assembly code is actually doing so that it can optimize better. Let's take our previous example of an `add` instruction: @@ -1225,12 +1208,26 @@ reg_spec := / "" operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" reg_operand := dir_spec "(" reg_spec ")" operand_expr operand := reg_operand / "const" const_expr / "sym" path +clobber_abi := "clobber_abi(" ")" option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw" options := "options(" option *["," option] [","] ")" -asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," options] [","] ")" +asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," clobber_abi] *("," options) [","] ")" ``` -The macro will initially be supported only on ARM, AArch64, Hexagon, PowerPC, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target. +Inline assembly is currently supported on the following architectures: +- x86 and x86-64 +- ARM +- AArch64 +- RISC-V +- NVPTX +- PowerPC +- Hexagon +- MIPS32r2 and MIPS64r2 +- wasm32 +- BPF +- SPIR-V + +Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target. [format-syntax]: https://doc.rust-lang.org/std/fmt/#syntax @@ -1314,9 +1311,12 @@ Here is the list of currently supported register classes: | x86 | `ymm_reg` | `ymm[0-7]` (x86) `ymm[0-15]` (x86-64) | `x` | | x86 | `zmm_reg` | `zmm[0-7]` (x86) `zmm[0-31]` (x86-64) | `v` | | x86 | `kreg` | `k[1-7]` | `Yk` | +| x86 | `x87_reg` | `st([0-7])` | Only clobbers | +| x86 | `mmx_reg` | `mm[0-7]` | Only clobbers | | AArch64 | `reg` | `x[0-30]` | `r` | | AArch64 | `vreg` | `v[0-31]` | `w` | | AArch64 | `vreg_low16` | `v[0-15]` | `x` | +| AArch64 | `preg` | `p[0-15]`, `ffr` | Only clobbers | | ARM | `reg` | `r[0-12]`, `r14` | `r` | | ARM (Thumb) | `reg_thumb` | `r[0-r7]` | `l` | | ARM (ARM) | `reg_thumb` | `r[0-r12]`, `r14` | `l` | @@ -1335,10 +1335,13 @@ Here is the list of currently supported register classes: | NVPTX | `reg64` | None\* | `l` | | RISC-V | `reg` | `x1`, `x[5-7]`, `x[9-15]`, `x[16-31]` (non-RV32E) | `r` | | RISC-V | `freg` | `f[0-31]` | `f` | +| RISC-V | `vreg` | `v[0-31]` | Only clobbers | | Hexagon | `reg` | `r[0-28]` | `r` | | PowerPC | `reg` | `r[0-31]` | `r` | | PowerPC | `reg_nonzero` | | `r[1-31]` | `b` | | PowerPC | `freg` | `f[0-31]` | `f` | +| PowerPC | `cr` | `cr[0-7]`, `cr` | Only clobbers | +| PowerPC | `xer` | `xer` | Only clobbers | | wasm32 | `local` | None\* | `r` | | BPF | `reg` | `r[0-10]` | `r` | | BPF | `wreg` | `w[0-10]` | `w` | @@ -1350,6 +1353,8 @@ Here is the list of currently supported register classes: > Note #3: NVPTX doesn't have a fixed register set, so named registers are not supported. > > Note #4: WebAssembly doesn't have registers, so named registers are not supported. +> +> Note #5: Some register classes are marked as "Only clobbers" which means that they cannot be used for inputs or outputs, only clobbers of the form `out("reg") _` or `lateout("reg") _`. Additional register classes may be added in the future based on demand (e.g. MMX, x87, etc). @@ -1363,10 +1368,13 @@ Each register class has constraints on which value types they can be used with. | x86 | `xmm_reg` | `sse` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | | x86 | `ymm_reg` | `avx` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4` | | x86 | `zmm_reg` | `avx512f` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4`
`i8x64`, `i16x32`, `i32x16`, `i64x8`, `f32x16`, `f64x8` | -| x86 | `kreg` | `axv512f` | `i8`, `i16` | -| x86 | `kreg` | `axv512bw` | `i32`, `i64` | +| x86 | `kreg` | `avx512f` | `i8`, `i16` | +| x86 | `kreg` | `avx512bw` | `i32`, `i64` | +| x86 | `mmx_reg` | N/A | Only clobbers | +| x86 | `x87_reg` | N/A | Only clobbers | | AArch64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | | AArch64 | `vreg` | `fp` | `i8`, `i16`, `i32`, `f32`, `i64`, `f64`,
`i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2`, `f64x1`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | +| AArch64 | `preg` | N/A | Only clobbers | | ARM | `reg` | None | `i8`, `i16`, `i32`, `f32` | | ARM | `sreg` | `vfp2` | `i32`, `f32` | | ARM | `dreg` | `vfp2` | `i64`, `f64`, `i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2` | @@ -1382,10 +1390,13 @@ Each register class has constraints on which value types they can be used with. | RISC-V64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | | RISC-V | `freg` | `f` | `f32` | | RISC-V | `freg` | `d` | `f64` | +| RISC-V | `vreg` | N/A | Only clobbers | | Hexagon | `reg` | None | `i8`, `i16`, `i32`, `f32` | | PowerPC | `reg` | None | `i8`, `i16`, `i32` | | PowerPC | `reg_nonzero` | None | `i8`, `i16`, `i32` | | PowerPC | `freg` | None | `f32`, `f64` | +| PowerPC | `cr` | N/A | Only clobbers | +| PowerPC | `xer` | N/A | Only clobbers | | wasm32 | `local` | None | `i8` `i16` `i32` `i64` `f32` `f64` | | BPF | `reg` | None | `i8` `i16` `i32` `i64` | | BPF | `wreg` | `alu32` | `i8` `i16` `i32` | @@ -1539,6 +1550,24 @@ As stated in the previous section, passing an input value smaller than the regis [llvm-argmod]: http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers +## ABI clobbers + +The `clobber_abi` keyword can be used to apply a default set of clobbers to an `asm` block. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then a `lateout("reg") _` is implicitly added to the operands list. + +Generic register class outputs are disallowed by the compiler when `clobber_abi` is used: all outputs must specify an explicit register. Explicit register outputs have precedence over the implicit clobbers inserted by `clobber_abi`: a clobber will only be inserted for a register if that register is not used as an output. +The following ABIs can be used with `clobber_abi`: + +| Architecture | ABI name | Clobbered registers | +| ------------ | -------- | ------------------- | +| x86-32 | `"C"`, `"system"`, `"efiapi"`, `"cdecl"`, `"stdcall"`, `"fastcall"` | `ax`, `cx`, `dx`, `xmm[0-7]`, `mm[0-7]`, `k[1-7]`, `st([0-7])` | +| x86-64 | `"C"`, `"system"` (on Windows), `"efiapi"`, `"win64"` | `ax`, `cx`, `dx`, `r[8-11]`, `xmm[0-31]`, `mm[0-7]`, `k[1-7]`, `st([0-7])` | +| x86-64 | `"C"`, `"system"` (on non-Windows), `"sysv64"` | `ax`, `cx`, `dx`, `si`, `di`, `r[8-11]`, `xmm[0-31]`, `mm[0-7]`, `k[1-7]`, `st([0-7])` | +| AArch64 | `"C"`, `"system"`, `"efiapi"` | `x[0-17]`, `x30`, `v[0-31]`, `p[0-15]`, `ffr` | +| ARM | `"C"`, `"system"`, `"efiapi"`, `"aapcs"` | `r[0-3]`, `r12`, `r14`, `s[0-15]`, `d[0-7]`, `d[16-31]` | +| RISC-V | `"C"`, `"system"`, `"efiapi"` | `x1`, `x[5-7]`, `x[10-17]`, `x[28-31]`, `f[0-7]`, `f[10-17]`, `f[28-31]`, `v[0-31]` | + +The list of clobbered registers for each ABI is updated in rustc as architectures gain new registers: this ensures that `asm` clobbers will continue to be correct when LLVM starts using these new registers in its generated code. + ## Options Flags are used to further influence the behavior of the inline assembly block. @@ -1567,7 +1596,7 @@ The compiler performs some additional checks on options: - Note that a `lateout` may be allocated to the same register as an `in`, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation. - Behavior is undefined if execution unwinds out of an asm block. - This also applies if the assembly code calls a function which then unwinds. -- The set of memory locations that assembly code is allowed the read and write are the same as those allowed for an FFI function. +- The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function. - Refer to the unsafe code guidelines for the exact rules. - If the `readonly` option is set, then only memory reads are allowed. - If the `nomem` option is set then no reads or writes to memory are allowed. @@ -1601,6 +1630,7 @@ The compiler performs some additional checks on options: - Floating-point status (`FPSR` register). - RISC-V - Floating-point exception flags in `fcsr` (`fflags`). + - Vector extension state (`vtype`, `vl`, `vcsr`). - On x86, the direction flag (DF in `EFLAGS`) is clear on entry to an asm block and must be clear on exit. - Behavior is undefined if the direction flag is set on exiting an asm block. - The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting an `asm!` block. @@ -1998,6 +2028,22 @@ fn b() { This feature is internal to the Rust compiler and is not intended for general use. ------------------------ +"##, + }, + Lint { + label: "closure_track_caller", + description: r##"# `closure_track_caller` + +The tracking issue for this feature is: [#87417] + +[#87417]: https://github.com/rust-lang/rust/issues/87417 + +------------------------ + +Allows using the `#[track_caller]` attribute on closures and generators. +Calls made to the closure or generator will have caller information +available through `std::panic::Location::caller()`, just like using +`#[track_caller]` on a function. "##, }, Lint { @@ -2418,6 +2464,93 @@ See also its documentation in [the rustdoc book][rustdoc-book-notable_trait]. [#45040]: https://github.com/rust-lang/rust/issues/45040 [#45039]: https://github.com/rust-lang/rust/pull/45039 [rustdoc-book-notable_trait]: ../../rustdoc/unstable-features.html#adding-your-trait-to-the-notable-traits-dialog +"##, + }, + Lint { + label: "exclusive_range_pattern", + description: r##"# `exclusive_range_pattern` + +The tracking issue for this feature is: [#37854]. + + +[#67264]: https://github.com/rust-lang/rust/issues/67264 +[#37854]: https://github.com/rust-lang/rust/issues/37854 +----- + +The `exclusive_range_pattern` feature allows non-inclusive range +patterns (`0..10`) to be used in appropriate pattern matching +contexts. It also can be combined with `#![feature(half_open_range_patterns]` +to be able to use RangeTo patterns (`..10`). + +It also enabled RangeFrom patterns but that has since been +stabilized. + +```rust +#![feature(exclusive_range_pattern)] + let x = 5; + match x { + 0..10 => println!("single digit"), + 10 => println!("ten isn't part of the above range"), + _ => println!("nor is everything else.") + } +``` +"##, + }, + Lint { + label: "explicit_generic_args_with_impl_trait", + description: r##"# `explicit_generic_args_with_impl_trait` + +The tracking issue for this feature is: [#83701] + +[#83701]: https://github.com/rust-lang/rust/issues/83701 + +------------------------ + +The `explicit_generic_args_with_impl_trait` feature gate lets you specify generic arguments even +when `impl Trait` is used in argument position. + +A simple example is: + +```rust +#![feature(explicit_generic_args_with_impl_trait)] + +fn foo(_f: impl AsRef) {} + +fn main() { + foo::("".to_string()); +} +``` + +This is currently rejected: + +```text +error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position + --> src/main.rs:6:11 + | +6 | foo::("".to_string()); + | ^^^ explicit generic argument not allowed + +``` + +However it would compile if `explicit_generic_args_with_impl_trait` is enabled. + +Note that the synthetic type parameters from `impl Trait` are still implicit and you +cannot explicitly specify these: + +```rust,compile_fail +#![feature(explicit_generic_args_with_impl_trait)] + +fn foo(_f: impl AsRef) {} +fn bar>(_f: F) {} + +fn main() { + bar::("".to_string()); // Okay + bar::("".to_string()); // Okay + + foo::("".to_string()); // Okay + foo::("".to_string()); // Error, you cannot specify `impl Trait` explicitly +} +``` "##, }, Lint { @@ -3030,35 +3163,34 @@ assembly to `fn` bodies only, you might try the "##, }, Lint { - label: "impl_trait_in_bindings", - description: r##"# `impl_trait_in_bindings` + label: "half_open_range_patterns", + description: r##"# `half_open_range_patterns` -The tracking issue for this feature is: [#63065] +The tracking issue for this feature is: [#67264] +It is part of the `#![exclusive_range_pattern]` feature, +tracked at [#37854]. -[#63065]: https://github.com/rust-lang/rust/issues/63065 +[#67264]: https://github.com/rust-lang/rust/issues/67264 +[#37854]: https://github.com/rust-lang/rust/issues/37854 +----- ------------------------- +The `half_open_range_patterns` feature allows RangeTo patterns +(`..10`) to be used in appropriate pattern matching contexts. +This requires also enabling the `exclusive_range_pattern` feature. -The `impl_trait_in_bindings` feature gate lets you use `impl Trait` syntax in -`let`, `static`, and `const` bindings. - -A simple example is: +It also enabled RangeFrom patterns but that has since been +stabilized. ```rust -#![feature(impl_trait_in_bindings)] - -use std::fmt::Debug; - -fn main() { - let a: impl Debug + Clone = 42; - let b = a.clone(); - println!("{:?}", b); // prints `42` -} +#![feature(half_open_range_patterns)] +#![feature(exclusive_range_pattern)] + let x = 5; + match x { + ..0 => println!("negative!"), // "RangeTo" pattern. Unstable. + 0 => println!("zero!"), + 1.. => println!("positive!"), // "RangeFrom" pattern. Stable. + } ``` - -Note however that because the types of `a` and `b` are opaque in the above -example, calling inherent methods or methods outside of the specified traits -(e.g., `a.abs()` or `b.abs()`) is not allowed, and yields an error. "##, }, Lint { @@ -4082,9 +4214,7 @@ The tracking issue for this feature is: [#29597] This feature is part of "compiler plugins." It will often be used with the -[`plugin_registrar`] and `rustc_private` features. - -[`plugin_registrar`]: plugin-registrar.md +`rustc_private` feature. ------------------------ @@ -4115,7 +4245,6 @@ additional checks for code style, safety, etc. Now let's write a plugin that warns about any item named `lintme`. ```rust,ignore (requires-stage-2) -#![feature(plugin_registrar)] #![feature(box_syntax, rustc_private)] extern crate rustc_ast; @@ -4144,8 +4273,8 @@ impl EarlyLintPass for Pass { } } -#[plugin_registrar] -pub fn plugin_registrar(reg: &mut Registry) { +#[no_mangle] +fn __rustc_plugin_registrar(reg: &mut Registry) { reg.lint_store.register_lints(&[&TEST_LINT]); reg.lint_store.register_early_pass(|| box Pass); } @@ -4193,23 +4322,6 @@ conversion. You can run `rustc -W help foo.rs` to see a list of lints known to `rustc`, including those provided by plugins loaded by `foo.rs`. -"##, - }, - Lint { - label: "plugin_registrar", - description: r##"# `plugin_registrar` - -The tracking issue for this feature is: [#29597] - -[#29597]: https://github.com/rust-lang/rust/issues/29597 - -This feature is part of "compiler plugins." It will often be used with the -[`plugin`] and `rustc_private` features as well. For more details, see -their docs. - -[`plugin`]: plugin.md - ------------------------- "##, }, Lint { @@ -4237,6 +4349,44 @@ The tracking issue for this feature is: [#42524](https://github.com/rust-lang/ru This feature is internal to the Rust compiler and is not intended for general use. ------------------------ +"##, + }, + Lint { + label: "raw_dylib", + description: r##"# `raw_dylib` + +The tracking issue for this feature is: [#58713] + +[#58713]: https://github.com/rust-lang/rust/issues/58713 + +------------------------ + +The `raw_dylib` feature allows you to link against the implementations of functions in an `extern` +block without, on Windows, linking against an import library. + +```rust,ignore (partial-example) +#![feature(raw_dylib)] + +#[link(name="library", kind="raw-dylib")] +extern { + fn extern_function(x: i32); +} + +fn main() { + unsafe { + extern_function(14); + } +} +``` + +## Limitations + +Currently, this feature is only supported on `-windows-msvc` targets. Non-Windows platforms don't have import +libraries, and an incompatibility between LLVM and the BFD linker means that it is not currently supported on +`-windows-gnu` targets. + +On the `i686-pc-windows-msvc` target, this feature supports only the `cdecl`, `stdcall`, `system`, and `fastcall` +calling conventions. "##, }, Lint { @@ -4595,6 +4745,37 @@ pub fn main() { println!("{:?}", b); } ``` +"##, + }, + Lint { + label: "trait_upcasting", + description: r##"# `trait_upcasting` + +The tracking issue for this feature is: [#65991] + +[#65991]: https://github.com/rust-lang/rust/issues/65991 + +------------------------ + +The `trait_upcasting` feature adds support for trait upcasting coercion. This allows a +trait object of type `dyn Bar` to be cast to a trait object of type `dyn Foo` +so long as `Bar: Foo`. + +```rust,edition2018 +#![feature(trait_upcasting)] +#![allow(incomplete_features)] + +trait Foo {} + +trait Bar: Foo {} + +impl Foo for i32 {} + +impl Bar for T {} + +let bar: &dyn Bar = &123; +let foo: &dyn Foo = bar; +``` "##, }, Lint { @@ -5016,10 +5197,6 @@ checked."##, label: "clippy::almost_swapped", description: r##"Checks for `foo = bar; bar = foo` sequences."##, }, - Lint { - label: "clippy::append_instead_of_extend", - description: r##"Checks for occurrences where one vector gets extended instead of append"##, - }, Lint { label: "clippy::approx_constant", description: r##"Checks for floating point literals that approximate @@ -5127,8 +5304,8 @@ contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly."##, Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information."##, }, Lint { - label: "clippy::box_vec", - description: r##"Checks for use of `Box>` anywhere in the code. + label: "clippy::box_collection", + description: r##"Checks for use of `Box` where T is a collection such as Vec anywhere in the code. Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information."##, }, Lint { @@ -5342,6 +5519,10 @@ field that is not a valid semantic version."##, label: "clippy::deref_addrof", description: r##"Checks for usage of `*&` and `*&mut` in expressions."##, }, + Lint { + label: "clippy::derivable_impls", + description: r##"Detects manual `std::default::Default` implementations that are identical to a derived implementation."##, + }, Lint { label: "clippy::derive_hash_xor_eq", description: r##"Checks for deriving `Hash` but implementing `PartialEq` @@ -5460,6 +5641,10 @@ by the same characters."##, bitwise, difference and division binary operators (`==`, `>`, etc., `&&`, `||`, `&`, `|`, `^`, `-` and `/`)."##, }, + Lint { + label: "clippy::equatable_if_let", + description: r##"Checks for pattern matchings that can be expressed using equality."##, + }, Lint { label: "clippy::erasing_op", description: r##"Checks for erasing operations, e.g., `x * 0`."##, @@ -5530,6 +5715,10 @@ replaced with `(e)print!()` / `(e)println!()`"##, label: "clippy::extend_from_slice", description: r##"Nothing. This lint has been deprecated."##, }, + Lint { + label: "clippy::extend_with_drain", + description: r##"Checks for occurrences where one vector gets extended instead of append"##, + }, Lint { label: "clippy::extra_unused_lifetimes", description: r##"Checks for lifetimes in generics that are never used @@ -5605,6 +5794,10 @@ bools in function definitions."##, label: "clippy::fn_to_numeric_cast", description: r##"Checks for casts of function pointers to something other than usize"##, }, + Lint { + label: "clippy::fn_to_numeric_cast_any", + description: r##"Checks for casts of a function pointer to any integer type."##, + }, Lint { label: "clippy::fn_to_numeric_cast_with_truncation", description: r##"Checks for casts of a function pointer to a numeric type not wide enough to @@ -5672,10 +5865,6 @@ with lock calls in any of the else blocks."##, label: "clippy::if_let_redundant_pattern_matching", description: r##"Nothing. This lint has been deprecated."##, }, - Lint { - label: "clippy::if_let_some_result", - description: r##"* Checks for unnecessary `ok()` in if let."##, - }, Lint { label: "clippy::if_not_else", description: r##"Checks for usage of `!` or `!=` in an if condition with an @@ -5686,6 +5875,10 @@ else branch."##, description: r##"Checks for `if/else` with the same body as the *then* part and the *else* part."##, }, + Lint { + label: "clippy::if_then_panic", + description: r##"Detects `if`-then-`panic!` that can be replaced with `assert!`."##, + }, Lint { label: "clippy::if_then_some_else_none", description: r##"Checks for if-else that could be written to `bool::then`."##, @@ -5808,12 +6001,6 @@ attempted."##, label: "clippy::into_iter_on_ref", description: r##"Checks for `into_iter` calls on references which should be replaced by `iter` or `iter_mut`."##, - }, - Lint { - label: "clippy::invalid_atomic_ordering", - description: r##"Checks for usage of invalid atomic -ordering in atomic loads/stores/exchanges/updates and -memory fences."##, }, Lint { label: "clippy::invalid_null_ptr_usage", @@ -5853,10 +6040,14 @@ create a `Vec`."##, label: "clippy::iter_next_slice", description: r##"Checks for usage of `iter().next()` on a Slice or an Array"##, }, + Lint { + label: "clippy::iter_not_returning_iterator", + description: r##"Detects methods named `iter` or `iter_mut` that do not have a return type that implements `Iterator`."##, + }, Lint { label: "clippy::iter_nth", description: r##"Checks for use of `.iter().nth()` (and the related -`.iter_mut().nth()`) on standard library types with O(1) element access."##, +`.iter_mut().nth()`) on standard library types with *O*(1) element access."##, }, Lint { label: "clippy::iter_nth_zero", @@ -6001,6 +6192,26 @@ be more readably expressed as `(3..8).contains(x)`."##, label: "clippy::manual_saturating_arithmetic", description: r##"Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`."##, }, + Lint { + label: "clippy::manual_split_once", + description: r##"**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. + +**Known problems:** None. + +**Example:** + +```rust +// Bad + let (key, value) = _.splitn(2, '=').next_tuple()?; + let value = _.splitn(2, '=').nth(1)?; + +// Good +let (key, value) = _.split_once('=')?; +let value = _.split_once('=')?.1; +```"##, + }, Lint { label: "clippy::manual_str_repeat", description: r##"Checks for manual implementations of `str::repeat`"##, @@ -6081,6 +6292,10 @@ suggests to replace the expression with an `if...else` block."##, suggesting to remove the reference and deref the matched expression instead. It also checks for `if let &foo = bar` blocks."##, }, + Lint { + label: "clippy::match_result_ok", + description: r##"Checks for unnecessary `ok()` in `while let`."##, + }, Lint { label: "clippy::match_same_arms", description: r##"Checks for `match` with identical arm bodies."##, @@ -6186,6 +6401,10 @@ unsafe functions and warns if there is no `# Safety` section."##, description: r##"Warns on hexadecimal literals with mixed-case letter digits."##, }, + Lint { + label: "clippy::mod_module_files", + description: r##"Checks that module layout uses only self named module files, bans mod.rs files."##, + }, Lint { label: "clippy::module_inception", description: r##"Checks for modules that have the same name as their @@ -6307,6 +6526,11 @@ rearrangement of code can make the code easier to understand."##, label: "clippy::needless_lifetimes", description: r##"Checks for lifetime annotations which can be removed by relying on lifetime elision."##, + }, + Lint { + label: "clippy::needless_option_as_deref", + description: r##"Checks for no-op uses of Option::{as_deref,as_deref_mut}, +for example, `Option<&T>::as_deref()` returns the same type."##, }, Lint { label: "clippy::needless_pass_by_value", @@ -6344,6 +6568,10 @@ This lint is not applied to structs marked with label: "clippy::neg_multiply", description: r##"Checks for multiplication by -1 as a form of negation."##, }, + Lint { + label: "clippy::negative_feature_names", + description: r##"Checks for negative feature names with prefix `no-` or `not-`"##, + }, Lint { label: "clippy::never_loop", description: r##"Checks for loops that will always `break`, `return` or @@ -6371,6 +6599,10 @@ implementation of label: "clippy::non_octal_unix_permissions", description: r##"Checks for non-octal values used to set Unix file permissions."##, }, + Lint { + label: "clippy::non_send_fields_in_send_ty", + description: r##"Warns about fields in struct implementing `Send` that are neither `Send` nor `Copy`."##, + }, Lint { label: "clippy::nonminimal_bool", description: r##"Checks for boolean expressions that can be written more @@ -6603,6 +6835,10 @@ argument and can be replaced by referencing the method directly."##, label: "clippy::redundant_else", description: r##"Checks for `else` blocks that can be removed without changing semantics."##, }, + Lint { + label: "clippy::redundant_feature_names", + description: r##"Checks for feature names with prefix `use-`, `with-` or suffix `-support`"##, + }, Lint { label: "clippy::redundant_field_names", description: r##"Checks for fields in struct literals where shorthands @@ -6653,7 +6889,10 @@ auto dereference."##, description: r##"Checks for usage of `.repeat(1)` and suggest the following method for each types. - `.to_string()` for `str` - `.clone()` for `String` -- `.to_vec()` for `slice`"##, +- `.to_vec()` for `slice` + +The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if +they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306))"##, }, Lint { label: "clippy::replace_consts", @@ -6691,6 +6930,11 @@ are constant and `x` is greater or equal to `y`."##, label: "clippy::same_item_push", description: r##"Checks whether a for loop is being used to push a constant value into a Vec."##, + }, + Lint { + label: "clippy::same_name_method", + description: r##"It lints if a struct has two method with same time: +one from a trait, another not from trait."##, }, Lint { label: "clippy::search_is_some", @@ -6701,6 +6945,14 @@ value into a Vec."##, label: "clippy::self_assignment", description: r##"Checks for explicit self-assignments."##, }, + Lint { + label: "clippy::self_named_constructors", + description: r##"Warns when constructors have the same name as their types."##, + }, + Lint { + label: "clippy::self_named_module_files", + description: r##"Checks that module layout uses only mod.rs files."##, + }, Lint { label: "clippy::semicolon_if_nothing_returned", description: r##"Looks for blocks of expressions and fires if the last expression returns @@ -6723,7 +6975,7 @@ scope, while just changing reference level or mutability."##, Lint { label: "clippy::shadow_unrelated", description: r##"Checks for bindings that shadow other bindings already in -scope, either without a initialization or with one that does not even use +scope, either without an initialization or with one that does not even use the original value."##, }, Lint { @@ -6828,6 +7080,11 @@ that contain only ASCII characters."##, label: "clippy::string_to_string", description: r##"This lint checks for `.to_string()` method calls on values of type `String`."##, }, + Lint { + label: "clippy::strlen_on_c_strings", + description: r##"Checks for usage of `libc::strlen` on a `CString` or `CStr` value, +and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead."##, + }, Lint { label: "clippy::struct_excessive_bools", description: r##"Checks for excessive @@ -6975,6 +7232,12 @@ declarations above a certain complexity threshold."##, label: "clippy::type_repetition_in_bounds", description: r##"This lint warns about unnecessary type repetitions in trait bounds"##, }, + Lint { + label: "clippy::undocumented_unsafe_blocks", + description: r##"Checks for `unsafe` blocks without a `// Safety: ` comment +explaining why the unsafe operations performed inside +the block are safe."##, + }, Lint { label: "clippy::undropped_manually_drops", description: r##"Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`."##, @@ -7155,6 +7418,11 @@ by nibble or byte."##, label: "clippy::unwrap_in_result", description: r##"Checks for functions of type Result that contain `expect()` or `unwrap()`"##, }, + Lint { + label: "clippy::unwrap_or_else_default", + description: r##"Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and +`Result` values."##, + }, Lint { label: "clippy::unwrap_used", description: r##"Checks for `.unwrap()` calls on `Option`s and on `Result`s."##, @@ -7315,6 +7583,8 @@ Clippy can not tell if a type that implements a trait is `Copy` or not. 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"##, }, diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index de7b889d71..eaa9291fce 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -165,5 +165,5 @@ pub enum SymbolKind { #[cfg(test)] mod tests { - mod sourcegen_lint_completions; + mod sourcegen_lints; } diff --git a/crates/ide_db/src/tests/sourcegen_lint_completions.rs b/crates/ide_db/src/tests/sourcegen_lint_completions.rs deleted file mode 100644 index ed3ac7203b..0000000000 --- a/crates/ide_db/src/tests/sourcegen_lint_completions.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Generates descriptors structure for unstable feature from Unstable Book -use std::{borrow::Cow, fs, path::Path}; - -use stdx::format_to; -use test_utils::project_root; -use xshell::cmd; - -/// This clones rustc repo, and so is not worth to keep up-to-date. We update -/// manually by un-ignoring the test from time to time. -#[test] -#[ignore] -fn sourcegen_lint_completions() { - let rust_repo = project_root().join("./target/rust"); - if !rust_repo.exists() { - cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap(); - } - - let mut contents = r" -pub struct Lint { - pub label: &'static str, - pub description: &'static str, -} -" - .to_string(); - generate_lint_descriptor(&mut contents); - contents.push('\n'); - - generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src")); - contents.push('\n'); - - let lints_json = project_root().join("./target/clippy_lints.json"); - cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}") - .run() - .unwrap(); - generate_descriptor_clippy(&mut contents, &lints_json); - - let contents = - sourcegen::add_preamble("sourcegen_lint_completions", sourcegen::reformat(contents)); - - let destination = project_root().join("crates/ide_db/src/helpers/generated_lints.rs"); - sourcegen::ensure_file_contents(destination.as_path(), &contents); -} - -fn generate_lint_descriptor(buf: &mut String) { - let stdout = cmd!("rustc -W help").read().unwrap(); - let start_lints = stdout.find("---- ------- -------").unwrap(); - let start_lint_groups = stdout.find("---- ---------").unwrap(); - let end_lints = stdout.find("Lint groups provided by rustc:").unwrap(); - let end_lint_groups = stdout - .find("Lint tools like Clippy can provide additional lints and lint groups.") - .unwrap(); - buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#); - buf.push('\n'); - let mut lints = stdout[start_lints..end_lints] - .lines() - .skip(1) - .filter(|l| !l.is_empty()) - .map(|line| { - let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap(); - let (_default_level, description) = - rest.trim().split_once(char::is_whitespace).unwrap(); - (name.trim(), Cow::Borrowed(description.trim())) - }) - .collect::>(); - lints.extend( - stdout[start_lint_groups..end_lint_groups].lines().skip(1).filter(|l| !l.is_empty()).map( - |line| { - let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap(); - (name.trim(), format!("lint group for: {}", lints.trim()).into()) - }, - ), - ); - - lints.sort_by(|(ident, _), (ident2, _)| ident.cmp(ident2)); - lints.into_iter().for_each(|(name, description)| { - push_lint_completion(buf, &name.replace("-", "_"), &description) - }); - buf.push_str("];\n"); -} - -fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) { - let mut features = ["language-features", "library-features"] - .iter() - .flat_map(|it| sourcegen::list_files(&src_dir.join(it))) - .filter(|path| { - // Get all `.md ` files - path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md" - }) - .map(|path| { - let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_"); - let doc = fs::read_to_string(path).unwrap(); - (feature_ident, doc) - }) - .collect::>(); - features.sort_by(|(feature_ident, _), (feature_ident2, _)| feature_ident.cmp(feature_ident2)); - - buf.push_str(r#"pub const FEATURES: &[Lint] = &["#); - for (feature_ident, doc) in features.into_iter() { - push_lint_completion(buf, &feature_ident, &doc) - } - buf.push('\n'); - buf.push_str("];\n"); -} - -#[derive(Default)] -struct ClippyLint { - help: String, - id: String, -} - -fn unescape(s: &str) -> String { - s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "") -} - -fn generate_descriptor_clippy(buf: &mut String, path: &Path) { - let file_content = std::fs::read_to_string(path).unwrap(); - let mut clippy_lints: Vec = Vec::new(); - - for line in file_content.lines().map(|line| line.trim()) { - if line.starts_with(r#""id":"#) { - let clippy_lint = ClippyLint { - id: line - .strip_prefix(r#""id": ""#) - .expect("should be prefixed by id") - .strip_suffix(r#"","#) - .expect("should be suffixed by comma") - .into(), - help: String::new(), - }; - clippy_lints.push(clippy_lint) - } else if line.starts_with(r#""What it does":"#) { - // Typical line to strip: "What is doest": "Here is my useful content", - let prefix_to_strip = r#""What it does": ""#; - let suffix_to_strip = r#"","#; - - let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist"); - clippy_lint.help = line - .strip_prefix(prefix_to_strip) - .expect("should be prefixed by what it does") - .strip_suffix(suffix_to_strip) - .map(unescape) - .expect("should be suffixed by comma"); - } - } - clippy_lints.sort_by(|lint, lint2| lint.id.cmp(&lint2.id)); - - buf.push_str(r#"pub const CLIPPY_LINTS: &[Lint] = &["#); - buf.push('\n'); - for clippy_lint in clippy_lints.into_iter() { - let lint_ident = format!("clippy::{}", clippy_lint.id); - let doc = clippy_lint.help; - push_lint_completion(buf, &lint_ident, &doc); - } - buf.push_str("];\n"); -} - -fn push_lint_completion(buf: &mut String, label: &str, description: &str) { - format_to!( - buf, - r###" Lint {{ - label: "{}", - description: r##"{}"## - }},"###, - label, - description - ); -} diff --git a/crates/ide_completion/src/tests/sourcegen.rs b/crates/ide_db/src/tests/sourcegen_lints.rs similarity index 84% rename from crates/ide_completion/src/tests/sourcegen.rs rename to crates/ide_db/src/tests/sourcegen_lints.rs index ed3ac7203b..96ddce54ba 100644 --- a/crates/ide_completion/src/tests/sourcegen.rs +++ b/crates/ide_db/src/tests/sourcegen_lints.rs @@ -15,13 +15,15 @@ fn sourcegen_lint_completions() { cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap(); } - let mut contents = r" + let mut contents = String::from( + r" pub struct Lint { pub label: &'static str, pub description: &'static str, } -" - .to_string(); +", + ); + generate_lint_descriptor(&mut contents); contents.push('\n'); @@ -34,8 +36,7 @@ pub struct Lint { .unwrap(); generate_descriptor_clippy(&mut contents, &lints_json); - let contents = - sourcegen::add_preamble("sourcegen_lint_completions", sourcegen::reformat(contents)); + let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents)); let destination = project_root().join("crates/ide_db/src/helpers/generated_lints.rs"); sourcegen::ensure_file_contents(destination.as_path(), &contents); @@ -117,29 +118,23 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) { let mut clippy_lints: Vec = Vec::new(); for line in file_content.lines().map(|line| line.trim()) { - if line.starts_with(r#""id":"#) { + if let Some(line) = line.strip_prefix(r#""id": ""#) { let clippy_lint = ClippyLint { - id: line - .strip_prefix(r#""id": ""#) - .expect("should be prefixed by id") - .strip_suffix(r#"","#) - .expect("should be suffixed by comma") - .into(), + id: line.strip_suffix(r#"","#).expect("should be suffixed by comma").into(), help: String::new(), }; clippy_lints.push(clippy_lint) - } else if line.starts_with(r#""What it does":"#) { - // Typical line to strip: "What is doest": "Here is my useful content", - let prefix_to_strip = r#""What it does": ""#; - let suffix_to_strip = r#"","#; + } else if let Some(line) = line.strip_prefix(r#""docs": ""#) { + let prefix_to_strip = r#" ### What it does"#; + // FIXME: replace unwrap_or with expect again, currently there is one lint that uses a different format in the json... + let line = line.strip_prefix(prefix_to_strip).unwrap_or(line); + // Only take the description, any more than this is a lot of additional data we would embed into the exe + // which seems unnecessary + let up_to = line.find(r#"###"#).expect("no second section found?"); + let line = &line[..up_to]; let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist"); - clippy_lint.help = line - .strip_prefix(prefix_to_strip) - .expect("should be prefixed by what it does") - .strip_suffix(suffix_to_strip) - .map(unescape) - .expect("should be suffixed by comma"); + clippy_lint.help = unescape(line).trim().to_string(); } } clippy_lints.sort_by(|lint, lint2| lint.id.cmp(&lint2.id)); From acaf270911d3ae599370872ff12138293d47b280 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 12 Oct 2021 09:37:16 +0200 Subject: [PATCH 3/4] Remove stale module --- crates/ide_completion/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs index 9168956235..4028fc2242 100644 --- a/crates/ide_completion/src/tests.rs +++ b/crates/ide_completion/src/tests.rs @@ -17,7 +17,6 @@ mod pattern; mod predicate; mod proc_macros; mod record; -mod sourcegen; mod type_pos; mod use_tree; mod visibility; From 22f471d81fab6f6534ab4028160b522c828e509d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 12 Oct 2021 09:47:21 +0200 Subject: [PATCH 4/4] Update tidy ignore path --- crates/rust-analyzer/tests/slow-tests/tidy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs index 3071da45fa..bd34a5fda8 100644 --- a/crates/rust-analyzer/tests/slow-tests/tidy.rs +++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs @@ -342,7 +342,7 @@ fn check_test_attrs(path: &Path, text: &str) { // :( "hir_def/src/nameres/collector.rs", // Long sourcegen test to generate lint completions. - "ide_completion/src/tests/sourcegen.rs", + "ide_db/src/tests/sourcegen_lints.rs", // Obviously needs ignore. "ide_assists/src/handlers/toggle_ignore.rs", // See above.